From e0fb0e4c4602041ca219f87bdeb2b0a1f7233f57 Mon Sep 17 00:00:00 2001 From: "xgr.network" Date: Fri, 15 May 2026 07:33:05 +0000 Subject: [PATCH 1/7] Publish XGR 2.0 PoS node update --- back_Makefile | 224 -- blockchain/blockchain.go | 10 +- blockchain/blockchain_test.go | 4 +- bls/private.go | 2 +- chain/params.go | 4 +- command/bridge/README.md | 49 - command/bridge/bridge.go | 47 - command/bridge/common/params.go | 296 --- .../bridge/deposit/erc1155/deposit_erc1155.go | 305 --- command/bridge/deposit/erc20/deposit_erc20.go | 286 --- .../bridge/deposit/erc721/deposit_erc721.go | 273 --- command/bridge/exit/exit.go | 235 --- command/bridge/mint/mint_erc20.go | 150 -- command/bridge/mint/params.go | 78 - .../withdraw/erc1155/withdraw_erc1155.go | 193 -- .../bridge/withdraw/erc20/withdraw_erc20.go | 171 -- .../bridge/withdraw/erc721/withdraw_erc721.go | 171 -- command/default.go | 2 +- command/genesis/genesis.go | 177 +- command/genesis/params.go | 206 +- command/genesis/params_test.go | 239 +-- command/genesis/polybft_params.go | 567 ----- command/genesis/utils.go | 112 +- command/ibft/helper/account.go | 28 + command/ibft/ibft.go | 17 + command/ibft/join/README.md | 26 + command/ibft/join/join.go | 579 ++++++ command/ibft/join/join_command_test.go | 43 + command/ibft/join/join_decode_test.go | 97 + command/ibft/join/join_selector_test.go | 18 + command/ibft/join/result.go | 33 + command/ibft/poolconfig/pool_config.go | 241 +++ command/ibft/poolconfig/pool_config_test.go | 93 + command/ibft/poolconfig/result.go | 29 + command/ibft/setactive/result.go | 46 + command/ibft/setactive/setactive.go | 384 ++++ command/ibft/setactive/setactive_test.go | 123 ++ command/ibft/snapshot/result.go | 82 +- command/ibft/snapshot/result_test.go | 76 + command/ibft/stake/result.go | 26 + command/ibft/stake/stake.go | 214 ++ command/ibft/switch/params.go | 126 ++ command/ibft/switch/params_test.go | 300 +++ command/ibft/unstake/result.go | 24 + command/ibft/unstake/unstake.go | 295 +++ command/ibft/unstake/unstake_test.go | 72 + command/ibft/validators/result.go | 46 + command/ibft/validators/validators.go | 330 +++ .../ibft/validators/validators_decode_test.go | 63 + command/ibft/withdraw/result.go | 26 + command/ibft/withdraw/withdraw.go | 268 +++ command/ibft/withdraw/withdraw_test.go | 80 + command/polybft/polybft_command.go | 48 - command/polybftsecrets/params.go | 242 --- command/polybftsecrets/params_test.go | 109 - command/polybftsecrets/result.go | 83 - command/polybftsecrets/secrets_init.go | 41 - command/polybftsecrets/utils.go | 59 - command/regenesis/howtotest.md | 179 -- command/root/root.go | 8 - command/rootchain/README.md | 41 - command/rootchain/deploy/deploy.go | 788 ------- command/rootchain/deploy/deploy_test.go | 67 - command/rootchain/deploy/params.go | 55 - command/rootchain/deploy/result.go | 45 - command/rootchain/fund/fund.go | 187 -- command/rootchain/fund/params.go | 70 - command/rootchain/fund/params_test.go | 105 - command/rootchain/fund/result.go | 30 - command/rootchain/helper/utils.go | 330 --- command/rootchain/premine/params.go | 94 - command/rootchain/premine/premine.go | 158 -- command/rootchain/registration/params.go | 45 - .../registration/register_validator.go | 166 -- command/rootchain/rootchain.go | 31 - command/rootchain/server/params.go | 11 - command/rootchain/server/result.go | 41 - command/rootchain/server/server.go | 299 --- command/rootchain/staking/params.go | 57 - command/rootchain/staking/stake.go | 177 -- command/rootchain/supernet/params.go | 69 - .../rootchain/supernet/stakemanager/params.go | 53 - .../supernet/stakemanager/params_test.go | 48 - .../stakemanager/stake_manager_deploy.go | 282 --- command/rootchain/supernet/supernet.go | 400 ---- command/rootchain/validators/params.go | 50 - .../rootchain/validators/validator_info.go | 111 - command/rootchain/whitelist/params.go | 62 - .../whitelist/whitelist_validators.go | 152 -- command/rootchain/withdraw/params.go | 59 - command/rootchain/withdraw/withdraw.go | 150 -- command/server/server.go | 2 +- command/sidechain/helper.go | 90 - command/sidechain/rewards/params.go | 43 - command/sidechain/rewards/rewards.go | 119 -- command/sidechain/unstaking/params.go | 55 - command/sidechain/unstaking/unstake.go | 136 -- command/sidechain/withdraw/params.go | 48 - command/sidechain/withdraw/withdraw.go | 131 -- consensus/ibft/consensus.go | 18 +- consensus/ibft/consensus_backend.go | 77 +- consensus/ibft/consensus_backend_test.go | 113 - consensus/ibft/consensus_no_unsafe_test.go | 19 + consensus/ibft/effective_power.go | 183 ++ .../effective_power_snapshot_height_test.go | 32 + consensus/ibft/effective_power_stake_test.go | 1552 ++++++++++++++ consensus/ibft/fork/hookregister.go | 25 +- consensus/ibft/fork/hooks.go | 319 ++- consensus/ibft/fork/hooks_test.go | 406 +++- consensus/ibft/fork/manager.go | 11 + consensus/ibft/fork/storewrapper.go | 140 +- consensus/ibft/fork/storewrapper_test.go | 109 +- consensus/ibft/ibft.go | 471 ++++- consensus/ibft/ibft_config_validation_test.go | 35 + consensus/ibft/ibft_gate_alignment_test.go | 72 + consensus/ibft/ibft_hardening_test.go | 323 +++ .../ibft/ibft_parent_committed_seals_test.go | 116 ++ consensus/ibft/messages.go | 26 +- consensus/ibft/operator_service.go | 38 +- consensus/ibft/pos/epoch_validators.go | 83 + consensus/ibft/pos/epoch_validators_test.go | 79 + consensus/ibft/pos/events.go | 134 ++ consensus/ibft/pos/finalize.go | 1038 ++++++++++ .../ibft/pos/finalize_epoch_rewards_test.go | 712 +++++++ consensus/ibft/pos/finalize_epoch_test.go | 203 ++ consensus/ibft/pos/finalize_test.go | 274 +++ consensus/ibft/pos/keys.go | 222 ++ .../ibft/pos/signer_test_helpers_test.go | 27 + consensus/ibft/pos/sys_account.go | 20 + consensus/ibft/pos/system_tx.go | 33 + consensus/ibft/pos/test_txn_helper_test.go | 10 + consensus/ibft/pos/uptime.go | 289 +++ consensus/ibft/pos/uptime_bls_test.go | 67 + consensus/ibft/pos/uptime_config.go | 68 + consensus/ibft/pos/uptime_config_test.go | 39 + .../ibft/pos/uptime_extra_decode_test.go | 58 + consensus/ibft/pos/uptime_microepoch.go | 66 + consensus/ibft/pos/uptime_microepoch_test.go | 178 ++ consensus/ibft/pos/uptime_record_test.go | 293 +++ consensus/ibft/pos/uptime_state_account.go | 25 + consensus/ibft/pos/uptime_test.go | 44 + .../ibft/pos/uptime_test_helpers_test.go | 82 + consensus/ibft/pos/uptime_weight.go | 44 + consensus/ibft/proto/ibft_operator.pb.go | 70 +- consensus/ibft/proto/ibft_operator.proto | 8 + consensus/ibft/quorum_semantics_test.go | 96 + consensus/ibft/signer/extra.go | 8 + consensus/ibft/signer/extra_core_test.go | 35 + consensus/ibft/signer/extra_test.go | 491 ----- consensus/ibft/signer/helper.go | 14 +- consensus/ibft/signer/legacy_commit.go | 10 + consensus/ibft/signer/signer.go | 1 - consensus/ibft/transport.go | 26 +- consensus/ibft/verifier.go | 19 +- consensus/ibft/weighted_quorum_state_test.go | 19 + consensus/polybft/README.md | 158 -- consensus/polybft/bitmap/bitmap.go | 37 - consensus/polybft/bitmap/bitmap_test.go | 63 - consensus/polybft/block_builder.go | 221 -- consensus/polybft/block_builder_test.go | 160 -- consensus/polybft/blockchain_wrapper.go | 227 -- consensus/polybft/checkpoint_manager.go | 492 ----- consensus/polybft/checkpoint_manager_test.go | 524 ----- consensus/polybft/consensus_metrics.go | 58 - consensus/polybft/consensus_runtime.go | 1091 ---------- consensus/polybft/consensus_runtime_test.go | 1093 ---------- consensus/polybft/contracts_initializer.go | 278 --- .../contractsapi/artifact/artifacts.go | 47 - .../contractsapi/artifacts-gen/main.go | 237 --- .../polybft/contractsapi/bindings-gen/main.go | 805 -------- .../polybft/contractsapi/contractsapi.go | 1816 ---------------- .../polybft/contractsapi/contractsapi_test.go | 130 -- consensus/polybft/contractsapi/decoder.go | 99 - consensus/polybft/contractsapi/gen_sc_data.go | 45 - consensus/polybft/contractsapi/helper.go | 55 - consensus/polybft/contractsapi/init.go | 305 --- consensus/polybft/contractsapi/init_test.go | 29 - .../test-contracts/TestRewardToken.json | 330 --- .../test-contracts/TestSimple.json | 39 - .../test-contracts/TestSimple.sol | 14 - .../TestWriteBlockMetadata.json | 50 - .../test-contracts/TestWriteBlockMetadata.sol | 19 - consensus/polybft/extra.go | 505 ----- consensus/polybft/extra_test.go | 779 ------- consensus/polybft/fsm.go | 717 ------- consensus/polybft/fsm_test.go | 1677 --------------- consensus/polybft/handlers.go | 38 - consensus/polybft/hash.go | 32 - consensus/polybft/hash_test.go | 39 - consensus/polybft/helpers_test.go | 196 -- consensus/polybft/ibft_consensus.go | 43 - consensus/polybft/mocks_test.go | 401 ---- consensus/polybft/polybft.go | 848 -------- consensus/polybft/polybft_config.go | 316 --- consensus/polybft/polybft_test.go | 354 ---- consensus/polybft/proposer_calculator.go | 504 ----- consensus/polybft/proposer_calculator_test.go | 712 ------- consensus/polybft/proto/transport.pb.go | 144 -- .../polybft/proto/transport.pb.validate.go | 138 -- consensus/polybft/proto/transport.proto | 9 - consensus/polybft/runtime_helpers.go | 50 - consensus/polybft/runtime_helpers_test.go | 88 - consensus/polybft/sc_integration_test.go | 413 ---- consensus/polybft/signer/utils.go | 60 - consensus/polybft/signer/utils_test.go | 40 - consensus/polybft/stake_manager.go | 526 ----- consensus/polybft/stake_manager_fuzz_test.go | 248 --- consensus/polybft/stake_manager_test.go | 580 ------ consensus/polybft/state.go | 184 -- consensus/polybft/state_event_getter.go | 256 --- consensus/polybft/state_event_getter_test.go | 50 - consensus/polybft/state_store_checkpoint.go | 245 --- .../polybft/state_store_checkpoint_test.go | 206 -- consensus/polybft/state_store_epoch.go | 289 --- consensus/polybft/state_store_epoch_test.go | 219 -- .../polybft/state_store_proposer_snapshot.go | 82 - .../state_store_proposer_snapshot_test.go | 29 - consensus/polybft/state_store_stake.go | 81 - consensus/polybft/state_store_stake_test.go | 51 - consensus/polybft/state_store_state_sync.go | 442 ---- .../polybft/state_store_state_sync_test.go | 355 ---- consensus/polybft/state_sync_commitment.go | 202 -- .../polybft/state_sync_commitment_test.go | 178 -- consensus/polybft/state_sync_manager.go | 651 ------ consensus/polybft/state_sync_manager_test.go | 702 ------- consensus/polybft/state_sync_relayer.go | 347 ---- consensus/polybft/state_sync_relayer_test.go | 203 -- consensus/polybft/state_transaction.go | 44 - consensus/polybft/state_transaction_test.go | 63 - consensus/polybft/stats.go | 210 -- consensus/polybft/system_state.go | 83 - consensus/polybft/system_state_test.go | 158 -- consensus/polybft/transport.go | 65 - .../polybft/validator/genesis_validator.go | 83 - consensus/polybft/validator/test_helpers.go | 243 --- .../polybft/validator/validator_metadata.go | 360 ---- .../validator/validator_metadata_test.go | 281 --- consensus/polybft/validator/validator_set.go | 121 -- .../polybft/validator/validator_set_delta.go | 144 -- .../validator/validator_set_delta_test.go | 237 --- .../polybft/validator/validator_set_test.go | 53 - consensus/polybft/validators_snapshot.go | 358 ---- consensus/polybft/validators_snapshot_test.go | 316 --- consensus/polybft/wallet/account.go | 123 -- consensus/polybft/wallet/account_test.go | 82 - consensus/polybft/wallet/key.go | 87 - consensus/polybft/wallet/key_test.go | 61 - consensus/utils.go | 2 + contracts/abis/json.go | 692 ++++++- contracts/staking/StakingV2.sol | 452 ++++ contracts/staking/query.go | 356 +++- contracts/staking/query_test.go | 91 + docker/README.md | 66 - docker/local/docker-compose.yml | 4 +- docker/local/xgrchain.sh | 81 +- docs/Dockerfile | 17 - docs/README.md | 48 - docs/docker-compose.yml | 13 - docs/docs/api/json-rpc-bridge.md | 31 - docs/docs/api/json-rpc-debug.md | 137 -- docs/docs/api/json-rpc-eth.md | 580 ------ docs/docs/api/json-rpc-net.md | 56 - docs/docs/api/json-rpc-txpool.md | 40 - docs/docs/api/json-rpc-web3.md | 37 - docs/docs/api/json-rpc-xgr.md | 22 - docs/docs/design/blockchain.md | 18 - docs/docs/design/bridge/assets/erc/erc1155.md | 19 - docs/docs/design/bridge/assets/erc/erc20.md | 40 - docs/docs/design/bridge/assets/erc/erc721.md | 18 - docs/docs/design/bridge/checkpoint.md | 35 - docs/docs/design/bridge/overview.md | 41 - docs/docs/design/bridge/sequences.md | 64 - docs/docs/design/bridge/statesync.md | 37 - docs/docs/design/consensus/polybft/ibft.md | 54 - .../docs/design/consensus/polybft/overview.md | 22 - .../docs/design/consensus/polybft/protocol.md | 123 -- docs/docs/design/grpc.md | 20 - docs/docs/design/jsonrpc.md | 19 - docs/docs/design/libp2p.md | 26 - docs/docs/design/mempool.md | 17 - docs/docs/design/overview.md | 21 - docs/docs/design/runtime/allowlist.md | 51 - docs/docs/design/runtime/overview.md | 10 - docs/docs/design/txpool.md | 21 - docs/docs/design/txrelayer.md | 19 - docs/docs/disclaimer.md | 7 - .../bridge-deposit-childchain.excalidraw.png | Bin 146942 -> 0 bytes .../bridge-deposit-rootchain.excalidraw.png | Bin 146464 -> 0 bytes .../bridge-withdraw-childchain.excalidraw.png | Bin 142336 -> 0 bytes .../bridge-withdraw-rootchain.excalidraw.png | Bin 142052 -> 0 bytes docs/docs/img/edge/consensus.excalidraw.png | Bin 106908 -> 0 bytes .../img/edge/contracts-bridge.excalidraw.png | Bin 3052844 -> 0 bytes docs/docs/img/edge/contracts.excalidraw.png | Bin 862521 -> 0 bytes docs/docs/img/edge/l1-l2-l3.excalidraw.png | Bin 272382 -> 0 bytes .../docs/img/edge/polybft-node.excalidraw.png | Bin 123088 -> 0 bytes docs/docs/img/edge/polybft.excalidraw.png | Bin 356590 -> 0 bytes docs/docs/img/edge/supernet.svg | 3 - .../supernets-checkpoint-exit.excalidraw.png | Bin 598940 -> 0 bytes .../edge/supernets-checkpoint.excalidraw.png | Bin 579358 -> 0 bytes .../edge/supernets-consensus.excalidraw.png | Bin 118916 -> 0 bytes .../supernets-interconnected.excalidraw.png | Bin 483924 -> 0 bytes docs/docs/img/edge/supernets-logo.png | Bin 34172 -> 0 bytes .../edge/supernets-overview.excalidraw.png | Bin 992286 -> 0 bytes .../edge/supernets-setup-non.excalidraw.png | Bin 729624 -> 0 bytes .../img/edge/supernets-setup.excalidraw.png | Bin 995172 -> 0 bytes .../edge/supernets-together-lp.excalidraw.png | Bin 266042 -> 0 bytes .../edge/supernets-together.excalidraw.png | Bin 946105 -> 0 bytes docs/docs/img/edge/val-select.excalidraw.png | Bin 162628 -> 0 bytes docs/docs/index.md | 38 - docs/docs/interfaces/cryptography/bls.md | 111 - docs/docs/interfaces/cryptography/bn256g2.md | 35 - docs/docs/interfaces/eip1559.md | 47 - .../erc1155/childerc1155-predicate.md | 77 - docs/docs/interfaces/erc1155/childerc1155.md | 117 -- .../erc1155/rooterc1155-predicate.md | 81 - .../interfaces/erc20/childerc20-predicate.md | 55 - docs/docs/interfaces/erc20/childerc20.md | 65 - docs/docs/interfaces/erc20/native-erc20.md | 84 - .../interfaces/erc20/rooterc20-predicate.md | 50 - .../erc721/childerc721-predicate.md | 89 - docs/docs/interfaces/erc721/childerc721.md | 113 - .../interfaces/erc721/rooterc721-predicate.md | 68 - .../interfaces/network/checkpoint-manager.md | 76 - docs/docs/interfaces/network/exit-helper.md | 59 - .../docs/interfaces/network/state-receiver.md | 30 - docs/docs/interfaces/network/state-sender.md | 29 - .../staking/custom-supernet-manager.md | 147 -- docs/docs/interfaces/staking/stake-manager.md | 218 -- .../interfaces/staking/supernet-manager.md | 28 - .../validators/validator-set-base.md | 68 - docs/docs/operate/benchmarks.md | 108 - .../access-control/allowlist-general.md | 216 -- .../docs/operate/deploy/genesis-validators.md | 138 -- docs/docs/operate/deploy/genesis.md | 557 ----- docs/docs/operate/deploy/index.md | 164 -- docs/docs/operate/deploy/local-chain.md | 140 -- docs/docs/operate/deploy/rootchain-config.md | 652 ------ docs/docs/operate/deploy/staking/stake.md | 75 - docs/docs/operate/deploy/staking/unstake.md | 137 -- docs/docs/operate/deploy/start-chain.md | 70 - docs/docs/operate/deploy/transfers/deposit.md | 217 -- .../docs/operate/deploy/transfers/withdraw.md | 322 --- docs/docs/operate/deploy/upgrades/hardfork.md | 185 -- docs/docs/operate/deploy/upgrades/v1.1.md | 95 - docs/docs/operate/install.md | 41 - docs/docs/operate/param-reference.md | 153 -- docs/docs/operate/system.md | 38 - docs/docs/quickstart.md | 347 ---- docs/mkdocs.yml | 119 -- docs/requirements.txt | 4 - docs/run.sh | 8 - e2e-polybft/e2e/README.md | 22 - e2e-polybft/e2e/acls_test.go | 415 ---- e2e-polybft/e2e/bridge_test.go | 1840 ----------------- e2e-polybft/e2e/burn_contract_test.go | 38 - e2e-polybft/e2e/consensus_test.go | 749 ------- e2e-polybft/e2e/helpers_test.go | 347 ---- e2e-polybft/e2e/jsonrpc_test.go | 423 ---- e2e-polybft/e2e/migration_test.go | 210 -- e2e-polybft/e2e/network_test.go | 38 - e2e-polybft/e2e/txpool_test.go | 325 --- e2e-polybft/framework/node.go | 77 - e2e-polybft/framework/test-bridge.go | 680 ------ e2e-polybft/framework/test-cluster.go | 1102 ---------- e2e-polybft/framework/test-server.go | 385 ---- e2e-polybft/grpc_validation_test.go | 46 - e2e-polybft/property/property_test.go | 135 -- e2e/TEST_CATALOG.md | 56 + e2e/broadcast_test.go | 2 +- e2e/framework/config.go | 19 + e2e/framework/helper.go | 45 +- e2e/framework/ibft.go | 48 +- e2e/framework/ibft_test.go | 23 + e2e/framework/slashing.go | 21 + e2e/framework/testserver.go | 72 +- e2e/poa_pos_cutover_boundary_test.go | 78 + e2e/pos_micro_weight_decay_restore_test.go | 357 ++++ e2e/pos_poa_switch_test.go | 167 -- e2e/pos_restart_resync_test.go | 322 +++ e2e/pos_smallset_notbetrieb_test.go | 59 + ...stakingv2_delegation_lifecycle_e2e_test.go | 1468 +++++++++++++ ...stakingv2_delegation_pool_edge_e2e_test.go | 1081 ++++++++++ e2e/pos_stakingv2_delegation_pool_test.go | 201 ++ e2e/pos_stakingv2_filter_test.go | 925 +++++++++ e2e/pos_stakingv2_lifecycle_test.go | 632 ++++++ e2e/pos_stakingv2_reentrancy_helpers_test.go | 119 ++ e2e/pos_test.go | 917 -------- e2e/transaction_test.go | 2 +- go.mod | 3 + go.sum | 8 + helper/common/common.go | 7 +- helper/secrets/secrets.go | 15 + helper/staking/staking.go | 227 +- helper/staking/staking_test.go | 221 ++ jsonrpc/dispatcher.go | 10 +- jsonrpc/eth_blockchain_test.go | 6 +- jsonrpc/eth_endpoint.go | 36 +- jsonrpc/eth_endpoint_test.go | 11 +- jsonrpc/eth_pos_microepoch_status.go | 209 ++ jsonrpc/eth_pos_microepoch_status_test.go | 19 + jsonrpc/eth_pos_overview.go | 1448 +++++++++++++ jsonrpc/eth_pos_overview_test.go | 1422 +++++++++++++ jsonrpc/eth_state_test.go | 25 +- jsonrpc/filter_manager_test.go | 48 +- jsonrpc/mocks_test.go | 5 + jsonrpc/throttling_test.go | 107 +- jsonrpc/xgr_engine_embedded.go | 10 +- network/gossip_test.go | 64 +- network/server.go | 18 +- network/server_test.go | 12 +- scripts/Makefile | 36 +- scripts/README.md | 5 +- scripts/cluster | 227 +- scripts/monitor_balances.sh | 76 + server/builtin.go | 27 +- server/server.go | 31 +- state/executor.go | 370 +++- state/executor_test.go | 111 + .../precompiled/bls_agg_sigs_verification.go | 5 +- .../bls_agg_sigs_verification_test.go | 3 +- state/runtime/precompiled/engine_execute.go | 135 +- stub.sum | 2 + syncer/client.go | 14 +- syncer/client_test.go | 43 +- tests/state_test.go | 6 + tools/testapi/testapi.go | 20 +- tracker/event_tracker_test.go | 4 + txpool/event_subscription_test.go | 2 +- txpool/txpool_test.go | 131 +- types/header_hash.go | 27 +- types/transaction.go | 35 + validators/store/contract/contract_test.go | 3 +- validators/store/contract/fetcher.go | 574 ++++- validators/store/contract/fetcher_test.go | 885 +++++++- validators/store/contract/minstake_test.go | 151 ++ 435 files changed, 25100 insertions(+), 52086 deletions(-) delete mode 100644 back_Makefile delete mode 100644 command/bridge/README.md delete mode 100644 command/bridge/bridge.go delete mode 100644 command/bridge/common/params.go delete mode 100644 command/bridge/deposit/erc1155/deposit_erc1155.go delete mode 100644 command/bridge/deposit/erc20/deposit_erc20.go delete mode 100644 command/bridge/deposit/erc721/deposit_erc721.go delete mode 100644 command/bridge/exit/exit.go delete mode 100644 command/bridge/mint/mint_erc20.go delete mode 100644 command/bridge/mint/params.go delete mode 100644 command/bridge/withdraw/erc1155/withdraw_erc1155.go delete mode 100644 command/bridge/withdraw/erc20/withdraw_erc20.go delete mode 100644 command/bridge/withdraw/erc721/withdraw_erc721.go delete mode 100644 command/genesis/polybft_params.go create mode 100644 command/ibft/helper/account.go create mode 100644 command/ibft/join/README.md create mode 100644 command/ibft/join/join.go create mode 100644 command/ibft/join/join_command_test.go create mode 100644 command/ibft/join/join_decode_test.go create mode 100644 command/ibft/join/join_selector_test.go create mode 100644 command/ibft/join/result.go create mode 100644 command/ibft/poolconfig/pool_config.go create mode 100644 command/ibft/poolconfig/pool_config_test.go create mode 100644 command/ibft/poolconfig/result.go create mode 100644 command/ibft/setactive/result.go create mode 100644 command/ibft/setactive/setactive.go create mode 100644 command/ibft/setactive/setactive_test.go create mode 100644 command/ibft/snapshot/result_test.go create mode 100644 command/ibft/stake/result.go create mode 100644 command/ibft/stake/stake.go create mode 100644 command/ibft/switch/params_test.go create mode 100644 command/ibft/unstake/result.go create mode 100644 command/ibft/unstake/unstake.go create mode 100644 command/ibft/unstake/unstake_test.go create mode 100644 command/ibft/validators/result.go create mode 100644 command/ibft/validators/validators.go create mode 100644 command/ibft/validators/validators_decode_test.go create mode 100644 command/ibft/withdraw/result.go create mode 100644 command/ibft/withdraw/withdraw.go create mode 100644 command/ibft/withdraw/withdraw_test.go delete mode 100644 command/polybft/polybft_command.go delete mode 100644 command/polybftsecrets/params.go delete mode 100644 command/polybftsecrets/params_test.go delete mode 100644 command/polybftsecrets/result.go delete mode 100644 command/polybftsecrets/secrets_init.go delete mode 100644 command/polybftsecrets/utils.go delete mode 100644 command/regenesis/howtotest.md delete mode 100644 command/rootchain/README.md delete mode 100644 command/rootchain/deploy/deploy.go delete mode 100644 command/rootchain/deploy/deploy_test.go delete mode 100644 command/rootchain/deploy/params.go delete mode 100644 command/rootchain/deploy/result.go delete mode 100644 command/rootchain/fund/fund.go delete mode 100644 command/rootchain/fund/params.go delete mode 100644 command/rootchain/fund/params_test.go delete mode 100644 command/rootchain/fund/result.go delete mode 100644 command/rootchain/helper/utils.go delete mode 100644 command/rootchain/premine/params.go delete mode 100644 command/rootchain/premine/premine.go delete mode 100644 command/rootchain/registration/params.go delete mode 100644 command/rootchain/registration/register_validator.go delete mode 100644 command/rootchain/rootchain.go delete mode 100644 command/rootchain/server/params.go delete mode 100644 command/rootchain/server/result.go delete mode 100644 command/rootchain/server/server.go delete mode 100644 command/rootchain/staking/params.go delete mode 100644 command/rootchain/staking/stake.go delete mode 100644 command/rootchain/supernet/params.go delete mode 100644 command/rootchain/supernet/stakemanager/params.go delete mode 100644 command/rootchain/supernet/stakemanager/params_test.go delete mode 100644 command/rootchain/supernet/stakemanager/stake_manager_deploy.go delete mode 100644 command/rootchain/supernet/supernet.go delete mode 100644 command/rootchain/validators/params.go delete mode 100644 command/rootchain/validators/validator_info.go delete mode 100644 command/rootchain/whitelist/params.go delete mode 100644 command/rootchain/whitelist/whitelist_validators.go delete mode 100644 command/rootchain/withdraw/params.go delete mode 100644 command/rootchain/withdraw/withdraw.go delete mode 100644 command/sidechain/helper.go delete mode 100644 command/sidechain/rewards/params.go delete mode 100644 command/sidechain/rewards/rewards.go delete mode 100644 command/sidechain/unstaking/params.go delete mode 100644 command/sidechain/unstaking/unstake.go delete mode 100644 command/sidechain/withdraw/params.go delete mode 100644 command/sidechain/withdraw/withdraw.go delete mode 100644 consensus/ibft/consensus_backend_test.go create mode 100644 consensus/ibft/consensus_no_unsafe_test.go create mode 100644 consensus/ibft/effective_power.go create mode 100644 consensus/ibft/effective_power_snapshot_height_test.go create mode 100644 consensus/ibft/effective_power_stake_test.go create mode 100644 consensus/ibft/ibft_config_validation_test.go create mode 100644 consensus/ibft/ibft_gate_alignment_test.go create mode 100644 consensus/ibft/ibft_hardening_test.go create mode 100644 consensus/ibft/ibft_parent_committed_seals_test.go create mode 100644 consensus/ibft/pos/epoch_validators.go create mode 100644 consensus/ibft/pos/epoch_validators_test.go create mode 100644 consensus/ibft/pos/events.go create mode 100644 consensus/ibft/pos/finalize.go create mode 100644 consensus/ibft/pos/finalize_epoch_rewards_test.go create mode 100644 consensus/ibft/pos/finalize_epoch_test.go create mode 100644 consensus/ibft/pos/finalize_test.go create mode 100644 consensus/ibft/pos/keys.go create mode 100644 consensus/ibft/pos/signer_test_helpers_test.go create mode 100644 consensus/ibft/pos/sys_account.go create mode 100644 consensus/ibft/pos/system_tx.go create mode 100644 consensus/ibft/pos/test_txn_helper_test.go create mode 100644 consensus/ibft/pos/uptime.go create mode 100644 consensus/ibft/pos/uptime_bls_test.go create mode 100644 consensus/ibft/pos/uptime_config.go create mode 100644 consensus/ibft/pos/uptime_config_test.go create mode 100644 consensus/ibft/pos/uptime_extra_decode_test.go create mode 100644 consensus/ibft/pos/uptime_microepoch.go create mode 100644 consensus/ibft/pos/uptime_microepoch_test.go create mode 100644 consensus/ibft/pos/uptime_record_test.go create mode 100644 consensus/ibft/pos/uptime_state_account.go create mode 100644 consensus/ibft/pos/uptime_test.go create mode 100644 consensus/ibft/pos/uptime_test_helpers_test.go create mode 100644 consensus/ibft/pos/uptime_weight.go create mode 100644 consensus/ibft/quorum_semantics_test.go create mode 100644 consensus/ibft/signer/extra_core_test.go delete mode 100644 consensus/ibft/signer/extra_test.go create mode 100644 consensus/ibft/signer/legacy_commit.go create mode 100644 consensus/ibft/weighted_quorum_state_test.go delete mode 100644 consensus/polybft/README.md delete mode 100644 consensus/polybft/bitmap/bitmap.go delete mode 100644 consensus/polybft/bitmap/bitmap_test.go delete mode 100644 consensus/polybft/block_builder.go delete mode 100644 consensus/polybft/block_builder_test.go delete mode 100644 consensus/polybft/blockchain_wrapper.go delete mode 100644 consensus/polybft/checkpoint_manager.go delete mode 100644 consensus/polybft/checkpoint_manager_test.go delete mode 100644 consensus/polybft/consensus_metrics.go delete mode 100644 consensus/polybft/consensus_runtime.go delete mode 100644 consensus/polybft/consensus_runtime_test.go delete mode 100644 consensus/polybft/contracts_initializer.go delete mode 100644 consensus/polybft/contractsapi/artifact/artifacts.go delete mode 100644 consensus/polybft/contractsapi/artifacts-gen/main.go delete mode 100644 consensus/polybft/contractsapi/bindings-gen/main.go delete mode 100644 consensus/polybft/contractsapi/contractsapi.go delete mode 100644 consensus/polybft/contractsapi/contractsapi_test.go delete mode 100644 consensus/polybft/contractsapi/decoder.go delete mode 100644 consensus/polybft/contractsapi/gen_sc_data.go delete mode 100644 consensus/polybft/contractsapi/helper.go delete mode 100644 consensus/polybft/contractsapi/init.go delete mode 100644 consensus/polybft/contractsapi/init_test.go delete mode 100644 consensus/polybft/contractsapi/test-contracts/TestRewardToken.json delete mode 100644 consensus/polybft/contractsapi/test-contracts/TestSimple.json delete mode 100644 consensus/polybft/contractsapi/test-contracts/TestSimple.sol delete mode 100644 consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.json delete mode 100644 consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.sol delete mode 100644 consensus/polybft/extra.go delete mode 100644 consensus/polybft/extra_test.go delete mode 100644 consensus/polybft/fsm.go delete mode 100644 consensus/polybft/fsm_test.go delete mode 100644 consensus/polybft/handlers.go delete mode 100644 consensus/polybft/hash.go delete mode 100644 consensus/polybft/hash_test.go delete mode 100644 consensus/polybft/helpers_test.go delete mode 100644 consensus/polybft/ibft_consensus.go delete mode 100644 consensus/polybft/mocks_test.go delete mode 100644 consensus/polybft/polybft.go delete mode 100644 consensus/polybft/polybft_config.go delete mode 100644 consensus/polybft/polybft_test.go delete mode 100644 consensus/polybft/proposer_calculator.go delete mode 100644 consensus/polybft/proposer_calculator_test.go delete mode 100644 consensus/polybft/proto/transport.pb.go delete mode 100644 consensus/polybft/proto/transport.pb.validate.go delete mode 100644 consensus/polybft/proto/transport.proto delete mode 100644 consensus/polybft/runtime_helpers.go delete mode 100644 consensus/polybft/runtime_helpers_test.go delete mode 100644 consensus/polybft/sc_integration_test.go delete mode 100644 consensus/polybft/signer/utils.go delete mode 100644 consensus/polybft/signer/utils_test.go delete mode 100644 consensus/polybft/stake_manager.go delete mode 100644 consensus/polybft/stake_manager_fuzz_test.go delete mode 100644 consensus/polybft/stake_manager_test.go delete mode 100644 consensus/polybft/state.go delete mode 100644 consensus/polybft/state_event_getter.go delete mode 100644 consensus/polybft/state_event_getter_test.go delete mode 100644 consensus/polybft/state_store_checkpoint.go delete mode 100644 consensus/polybft/state_store_checkpoint_test.go delete mode 100644 consensus/polybft/state_store_epoch.go delete mode 100644 consensus/polybft/state_store_epoch_test.go delete mode 100644 consensus/polybft/state_store_proposer_snapshot.go delete mode 100644 consensus/polybft/state_store_proposer_snapshot_test.go delete mode 100644 consensus/polybft/state_store_stake.go delete mode 100644 consensus/polybft/state_store_stake_test.go delete mode 100644 consensus/polybft/state_store_state_sync.go delete mode 100644 consensus/polybft/state_store_state_sync_test.go delete mode 100644 consensus/polybft/state_sync_commitment.go delete mode 100644 consensus/polybft/state_sync_commitment_test.go delete mode 100644 consensus/polybft/state_sync_manager.go delete mode 100644 consensus/polybft/state_sync_manager_test.go delete mode 100644 consensus/polybft/state_sync_relayer.go delete mode 100644 consensus/polybft/state_sync_relayer_test.go delete mode 100644 consensus/polybft/state_transaction.go delete mode 100644 consensus/polybft/state_transaction_test.go delete mode 100644 consensus/polybft/stats.go delete mode 100644 consensus/polybft/system_state.go delete mode 100644 consensus/polybft/system_state_test.go delete mode 100644 consensus/polybft/transport.go delete mode 100644 consensus/polybft/validator/genesis_validator.go delete mode 100644 consensus/polybft/validator/test_helpers.go delete mode 100644 consensus/polybft/validator/validator_metadata.go delete mode 100644 consensus/polybft/validator/validator_metadata_test.go delete mode 100644 consensus/polybft/validator/validator_set.go delete mode 100644 consensus/polybft/validator/validator_set_delta.go delete mode 100644 consensus/polybft/validator/validator_set_delta_test.go delete mode 100644 consensus/polybft/validator/validator_set_test.go delete mode 100644 consensus/polybft/validators_snapshot.go delete mode 100644 consensus/polybft/validators_snapshot_test.go delete mode 100644 consensus/polybft/wallet/account.go delete mode 100644 consensus/polybft/wallet/account_test.go delete mode 100644 consensus/polybft/wallet/key.go delete mode 100644 consensus/polybft/wallet/key_test.go create mode 100644 contracts/staking/StakingV2.sol delete mode 100644 docker/README.md delete mode 100644 docs/Dockerfile delete mode 100644 docs/README.md delete mode 100644 docs/docker-compose.yml delete mode 100644 docs/docs/api/json-rpc-bridge.md delete mode 100644 docs/docs/api/json-rpc-debug.md delete mode 100644 docs/docs/api/json-rpc-eth.md delete mode 100644 docs/docs/api/json-rpc-net.md delete mode 100644 docs/docs/api/json-rpc-txpool.md delete mode 100644 docs/docs/api/json-rpc-web3.md delete mode 100644 docs/docs/api/json-rpc-xgr.md delete mode 100644 docs/docs/design/blockchain.md delete mode 100644 docs/docs/design/bridge/assets/erc/erc1155.md delete mode 100644 docs/docs/design/bridge/assets/erc/erc20.md delete mode 100644 docs/docs/design/bridge/assets/erc/erc721.md delete mode 100644 docs/docs/design/bridge/checkpoint.md delete mode 100644 docs/docs/design/bridge/overview.md delete mode 100644 docs/docs/design/bridge/sequences.md delete mode 100644 docs/docs/design/bridge/statesync.md delete mode 100644 docs/docs/design/consensus/polybft/ibft.md delete mode 100644 docs/docs/design/consensus/polybft/overview.md delete mode 100644 docs/docs/design/consensus/polybft/protocol.md delete mode 100644 docs/docs/design/grpc.md delete mode 100644 docs/docs/design/jsonrpc.md delete mode 100644 docs/docs/design/libp2p.md delete mode 100644 docs/docs/design/mempool.md delete mode 100644 docs/docs/design/overview.md delete mode 100644 docs/docs/design/runtime/allowlist.md delete mode 100644 docs/docs/design/runtime/overview.md delete mode 100644 docs/docs/design/txpool.md delete mode 100644 docs/docs/design/txrelayer.md delete mode 100644 docs/docs/disclaimer.md delete mode 100644 docs/docs/img/edge/bridge-deposit-childchain.excalidraw.png delete mode 100644 docs/docs/img/edge/bridge-deposit-rootchain.excalidraw.png delete mode 100644 docs/docs/img/edge/bridge-withdraw-childchain.excalidraw.png delete mode 100644 docs/docs/img/edge/bridge-withdraw-rootchain.excalidraw.png delete mode 100644 docs/docs/img/edge/consensus.excalidraw.png delete mode 100644 docs/docs/img/edge/contracts-bridge.excalidraw.png delete mode 100644 docs/docs/img/edge/contracts.excalidraw.png delete mode 100644 docs/docs/img/edge/l1-l2-l3.excalidraw.png delete mode 100644 docs/docs/img/edge/polybft-node.excalidraw.png delete mode 100644 docs/docs/img/edge/polybft.excalidraw.png delete mode 100644 docs/docs/img/edge/supernet.svg delete mode 100644 docs/docs/img/edge/supernets-checkpoint-exit.excalidraw.png delete mode 100644 docs/docs/img/edge/supernets-checkpoint.excalidraw.png delete mode 100644 docs/docs/img/edge/supernets-consensus.excalidraw.png delete mode 100644 docs/docs/img/edge/supernets-interconnected.excalidraw.png delete mode 100644 docs/docs/img/edge/supernets-logo.png delete mode 100644 docs/docs/img/edge/supernets-overview.excalidraw.png delete mode 100644 docs/docs/img/edge/supernets-setup-non.excalidraw.png delete mode 100644 docs/docs/img/edge/supernets-setup.excalidraw.png delete mode 100644 docs/docs/img/edge/supernets-together-lp.excalidraw.png delete mode 100644 docs/docs/img/edge/supernets-together.excalidraw.png delete mode 100644 docs/docs/img/edge/val-select.excalidraw.png delete mode 100644 docs/docs/index.md delete mode 100644 docs/docs/interfaces/cryptography/bls.md delete mode 100644 docs/docs/interfaces/cryptography/bn256g2.md delete mode 100644 docs/docs/interfaces/eip1559.md delete mode 100644 docs/docs/interfaces/erc1155/childerc1155-predicate.md delete mode 100644 docs/docs/interfaces/erc1155/childerc1155.md delete mode 100644 docs/docs/interfaces/erc1155/rooterc1155-predicate.md delete mode 100644 docs/docs/interfaces/erc20/childerc20-predicate.md delete mode 100644 docs/docs/interfaces/erc20/childerc20.md delete mode 100644 docs/docs/interfaces/erc20/native-erc20.md delete mode 100644 docs/docs/interfaces/erc20/rooterc20-predicate.md delete mode 100644 docs/docs/interfaces/erc721/childerc721-predicate.md delete mode 100644 docs/docs/interfaces/erc721/childerc721.md delete mode 100644 docs/docs/interfaces/erc721/rooterc721-predicate.md delete mode 100644 docs/docs/interfaces/network/checkpoint-manager.md delete mode 100644 docs/docs/interfaces/network/exit-helper.md delete mode 100644 docs/docs/interfaces/network/state-receiver.md delete mode 100644 docs/docs/interfaces/network/state-sender.md delete mode 100644 docs/docs/interfaces/staking/custom-supernet-manager.md delete mode 100644 docs/docs/interfaces/staking/stake-manager.md delete mode 100644 docs/docs/interfaces/staking/supernet-manager.md delete mode 100644 docs/docs/interfaces/validators/validator-set-base.md delete mode 100644 docs/docs/operate/benchmarks.md delete mode 100644 docs/docs/operate/deploy/access-control/allowlist-general.md delete mode 100644 docs/docs/operate/deploy/genesis-validators.md delete mode 100644 docs/docs/operate/deploy/genesis.md delete mode 100644 docs/docs/operate/deploy/index.md delete mode 100644 docs/docs/operate/deploy/local-chain.md delete mode 100644 docs/docs/operate/deploy/rootchain-config.md delete mode 100644 docs/docs/operate/deploy/staking/stake.md delete mode 100644 docs/docs/operate/deploy/staking/unstake.md delete mode 100644 docs/docs/operate/deploy/start-chain.md delete mode 100644 docs/docs/operate/deploy/transfers/deposit.md delete mode 100644 docs/docs/operate/deploy/transfers/withdraw.md delete mode 100644 docs/docs/operate/deploy/upgrades/hardfork.md delete mode 100644 docs/docs/operate/deploy/upgrades/v1.1.md delete mode 100644 docs/docs/operate/install.md delete mode 100644 docs/docs/operate/param-reference.md delete mode 100644 docs/docs/operate/system.md delete mode 100644 docs/docs/quickstart.md delete mode 100644 docs/mkdocs.yml delete mode 100644 docs/requirements.txt delete mode 100644 docs/run.sh delete mode 100644 e2e-polybft/e2e/README.md delete mode 100644 e2e-polybft/e2e/acls_test.go delete mode 100644 e2e-polybft/e2e/bridge_test.go delete mode 100644 e2e-polybft/e2e/burn_contract_test.go delete mode 100644 e2e-polybft/e2e/consensus_test.go delete mode 100644 e2e-polybft/e2e/helpers_test.go delete mode 100644 e2e-polybft/e2e/jsonrpc_test.go delete mode 100644 e2e-polybft/e2e/migration_test.go delete mode 100644 e2e-polybft/e2e/network_test.go delete mode 100644 e2e-polybft/e2e/txpool_test.go delete mode 100644 e2e-polybft/framework/node.go delete mode 100644 e2e-polybft/framework/test-bridge.go delete mode 100644 e2e-polybft/framework/test-cluster.go delete mode 100644 e2e-polybft/framework/test-server.go delete mode 100644 e2e-polybft/grpc_validation_test.go delete mode 100644 e2e-polybft/property/property_test.go create mode 100644 e2e/TEST_CATALOG.md create mode 100644 e2e/framework/ibft_test.go create mode 100644 e2e/framework/slashing.go create mode 100644 e2e/poa_pos_cutover_boundary_test.go create mode 100644 e2e/pos_micro_weight_decay_restore_test.go delete mode 100644 e2e/pos_poa_switch_test.go create mode 100644 e2e/pos_restart_resync_test.go create mode 100644 e2e/pos_smallset_notbetrieb_test.go create mode 100644 e2e/pos_stakingv2_delegation_lifecycle_e2e_test.go create mode 100644 e2e/pos_stakingv2_delegation_pool_edge_e2e_test.go create mode 100644 e2e/pos_stakingv2_delegation_pool_test.go create mode 100644 e2e/pos_stakingv2_filter_test.go create mode 100644 e2e/pos_stakingv2_lifecycle_test.go create mode 100644 e2e/pos_stakingv2_reentrancy_helpers_test.go delete mode 100644 e2e/pos_test.go create mode 100644 helper/secrets/secrets.go create mode 100644 helper/staking/staking_test.go create mode 100644 jsonrpc/eth_pos_microepoch_status.go create mode 100644 jsonrpc/eth_pos_microepoch_status_test.go create mode 100644 jsonrpc/eth_pos_overview.go create mode 100644 jsonrpc/eth_pos_overview_test.go mode change 100644 => 100755 scripts/cluster create mode 100755 scripts/monitor_balances.sh create mode 100644 validators/store/contract/minstake_test.go diff --git a/back_Makefile b/back_Makefile deleted file mode 100644 index ec9cf63..0000000 --- a/back_Makefile +++ /dev/null @@ -1,224 +0,0 @@ -# Makefile (XGRChain) -# - lean: only build / test / lint / proto / contracts / deploy -# - engine mode: -# make xgr-build # default: stub -# make xgr-build embedded # embedded + build tag -# make xgr-deploy-all # default: stub -# make xgr-deploy-all embedded # embedded + build tag - -SHELL := /bin/bash - -# ----------------------------- -# TOOLING CHECKS -# ----------------------------- -.PHONY: check-git check-go check-protoc check-lint check-npm - -check-git: - @command -v git >/dev/null || (echo "git is not installed. Please install and try again."; exit 1) - -check-go: - @command -v go >/dev/null || (echo "go is not installed. Please install and try again."; exit 1) - -check-protoc: - @command -v protoc >/dev/null || (echo "protoc is not installed. Please install and try again."; exit 1) - -check-lint: - @command -v golangci-lint >/dev/null || (echo "golangci-lint is not installed. Please install and try again."; exit 1) - -check-npm: - @command -v npm >/dev/null || (echo "npm is not installed. Please install and try again."; exit 1) - -# ----------------------------- -# SUBMODULES -# ----------------------------- -.PHONY: download-submodules -download-submodules: check-git - git submodule init - git submodule update - -# ----------------------------- -# PROTO / CODEGEN -# ----------------------------- -.PHONY: protoc -protoc: check-protoc - protoc --go_out=. --go-grpc_out=. -I . -I=./validate --validate_out="lang=go:." \ - ./server/proto/*.proto \ - ./network/proto/*.proto \ - ./txpool/proto/*.proto \ - ./consensus/ibft/**/*.proto \ - ./consensus/polybft/**/*.proto - -# ----------------------------- -# LINT / TEST -# ----------------------------- -.PHONY: lint test fuzz-test test-e2e test-e2e-polybft test-property-polybft - -lint: check-lint - golangci-lint run --config .golangci.yml - -test: check-go - go test -race -shuffle=on -coverprofile coverage.out -timeout 20m `go list ./... | grep -v e2e` - -fuzz-test: check-go - ./scripts/fuzzAll - -test-e2e: check-go - go build -race -o artifacts/polygon-edge . - env EDGE_BINARY=${PWD}/artifacts/polygon-edge go test -v -timeout=30m ./e2e/... - -test-e2e-polybft: check-go - go build -o artifacts/polygon-edge . - env EDGE_BINARY=${PWD}/artifacts/polygon-edge E2E_TESTS=true E2E_LOGS=true \ - go test -v -timeout=1h30m ./e2e-polybft/e2e/... - -test-property-polybft: check-go - go build -o artifacts/polygon-edge . - env EDGE_BINARY=${PWD}/artifacts/polygon-edge E2E_TESTS=true E2E_LOGS=true \ - go test -v -timeout=30m ./e2e-polybft/property/... -rapid.checks=10 - -# ----------------------------- -# CONTRACTS / BINDINGS -# ----------------------------- -.PHONY: compile-core-contracts generate-smart-contract-bindings - -compile-core-contracts: check-npm - cd core-contracts && npm install && npm run compile - $(MAKE) generate-smart-contract-bindings - -generate-smart-contract-bindings: check-go - go run ./consensus/polybft/contractsapi/artifacts-gen/main.go - go run ./consensus/polybft/contractsapi/bindings-gen/main.go - -# ----------------------------- -# LICENSES -# ----------------------------- -.PHONY: generate-bsd-licenses -generate-bsd-licenses: check-git - ./generate_dependency_licenses.sh BSD-3-Clause,BSD-2-Clause > ./licenses/bsd_licenses.json - -# ----------------------------- -# XGRCHAIN CUSTOM BUILD & DEPLOY -# ----------------------------- -BINARY_NAME := xgrchain -BUILD_DIR := bin -BUILD_PATH := $(BUILD_DIR)/$(BINARY_NAME) -PKG_PATH := github.com/xgr-network/xgr-node/versioning - -GENESIS_SRC := $(shell pwd)/genesis.json -GENESIS_DEST := ~/xgrchain/genesis.json - -MAIN_NODE := 87.106.3.182 -REMOTE_NODES := \ - xgradmin@87.106.3.183 \ - xgradmin@87.106.3.180 \ - xgradmin@87.106.3.177 - -# Engine selection via extra goal token: -# make xgr-build -> ENGINE=stub -# make xgr-build embedded -> ENGINE=embedded -# make xgr-build stub -> ENGINE=stub -ENGINE ?= stub -ENGINE_GOAL := $(filter embedded stub,$(MAKECMDGOALS)) -ifneq ($(ENGINE_GOAL),) -ENGINE := $(ENGINE_GOAL) -endif - -.PHONY: embedded stub -embedded stub: - @: - -XGR_ENGINE_MODE := $(ENGINE) -ENGINE_TAGS := $(if $(filter embedded,$(ENGINE)),-tags engine_embedded,) - -.PHONY: xgr-build -xgr-build: check-go check-git - @echo "πŸ”¨ Building $(BINARY_NAME) [engine=$(ENGINE)]..." - @mkdir -p $(BUILD_DIR) - @COMMIT=$$(git rev-parse --short HEAD) && \ - BRANCH=$$(git rev-parse --abbrev-ref HEAD) && \ - TIME=$$(date -u +"%Y-%m-%dT%H:%M:%SZ") && \ - VERSION="v1.0-$$(date +%Y%m%d)-$$COMMIT" && \ - echo "πŸ“¦ Version: $$VERSION" && \ - GOOS=linux GOARCH=amd64 \ - XGR_ENGINE_MODE=$(XGR_ENGINE_MODE) \ - go build $(ENGINE_TAGS) -o $(BUILD_PATH) \ - -ldflags "-X $(PKG_PATH).Version=$$VERSION -X $(PKG_PATH).Commit=$$COMMIT -X $(PKG_PATH).Branch=$$BRANCH -X $(PKG_PATH).BuildTime=$$TIME" \ - main.go && \ - echo "βœ… Build complete: $(BUILD_PATH)" - -BUILD_PATH_DEBUG := $(BUILD_DIR)/$(BINARY_NAME).debug - -.PHONY: xgr-build-debug -xgr-build-debug: check-go check-git - @echo "πŸͺ² Building $(BINARY_NAME) (DEBUG -N -l) [engine=$(ENGINE)]..." - @mkdir -p $(BUILD_DIR) - @COMMIT=$$(git rev-parse --short HEAD) && \ - BRANCH=$$(git rev-parse --abbrev-ref HEAD) && \ - TIME=$$(date -u +"%Y-%m-%dT%H:%M:%SZ") && \ - VERSION="v1.0-$$(date +%Y%m%d)-$$COMMIT" && \ - echo "πŸ“¦ Version: $$VERSION" && \ - GOOS=linux GOARCH=amd64 \ - XGR_ENGINE_MODE=$(XGR_ENGINE_MODE) \ - go build $(ENGINE_TAGS) \ - -gcflags="all=-N -l" \ - -o "$(BUILD_PATH_DEBUG)" \ - -ldflags "-X $(PKG_PATH).Version=$$VERSION -X $(PKG_PATH).Commit=$$COMMIT -X $(PKG_PATH).Branch=$$BRANCH -X $(PKG_PATH).BuildTime=$$TIME" \ - main.go && \ - echo "βœ… Debug build complete: $(BUILD_PATH_DEBUG)" - -.PHONY: xgr-install-local -xgr-install-local: - @echo "πŸ“¦ Installing locally on MAIN_NODE ($(MAIN_NODE))..." - sudo mkdir -p /usr/local/bin - sudo cp $(BUILD_PATH) /usr/local/bin/$(BINARY_NAME) - sudo chmod +x /usr/local/bin/$(BINARY_NAME) - mkdir -p ~/xgrchain - cp $(GENESIS_SRC) $(GENESIS_DEST) - @echo "βœ… Genesis-Datei wurde in $(GENESIS_DEST) kopiert" - @echo "βœ… Installed locally" - -.PHONY: xgr-deploy-remote -xgr-deploy-remote: - @echo "πŸš€ Deploying to remote validator nodes..." - @for host in $(REMOTE_NODES); do \ - echo "β†’ Copying binary to $$host..."; \ - scp $(BUILD_PATH) $$host:/tmp/$(BINARY_NAME); \ - ssh -tt $$host "sudo mv /tmp/$(BINARY_NAME) /usr/local/bin/$(BINARY_NAME) && sudo chmod +x /usr/local/bin/$(BINARY_NAME)"; \ - echo "β†’ Copying genesis file to $$host..."; \ - scp $(GENESIS_SRC) $$host:/tmp/genesis-mainnet.json; \ - ssh -tt $$host "mkdir -p ~/xgrchain && mv /tmp/genesis-mainnet.json $(GENESIS_DEST)"; \ - echo " βœ… Deployed to $$host"; \ - done - -.PHONY: xgr-deploy-all -xgr-deploy-all: xgr-build xgr-install-local xgr-deploy-remote - -# ----------------------------- -# HELP -# ----------------------------- -.PHONY: help -help: - @echo "Available targets:" - @printf " %-28s - %s\n" "download-submodules" "Initialize and update Git submodules" - @printf " %-28s - %s\n" "protoc" "Compile Protocol Buffers files" - @printf " %-28s - %s\n" "lint" "Run linters on the codebase" - @printf " %-28s - %s\n" "test" "Run unit tests" - @printf " %-28s - %s\n" "fuzz-test" "Run fuzz tests" - @printf " %-28s - %s\n" "test-e2e" "Run end-to-end tests" - @printf " %-28s - %s\n" "test-e2e-polybft" "Run end-to-end tests for PolyBFT" - @printf " %-28s - %s\n" "test-property-polybft" "Run property tests for PolyBFT" - @printf " %-28s - %s\n" "compile-core-contracts" "Compile core contracts" - @printf " %-28s - %s\n" "generate-smart-contract-bindings" "Generate smart contract bindings" - @printf " %-28s - %s\n" "generate-bsd-licenses" "Generate BSD licenses JSON" - @printf " %-28s - %s\n" "xgr-build" "Build xgrchain (default engine=stub)" - @printf " %-28s - %s\n" "xgr-build-debug" "Build xgrchain debug (-N -l)" - @printf " %-28s - %s\n" "xgr-install-local" "Install binary + genesis locally" - @printf " %-28s - %s\n" "xgr-deploy-remote" "Deploy binary + genesis to validator nodes" - @printf " %-28s - %s\n" "xgr-deploy-all" "Build + install local + deploy remote" - @echo "" - @echo "Engine mode usage:" - @echo " make xgr-build # stub (default)" - @echo " make xgr-build embedded # embedded + -tags engine_embedded" - @echo " make xgr-deploy-all # stub (default)" - @echo " make xgr-deploy-all embedded" - diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 22dbd8a..1ed754e 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -115,13 +115,13 @@ func (b *Blockchain) updateGasPriceAvg(newValues []*big.Int) { sum = sum.Add(sum, val) } if len(newValues) == 0 { - // Kein neuer Wert, setze Default falls kein alter Wert existiert + // No new value; set a default only when no previous value exists if b.gpAverage.count.Uint64() == 0 { b.gpAverage.price = big.NewInt(int64(chain.MinBaseFee)) b.gpAverage.count = big.NewInt(1) return } - // Ansonsten nichts Γ€ndern + // Otherwise keep the current value unchanged return } @@ -1417,12 +1417,12 @@ func mulDivClampU64(a, b, c, d uint64) uint64 { // CalculateBaseFee calculates the basefee of the header. func (b *Blockchain) CalculateBaseFee(parent *types.Header) uint64 { - // Minimum BaseFee (statisch oder dynamisch) + // Minimum base fee (static or dynamic) minBaseFee := b.resolveMinBaseFee(parent) var baseFee uint64 - // Genesis oder erster London-Block? + // Genesis or first London block? if parent.BaseFee == 0 { if b.config.Genesis.BaseFee > 0 { baseFee = b.config.Genesis.BaseFee @@ -1466,7 +1466,7 @@ func (b *Blockchain) CalculateBaseFee(parent *types.Header) uint64 { } } - // Sicherheitsanker: niemals unter minBaseFee + // Safety guard: never go below minBaseFee. if baseFee < minBaseFee { return minBaseFee } diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index edfc5c3..67e6ab3 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -1494,7 +1494,7 @@ func TestBlockchain_WriteFullBlock(t *testing.T) { Transactions: []*types.Transaction{tx}, }, Receipts: receipts, - }, "polybft") + }, "ibft") require.NoError(t, err) require.Equal(t, 0, len(db)) @@ -1507,7 +1507,7 @@ func TestBlockchain_WriteFullBlock(t *testing.T) { Transactions: []*types.Transaction{tx}, }, Receipts: receipts, - }, "polybft") + }, "ibft") require.NoError(t, err) require.Equal(t, 8, len(db)) diff --git a/bls/private.go b/bls/private.go index b33e8a7..0ef8d26 100644 --- a/bls/private.go +++ b/bls/private.go @@ -33,7 +33,7 @@ func UnmarshalPrivateKey(data []byte) (*PrivateKey, error) { } // Otherwise, trying to assume the given data is a hex encoded big int represented as a bytes array. - // This is needed in order to be compatible with the currently stored polybft BLS keys. + // This is needed in order to be compatible with the currently stored legacy BLS keys. var err error if pk.s, err = hex.DecodeHexToBig(string(data)); err != nil { return nil, err diff --git a/chain/params.go b/chain/params.go index 6acd3dd..cc2189b 100644 --- a/chain/params.go +++ b/chain/params.go @@ -111,6 +111,7 @@ const ( EIP2929 = "EIP2929" EIP2930 = "EIP2930" EIP3651 = "EIP3651" + FeePoolSplit = "feePoolSplit" ) // Forks is map which contains all forks and their starting blocks from genesis @@ -153,6 +154,7 @@ func (f *Forks) At(block uint64) ForksInTime { EIP2929: f.IsActive(EIP2929, block), EIP2930: f.IsActive(EIP2930, block), EIP3651: f.IsActive(EIP3651, block), + FeePoolSplit: f.IsActive(FeePoolSplit, block), } } @@ -205,7 +207,7 @@ type ForksInTime struct { EIP155, QuorumCalcAlignment, TxHashWithType, - LondonFix, EIP3860, EIP2929, EIP2930, EIP3651 bool + LondonFix, EIP3860, EIP2929, EIP2930, EIP3651, FeePoolSplit bool } // AllForksEnabled should contain all supported forks by current edge version diff --git a/command/bridge/README.md b/command/bridge/README.md deleted file mode 100644 index c5d1fe5..0000000 --- a/command/bridge/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Bridge helper command - -This is a helper command, which allows sending deposits from root to child chain and make withdrawals from child chain to root chain. - -## Deposit ERC20 - -This is a helper command which deposits ERC20 tokens from the root chain to the child chain - -```bash -$ xgrchain bridge deposit-erc20 \ - --sender-key \ - --receivers \ - --amounts \ - --root-token \ - --root-predicate \ - --json-rpc - [--minter-key ] -``` - -**Note:** in case `minter-key` is provided, tokens are going to be minted to sender account. Note that provided minter private key must belong to the account which has minter role. - -## Withdraw ERC20 - -This is a helper command which withdraws ERC20 tokens from the child chain to the root chain - -```bash -$ xgrchain bridge withdraw-erc20 \ - --sender-key \ - --receivers \ - --amounts \ - --child-predicate \ - [--child-token ] \ - --json-rpc -``` - -## Exit - -This is a helper command which qeuries child chain for exit event proof and sends an exit transaction to ExitHelper smart contract. - -```bash -$ xgrchain bridge exit \ - --sender-key \ - --exit-helper \ - --exit-id \ - --root-json-rpc \ - --child-json-rpc -``` - -**Note:** for using test account provided by Geth dev instance, use `--test` flag. In that case `--sender-key` flag can be omitted and test account is used as an exit transaction sender. diff --git a/command/bridge/bridge.go b/command/bridge/bridge.go deleted file mode 100644 index 414e26e..0000000 --- a/command/bridge/bridge.go +++ /dev/null @@ -1,47 +0,0 @@ -package bridge - -import ( - "github.com/spf13/cobra" - - depositERC1155 "github.com/xgr-network/xgr-node/command/bridge/deposit/erc1155" - depositERC20 "github.com/xgr-network/xgr-node/command/bridge/deposit/erc20" - depositERC721 "github.com/xgr-network/xgr-node/command/bridge/deposit/erc721" - "github.com/xgr-network/xgr-node/command/bridge/exit" - "github.com/xgr-network/xgr-node/command/bridge/mint" - withdrawERC1155 "github.com/xgr-network/xgr-node/command/bridge/withdraw/erc1155" - withdrawERC20 "github.com/xgr-network/xgr-node/command/bridge/withdraw/erc20" - withdrawERC721 "github.com/xgr-network/xgr-node/command/bridge/withdraw/erc721" -) - -// GetCommand creates "bridge" helper command -func GetCommand() *cobra.Command { - bridgeCmd := &cobra.Command{ - Use: "bridge", - Short: "Top level bridge command.", - } - - registerSubcommands(bridgeCmd) - - return bridgeCmd -} - -func registerSubcommands(baseCmd *cobra.Command) { - baseCmd.AddCommand( - // bridge deposit-erc20 - depositERC20.GetCommand(), - // bridge deposit-erc721 - depositERC721.GetCommand(), - // bridge deposit-erc1155 - depositERC1155.GetCommand(), - // bridge withdraw-erc20 - withdrawERC20.GetCommand(), - // bridge withdraw-erc721 - withdrawERC721.GetCommand(), - // bridge withdraw-erc1155 - withdrawERC1155.GetCommand(), - // bridge exit - exit.GetCommand(), - // bridge mint erc-20 - mint.GetCommand(), - ) -} diff --git a/command/bridge/common/params.go b/command/bridge/common/params.go deleted file mode 100644 index 712b204..0000000 --- a/command/bridge/common/params.go +++ /dev/null @@ -1,296 +0,0 @@ -package common - -import ( - "bytes" - "errors" - "fmt" - "math/big" - "strings" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - - cmdHelper "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -type TokenType int - -const ( - ERC20 TokenType = iota - ERC721 - ERC1155 -) - -const ( - SenderKeyFlag = "sender-key" - ReceiversFlag = "receivers" - AmountsFlag = "amounts" - TokenIDsFlag = "token-ids" - RootTokenFlag = "root-token" - RootPredicateFlag = "root-predicate" - ChildPredicateFlag = "child-predicate" - ChildTokenFlag = "child-token" - JSONRPCFlag = "json-rpc" - ChildChainMintableFlag = "child-chain-mintable" - - MinterKeyFlag = "minter-key" - MinterKeyFlagDesc = "minter key is the account which is able to mint tokens to sender account " + - "(if provided tokens are minted prior to depositing)" -) - -var ( - errInconsistentAmounts = errors.New("receivers and amounts must be equal length") - errInconsistentTokenIds = errors.New("receivers and token ids must be equal length") -) - -type BridgeParams struct { - SenderKey string - Receivers []string - TokenAddr string - PredicateAddr string - JSONRPCAddr string - ChildChainMintable bool -} - -// RegisterCommonFlags registers common bridge flags to a given command -func (p *BridgeParams) RegisterCommonFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - &p.SenderKey, - SenderKeyFlag, - "", - "hex encoded private key of the account which sends bridge transactions", - ) - - cmd.Flags().StringSliceVar( - &p.Receivers, - ReceiversFlag, - nil, - "receiving accounts addresses", - ) - - cmd.Flags().StringVar( - &p.JSONRPCAddr, - JSONRPCFlag, - txrelayer.DefaultRPCAddress, - "the JSON RPC endpoint", - ) - - cmd.Flags().BoolVar( - &p.ChildChainMintable, - ChildChainMintableFlag, - false, - "flag indicating whether tokens originate from child chain", - ) -} - -func (p *BridgeParams) Validate() error { - if p == nil { - return nil - } - - _, err := cmdHelper.ParseJSONRPCAddress(p.JSONRPCAddr) - if err != nil { - return fmt.Errorf("failed to parse json rpc address. Error: %w", err) - } - - return nil -} - -type ERC20BridgeParams struct { - *BridgeParams - Amounts []string -} - -func NewERC20BridgeParams() *ERC20BridgeParams { - return &ERC20BridgeParams{BridgeParams: &BridgeParams{}} -} - -func (bp *ERC20BridgeParams) Validate() error { - if err := bp.BridgeParams.Validate(); err != nil { - return err - } - - if len(bp.Receivers) != len(bp.Amounts) { - return errInconsistentAmounts - } - - return nil -} - -type ERC721BridgeParams struct { - *BridgeParams - TokenIDs []string -} - -func NewERC721BridgeParams() *ERC721BridgeParams { - return &ERC721BridgeParams{BridgeParams: &BridgeParams{}} -} - -func (bp *ERC721BridgeParams) Validate() error { - if err := bp.BridgeParams.Validate(); err != nil { - return err - } - - if len(bp.Receivers) != len(bp.TokenIDs) { - return errInconsistentTokenIds - } - - return nil -} - -type ERC1155BridgeParams struct { - *BridgeParams - Amounts []string - TokenIDs []string -} - -func NewERC1155BridgeParams() *ERC1155BridgeParams { - return &ERC1155BridgeParams{BridgeParams: &BridgeParams{}} -} - -func (bp *ERC1155BridgeParams) Validate() error { - if err := bp.BridgeParams.Validate(); err != nil { - return err - } - - if len(bp.Receivers) != len(bp.Amounts) { - return errInconsistentAmounts - } - - if len(bp.Receivers) != len(bp.TokenIDs) { - return errInconsistentTokenIds - } - - return nil -} - -// ExtractExitEventIDs tries to extract all exit event ids from provided receipt -func ExtractExitEventIDs(receipt *ethgo.Receipt) ([]*big.Int, error) { - exitEventIDs := make([]*big.Int, 0, len(receipt.Logs)) - - for _, log := range receipt.Logs { - var exitEvent contractsapi.L2StateSyncedEvent - - doesMatch, err := exitEvent.ParseLog(log) - if err != nil { - return nil, err - } - - if !doesMatch { - continue - } - - exitEventIDs = append(exitEventIDs, exitEvent.ID) - } - - if len(exitEventIDs) != 0 { - return exitEventIDs, nil - } - - return nil, errors.New("failed to find exit event log") -} - -// ExtractChildTokenAddr extracts predicted deterministic child token address -func ExtractChildTokenAddr(receipt *ethgo.Receipt, childChainMintable bool) (*types.Address, error) { - var ( - l1TokenMapped contractsapi.TokenMappedEvent - l2TokenMapped contractsapi.L2MintableTokenMappedEvent - ) - - for _, log := range receipt.Logs { - if childChainMintable { - doesMatch, err := l2TokenMapped.ParseLog(log) - if err != nil { - return nil, err - } - - if !doesMatch { - continue - } - - return &l2TokenMapped.ChildToken, nil - } else { - doesMatch, err := l1TokenMapped.ParseLog(log) - if err != nil { - return nil, err - } - - if !doesMatch { - continue - } - - return &l1TokenMapped.ChildToken, nil - } - } - - return nil, nil -} - -type BridgeTxResult struct { - Sender string `json:"sender"` - Receivers []string `json:"receivers"` - ExitEventIDs []*big.Int `json:"exitEventIDs"` - Amounts []string `json:"amounts"` - TokenIDs []string `json:"tokenIds"` - BlockNumbers []uint64 `json:"blockNumbers"` - ChildTokenAddr *types.Address `json:"childTokenAddr"` - - Title string `json:"title"` -} - -func (r *BridgeTxResult) GetOutput() string { - var buffer bytes.Buffer - - vals := make([]string, 0, 7) - vals = append(vals, fmt.Sprintf("Sender|%s", r.Sender)) - vals = append(vals, fmt.Sprintf("Receivers|%s", strings.Join(r.Receivers, ", "))) - - if len(r.Amounts) > 0 { - vals = append(vals, fmt.Sprintf("Amounts|%s", strings.Join(r.Amounts, ", "))) - } - - if len(r.TokenIDs) > 0 { - vals = append(vals, fmt.Sprintf("Token Ids|%s", strings.Join(r.TokenIDs, ", "))) - } - - if len(r.ExitEventIDs) > 0 { - var buf bytes.Buffer - - for i, id := range r.ExitEventIDs { - buf.WriteString(id.String()) - - if i != len(r.ExitEventIDs)-1 { - buf.WriteString(", ") - } - } - - vals = append(vals, fmt.Sprintf("Exit Event IDs|%s", buf.String())) - } - - if len(r.BlockNumbers) > 0 { - var buf bytes.Buffer - - for i, blockNum := range r.BlockNumbers { - buf.WriteString(fmt.Sprintf("%d", blockNum)) - - if i != len(r.BlockNumbers)-1 { - buf.WriteString(", ") - } - } - - vals = append(vals, fmt.Sprintf("Inclusion Block Numbers|%s", buf.String())) - } - - if r.ChildTokenAddr != nil { - vals = append(vals, fmt.Sprintf("Child Token Address|%s", (*r.ChildTokenAddr).String())) - } - - _, _ = buffer.WriteString(fmt.Sprintf("\n[%s]\n", r.Title)) - _, _ = buffer.WriteString(cmdHelper.FormatKV(vals)) - _, _ = buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/bridge/deposit/erc1155/deposit_erc1155.go b/command/bridge/deposit/erc1155/deposit_erc1155.go deleted file mode 100644 index c4b141f..0000000 --- a/command/bridge/deposit/erc1155/deposit_erc1155.go +++ /dev/null @@ -1,305 +0,0 @@ -package erc1155 - -import ( - "fmt" - "math/big" - "strings" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/bridge/common" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - helperCommon "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -type depositERC1155Params struct { - *common.ERC1155BridgeParams - minterKey string -} - -var ( - // depositParams is abstraction for provided bridge parameter values - dp *depositERC1155Params = &depositERC1155Params{ERC1155BridgeParams: common.NewERC1155BridgeParams()} -) - -// GetCommand returns the bridge deposit command -func GetCommand() *cobra.Command { - depositCmd := &cobra.Command{ - Use: "deposit-erc1155", - Short: "Deposits ERC 1155 tokens from the origin to the destination chain", - PreRunE: preRunCommand, - Run: runCommand, - } - - dp.RegisterCommonFlags(depositCmd) - - depositCmd.Flags().StringSliceVar( - &dp.Amounts, - common.AmountsFlag, - nil, - "amounts that are sent to the receivers accounts", - ) - - depositCmd.Flags().StringSliceVar( - &dp.TokenIDs, - common.TokenIDsFlag, - nil, - "token ids that are sent to the receivers accounts", - ) - - depositCmd.Flags().StringVar( - &dp.TokenAddr, - common.RootTokenFlag, - "", - "root ERC 1155 token address", - ) - - depositCmd.Flags().StringVar( - &dp.PredicateAddr, - common.RootPredicateFlag, - "", - "root ERC 1155 token predicate address", - ) - - depositCmd.Flags().StringVar( - &dp.minterKey, - common.MinterKeyFlag, - "", - common.MinterKeyFlagDesc, - ) - - _ = depositCmd.MarkFlagRequired(common.ReceiversFlag) - _ = depositCmd.MarkFlagRequired(common.AmountsFlag) - _ = depositCmd.MarkFlagRequired(common.TokenIDsFlag) - _ = depositCmd.MarkFlagRequired(common.RootTokenFlag) - _ = depositCmd.MarkFlagRequired(common.RootPredicateFlag) - - return depositCmd -} - -func preRunCommand(_ *cobra.Command, _ []string) error { - return dp.Validate() -} - -func runCommand(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - depositorKey, err := helper.DecodePrivateKey(dp.SenderKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize depositor private key: %w", err)) - } - - depositorAddr := depositorKey.Address() - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(dp.JSONRPCAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize tx relayer: %w", err)) - - return - } - - amounts := make([]*big.Int, len(dp.Amounts)) - tokenIDs := make([]*big.Int, len(dp.TokenIDs)) - - for i := range dp.Receivers { - amountRaw := dp.Amounts[i] - tokenIDRaw := dp.TokenIDs[i] - - amount, err := helperCommon.ParseUint256orHex(&amountRaw) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode provided amount %s: %w", amountRaw, err)) - - return - } - - amounts[i] = amount - - tokenID, err := helperCommon.ParseUint256orHex(&tokenIDRaw) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode provided token id %s: %w", tokenIDRaw, err)) - - return - } - - tokenIDs[i] = tokenID - } - - if dp.minterKey != "" { - minterKey, err := helper.DecodePrivateKey(dp.minterKey) - if err != nil { - outputter.SetError(fmt.Errorf("invalid minter key provided: %w", err)) - - return - } - - // mint tokens to depositor, so he is able to send them - mintTxn, err := createMintTxn(types.Address(depositorAddr), types.Address(depositorAddr), amounts, tokenIDs) - if err != nil { - outputter.SetError(fmt.Errorf("mint transaction creation failed: %w", err)) - - return - } - - receipt, err := txRelayer.SendTransaction(mintTxn, minterKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send mint transaction to depositor %s: %w", depositorAddr, err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to mint tokens to depositor %s", depositorAddr)) - - return - } - } - - // approve root erc1155 predicate - approveTxn, err := createApproveERC1155PredicateTxn( - types.StringToAddress(dp.PredicateAddr), - types.StringToAddress(dp.TokenAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("failed to create root erc 1155 approve transaction: %w", err)) - - return - } - - receipt, err := txRelayer.SendTransaction(approveTxn, depositorKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send root erc 1155 approve transaction: %w", err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to approve root erc 1155 predicate")) - - return - } - - receivers := make([]ethgo.Address, len(dp.Receivers)) - for i, receiverRaw := range dp.Receivers { - receivers[i] = ethgo.Address(types.StringToAddress(receiverRaw)) - } - - // deposit tokens - depositTxn, err := createDepositTxn(depositorAddr, receivers, amounts, tokenIDs) - if err != nil { - outputter.SetError(fmt.Errorf("failed to create tx input: %w", err)) - - return - } - - receipt, err = txRelayer.SendTransaction(depositTxn, depositorKey) - if err != nil { - outputter.SetError(fmt.Errorf("sending deposit transactions failed (receivers: %s, amount: %s): %w", - strings.Join(dp.Receivers, ", "), strings.Join(dp.Amounts, ", "), err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("sending deposit transactions failed (receivers: %s, amounts: %s)", - strings.Join(dp.Receivers, ", "), strings.Join(dp.Amounts, ", "))) - - return - } - - res := &common.BridgeTxResult{ - Sender: depositorAddr.String(), - Receivers: dp.Receivers, - Amounts: dp.Amounts, - TokenIDs: dp.TokenIDs, - BlockNumbers: []uint64{receipt.BlockNumber}, - Title: "DEPOSIT ERC 1155", - } - - if dp.ChildChainMintable { - exitEventIDs, err := common.ExtractExitEventIDs(receipt) - if err != nil { - outputter.SetError(fmt.Errorf("failed to extract exit event: %w", err)) - - return - } - - res.ExitEventIDs = exitEventIDs - } - - // populate child token address if a token is mapped alongside with deposit - childToken, err := common.ExtractChildTokenAddr(receipt, dp.ChildChainMintable) - if err != nil { - outputter.SetError(fmt.Errorf("failed to extract child token address: %w", err)) - - return - } - - res.ChildTokenAddr = childToken - - outputter.SetCommandResult(res) -} - -// createDepositTxn encodes parameters for deposit function on rootchain predicate contract -func createDepositTxn(sender ethgo.Address, receivers []ethgo.Address, - amounts, tokenIDs []*big.Int) (*ethgo.Transaction, error) { - depositBatchFn := &contractsapi.DepositBatchRootERC1155PredicateFn{ - RootToken: types.StringToAddress(dp.TokenAddr), - Receivers: receivers, - TokenIDs: tokenIDs, - Amounts: amounts, - } - - input, err := depositBatchFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - addr := ethgo.Address(types.StringToAddress(dp.PredicateAddr)) - - return helper.CreateTransaction(sender, &addr, input, - nil, !dp.ChildChainMintable), nil -} - -// createMintTxn encodes parameters for mint function on rootchain token contract -func createMintTxn(sender, receiver types.Address, amounts, tokenIDs []*big.Int) (*ethgo.Transaction, error) { - mintFn := &contractsapi.MintBatchRootERC1155Fn{ - To: receiver, - Amounts: amounts, - IDs: tokenIDs, - } - - input, err := mintFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - addr := ethgo.Address(types.StringToAddress(dp.TokenAddr)) - - return helper.CreateTransaction(ethgo.Address(sender), &addr, - input, nil, !dp.ChildChainMintable), nil -} - -// createApproveERC1155PredicateTxn sends approve transaction -// to ERC1155 token for ERC1155 predicate so that it is able to spend given tokens -func createApproveERC1155PredicateTxn(rootERC1155Predicate, - rootERC1155Token types.Address) (*ethgo.Transaction, error) { - approveFnParams := &contractsapi.SetApprovalForAllRootERC1155Fn{ - Operator: rootERC1155Predicate, - Approved: true, - } - - input, err := approveFnParams.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode parameters for RootERC1155.setApprovalForAll. error: %w", err) - } - - addr := ethgo.Address(rootERC1155Token) - - return helper.CreateTransaction(ethgo.ZeroAddress, &addr, - input, nil, !dp.ChildChainMintable), nil -} diff --git a/command/bridge/deposit/erc20/deposit_erc20.go b/command/bridge/deposit/erc20/deposit_erc20.go deleted file mode 100644 index 75839f5..0000000 --- a/command/bridge/deposit/erc20/deposit_erc20.go +++ /dev/null @@ -1,286 +0,0 @@ -package erc20 - -import ( - "fmt" - "math/big" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "golang.org/x/sync/errgroup" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/bridge/common" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - helperCommon "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -type depositERC20Params struct { - *common.ERC20BridgeParams - minterKey string -} - -var ( - // depositParams is abstraction for provided bridge parameter values - dp *depositERC20Params = &depositERC20Params{ERC20BridgeParams: common.NewERC20BridgeParams()} -) - -// GetCommand returns the bridge deposit command -func GetCommand() *cobra.Command { - depositCmd := &cobra.Command{ - Use: "deposit-erc20", - Short: "Deposits ERC 20 tokens from the origin to the destination chain", - PreRunE: preRunCommand, - Run: runCommand, - } - - dp.RegisterCommonFlags(depositCmd) - - depositCmd.Flags().StringSliceVar( - &dp.Amounts, - common.AmountsFlag, - nil, - "amounts to send to receiving accounts", - ) - - depositCmd.Flags().StringVar( - &dp.TokenAddr, - common.RootTokenFlag, - "", - "root ERC 20 token address", - ) - - depositCmd.Flags().StringVar( - &dp.PredicateAddr, - common.RootPredicateFlag, - "", - "root ERC 20 token predicate address", - ) - - depositCmd.Flags().StringVar( - &dp.minterKey, - common.MinterKeyFlag, - "", - common.MinterKeyFlagDesc, - ) - - _ = depositCmd.MarkFlagRequired(common.ReceiversFlag) - _ = depositCmd.MarkFlagRequired(common.AmountsFlag) - _ = depositCmd.MarkFlagRequired(common.RootTokenFlag) - _ = depositCmd.MarkFlagRequired(common.RootPredicateFlag) - - return depositCmd -} - -func preRunCommand(cmd *cobra.Command, _ []string) error { - return dp.Validate() -} - -func runCommand(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - depositorKey, err := helper.DecodePrivateKey(dp.SenderKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize depositor private key: %w", err)) - } - - depositorAddr := depositorKey.Address() - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(dp.JSONRPCAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize tx relayer: %w", err)) - - return - } - - amounts := make([]*big.Int, len(dp.Amounts)) - aggregateAmount := new(big.Int) - - for i, amountRaw := range dp.Amounts { - amountRaw := amountRaw - - amount, err := helperCommon.ParseUint256orHex(&amountRaw) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode provided amount %s: %w", amountRaw, err)) - - return - } - - amounts[i] = amount - aggregateAmount.Add(aggregateAmount, amount) - } - - if dp.minterKey != "" { - minterKey, err := helper.DecodePrivateKey(dp.minterKey) - if err != nil { - outputter.SetError(fmt.Errorf("invalid minter key provided: %w", err)) - - return - } - - // mint tokens to depositor, so he is able to send them - mintTxn, err := helper.CreateMintTxn(types.Address(depositorAddr), - types.StringToAddress(dp.TokenAddr), aggregateAmount, !dp.ChildChainMintable) - if err != nil { - outputter.SetError(fmt.Errorf("mint transaction creation failed: %w", err)) - - return - } - - receipt, err := txRelayer.SendTransaction(mintTxn, minterKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send mint transaction to depositor %s: %w", depositorAddr, err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to mint tokens to depositor %s", depositorAddr)) - - return - } - } - - // approve erc20 predicate - approveTxn, err := helper.CreateApproveERC20Txn(aggregateAmount, - types.StringToAddress(dp.PredicateAddr), - types.StringToAddress(dp.TokenAddr), - !dp.ChildChainMintable, - ) - if err != nil { - outputter.SetError(fmt.Errorf("failed to create root erc 20 approve transaction: %w", err)) - - return - } - - receipt, err := txRelayer.SendTransaction(approveTxn, depositorKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send root erc 20 approve transaction: %w", err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to approve root erc 20 predicate")) - - return - } - - type bridgeTxData struct { - exitEventIDs []*big.Int - blockNumber uint64 - childTokenAddr *types.Address - } - - g, ctx := errgroup.WithContext(cmd.Context()) - bridgeTxCh := make(chan bridgeTxData, len(dp.Receivers)) - exitEventIDs := make([]*big.Int, 0, len(dp.Receivers)) - blockNumbers := make([]uint64, 0, len(dp.Receivers)) - - for i := range dp.Receivers { - receiver := dp.Receivers[i] - amount := amounts[i] - - g.Go(func() error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - // deposit tokens - depositTxn, err := createDepositTxn(types.Address(depositorAddr), types.StringToAddress(receiver), amount) - if err != nil { - return fmt.Errorf("failed to create tx input: %w", err) - } - - var receipt *ethgo.Receipt - - receipt, err = txRelayer.SendTransaction(depositTxn, depositorKey) - if err != nil { - return fmt.Errorf("receiver: %s, amount: %s, error: %w", receiver, amount, err) - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("receiver: %s, amount: %s", receiver, amount) - } - - var exitEventIDs []*big.Int - - if dp.ChildChainMintable { - if exitEventIDs, err = common.ExtractExitEventIDs(receipt); err != nil { - return fmt.Errorf("failed to extract exit event: %w", err) - } - } - - // populate child token address if a token is mapped alongside with deposit - childToken, err := common.ExtractChildTokenAddr(receipt, dp.ChildChainMintable) - if err != nil { - return fmt.Errorf("failed to extract child token address: %w", err) - } - - // send aggregated data to channel if everything went ok - bridgeTxCh <- bridgeTxData{ - blockNumber: receipt.BlockNumber, - exitEventIDs: exitEventIDs, - childTokenAddr: childToken, - } - - return nil - } - }) - } - - if err = g.Wait(); err != nil { - outputter.SetError(fmt.Errorf("sending deposit transactions failed: %w", err)) - - return - } - - close(bridgeTxCh) - - var childToken *types.Address - - for x := range bridgeTxCh { - if x.exitEventIDs != nil { - exitEventIDs = append(exitEventIDs, x.exitEventIDs...) - } - - blockNumbers = append(blockNumbers, x.blockNumber) - - if x.childTokenAddr != nil { - childToken = x.childTokenAddr - } - } - - outputter.SetCommandResult( - &common.BridgeTxResult{ - Sender: depositorAddr.String(), - Receivers: dp.Receivers, - Amounts: dp.Amounts, - ExitEventIDs: exitEventIDs, - ChildTokenAddr: childToken, - BlockNumbers: blockNumbers, - Title: "DEPOSIT ERC 20", - }) -} - -// createDepositTxn encodes parameters for deposit function on rootchain predicate contract -func createDepositTxn(sender, receiver types.Address, amount *big.Int) (*ethgo.Transaction, error) { - depositToFn := &contractsapi.DepositToRootERC20PredicateFn{ - RootToken: types.StringToAddress(dp.TokenAddr), - Receiver: receiver, - Amount: amount, - } - - input, err := depositToFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - addr := ethgo.Address(types.StringToAddress(dp.PredicateAddr)) - - return helper.CreateTransaction(ethgo.Address(sender), &addr, - input, nil, !dp.ChildChainMintable), nil -} diff --git a/command/bridge/deposit/erc721/deposit_erc721.go b/command/bridge/deposit/erc721/deposit_erc721.go deleted file mode 100644 index 46cb8f4..0000000 --- a/command/bridge/deposit/erc721/deposit_erc721.go +++ /dev/null @@ -1,273 +0,0 @@ -package deposit - -import ( - "fmt" - "math/big" - "strings" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/bridge/common" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - helperCommon "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -type depositERC721Params struct { - *common.ERC721BridgeParams - minterKey string -} - -var ( - dp *depositERC721Params = &depositERC721Params{ERC721BridgeParams: common.NewERC721BridgeParams()} -) - -func GetCommand() *cobra.Command { - depositCmd := &cobra.Command{ - Use: "deposit-erc721", - Short: "Deposits ERC721 tokens from the origin to the destination chain", - PreRunE: preRunCommand, - Run: runCommand, - } - - dp.RegisterCommonFlags(depositCmd) - - depositCmd.Flags().StringSliceVar( - &dp.TokenIDs, - common.TokenIDsFlag, - nil, - "token ids that are sent to the receivers accounts", - ) - - depositCmd.Flags().StringVar( - &dp.TokenAddr, - common.RootTokenFlag, - "", - "root ERC 721 token address", - ) - - depositCmd.Flags().StringVar( - &dp.PredicateAddr, - common.RootPredicateFlag, - "", - "root ERC 721 token predicate address", - ) - - depositCmd.Flags().StringVar( - &dp.minterKey, - common.MinterKeyFlag, - "", - common.MinterKeyFlagDesc, - ) - - _ = depositCmd.MarkFlagRequired(common.ReceiversFlag) - _ = depositCmd.MarkFlagRequired(common.TokenIDsFlag) - _ = depositCmd.MarkFlagRequired(common.RootTokenFlag) - _ = depositCmd.MarkFlagRequired(common.RootPredicateFlag) - - return depositCmd -} - -func preRunCommand(_ *cobra.Command, _ []string) error { - return dp.Validate() -} - -func runCommand(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - depositorKey, err := helper.DecodePrivateKey(dp.SenderKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize depositor private key: %w", err)) - } - - depositorAddr := depositorKey.Address() - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(dp.JSONRPCAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize tx relayer: %w", err)) - - return - } - - receivers := make([]ethgo.Address, len(dp.Receivers)) - tokenIDs := make([]*big.Int, len(dp.Receivers)) - - for i, tokenIDRaw := range dp.TokenIDs { - tokenIDRaw := tokenIDRaw - - tokenID, err := helperCommon.ParseUint256orHex(&tokenIDRaw) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode provided token id %s: %w", tokenIDRaw, err)) - - return - } - - receivers[i] = ethgo.Address(types.StringToAddress(dp.Receivers[i])) - tokenIDs[i] = tokenID - } - - if dp.minterKey != "" { - minterKey, err := helper.DecodePrivateKey(dp.minterKey) - if err != nil { - outputter.SetError(fmt.Errorf("invalid minter key provided: %w", err)) - - return - } - - for i := 0; i < len(tokenIDs); i++ { - mintTxn, err := createMintTxn(types.Address(depositorAddr), types.Address(depositorAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("mint transaction creation failed: %w", err)) - - return - } - - receipt, err := txRelayer.SendTransaction(mintTxn, minterKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send mint transaction to depositor %s: %w", - depositorAddr, err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to mint tokens to depositor %s", depositorAddr)) - - return - } - } - } - - approveTxn, err := createApproveERC721PredicateTxn(types.StringToAddress(dp.PredicateAddr), - types.StringToAddress(dp.TokenAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("failed to approve predicate: %w", err)) - - return - } - - receipt, err := txRelayer.SendTransaction(approveTxn, depositorKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send root erc 721 approve transaction: %w", err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to approve root erc 721 predicate")) - - return - } - - // deposit tokens - depositTxn, err := createDepositTxn(depositorAddr, receivers, tokenIDs) - if err != nil { - outputter.SetError(fmt.Errorf("failed to create tx input: %w", err)) - - return - } - - receipt, err = txRelayer.SendTransaction(depositTxn, depositorKey) - if err != nil { - outputter.SetError(fmt.Errorf("sending deposit transactions failed (receivers: %s, tokenIDs: %s): %w", - strings.Join(dp.Receivers, ", "), strings.Join(dp.TokenIDs, ", "), err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("sending deposit transactions failed (receivers: %s, tokenIDs: %s)", - strings.Join(dp.Receivers, ", "), strings.Join(dp.TokenIDs, ", "))) - - return - } - - res := &common.BridgeTxResult{ - Sender: depositorAddr.String(), - Receivers: dp.Receivers, - TokenIDs: dp.TokenIDs, - BlockNumbers: []uint64{receipt.BlockNumber}, - Title: "DEPOSIT ERC 721", - } - - if dp.ChildChainMintable { - exitEventIDs, err := common.ExtractExitEventIDs(receipt) - if err != nil { - outputter.SetError(fmt.Errorf("failed to extract exit event: %w", err)) - - return - } - - res.ExitEventIDs = exitEventIDs - } - - // populate child token address if a token is mapped alongside with deposit - childToken, err := common.ExtractChildTokenAddr(receipt, dp.ChildChainMintable) - if err != nil { - outputter.SetError(fmt.Errorf("failed to extract child token address: %w", err)) - - return - } - - res.ChildTokenAddr = childToken - - outputter.SetCommandResult(res) -} - -// createDepositTxn encodes parameters for deposit function on rootchain predicate contract -func createDepositTxn(sender ethgo.Address, - receivers []ethgo.Address, tokenIDs []*big.Int) (*ethgo.Transaction, error) { - depositToRoot := &contractsapi.DepositBatchRootERC721PredicateFn{ - RootToken: types.StringToAddress(dp.TokenAddr), - Receivers: receivers, - TokenIDs: tokenIDs, - } - - input, err := depositToRoot.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - addr := ethgo.Address(types.StringToAddress(dp.PredicateAddr)) - - return helper.CreateTransaction(sender, &addr, input, nil, !dp.ChildChainMintable), nil -} - -// createMintTxn encodes parameters for mint function on rootchain token contract -func createMintTxn(sender, receiver types.Address) (*ethgo.Transaction, error) { - mintFn := &contractsapi.MintRootERC721Fn{ - To: receiver, - } - - input, err := mintFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - addr := ethgo.Address(types.StringToAddress(dp.TokenAddr)) - - return helper.CreateTransaction(ethgo.Address(sender), &addr, - input, nil, !dp.ChildChainMintable), nil -} - -// createApproveERC721PredicateTxn sends approve transaction -func createApproveERC721PredicateTxn(rootERC721Predicate, rootERC721Token types.Address) (*ethgo.Transaction, error) { - approveFnParams := &contractsapi.SetApprovalForAllRootERC721Fn{ - Operator: rootERC721Predicate, - Approved: true, - } - - input, err := approveFnParams.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode parameters for RootERC721.approve. error: %w", err) - } - - addr := ethgo.Address(rootERC721Token) - - return helper.CreateTransaction(ethgo.ZeroAddress, &addr, input, - nil, !dp.ChildChainMintable), nil -} diff --git a/command/bridge/exit/exit.go b/command/bridge/exit/exit.go deleted file mode 100644 index 087fee8..0000000 --- a/command/bridge/exit/exit.go +++ /dev/null @@ -1,235 +0,0 @@ -package exit - -import ( - "bytes" - "encoding/hex" - "errors" - "fmt" - "math/big" - "strconv" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/jsonrpc" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/bridge/common" - cmdHelper "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -const ( - // flag names - exitHelperFlag = "exit-helper" - exitEventIDFlag = "exit-id" - rootJSONRPCFlag = "root-json-rpc" - childJSONRPCFlag = "child-json-rpc" - - // generateExitProofFn is JSON RPC endpoint which creates exit proof - generateExitProofFn = "bridge_generateExitProof" -) - -type exitParams struct { - senderKey string - exitHelperAddrRaw string - exitID uint64 - rootJSONRPCAddr string - childJSONRPCAddr string - isTestMode bool -} - -var ( - // ep represents exit command parameters - ep *exitParams = &exitParams{} -) - -// GetCommand returns the bridge exit command -func GetCommand() *cobra.Command { - exitCmd := &cobra.Command{ - Use: "exit", - Short: "Sends exit transaction to the Exit helper contract on the root chain", - Run: run, - } - - exitCmd.Flags().StringVar( - &ep.senderKey, - common.SenderKeyFlag, - "", - "hex encoded private key of the account which sends exit transaction to the root chain", - ) - - exitCmd.Flags().StringVar( - &ep.exitHelperAddrRaw, - exitHelperFlag, - "", - "address of ExitHelper smart contract on root chain", - ) - - exitCmd.Flags().Uint64Var( - &ep.exitID, - exitEventIDFlag, - 0, - "child chain exit event ID", - ) - - exitCmd.Flags().StringVar( - &ep.rootJSONRPCAddr, - rootJSONRPCFlag, - txrelayer.DefaultRPCAddress, - "the JSON RPC root chain endpoint", - ) - - exitCmd.Flags().StringVar( - &ep.childJSONRPCAddr, - childJSONRPCFlag, - "http://127.0.0.1:9545", - "the JSON RPC child chain endpoint", - ) - - exitCmd.Flags().BoolVar( - &ep.isTestMode, - helper.TestModeFlag, - false, - "test indicates whether exit transaction sender is hardcoded test account", - ) - - _ = exitCmd.MarkFlagRequired(exitHelperFlag) - exitCmd.MarkFlagsMutuallyExclusive(helper.TestModeFlag, common.SenderKeyFlag) - - return exitCmd -} - -func run(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - senderKey, err := helper.DecodePrivateKey(ep.senderKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to create wallet from private key: %w", err)) - - return - } - - rootTxRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(ep.rootJSONRPCAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("could not create root chain tx relayer: %w", err)) - - return - } - - childClient, err := jsonrpc.NewClient(ep.childJSONRPCAddr) - if err != nil { - outputter.SetError(fmt.Errorf("could not create child chain JSON RPC client: %w", err)) - - return - } - - // acquire proof for given exit event - var proof types.Proof - - err = childClient.Call(generateExitProofFn, &proof, fmt.Sprintf("0x%x", ep.exitID)) - if err != nil { - outputter.SetError(fmt.Errorf("failed to get exit proof (exit id=%d): %w", ep.exitID, err)) - - return - } - - // create exit transaction - txn, exitEvent, err := createExitTxn(senderKey.Address(), proof) - if err != nil { - outputter.SetError(fmt.Errorf("failed to create tx input: %w", err)) - - return - } - - // send exit transaction - receipt, err := rootTxRelayer.SendTransaction(txn, senderKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send exit transaction (exit id=%d): %w", ep.exitID, err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to execute exit transaction (exit id=%d)", ep.exitID)) - - return - } - - outputter.SetCommandResult(&exitResult{ - ID: strconv.FormatUint(exitEvent.ID.Uint64(), 10), - Sender: exitEvent.Sender.String(), - Receiver: exitEvent.Receiver.String(), - }) -} - -// createExitTxn encodes parameters for exit function on root chain ExitHelper contract -func createExitTxn(sender ethgo.Address, proof types.Proof) (*ethgo.Transaction, - *contractsapi.L2StateSyncedEvent, error) { - leafIndex, ok := proof.Metadata["LeafIndex"].(float64) - if !ok { - return nil, nil, errors.New("failed to convert proof leaf index") - } - - checkpointBlock, ok := proof.Metadata["CheckpointBlock"].(float64) - if !ok { - return nil, nil, errors.New("failed to convert proof checkpoint block") - } - - exitEventHex, ok := proof.Metadata["ExitEvent"].(string) - if !ok { - return nil, nil, errors.New("failed to convert exit event") - } - - exitEventEncoded, err := hex.DecodeString(exitEventHex) - if err != nil { - return nil, nil, fmt.Errorf("failed to decode hex-encoded exit event '%s': %w", exitEventHex, err) - } - - exitEvent := new(contractsapi.L2StateSyncedEvent) - if err := exitEvent.Decode(exitEventEncoded); err != nil { - return nil, nil, fmt.Errorf("failed to decode exit event: %w", err) - } - - exitFn := &contractsapi.ExitExitHelperFn{ - BlockNumber: new(big.Int).SetUint64(uint64(checkpointBlock)), - LeafIndex: new(big.Int).SetUint64(uint64(leafIndex)), - UnhashedLeaf: exitEventEncoded, - Proof: proof.Data, - } - - input, err := exitFn.EncodeAbi() - if err != nil { - return nil, nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - exitHelperAddr := ethgo.Address(types.StringToAddress(ep.exitHelperAddrRaw)) - txn := helper.CreateTransaction(sender, &exitHelperAddr, input, nil, true) - txn.Gas = txrelayer.DefaultGasLimit - - return txn, exitEvent, err -} - -type exitResult struct { - ID string `json:"id"` - Sender string `json:"sender"` - Receiver string `json:"receiver"` -} - -func (r *exitResult) GetOutput() string { - var buffer bytes.Buffer - - vals := make([]string, 0, 3) - vals = append(vals, fmt.Sprintf("Exit Event ID|%s", r.ID)) - vals = append(vals, fmt.Sprintf("Sender|%s", r.Sender)) - vals = append(vals, fmt.Sprintf("Receiver|%s", r.Receiver)) - - buffer.WriteString("\n[EXIT TRANSACTION RELAYER]\n") - buffer.WriteString(cmdHelper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/bridge/mint/mint_erc20.go b/command/bridge/mint/mint_erc20.go deleted file mode 100644 index e59aa20..0000000 --- a/command/bridge/mint/mint_erc20.go +++ /dev/null @@ -1,150 +0,0 @@ -package mint - -import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" - "golang.org/x/sync/errgroup" -) - -var ( - params mintParams -) - -// GetCommand returns the rootchain fund command -func GetCommand() *cobra.Command { - mintCmd := &cobra.Command{ - Use: "mint-erc20", - Short: "Mints ERC20 tokens to specified addresses", - PreRunE: preRunCommand, - Run: runCommand, - } - - helper.RegisterJSONRPCFlag(mintCmd) - - setFlags(mintCmd) - - return mintCmd -} - -func preRunCommand(cmd *cobra.Command, _ []string) error { - params.jsonRPCAddress = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringSliceVar( - ¶ms.addresses, - rootHelper.AddressesFlag, - nil, - "addresses to which tokens should be minted", - ) - - cmd.Flags().StringSliceVar( - ¶ms.amounts, - rootHelper.AmountsFlag, - nil, - "token amounts which should be minted to given addresses", - ) - - cmd.Flags().StringVar( - ¶ms.tokenAddr, - rootHelper.Erc20TokenFlag, - "", - "address of the erc20 token to be minted", - ) - - cmd.Flags().StringVar( - ¶ms.deployerPrivateKey, - polybftsecrets.PrivateKeyFlag, - "", - "private key of the token deployer (minter)", - ) -} - -func runCommand(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPCAddress)) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize tx relayer: %w", err)) - - return - } - - deployerKey, err := rootHelper.DecodePrivateKey(params.deployerPrivateKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize deployer private key: %w", err)) - - return - } - - g, ctx := errgroup.WithContext(cmd.Context()) - - results := make([]command.CommandResult, len(params.addresses)) - tokenAddr := types.StringToAddress(params.tokenAddr) - - for i := 0; i < len(params.addresses); i++ { - i := i - - g.Go(func() error { - select { - case <-ctx.Done(): - return ctx.Err() - - default: - // mint tokens to address - addr := types.StringToAddress(params.addresses[i]) - amount := params.amountValues[i] - - mintTxn, err := rootHelper.CreateMintTxn(addr, tokenAddr, amount, true) - if err != nil { - return fmt.Errorf("failed to create mint native tokens transaction for validator '%s'. err: %w", - addr, err) - } - - receipt, err := txRelayer.SendTransaction(mintTxn, deployerKey) - if err != nil { - return fmt.Errorf("failed to send mint native tokens transaction to validator '%s'. err: %w", addr, err) - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("failed to mint native tokens to validator '%s'", addr) - } - - results[i] = &mintResult{ - Address: addr, - Amount: amount, - TxHash: types.Hash(receipt.TransactionHash), - } - - return nil - } - }) - } - - if err := g.Wait(); err != nil { - outputter.SetError(err) - _, _ = outputter.Write([]byte("[MINT-ERC20] Successfully minted tokens to following accounts\n")) - - for _, result := range results { - if result != nil { - // In case an error happened, some of the indices may not be populated. - // Filter those out. - outputter.SetCommandResult(result) - } - } - - return - } - - outputter.SetCommandResult(command.Results(results)) -} diff --git a/command/bridge/mint/params.go b/command/bridge/mint/params.go deleted file mode 100644 index 407b8f3..0000000 --- a/command/bridge/mint/params.go +++ /dev/null @@ -1,78 +0,0 @@ -package mint - -import ( - "bytes" - "fmt" - "math/big" - - "github.com/xgr-network/xgr-node/command/helper" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/types" -) - -type mintParams struct { - addresses []string - amounts []string - tokenAddr string - deployerPrivateKey string - jsonRPCAddress string - - amountValues []*big.Int -} - -func (m *mintParams) validateFlags() error { - if len(m.addresses) == 0 { - return rootHelper.ErrNoAddressesProvided - } - - if len(m.amounts) != len(m.addresses) { - return rootHelper.ErrInconsistentLength - } - - for _, addr := range m.addresses { - if err := types.IsValidAddress(addr); err != nil { - return err - } - } - - m.amountValues = make([]*big.Int, len(m.amounts)) - for i, amountRaw := range m.amounts { - amountValue, err := helper.ParseAmount(amountRaw) - if err != nil { - return err - } - - m.amountValues[i] = amountValue - } - - if m.tokenAddr == "" { - return rootHelper.ErrMandatoryERC20Token - } - - if err := types.IsValidAddress(m.tokenAddr); err != nil { - return fmt.Errorf("invalid erc20 token address is provided: %w", err) - } - - return nil -} - -type mintResult struct { - Address types.Address `json:"address"` - Amount *big.Int `json:"amount"` - TxHash types.Hash `json:"tx_hash"` -} - -func (m *mintResult) GetOutput() string { - var buffer bytes.Buffer - - vals := make([]string, 0, 3) - vals = append(vals, fmt.Sprintf("Address|%s", m.Address)) - vals = append(vals, fmt.Sprintf("Amount|%s", m.Amount)) - vals = append(vals, fmt.Sprintf("Transaction (hash)|%s", m.TxHash)) - - buffer.WriteString("\n[MINT-ERC20]\n") - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/bridge/withdraw/erc1155/withdraw_erc1155.go b/command/bridge/withdraw/erc1155/withdraw_erc1155.go deleted file mode 100644 index 97045f5..0000000 --- a/command/bridge/withdraw/erc1155/withdraw_erc1155.go +++ /dev/null @@ -1,193 +0,0 @@ -package erc1155 - -import ( - "encoding/hex" - "fmt" - "math/big" - "strings" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/wallet" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/bridge/common" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/contracts" - helperCommon "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var ( - wp *common.ERC1155BridgeParams = common.NewERC1155BridgeParams() -) - -// GetCommand returns the bridge withdraw command -func GetCommand() *cobra.Command { - withdrawCmd := &cobra.Command{ - Use: "withdraw-erc1155", - Short: "Withdraws ERC 1155 tokens from the destination to the origin chain", - PreRunE: preRunCommand, - Run: runCommand, - } - - wp.RegisterCommonFlags(withdrawCmd) - - withdrawCmd.Flags().StringSliceVar( - &wp.Amounts, - common.AmountsFlag, - nil, - "amounts that are sent to the receivers accounts", - ) - - withdrawCmd.Flags().StringSliceVar( - &wp.TokenIDs, - common.TokenIDsFlag, - nil, - "token ids that are sent to the receivers accounts", - ) - - withdrawCmd.Flags().StringVar( - &wp.PredicateAddr, - common.ChildPredicateFlag, - contracts.ChildERC1155PredicateContract.String(), - "ERC 1155 child chain predicate address", - ) - - withdrawCmd.Flags().StringVar( - &wp.TokenAddr, - common.ChildTokenFlag, - contracts.ChildERC1155Contract.String(), - "ERC 1155 child chain token address", - ) - - _ = withdrawCmd.MarkFlagRequired(common.ReceiversFlag) - _ = withdrawCmd.MarkFlagRequired(common.AmountsFlag) - _ = withdrawCmd.MarkFlagRequired(common.TokenIDsFlag) - - return withdrawCmd -} - -func preRunCommand(_ *cobra.Command, _ []string) error { - return wp.Validate() -} - -func runCommand(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - senderKeyRaw, err := hex.DecodeString(wp.SenderKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode sender private key: %w", err)) - - return - } - - senderAccount, err := wallet.NewWalletFromPrivKey(senderKeyRaw) - if err != nil { - outputter.SetError(err) - - return - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(wp.JSONRPCAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("could not create child chain tx relayer: %w", err)) - - return - } - - receivers := make([]ethgo.Address, len(wp.Receivers)) - amounts := make([]*big.Int, len(wp.Receivers)) - TokenIDs := make([]*big.Int, len(wp.Receivers)) - - for i, receiverRaw := range wp.Receivers { - receivers[i] = ethgo.Address(types.StringToAddress(receiverRaw)) - amountRaw := wp.Amounts[i] - tokenIDRaw := wp.TokenIDs[i] - - amount, err := helperCommon.ParseUint256orHex(&amountRaw) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode provided amount %s: %w", amountRaw, err)) - - return - } - - tokenID, err := helperCommon.ParseUint256orHex(&tokenIDRaw) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode provided token id %s: %w", amountRaw, err)) - - return - } - - amounts[i] = amount - TokenIDs[i] = tokenID - } - - // withdraw tokens transaction - txn, err := createWithdrawTxn(receivers, amounts, TokenIDs) - if err != nil { - outputter.SetError(fmt.Errorf("failed to create tx input: %w", err)) - - return - } - - receipt, err := txRelayer.SendTransaction(txn, senderAccount) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send withdrawal transaction (receivers: %s, amounts: %s, token ids: %s): %w", - strings.Join(wp.Receivers, ", "), strings.Join(wp.Amounts, ", "), strings.Join(wp.TokenIDs, ", "), err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to execute withdrawal transaction (receivers: %s, amounts: %s, token ids: %s)", - strings.Join(wp.Receivers, ", "), strings.Join(wp.Amounts, ", "), strings.Join(wp.TokenIDs, ", "))) - - return - } - - res := &common.BridgeTxResult{ - Sender: senderAccount.Address().String(), - Receivers: wp.Receivers, - Amounts: wp.Amounts, - TokenIDs: wp.TokenIDs, - BlockNumbers: []uint64{receipt.BlockNumber}, - Title: "WITHDRAW ERC 1155", - } - - if !wp.ChildChainMintable { - exitEventIDs, err := common.ExtractExitEventIDs(receipt) - if err != nil { - outputter.SetError(fmt.Errorf("failed to extract exit event: %w", err)) - - return - } - - res.ExitEventIDs = exitEventIDs - } - - outputter.SetCommandResult(res) -} - -// createWithdrawTxn encodes parameters for withdraw function on child chain predicate contract -func createWithdrawTxn(receivers []ethgo.Address, amounts, TokenIDs []*big.Int) (*ethgo.Transaction, error) { - withdrawFn := &contractsapi.WithdrawBatchChildERC1155PredicateFn{ - ChildToken: types.StringToAddress(wp.TokenAddr), - Receivers: receivers, - Amounts: amounts, - TokenIDs: TokenIDs, - } - - input, err := withdrawFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - addr := ethgo.Address(types.StringToAddress(wp.PredicateAddr)) - - return helper.CreateTransaction(ethgo.ZeroAddress, &addr, input, - nil, wp.ChildChainMintable), nil -} diff --git a/command/bridge/withdraw/erc20/withdraw_erc20.go b/command/bridge/withdraw/erc20/withdraw_erc20.go deleted file mode 100644 index f795108..0000000 --- a/command/bridge/withdraw/erc20/withdraw_erc20.go +++ /dev/null @@ -1,171 +0,0 @@ -package erc20 - -import ( - "encoding/hex" - "fmt" - "math/big" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/wallet" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/bridge/common" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/contracts" - helperCommon "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var ( - wp *common.ERC20BridgeParams = common.NewERC20BridgeParams() -) - -// GetCommand returns the bridge withdraw command -func GetCommand() *cobra.Command { - withdrawCmd := &cobra.Command{ - Use: "withdraw-erc20", - Short: "Withdraws ERC 20 tokens from the destination to the origin chain", - PreRunE: preRunCommand, - Run: runCommand, - } - - wp.RegisterCommonFlags(withdrawCmd) - - withdrawCmd.Flags().StringSliceVar( - &wp.Amounts, - common.AmountsFlag, - nil, - "amounts to send to receiving accounts", - ) - - withdrawCmd.Flags().StringVar( - &wp.PredicateAddr, - common.ChildPredicateFlag, - contracts.ChildERC20PredicateContract.String(), - "child ERC 20 predicate address", - ) - - withdrawCmd.Flags().StringVar( - &wp.TokenAddr, - common.ChildTokenFlag, - contracts.NativeERC20TokenContract.String(), - "child ERC 20 token address", - ) - - _ = withdrawCmd.MarkFlagRequired(common.ReceiversFlag) - _ = withdrawCmd.MarkFlagRequired(common.AmountsFlag) - - return withdrawCmd -} - -func preRunCommand(_ *cobra.Command, _ []string) error { - return wp.Validate() -} - -func runCommand(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - senderKeyRaw, err := hex.DecodeString(wp.SenderKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode sender private key: %w", err)) - - return - } - - senderAccount, err := wallet.NewWalletFromPrivKey(senderKeyRaw) - if err != nil { - outputter.SetError(err) - - return - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(wp.JSONRPCAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("could not create destination chain tx relayer: %w", err)) - - return - } - - exitEventIDs := make([]*big.Int, 0, len(wp.Receivers)) - blockNumbers := make([]uint64, len(wp.Receivers)) - - for i := range wp.Receivers { - receiver := wp.Receivers[i] - amount := wp.Amounts[i] - - amountBig, err := helperCommon.ParseUint256orHex(&amount) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode provided amount %s: %w", amount, err)) - - return - } - - // withdraw tokens transaction - txn, err := createWithdrawTxn(types.StringToAddress(receiver), amountBig) - if err != nil { - outputter.SetError(fmt.Errorf("failed to create tx input: %w", err)) - - return - } - - receipt, err := txRelayer.SendTransaction(txn, senderAccount) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send withdraw transaction (receiver: %s, amount: %s). error: %w)", - receiver, amount, err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to execute withdrawal (receiver: %s, amount: %s)", receiver, amount)) - - return - } - - if !wp.ChildChainMintable { - extractedExitEventIDs, err := common.ExtractExitEventIDs(receipt) - if err != nil { - outputter.SetError(fmt.Errorf("failed to extract exit event: %w", err)) - - return - } - - exitEventIDs = append(exitEventIDs, extractedExitEventIDs...) - } - - blockNumbers[i] = receipt.BlockNumber - } - - outputter.SetCommandResult( - &common.BridgeTxResult{ - Sender: senderAccount.Address().String(), - Receivers: wp.Receivers, - Amounts: wp.Amounts, - ExitEventIDs: exitEventIDs, - BlockNumbers: blockNumbers, - Title: "WITHDRAW ERC 20", - }) -} - -// createWithdrawTxn encodes parameters for withdraw function on destination predicate contract -func createWithdrawTxn(receiver types.Address, amount *big.Int) (*ethgo.Transaction, error) { - withdrawToFn := &contractsapi.WithdrawToChildERC20PredicateFn{ - ChildToken: types.StringToAddress(wp.TokenAddr), - Receiver: receiver, - Amount: amount, - } - - input, err := withdrawToFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - addr := ethgo.Address(types.StringToAddress(wp.PredicateAddr)) - - return helper.CreateTransaction(ethgo.ZeroAddress, &addr, input, - nil, wp.ChildChainMintable), nil -} diff --git a/command/bridge/withdraw/erc721/withdraw_erc721.go b/command/bridge/withdraw/erc721/withdraw_erc721.go deleted file mode 100644 index 850f44f..0000000 --- a/command/bridge/withdraw/erc721/withdraw_erc721.go +++ /dev/null @@ -1,171 +0,0 @@ -package withdraw - -import ( - "encoding/hex" - "fmt" - "math/big" - "strings" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/wallet" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/bridge/common" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/contracts" - helperCommon "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var ( - wp *common.ERC721BridgeParams = common.NewERC721BridgeParams() -) - -func GetCommand() *cobra.Command { - withdrawCmd := &cobra.Command{ - Use: "withdraw-erc721", - Short: "Withdraws ERC 721 tokens from the destination to the origin chain", - PreRunE: preRun, - Run: run, - } - - wp.RegisterCommonFlags(withdrawCmd) - - withdrawCmd.Flags().StringSliceVar( - &wp.TokenIDs, - common.TokenIDsFlag, - nil, - "tokens ids to send to receiving accounts", - ) - - withdrawCmd.Flags().StringVar( - &wp.PredicateAddr, - common.ChildPredicateFlag, - contracts.ChildERC721PredicateContract.String(), - "ERC 721 child chain predicate address", - ) - - withdrawCmd.Flags().StringVar( - &wp.TokenAddr, - common.ChildTokenFlag, - contracts.ChildERC721Contract.String(), - "ERC 721 child chain token address", - ) - - _ = withdrawCmd.MarkFlagRequired(common.SenderKeyFlag) - _ = withdrawCmd.MarkFlagRequired(common.ReceiversFlag) - _ = withdrawCmd.MarkFlagRequired(common.TokenIDsFlag) - - return withdrawCmd -} - -func preRun(_ *cobra.Command, _ []string) error { - return wp.Validate() -} - -func run(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - senderKeyRaw, err := hex.DecodeString(wp.SenderKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode sender private key: %w", err)) - - return - } - - senderAccount, err := wallet.NewWalletFromPrivKey(senderKeyRaw) - if err != nil { - outputter.SetError(err) - - return - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(wp.JSONRPCAddr)) - if err != nil { - outputter.SetError(fmt.Errorf("could not create child chain tx relayer: %w", err)) - - return - } - - receivers := make([]ethgo.Address, len(wp.Receivers)) - tokenIDs := make([]*big.Int, len(wp.Receivers)) - - for i, tokenIDRaw := range wp.TokenIDs { - tokenIDRaw := tokenIDRaw - - tokenID, err := helperCommon.ParseUint256orHex(&tokenIDRaw) - if err != nil { - outputter.SetError(fmt.Errorf("failed to decode provided token id %s: %w", tokenIDRaw, err)) - - return - } - - receivers[i] = ethgo.Address(types.StringToAddress(wp.Receivers[i])) - tokenIDs[i] = tokenID - } - - txn, err := createWithdrawTxn(receivers, tokenIDs) - if err != nil { - outputter.SetError(fmt.Errorf("failed to create tx input: %w", err)) - - return - } - - receipt, err := txRelayer.SendTransaction(txn, senderAccount) - if err != nil { - outputter.SetError(fmt.Errorf("failed to send withdrawal transaction (receivers: %s, token ids: %s): %w", - strings.Join(wp.Receivers, ", "), strings.Join(wp.TokenIDs, ", "), err)) - - return - } - - if receipt.Status == uint64(types.ReceiptFailed) { - outputter.SetError(fmt.Errorf("failed to execute withdrawal transaction (receivers: %s, token ids: %s)", - strings.Join(wp.Receivers, ", "), strings.Join(wp.TokenIDs, ", "))) - - return - } - - res := &common.BridgeTxResult{ - Sender: senderAccount.Address().String(), - Receivers: wp.Receivers, - TokenIDs: wp.TokenIDs, - BlockNumbers: []uint64{receipt.BlockNumber}, - Title: "WITHDRAW ERC 721", - } - - if !wp.ChildChainMintable { - exitEventIDs, err := common.ExtractExitEventIDs(receipt) - if err != nil { - outputter.SetError(fmt.Errorf("failed to extract exit event: %w", err)) - - return - } - - res.ExitEventIDs = exitEventIDs - } - - outputter.SetCommandResult(res) -} - -// createWithdrawTxn encodes parameters for withdraw function on child chain predicate contract -func createWithdrawTxn(receivers []ethgo.Address, tokenIDs []*big.Int) (*ethgo.Transaction, error) { - withdrawToFn := &contractsapi.WithdrawBatchChildERC721PredicateFn{ - ChildToken: types.StringToAddress(wp.TokenAddr), - Receivers: receivers, - TokenIDs: tokenIDs, - } - - input, err := withdrawToFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - addr := ethgo.Address(types.StringToAddress(wp.PredicateAddr)) - - return helper.CreateTransaction(ethgo.ZeroAddress, &addr, input, - nil, wp.ChildChainMintable), nil -} diff --git a/command/default.go b/command/default.go index da12d7a..71b2d8e 100644 --- a/command/default.go +++ b/command/default.go @@ -13,7 +13,7 @@ const ( DefaultGenesisFileName = "genesis.json" DefaultChainName = "xgrchain" DefaultChainID = 100 - DefaultConsensus = server.PolyBFTConsensus + DefaultConsensus = server.IBFTConsensus DefaultGenesisGasUsed = 458752 // 0x70000 DefaultGenesisGasLimit = 5242880 // 0x500000 DefaultGenesisBaseFeeEM = chain.GenesisBaseFeeEM diff --git a/command/genesis/genesis.go b/command/genesis/genesis.go index 7204660..a74f960 100644 --- a/command/genesis/genesis.go +++ b/command/genesis/genesis.go @@ -110,13 +110,6 @@ func setFlags(cmd *cobra.Command) { "the epoch size for the chain", ) - cmd.Flags().StringVar( - ¶ms.proxyContractsAdmin, - proxyContractsAdminFlag, - "", - "admin for proxy contracts", - ) - // PoS { cmd.Flags().BoolVar( @@ -159,7 +152,7 @@ func setFlags(cmd *cobra.Command) { ¶ms.validators, command.ValidatorFlag, []string{}, - "validators defined by user (polybft format: ::)", + "validators defined by user", ) cmd.MarkFlagsMutuallyExclusive(command.ValidatorFlag, command.ValidatorRootFlag) @@ -176,160 +169,12 @@ func setFlags(cmd *cobra.Command) { ) } - // PolyBFT - { - cmd.Flags().Uint64Var( - ¶ms.sprintSize, - sprintSizeFlag, - defaultSprintSize, - "the number of block included into a sprint", - ) - - cmd.Flags().DurationVar( - ¶ms.blockTime, - blockTimeFlag, - defaultBlockTime, - "the predefined period which determines block creation frequency", - ) - - cmd.Flags().Uint64Var( - ¶ms.epochReward, - epochRewardFlag, - defaultEpochReward, - "reward size for block sealing", - ) - - // regenesis flag that allows to start from non-empty database - cmd.Flags().StringVar( - ¶ms.initialStateRoot, - trieRootFlag, - "", - "trie root from the corresponding triedb", - ) - - cmd.Flags().StringVar( - ¶ms.nativeTokenConfigRaw, - nativeTokenConfigFlag, - "", - "native token configuration, provided in the following format: "+ - "", - ) - - cmd.Flags().StringVar( - ¶ms.rewardTokenCode, - rewardTokenCodeFlag, - "", - "hex encoded reward token byte code", - ) - - cmd.Flags().StringVar( - ¶ms.rewardWallet, - rewardWalletFlag, - "", - "configuration of reward wallet in format ", - ) - - cmd.Flags().Uint64Var( - ¶ms.blockTimeDrift, - blockTimeDriftFlag, - defaultBlockTimeDrift, - "configuration for block time drift value (in seconds)", - ) - - cmd.Flags().DurationVar( - ¶ms.blockTrackerPollInterval, - blockTrackerPollIntervalFlag, - defaultBlockTrackerPollInterval, - "interval (number of seconds) at which block tracker polls for latest block at rootchain", - ) - } - - // Access Control Lists - { - cmd.Flags().StringArrayVar( - ¶ms.contractDeployerAllowListAdmin, - contractDeployerAllowListAdminFlag, - []string{}, - "list of addresses to use as admin accounts in the contract deployer allow list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.contractDeployerAllowListEnabled, - contractDeployerAllowListEnabledFlag, - []string{}, - "list of addresses to enable by default in the contract deployer allow list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.contractDeployerBlockListAdmin, - contractDeployerBlockListAdminFlag, - []string{}, - "list of addresses to use as admin accounts in the contract deployer block list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.contractDeployerBlockListEnabled, - contractDeployerBlockListEnabledFlag, - []string{}, - "list of addresses to enable by default in the contract deployer block list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.transactionsAllowListAdmin, - transactionsAllowListAdminFlag, - []string{}, - "list of addresses to use as admin accounts in the transactions allow list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.transactionsAllowListEnabled, - transactionsAllowListEnabledFlag, - []string{}, - "list of addresses to enable by default in the transactions allow list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.transactionsBlockListAdmin, - transactionsBlockListAdminFlag, - []string{}, - "list of addresses to use as admin accounts in the transactions block list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.transactionsBlockListEnabled, - transactionsBlockListEnabledFlag, - []string{}, - "list of addresses to enable by default in the transactions block list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.bridgeAllowListAdmin, - bridgeAllowListAdminFlag, - []string{}, - "list of addresses to use as admin accounts in the bridge allow list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.bridgeAllowListEnabled, - bridgeAllowListEnabledFlag, - []string{}, - "list of addresses to enable by default in the bridge allow list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.bridgeBlockListAdmin, - bridgeBlockListAdminFlag, - []string{}, - "list of addresses to use as admin accounts in the bridge block list", - ) - - cmd.Flags().StringArrayVar( - ¶ms.bridgeBlockListEnabled, - bridgeBlockListEnabledFlag, - []string{}, - "list of addresses to enable by default in the bridge block list", - ) - } + cmd.Flags().DurationVar( + ¶ms.blockTime, + blockTimeFlag, + defaultBlockTime, + "the predefined period which determines block creation frequency", + ) } // setLegacyFlags sets the legacy flags to preserve backwards compatibility @@ -362,12 +207,8 @@ func runCommand(cmd *cobra.Command, _ []string) { var err error - if params.isPolyBFTConsensus() { - err = params.generatePolyBftChainConfig(outputter) - } else { - _, _ = outputter.Write([]byte(fmt.Sprintf("%s\n", common.IBFTImportantNotice))) - err = params.generateGenesis() - } + _, _ = outputter.Write([]byte(fmt.Sprintf("%s\n", common.IBFTImportantNotice))) + err = params.generateGenesis() if err != nil { outputter.SetError(err) diff --git a/command/genesis/params.go b/command/genesis/params.go index 6e26f88..a446271 100644 --- a/command/genesis/params.go +++ b/command/genesis/params.go @@ -3,9 +3,7 @@ package genesis import ( "errors" "fmt" - "math/big" "os" - "strings" "time" "github.com/xgr-network/xgr-node/chain" @@ -14,8 +12,6 @@ import ( "github.com/xgr-network/xgr-node/consensus/ibft" "github.com/xgr-network/xgr-node/consensus/ibft/fork" "github.com/xgr-network/xgr-node/consensus/ibft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft" - "github.com/xgr-network/xgr-node/contracts" "github.com/xgr-network/xgr-node/contracts/staking" stakingHelper "github.com/xgr-network/xgr-node/helper/staking" "github.com/xgr-network/xgr-node/server" @@ -24,21 +20,15 @@ import ( ) const ( - dirFlag = "dir" - nameFlag = "name" - premineFlag = "premine" - chainIDFlag = "chain-id" - epochSizeFlag = "epoch-size" - epochRewardFlag = "epoch-reward" - blockGasLimitFlag = "block-gas-limit" - burnContractFlag = "burn-contract" - genesisBaseFeeConfigFlag = "base-fee-config" - posFlag = "pos" - nativeTokenConfigFlag = "native-token-config" - rewardTokenCodeFlag = "reward-token-code" - rewardWalletFlag = "reward-wallet" - blockTrackerPollIntervalFlag = "block-tracker-poll-interval" - proxyContractsAdminFlag = "proxy-contracts-admin" + dirFlag = "dir" + nameFlag = "name" + premineFlag = "premine" + chainIDFlag = "chain-id" + epochSizeFlag = "epoch-size" + blockGasLimitFlag = "block-gas-limit" + burnContractFlag = "burn-contract" + genesisBaseFeeConfigFlag = "base-fee-config" + posFlag = "pos" ) // Legacy flags that need to be preserved for running clients @@ -46,24 +36,22 @@ const ( chainIDFlagLEGACY = "chainid" ) +const ( + blockTimeFlag = "block-time" + defaultBlockTime = 2 * time.Second +) + var ( params = &genesisParams{} ) var ( - errValidatorsNotSpecified = errors.New("validator information not specified") - errUnsupportedConsensus = errors.New("specified consensusRaw not supported") - errInvalidEpochSize = errors.New("epoch size must be greater than 1") - errRewardWalletAmountZero = errors.New("reward wallet amount can not be zero or negative") - errReserveAccMustBePremined = errors.New("it is mandatory to premine reserve account (0x0 address)") - errBlockTrackerPollInterval = errors.New("block tracker poll interval must be greater than 0") - errBaseFeeChangeDenomZero = errors.New("base fee change denominator must be greater than 0") - errBaseFeeEMZero = errors.New("base fee elasticity multiplier must be greater than 0") - errBaseFeeZero = errors.New("base fee must be greater than 0") - errRewardWalletNotDefined = errors.New("reward wallet address must be defined") - errRewardTokenOnNonMintable = errors.New("a custom reward token must be defined when " + - "native ERC20 token is non-mintable") - errRewardWalletZero = errors.New("reward wallet address must not be zero address") + errValidatorsNotSpecified = errors.New("validator information not specified") + errUnsupportedConsensus = errors.New("specified consensusRaw not supported") + errInvalidEpochSize = errors.New("epoch size must be greater than 1") + errBaseFeeChangeDenomZero = errors.New("base fee change denominator must be greater than 0") + errBaseFeeEMZero = errors.New("base fee elasticity multiplier must be greater than 0") + errBaseFeeZero = errors.New("base fee must be greater than 0") ) type genesisParams struct { @@ -102,40 +90,9 @@ type genesisParams struct { genesisConfig *chain.Chain - // PolyBFT - sprintSize uint64 - blockTime time.Duration - epochReward uint64 - blockTimeDrift uint64 - - initialStateRoot string - - // access lists - contractDeployerAllowListAdmin []string - contractDeployerAllowListEnabled []string - contractDeployerBlockListAdmin []string - contractDeployerBlockListEnabled []string - transactionsAllowListAdmin []string - transactionsAllowListEnabled []string - transactionsBlockListAdmin []string - transactionsBlockListEnabled []string - bridgeAllowListAdmin []string - bridgeAllowListEnabled []string - bridgeBlockListAdmin []string - bridgeBlockListEnabled []string - - nativeTokenConfigRaw string - nativeTokenConfig *polybft.TokenConfig - premineInfos []*helper.PremineInfo - // rewards - rewardTokenCode string - rewardWallet string - - blockTrackerPollInterval time.Duration - - proxyContractsAdmin string + blockTime time.Duration } func (p *genesisParams) validateFlags() error { @@ -159,35 +116,13 @@ func (p *genesisParams) validateFlags() error { return err } - if p.isPolyBFTConsensus() { - if err := p.extractNativeTokenMetadata(); err != nil { - return err - } - - if err := p.validateBurnContract(); err != nil { - return err - } - - if err := p.validateRewardWalletAndToken(); err != nil { - return err - } - - if err := p.validatePremineInfo(); err != nil { - return err - } - - if err := p.validateProxyContractsAdmin(); err != nil { - return err - } - } - // Check if the genesis file already exists if generateError := verifyGenesisExistence(p.genesisPath); generateError != nil { return errors.New(generateError.GetMessage()) } // Check that the epoch size is correct - if p.epochSize < 2 && (p.isIBFTConsensus() || p.isPolyBFTConsensus()) { + if p.epochSize < 2 && p.isIBFTConsensus() { // Epoch size must be greater than 1, so new transactions have a chance to be added to a block. // Otherwise, every block would be an endblock (meaning it will not have any transactions). // Check is placed here to avoid additional parsing if epochSize < 2 @@ -209,10 +144,6 @@ func (p *genesisParams) isIBFTConsensus() bool { return server.ConsensusType(p.consensusRaw) == server.IBFTConsensus } -func (p *genesisParams) isPolyBFTConsensus() bool { - return server.ConsensusType(p.consensusRaw) == server.PolyBFTConsensus -} - func (p *genesisParams) areValidatorsSetManually() bool { return len(p.validators) != 0 } @@ -234,10 +165,6 @@ func (p *genesisParams) getRequiredFlags() []string { func (p *genesisParams) initRawParams() error { p.consensus = server.ConsensusType(p.consensusRaw) - if p.consensus == server.PolyBFTConsensus { - return nil - } - if err := p.initIBFTValidatorType(); err != nil { return err } @@ -464,6 +391,7 @@ func (p *genesisParams) predeployStakingSC() (*chain.GenesisAccount, error) { stakingHelper.PredeployParams{ MinValidatorCount: p.minNumValidators, MaxValidatorCount: p.maxNumValidators, + EpochSize: p.epochSize, }) if predeployErr != nil { return nil, predeployErr @@ -472,33 +400,6 @@ func (p *genesisParams) predeployStakingSC() (*chain.GenesisAccount, error) { return stakingAccount, nil } -// validateRewardWalletAndToken validates reward wallet flag -func (p *genesisParams) validateRewardWalletAndToken() error { - if p.rewardWallet == "" { - return errRewardWalletNotDefined - } - - if !p.nativeTokenConfig.IsMintable && p.rewardTokenCode == "" { - return errRewardTokenOnNonMintable - } - - premineInfo, err := helper.ParsePremineInfo(p.rewardWallet) - if err != nil { - return err - } - - if premineInfo.Address == types.ZeroAddress { - return errRewardWalletZero - } - - // If epoch rewards are enabled, reward wallet must have some amount of premine - if p.epochReward > 0 && premineInfo.Amount.Cmp(big.NewInt(0)) < 1 { - return errRewardWalletAmountZero - } - - return nil -} - // parsePremineInfo parses premine flag func (p *genesisParams) parsePremineInfo() error { p.premineInfos = make([]*helper.PremineInfo, 0, len(p.premine)) @@ -515,28 +416,6 @@ func (p *genesisParams) parsePremineInfo() error { return nil } -// validatePremineInfo validates whether reserve account (0x0 address) is premined -func (p *genesisParams) validatePremineInfo() error { - for _, premineInfo := range p.premineInfos { - if premineInfo.Address == types.ZeroAddress { - // we have premine of zero address, just return - return nil - } - } - - return errReserveAccMustBePremined -} - -// validateBlockTrackerPollInterval validates block tracker block interval -// which can not be 0 -func (p *genesisParams) validateBlockTrackerPollInterval() error { - if p.blockTrackerPollInterval == 0 { - return helper.ErrBlockTrackerPollInterval - } - - return nil -} - // validateBurnContract validates burn contract. If native token is mintable, // burn contract flag must not be set. If native token is non mintable only one burn contract // can be set and the specified address will be used to predeploy default EIP1559 burn contract. @@ -547,14 +426,8 @@ func (p *genesisParams) validateBurnContract() error { return fmt.Errorf("invalid burn contract info provided: %w", err) } - if p.nativeTokenConfig.IsMintable { - if burnContractInfo.Address != types.ZeroAddress { - return errors.New("only zero address is allowed as burn destination for mintable native token") - } - } else { - if burnContractInfo.Address == types.ZeroAddress { - return errors.New("it is not allowed to deploy burn contract to 0x0 address") - } + if burnContractInfo.Address == types.ZeroAddress { + return errors.New("it is not allowed to deploy burn contract to 0x0 address") } } @@ -588,40 +461,11 @@ func (p *genesisParams) validateGenesisBaseFeeConfig() error { return nil } -func (p *genesisParams) validateProxyContractsAdmin() error { - if strings.TrimSpace(p.proxyContractsAdmin) == "" { - return errors.New("proxy contracts admin address must be set") - } - - proxyContractsAdminAddr := types.StringToAddress(p.proxyContractsAdmin) - if proxyContractsAdminAddr == types.ZeroAddress { - return errors.New("proxy contracts admin address must not be zero address") - } - - if proxyContractsAdminAddr == contracts.SystemCaller { - return errors.New("proxy contracts admin address must not be system caller address") - } - - return nil -} - // isBurnContractEnabled returns true in case burn contract info is provided func (p *genesisParams) isBurnContractEnabled() bool { return p.burnContract != "" } -// extractNativeTokenMetadata parses provided native token metadata (such as name, symbol and decimals count) -func (p *genesisParams) extractNativeTokenMetadata() error { - tokenConfig, err := polybft.ParseRawTokenConfig(p.nativeTokenConfigRaw) - if err != nil { - return err - } - - p.nativeTokenConfig = tokenConfig - - return nil -} - func (p *genesisParams) getResult() command.CommandResult { return &GenesisResult{ Message: fmt.Sprintf("\nGenesis written to %s\n", p.genesisPath), diff --git a/command/genesis/params_test.go b/command/genesis/params_test.go index 632ba5f..7b5a784 100644 --- a/command/genesis/params_test.go +++ b/command/genesis/params_test.go @@ -1,244 +1,27 @@ package genesis import ( - "fmt" + "math/big" "testing" "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/command" "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/consensus/polybft" "github.com/xgr-network/xgr-node/types" ) -func Test_extractNativeTokenMetadata(t *testing.T) { +func Test_parsePremineInfo(t *testing.T) { t.Parallel() - cases := []struct { - name string - rawConfig string - expectedCfg *polybft.TokenConfig - expectErr bool - }{ - { - name: "default token config", - rawConfig: "", - expectedCfg: polybft.DefaultTokenConfig, - expectErr: false, - }, - { - name: "not enough params provided", - rawConfig: "Test:TST:18", - expectErr: true, - }, - { - name: "empty name provided", - rawConfig: ":TST:18:false", - expectErr: true, - }, - { - name: "empty symbol provided", - rawConfig: "Test::18:false", - expectErr: true, - }, - { - name: "invalid decimals number provided", - rawConfig: "Test:TST:9999999999999999999999999999999999999999999999999999999999:false", - expectErr: true, - }, - { - name: "invalid mintable flag provided", - rawConfig: "Test:TST:18:bar", - expectErr: true, - }, - { - name: "mintable token not enough params provided", - rawConfig: "Test:TST:18:true", - expectErr: true, - }, - { - name: "non-mintable valid config", - rawConfig: "MyToken:MTK:9:false", - expectedCfg: &polybft.TokenConfig{ - Name: "MyToken", - Symbol: "MTK", - Decimals: 9, - IsMintable: false, - Owner: types.ZeroAddress, - }, - expectErr: false, - }, - { - name: "non-mintable token config, owner provided but ignored", - rawConfig: "MyToken:MTK:9:false:0x123456789", - expectedCfg: &polybft.TokenConfig{ - Name: "MyToken", - Symbol: "MTK", - Decimals: 9, - IsMintable: false, - Owner: types.ZeroAddress, - }, - expectErr: false, - }, - { - name: "mintable token valid config", - rawConfig: "MyMintToken:MMTK:9:true:0x123456789", - expectedCfg: &polybft.TokenConfig{ - Name: "MyMintToken", - Symbol: "MMTK", - Decimals: 9, - IsMintable: true, - Owner: types.StringToAddress("0x123456789"), - }, - expectErr: false, - }, + p := &genesisParams{ + premine: []string{types.StringToAddress("1").String() + ":1000"}, } - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - - p := &genesisParams{nativeTokenConfigRaw: c.rawConfig} - err := p.extractNativeTokenMetadata() - - if c.expectErr { - require.Error(t, err) - require.Nil(t, p.nativeTokenConfig) - } else { - require.NoError(t, err) - require.Equal(t, c.expectedCfg, p.nativeTokenConfig) - } - }) - } -} - -func Test_validatePremineInfo(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - premineRaw []string - expectedPremines []*helper.PremineInfo - expectValidateErrMsg string - expectedParseErrMsg string - }{ - { - name: "invalid premine balance", - premineRaw: []string{"0x12345:loremIpsum"}, - expectedPremines: []*helper.PremineInfo{}, - expectedParseErrMsg: "invalid premine balance amount provided", - }, - { - name: "missing zero address premine", - premineRaw: []string{types.StringToAddress("12").String()}, - expectedPremines: []*helper.PremineInfo{ - {Address: types.StringToAddress("12"), Amount: command.DefaultPremineBalance}, - }, - expectValidateErrMsg: errReserveAccMustBePremined.Error(), - }, - { - name: "valid premine information", - premineRaw: []string{ - fmt.Sprintf("%s:%d", types.StringToAddress("1"), ethgo.Ether(10)), - fmt.Sprintf("%s:%d", types.ZeroAddress, ethgo.Ether(10000)), - }, - expectedPremines: []*helper.PremineInfo{ - {Address: types.StringToAddress("1"), Amount: ethgo.Ether(10)}, - {Address: types.ZeroAddress, Amount: ethgo.Ether(10000)}, - }, - expectValidateErrMsg: "", - }, - } - - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - - p := &genesisParams{premine: c.premineRaw} - err := p.parsePremineInfo() - if c.expectedParseErrMsg != "" { - require.ErrorContains(t, err, c.expectedParseErrMsg) - - return - } else { - require.NoError(t, err) - } - - err = p.validatePremineInfo() - - if c.expectValidateErrMsg != "" { - require.ErrorContains(t, err, c.expectValidateErrMsg) - } else { - require.NoError(t, err) - } - - require.Equal(t, c.expectedPremines, p.premineInfos) - }) - } -} - -func Test_validateRewardWallet(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - rewardWallet string - epochReward uint64 - isNativeERC20Mintable bool - expectValidateErr error - }{ - { - name: "invalid reward wallet: no premine + reward", - rewardWallet: types.StringToAddress("1").String() + ":0", - epochReward: 10, - isNativeERC20Mintable: true, - expectValidateErr: errRewardWalletAmountZero, - }, - { - name: "invalid reward wallet: reward wallet not defined", - rewardWallet: "", - epochReward: 10, - isNativeERC20Mintable: true, - expectValidateErr: errRewardWalletNotDefined, - }, - { - name: "invalid reward wallet: reward wallet is zero", - rewardWallet: types.ZeroAddress.String() + ":0", - epochReward: 10, - isNativeERC20Mintable: true, - expectValidateErr: errRewardWalletZero, - }, - { - name: "valid reward wallet: no premine + no reward", - rewardWallet: types.StringToAddress("1").String() + ":0", - epochReward: 0, - isNativeERC20Mintable: true, - expectValidateErr: nil, - }, - { - name: "valid reward wallet: native ERC20 mintable", - rewardWallet: types.StringToAddress("1").String() + ":0", - epochReward: 0, - isNativeERC20Mintable: false, - expectValidateErr: errRewardTokenOnNonMintable, - }, - } - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - - p := &genesisParams{ - rewardWallet: c.rewardWallet, - epochReward: c.epochReward, - nativeTokenConfig: &polybft.TokenConfig{IsMintable: c.isNativeERC20Mintable}, - } - err := p.validateRewardWalletAndToken() - require.ErrorIs(t, err, c.expectValidateErr) - }) - } + err := p.parsePremineInfo() + require.NoError(t, err) + require.Len(t, p.premineInfos, 1) + require.Equal(t, &helper.PremineInfo{ + Address: types.StringToAddress("1"), + Amount: big.NewInt(1000), + }, p.premineInfos[0]) } diff --git a/command/genesis/polybft_params.go b/command/genesis/polybft_params.go deleted file mode 100644 index 0a92f51..0000000 --- a/command/genesis/polybft_params.go +++ /dev/null @@ -1,567 +0,0 @@ -package genesis - -import ( - "encoding/hex" - "errors" - "fmt" - "math/big" - "path" - "strings" - "time" - - "github.com/multiformats/go-multiaddr" - - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/consensus/polybft" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi/artifact" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/server" - "github.com/xgr-network/xgr-node/types" -) - -const ( - sprintSizeFlag = "sprint-size" - blockTimeFlag = "block-time" - trieRootFlag = "trieroot" - - blockTimeDriftFlag = "block-time-drift" - - defaultEpochSize = uint64(10) - defaultSprintSize = uint64(5) - defaultValidatorSetSize = 100 - defaultBlockTime = 2 * time.Second - defaultEpochReward = 1 - defaultBlockTimeDrift = uint64(10) - defaultBlockTrackerPollInterval = time.Second - - contractDeployerAllowListAdminFlag = "contract-deployer-allow-list-admin" - contractDeployerAllowListEnabledFlag = "contract-deployer-allow-list-enabled" - contractDeployerBlockListAdminFlag = "contract-deployer-block-list-admin" - contractDeployerBlockListEnabledFlag = "contract-deployer-block-list-enabled" - transactionsAllowListAdminFlag = "transactions-allow-list-admin" - transactionsAllowListEnabledFlag = "transactions-allow-list-enabled" - transactionsBlockListAdminFlag = "transactions-block-list-admin" - transactionsBlockListEnabledFlag = "transactions-block-list-enabled" - bridgeAllowListAdminFlag = "bridge-allow-list-admin" - bridgeAllowListEnabledFlag = "bridge-allow-list-enabled" - bridgeBlockListAdminFlag = "bridge-block-list-admin" - bridgeBlockListEnabledFlag = "bridge-block-list-enabled" - - bootnodePortStart = 30301 - - ecdsaAddressLength = 40 - blsKeyLength = 256 -) - -var ( - errNoGenesisValidators = errors.New("genesis validators aren't provided") - errNoPremineAllowed = errors.New("native token is not mintable, so no premine is allowed " + - "except for zero address and reward wallet if native token is used as reward token") -) - -type contractInfo struct { - artifact *artifact.Artifact - address types.Address -} - -// generatePolyBftChainConfig creates and persists polybft chain configuration to the provided file path -func (p *genesisParams) generatePolyBftChainConfig(o command.OutputFormatter) error { - // populate premine balance map - premineBalances := make(map[types.Address]*helper.PremineInfo, len(p.premine)) - - for _, premine := range p.premineInfos { - premineBalances[premine.Address] = premine - } - - walletPremineInfo, err := helper.ParsePremineInfo(p.rewardWallet) - if err != nil { - return fmt.Errorf("invalid reward wallet configuration provided '%s' : %w", p.rewardWallet, err) - } - - if !p.nativeTokenConfig.IsMintable { - // validate premine map, no premine is allowed if token is not mintable, - // except for the reward wallet (if native token is used as reward token) and zero address - for a := range premineBalances { - if a != types.ZeroAddress && (p.rewardTokenCode != "" || a != walletPremineInfo.Address) { - return errNoPremineAllowed - } - } - } - - var ( - rewardTokenByteCode []byte - rewardTokenAddr = contracts.NativeERC20TokenContract - ) - - if p.rewardTokenCode == "" { - // native token is used as a reward token, and reward wallet is not a zero address - if p.epochReward > 0 { - // epoch reward is non zero so premine reward wallet - premineBalances[walletPremineInfo.Address] = walletPremineInfo - } - } else { - bytes, err := hex.DecodeString(p.rewardTokenCode) - if err != nil { - return fmt.Errorf("could not decode reward token byte code '%s' : %w", p.rewardTokenCode, err) - } - - rewardTokenByteCode = bytes - rewardTokenAddr = contracts.RewardTokenContract - } - - initialValidators, err := p.getValidatorAccounts() - if err != nil { - return fmt.Errorf("failed to retrieve genesis validators: %w", err) - } - - if len(initialValidators) == 0 { - return errNoGenesisValidators - } - - if _, err := o.Write([]byte("[GENESIS VALIDATORS]\n")); err != nil { - return err - } - - for _, v := range initialValidators { - if _, err := o.Write([]byte(fmt.Sprintf("%v\n", v))); err != nil { - return err - } - } - - polyBftConfig := &polybft.PolyBFTConfig{ - InitialValidatorSet: initialValidators, - BlockTime: common.Duration{Duration: p.blockTime}, - EpochSize: p.epochSize, - SprintSize: p.sprintSize, - EpochReward: p.epochReward, - // use 1st account as governance address - Governance: types.ZeroAddress, - InitialTrieRoot: types.StringToHash(p.initialStateRoot), - NativeTokenConfig: p.nativeTokenConfig, - MinValidatorSetSize: p.minNumValidators, - MaxValidatorSetSize: p.maxNumValidators, - RewardConfig: &polybft.RewardsConfig{ - TokenAddress: rewardTokenAddr, - WalletAddress: walletPremineInfo.Address, - WalletAmount: walletPremineInfo.Amount, - }, - BlockTimeDrift: p.blockTimeDrift, - BlockTrackerPollInterval: common.Duration{Duration: p.blockTrackerPollInterval}, - ProxyContractsAdmin: types.StringToAddress(p.proxyContractsAdmin), - } - - enabledForks := chain.AllForksEnabled.Copy() - - chainConfig := &chain.Chain{ - Name: p.name, - Params: &chain.Params{ - ChainID: int64(p.chainID), - Forks: enabledForks, - Engine: map[string]interface{}{ - string(server.PolyBFTConsensus): polyBftConfig, - }, - }, - Bootnodes: p.bootnodes, - } - - burnContractAddr := types.ZeroAddress - - if p.isBurnContractEnabled() { - chainConfig.Params.BurnContract = make(map[uint64]types.Address, 1) - - burnContractInfo, err := parseBurnContractInfo(p.burnContract) - if err != nil { - return err - } - - if !p.nativeTokenConfig.IsMintable { - // burn contract can be specified on arbitrary address for non-mintable native tokens - burnContractAddr = burnContractInfo.Address - chainConfig.Params.BurnContract[burnContractInfo.BlockNumber] = burnContractAddr - chainConfig.Params.BurnContractDestinationAddress = burnContractInfo.DestinationAddress - } else { - // burnt funds are sent to zero address when dealing with mintable native tokens - chainConfig.Params.BurnContract[burnContractInfo.BlockNumber] = types.ZeroAddress - } - } - - // deploy genesis contracts - allocs, err := p.deployContracts(rewardTokenByteCode, polyBftConfig, chainConfig, burnContractAddr) - if err != nil { - return err - } - - // premine other accounts - for _, premine := range premineBalances { - // validators have already been premined, so no need to premine them again - if _, ok := allocs[premine.Address]; ok { - continue - } - - allocs[premine.Address] = &chain.GenesisAccount{ - Balance: premine.Amount, - } - } - - validatorMetadata := make([]*validator.ValidatorMetadata, len(initialValidators)) - - for i, validator := range initialValidators { - // create validator metadata instance - metadata, err := validator.ToValidatorMetadata() - if err != nil { - return err - } - - validatorMetadata[i] = metadata - - // set genesis validators as boot nodes if boot nodes not provided via CLI - if len(p.bootnodes) == 0 { - chainConfig.Bootnodes = append(chainConfig.Bootnodes, validator.MultiAddr) - } - } - - genesisExtraData, err := GenerateExtraDataPolyBft(validatorMetadata) - if err != nil { - return err - } - - // populate genesis parameters - chainConfig.Genesis = &chain.Genesis{ - GasLimit: p.blockGasLimit, - Difficulty: 0, - Alloc: allocs, - ExtraData: genesisExtraData, - GasUsed: command.DefaultGenesisGasUsed, - Mixhash: polybft.PolyBFTMixDigest, - } - - if len(p.contractDeployerAllowListAdmin) != 0 { - // only enable allow list if there is at least one address as **admin**, otherwise - // the allow list could never be updated - chainConfig.Params.ContractDeployerAllowList = &chain.AddressListConfig{ - AdminAddresses: stringSliceToAddressSlice(p.contractDeployerAllowListAdmin), - EnabledAddresses: stringSliceToAddressSlice(p.contractDeployerAllowListEnabled), - } - } - - if len(p.contractDeployerBlockListAdmin) != 0 { - // only enable block list if there is at least one address as **admin**, otherwise - // the block list could never be updated - chainConfig.Params.ContractDeployerBlockList = &chain.AddressListConfig{ - AdminAddresses: stringSliceToAddressSlice(p.contractDeployerBlockListAdmin), - EnabledAddresses: stringSliceToAddressSlice(p.contractDeployerBlockListEnabled), - } - } - - if len(p.transactionsAllowListAdmin) != 0 { - // only enable allow list if there is at least one address as **admin**, otherwise - // the allow list could never be updated - chainConfig.Params.TransactionsAllowList = &chain.AddressListConfig{ - AdminAddresses: stringSliceToAddressSlice(p.transactionsAllowListAdmin), - EnabledAddresses: stringSliceToAddressSlice(p.transactionsAllowListEnabled), - } - } - - if len(p.transactionsBlockListAdmin) != 0 { - // only enable block list if there is at least one address as **admin**, otherwise - // the block list could never be updated - chainConfig.Params.TransactionsBlockList = &chain.AddressListConfig{ - AdminAddresses: stringSliceToAddressSlice(p.transactionsBlockListAdmin), - EnabledAddresses: stringSliceToAddressSlice(p.transactionsBlockListEnabled), - } - } - - if len(p.bridgeAllowListAdmin) != 0 { - // only enable allow list if there is at least one address as **admin**, otherwise - // the allow list could never be updated - chainConfig.Params.BridgeAllowList = &chain.AddressListConfig{ - AdminAddresses: stringSliceToAddressSlice(p.bridgeAllowListAdmin), - EnabledAddresses: stringSliceToAddressSlice(p.bridgeAllowListEnabled), - } - } - - if len(p.bridgeBlockListAdmin) != 0 { - // only enable block list if there is at least one address as **admin**, otherwise - // the block list could never be updated - chainConfig.Params.BridgeBlockList = &chain.AddressListConfig{ - AdminAddresses: stringSliceToAddressSlice(p.bridgeBlockListAdmin), - EnabledAddresses: stringSliceToAddressSlice(p.bridgeBlockListEnabled), - } - } - - if p.isBurnContractEnabled() { - // only populate base fee and base fee multiplier values if burn contract(s) - // is provided - chainConfig.Genesis.BaseFee = p.parsedBaseFeeConfig.baseFee - chainConfig.Genesis.BaseFeeEM = p.parsedBaseFeeConfig.baseFeeEM - chainConfig.Genesis.BaseFeeChangeDenom = p.parsedBaseFeeConfig.baseFeeChangeDenom - } - - return helper.WriteGenesisConfigToDisk(chainConfig, params.genesisPath) -} - -func (p *genesisParams) deployContracts( - rewardTokenByteCode []byte, - polybftConfig *polybft.PolyBFTConfig, - chainConfig *chain.Chain, - burnContractAddr types.Address) (map[types.Address]*chain.GenesisAccount, error) { - proxyToImplAddrMap := contracts.GetProxyImplementationMapping() - proxyAddresses := make([]types.Address, 0, len(proxyToImplAddrMap)) - - for proxyAddr := range proxyToImplAddrMap { - proxyAddresses = append(proxyAddresses, proxyAddr) - } - - genesisContracts := []*contractInfo{ - { - // State receiver contract - artifact: contractsapi.StateReceiver, - address: contracts.StateReceiverContractV1, - }, - { - // ChildERC20 token contract - artifact: contractsapi.ChildERC20, - address: contracts.ChildERC20Contract, - }, - { - // ChildERC721 token contract - artifact: contractsapi.ChildERC721, - address: contracts.ChildERC721Contract, - }, - { - // ChildERC1155 contract - artifact: contractsapi.ChildERC1155, - address: contracts.ChildERC1155Contract, - }, - { - // BLS contract - artifact: contractsapi.BLS, - address: contracts.BLSContractV1, - }, - { - // Merkle contract - artifact: contractsapi.Merkle, - address: contracts.MerkleContractV1, - }, - { - // L2StateSender contract - artifact: contractsapi.L2StateSender, - address: contracts.L2StateSenderContractV1, - }, - { - artifact: contractsapi.ValidatorSet, - address: contracts.ValidatorSetContractV1, - }, - { - artifact: contractsapi.RewardPool, - address: contracts.RewardPoolContractV1, - }, - } - - if !params.nativeTokenConfig.IsMintable { - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.NativeERC20, - address: contracts.NativeERC20TokenContractV1, - }) - - // burn contract can be set only for non-mintable native token. If burn contract is set, - // default EIP1559 contract will be deployed. - if p.isBurnContractEnabled() { - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.EIP1559Burn, - address: burnContractAddr, - }) - - proxyAddresses = append(proxyAddresses, contracts.DefaultBurnContract) - } - } else { - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.NativeERC20Mintable, - address: contracts.NativeERC20TokenContractV1, - }) - } - - if len(params.bridgeAllowListAdmin) != 0 || len(params.bridgeBlockListAdmin) != 0 { - // rootchain originated tokens predicates (with access lists) - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.ChildERC20PredicateACL, - address: contracts.ChildERC20PredicateContractV1, - }) - - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.ChildERC721PredicateACL, - address: contracts.ChildERC721PredicateContractV1, - }) - - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.ChildERC1155PredicateACL, - address: contracts.ChildERC1155PredicateContractV1, - }) - - // childchain originated tokens predicates (with access lists) - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.RootMintableERC20PredicateACL, - address: contracts.RootMintableERC20PredicateContractV1, - }) - - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.RootMintableERC721PredicateACL, - address: contracts.RootMintableERC721PredicateContractV1, - }) - - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.RootMintableERC1155PredicateACL, - address: contracts.RootMintableERC1155PredicateContractV1, - }) - } else { - // rootchain originated tokens predicates - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.ChildERC20Predicate, - address: contracts.ChildERC20PredicateContractV1, - }) - - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.ChildERC721Predicate, - address: contracts.ChildERC721PredicateContractV1, - }) - - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.ChildERC1155Predicate, - address: contracts.ChildERC1155PredicateContractV1, - }) - - // childchain originated tokens predicates - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.RootMintableERC20Predicate, - address: contracts.RootMintableERC20PredicateContractV1, - }) - - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.RootMintableERC721Predicate, - address: contracts.RootMintableERC721PredicateContractV1, - }) - - genesisContracts = append(genesisContracts, - &contractInfo{ - artifact: contractsapi.RootMintableERC1155Predicate, - address: contracts.RootMintableERC1155PredicateContractV1, - }) - } - - allocations := make(map[types.Address]*chain.GenesisAccount, len(genesisContracts)+1) - - if rewardTokenByteCode != nil { - // if reward token is provided in genesis then, add it to allocations - // to RewardTokenContract address and update Polybft config - allocations[contracts.RewardTokenContractV1] = &chain.GenesisAccount{ - Balance: big.NewInt(0), - Code: rewardTokenByteCode, - } - - proxyAddresses = append(proxyAddresses, contracts.RewardTokenContract) - } - - genesisContracts = append(genesisContracts, getProxyContractsInfo(proxyAddresses)...) - - for _, contract := range genesisContracts { - allocations[contract.address] = &chain.GenesisAccount{ - Balance: big.NewInt(0), - Code: contract.artifact.DeployedBytecode, - } - } - - return allocations, nil -} - -// getValidatorAccounts gathers validator accounts info either from CLI or from provided local storage -func (p *genesisParams) getValidatorAccounts() ([]*validator.GenesisValidator, error) { - // populate validators premine info - if len(p.validators) > 0 { - validators := make([]*validator.GenesisValidator, len(p.validators)) - for i, val := range p.validators { - parts := strings.Split(val, ":") - if len(parts) != 3 { - return nil, fmt.Errorf("expected 3 parts provided in the following format "+ - ", but got %d part(s)", - len(parts)) - } - - if _, err := multiaddr.NewMultiaddr(parts[0]); err != nil { - return nil, fmt.Errorf("invalid P2P multi address '%s' provided: %w ", parts[0], err) - } - - trimmedAddress := strings.TrimPrefix(parts[1], "0x") - if len(trimmedAddress) != ecdsaAddressLength { - return nil, fmt.Errorf("invalid ECDSA address: %s", parts[1]) - } - - trimmedBLSKey := strings.TrimPrefix(parts[2], "0x") - if len(trimmedBLSKey) != blsKeyLength { - return nil, fmt.Errorf("invalid BLS key: %s", parts[2]) - } - - addr := types.StringToAddress(trimmedAddress) - validators[i] = &validator.GenesisValidator{ - MultiAddr: parts[0], - Address: addr, - BlsKey: trimmedBLSKey, - Stake: big.NewInt(0), - } - } - - return validators, nil - } - - validatorsPath := p.validatorsPath - if validatorsPath == "" { - validatorsPath = path.Dir(p.genesisPath) - } - - validators, err := ReadValidatorsByPrefix(validatorsPath, p.validatorsPrefixPath) - if err != nil { - return nil, err - } - - return validators, nil -} - -func stringSliceToAddressSlice(addrs []string) []types.Address { - res := make([]types.Address, len(addrs)) - for indx, addr := range addrs { - res[indx] = types.StringToAddress(addr) - } - - return res -} - -func getProxyContractsInfo(addresses []types.Address) []*contractInfo { - result := make([]*contractInfo, len(addresses)) - - for i, proxyAddress := range addresses { - result[i] = &contractInfo{ - artifact: contractsapi.GenesisProxy, - address: proxyAddress, - } - } - - return result -} diff --git a/command/genesis/utils.go b/command/genesis/utils.go index b06637f..349b82b 100644 --- a/command/genesis/utils.go +++ b/command/genesis/utils.go @@ -1,26 +1,15 @@ package genesis import ( - "encoding/hex" "errors" "fmt" - "math/big" "os" - "path/filepath" "sort" "strconv" "strings" - "github.com/hashicorp/go-hclog" "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/consensus/polybft" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/secrets" - "github.com/xgr-network/xgr-node/secrets/helper" - "github.com/xgr-network/xgr-node/secrets/local" "github.com/xgr-network/xgr-node/types" ) @@ -65,35 +54,8 @@ func verifyGenesisExistence(genesisPath string) *GenesisGenError { return nil } -// parseTrackerStartBlocks parses provided event tracker start blocks configuration. -// It is set in a following format: :. -// In case smart contract address isn't provided in the string, it is assumed its starting block is 0 implicitly. -func parseTrackerStartBlocks(trackerStartBlocksRaw []string) (map[types.Address]uint64, error) { - trackerStartBlocksConfig := make(map[types.Address]uint64, len(trackerStartBlocksRaw)) - - for _, startBlockRaw := range trackerStartBlocksRaw { - delimiterIdx := strings.Index(startBlockRaw, ":") - if delimiterIdx == -1 { - return nil, fmt.Errorf("invalid event tracker start block configuration provided: %s", trackerStartBlocksRaw) - } - - // : - address := types.StringToAddress(startBlockRaw[:delimiterIdx]) - startBlockRaw := startBlockRaw[delimiterIdx+1:] - - startBlock, err := strconv.ParseUint(startBlockRaw, 10, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse provided start block %s: %w", startBlockRaw, err) - } - - trackerStartBlocksConfig[address] = startBlock - } - - return trackerStartBlocksConfig, nil -} - // parseBurnContractInfo parses provided burn contract information and returns burn contract block and address -func parseBurnContractInfo(burnContractInfoRaw string) (*polybft.BurnContractInfo, error) { +func parseBurnContractInfo(burnContractInfoRaw string) (*burnContractInfo, error) { // :
[:] burnContractParts := strings.Split(burnContractInfoRaw, ":") if len(burnContractParts) < 2 || len(burnContractParts) > 3 { @@ -113,7 +75,7 @@ func parseBurnContractInfo(burnContractInfoRaw string) (*polybft.BurnContractInf } if len(burnContractParts) == 2 { - return &polybft.BurnContractInfo{ + return &burnContractInfo{ BlockNumber: blockNum, Address: types.StringToAddress(contractAddress), DestinationAddress: types.ZeroAddress, @@ -125,7 +87,7 @@ func parseBurnContractInfo(burnContractInfoRaw string) (*polybft.BurnContractInf return nil, fmt.Errorf("failed to parse burn destination address %s: %w", destinationAddress, err) } - return &polybft.BurnContractInfo{ + return &burnContractInfo{ BlockNumber: blockNum, Address: types.StringToAddress(contractAddress), DestinationAddress: types.StringToAddress(destinationAddress), @@ -218,68 +180,8 @@ func GetValidatorKeyFiles(rootDir, filePrefix string) ([]string, error) { return fileNames, nil } -// ReadValidatorsByPrefix reads validators secrets on a given root directory and with given folder prefix -func ReadValidatorsByPrefix(dir, prefix string) ([]*validator.GenesisValidator, error) { - validatorKeyFiles, err := GetValidatorKeyFiles(dir, prefix) - if err != nil { - return nil, err - } - - validators := make([]*validator.GenesisValidator, len(validatorKeyFiles)) - - for i, file := range validatorKeyFiles { - path := filepath.Join(dir, file) - - account, nodeID, err := getSecrets(path) - if err != nil { - return nil, err - } - - validators[i] = &validator.GenesisValidator{ - Address: types.Address(account.Ecdsa.Address()), - BlsKey: hex.EncodeToString(account.Bls.PublicKey().Marshal()), - MultiAddr: fmt.Sprintf("/ip4/%s/tcp/%d/p2p/%s", "127.0.0.1", bootnodePortStart+int64(i), nodeID), - Stake: big.NewInt(0), - } - } - - return validators, nil -} - -func getSecrets(directory string) (*wallet.Account, string, error) { - baseConfig := &secrets.SecretsManagerParams{ - Logger: hclog.NewNullLogger(), - Extra: map[string]interface{}{ - secrets.Path: directory, - }, - } - - localManager, err := local.SecretsManagerFactory(nil, baseConfig) - if err != nil { - return nil, "", fmt.Errorf("unable to instantiate local secrets manager, %w", err) - } - - nodeID, err := helper.LoadNodeID(localManager) - if err != nil { - return nil, "", err - } - - account, err := wallet.NewAccountFromSecret(localManager) - if err != nil { - return nil, "", err - } - - return account, nodeID, err -} - -// GenerateExtraDataPolyBft populates Extra with specific fields required for polybft consensus protocol -func GenerateExtraDataPolyBft(validators []*validator.ValidatorMetadata) ([]byte, error) { - delta := &validator.ValidatorSetDelta{ - Added: validators, - Removed: bitmap.Bitmap{}, - } - - extra := polybft.Extra{Validators: delta, Checkpoint: &polybft.CheckpointData{}} - - return extra.MarshalRLPTo(nil), nil +type burnContractInfo struct { + BlockNumber uint64 + Address types.Address + DestinationAddress types.Address } diff --git a/command/ibft/helper/account.go b/command/ibft/helper/account.go new file mode 100644 index 0000000..116ff00 --- /dev/null +++ b/command/ibft/helper/account.go @@ -0,0 +1,28 @@ +package helper + +import ( + "encoding/hex" + "fmt" + + ethgowallet "github.com/umbracle/ethgo/wallet" + "github.com/xgr-network/xgr-node/secrets" +) + +func GetECDSAKeyFromSecret(secretsManager secrets.SecretsManager) (*ethgowallet.Key, error) { + encodedKey, err := secretsManager.GetSecret(secrets.ValidatorKey) + if err != nil { + return nil, fmt.Errorf("failed to retrieve ecdsa key: %w", err) + } + + ecdsaRaw, err := hex.DecodeString(string(encodedKey)) + if err != nil { + return nil, fmt.Errorf("failed to retrieve ecdsa key: %w", err) + } + + key, err := ethgowallet.NewWalletFromPrivKey(ecdsaRaw) + if err != nil { + return nil, fmt.Errorf("failed to retrieve ecdsa key: %w", err) + } + + return key, nil +} diff --git a/command/ibft/ibft.go b/command/ibft/ibft.go index 0605c89..7db0934 100644 --- a/command/ibft/ibft.go +++ b/command/ibft/ibft.go @@ -4,11 +4,18 @@ import ( "github.com/spf13/cobra" "github.com/xgr-network/xgr-node/command/helper" "github.com/xgr-network/xgr-node/command/ibft/candidates" + "github.com/xgr-network/xgr-node/command/ibft/join" + "github.com/xgr-network/xgr-node/command/ibft/poolconfig" "github.com/xgr-network/xgr-node/command/ibft/propose" "github.com/xgr-network/xgr-node/command/ibft/quorum" + "github.com/xgr-network/xgr-node/command/ibft/setactive" "github.com/xgr-network/xgr-node/command/ibft/snapshot" + "github.com/xgr-network/xgr-node/command/ibft/stake" "github.com/xgr-network/xgr-node/command/ibft/status" _switch "github.com/xgr-network/xgr-node/command/ibft/switch" + "github.com/xgr-network/xgr-node/command/ibft/unstake" + "github.com/xgr-network/xgr-node/command/ibft/validators" + "github.com/xgr-network/xgr-node/command/ibft/withdraw" ) func GetCommand() *cobra.Command { @@ -38,5 +45,15 @@ func registerSubcommands(baseCmd *cobra.Command) { _switch.GetCommand(), // ibft quorum quorum.GetCommand(), + // ibft join (PoS validator onboarding) + join.GetCommand(), + poolconfig.GetCommand(), + // ibft stake / unstake / withdraw / set-active + stake.GetCommand(), + unstake.GetCommand(), + withdraw.GetCommand(), + setactive.GetCommand(), + // ibft validators + validators.GetCommand(), ) } diff --git a/command/ibft/join/README.md b/command/ibft/join/README.md new file mode 100644 index 0000000..430eb1e --- /dev/null +++ b/command/ibft/join/README.md @@ -0,0 +1,26 @@ +# ibft join + +One-command validator onboarding for PoS networks using the predeployed staking contract at `0x1001`. + +What it does: +- loads (or generates) ECDSA/BLS keys from the secrets manager (DataDir or cloud config) +- registers the BLS public key onchain if not already registered +- stakes the required amount (by sending `stake()` with native value) + +Example (local dev): + +``` +xgrchain ibft join \ + --jsonrpc http://127.0.0.1:8545 \ + --data-dir ./validator1 \ + --stake 200000 +``` + +Example (remote chain node): + +``` +xgrchain ibft join \ + --jsonrpc http://:8545 \ + --data-dir ./data \ + --stake 1000000 +``` diff --git a/command/ibft/join/join.go b/command/ibft/join/join.go new file mode 100644 index 0000000..345d782 --- /dev/null +++ b/command/ibft/join/join.go @@ -0,0 +1,579 @@ +package join + +import ( + "bytes" + "encoding/hex" + "fmt" + "math/big" + "net/url" + "reflect" + "strings" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + ethgowallet "github.com/umbracle/ethgo/wallet" + + "github.com/xgr-network/xgr-node/command" + "github.com/xgr-network/xgr-node/command/helper" + "github.com/xgr-network/xgr-node/contracts/abis" + "github.com/xgr-network/xgr-node/contracts/staking" + "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/secrets" + secretsHelper "github.com/xgr-network/xgr-node/secrets/helper" + "github.com/xgr-network/xgr-node/secrets/local" + "github.com/xgr-network/xgr-node/txrelayer" + "github.com/xgr-network/xgr-node/types" +) + +const ( + flagDataDir = "data-dir" + flagConfig = "config" + flagInitKeys = "init-keys" + flagInitOnly = "init-only" + flagStakeXGR = "stake" + flagDecimals = "decimals" + + defaultStakeXGR = "200000" + defaultDecimals = 18 +) + +type params struct { + jsonRPC string + dataDir string + config string + initKeys bool + initOnly bool + stakeStr string + decimals int +} + +var p params + +func GetCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "join", + Short: "One-shot PoS validator join: ensure keys, register BLS pubkey, stake required amount", + Example: ` # Local validator + xgrchain ibft join --jsonrpc http://127.0.0.1:8545 --data-dir ./data --stake 1000000 + + # Remote chain node + xgrchain ibft join --jsonrpc http://:8545 --data-dir ./data --stake 1000000`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + p.jsonRPC = helper.GetJSONRPCAddress(cmd) + if p.jsonRPC == "" { + return fmt.Errorf("missing --jsonrpc") + } + + p.jsonRPC = normalizeJSONRPCAddress(p.jsonRPC) + if p.dataDir == "" && p.config == "" { + return fmt.Errorf("either --%s or --%s must be provided", flagDataDir, flagConfig) + } + if p.decimals < 0 || p.decimals > 30 { + return fmt.Errorf("invalid --%s", flagDecimals) + } + if _, ok := new(big.Int).SetString(p.stakeStr, 10); !ok { + return fmt.Errorf("invalid --%s (must be integer, in XGR units): %s", flagStakeXGR, p.stakeStr) + } + return nil + }, + RunE: run, + } + + cmd.Flags().StringVar(&p.jsonRPC, "jsonrpc", "http://127.0.0.1:8545", "JSON-RPC endpoint") + + cmd.Flags().StringVar(&p.dataDir, flagDataDir, "", "data dir for local secrets manager (validator keys)") + cmd.Flags().StringVar(&p.config, flagConfig, "", "secrets manager config (cloud)") + cmd.Flags().BoolVar(&p.initKeys, flagInitKeys, true, "auto-generate missing ECDSA/BLS keys into secrets manager") + cmd.Flags().BoolVar(&p.initOnly, flagInitOnly, false, "only generate keys and print the validator address (no onchain tx)") + cmd.Flags().StringVar(&p.stakeStr, flagStakeXGR, defaultStakeXGR, "stake amount in XGR (integer units, default 200,000)") + cmd.Flags().IntVar(&p.decimals, flagDecimals, defaultDecimals, "native decimals for XGR (default 18)") + + cmd.MarkFlagsMutuallyExclusive(flagDataDir, flagConfig) + return cmd +} + +func run(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + // 1) secrets manager + sm, err := initSecretsManager() + if err != nil { + outputter.SetError(err) + return nil + } + + // 2) ensure keys exist (optional) + if p.initKeys { + if err := ensureKeys(sm); err != nil { + outputter.SetError(err) + return nil + } + } + + // 3) load ECDSA + BLS pubkey from secrets + ecdsaKey, blsPub, err := loadKeysFromSecrets(sm) + if err != nil { + outputter.SetError(err) + return nil + } + valAddr := types.Address(ecdsaKey.Address()) + + // INIT-ONLY: print address + pubkey and exit. + if p.initOnly { + outputter.SetCommandResult(&JoinResult{ + Validator: valAddr.String(), + JoinedAtBlock: 0, + BLSRegistered: false, + StakeWei: stakeToWei(p.stakeStr, p.decimals).String(), + AccountStakeWei: "0", + Eligible: false, + Note: "Keys generated. Fund this address (gas + stake) and rerun without --init-only.", + }) + return nil + } + + // 4) relayer + relayer, err := txrelayer.NewTxRelayer( + txrelayer.WithIPAddress(p.jsonRPC), + txrelayer.WithReceiptTimeout(250*time.Millisecond), + ) + if err != nil { + outputter.SetError(err) + return nil + } + + // 5) validate requested stake against validator self-stake minimum + requiredStakeWei := stakeToWei(p.stakeStr, p.decimals) + thresholdWei, err := queryValidatorMinSelfStake(relayer) + if err != nil { + outputter.SetError(fmt.Errorf("failed to query VALIDATOR_MIN_SELF_STAKE: %w", err)) + return nil + } + if requiredStakeWei.Cmp(thresholdWei) < 0 { + outputter.SetError(fmt.Errorf("requested --%s=%s is below validator self-stake minimum (%s XGR)", flagStakeXGR, p.stakeStr, weiToXGRString(thresholdWei, p.decimals))) + return nil + } + + // 6) check current onchain state + onchainStake, err := queryAccountStake(relayer, valAddr) + if err != nil { + outputter.SetError(fmt.Errorf("failed to query accountStake: %w", err)) + return nil + } + + alreadyRegistered, err := isBLSRegistered(relayer, valAddr, blsPub) + if err != nil { + outputter.SetError(fmt.Errorf("failed to query validator list/pubkeys: %w", err)) + return nil + } + + // 7) stake if needed (must happen before registerBLSPublicKey for new validators) + if onchainStake.Cmp(requiredStakeWei) < 0 { + stakeDeltaWei := new(big.Int).Sub(requiredStakeWei, onchainStake) + + balanceWei, balErr := relayer.Client().Eth().GetBalance(ecdsaKey.Address(), ethgo.Latest) + if balErr != nil { + outputter.SetError(fmt.Errorf("failed to query account balance: %w", balErr)) + return nil + } + if balanceWei.Cmp(stakeDeltaWei) < 0 { + outputter.SetError(fmt.Errorf( + "insufficient balance for staking value: need %s wei (%s XGR), have %s wei (%s XGR); fund validator address %s", + stakeDeltaWei.String(), weiToXGRString(stakeDeltaWei, p.decimals), + balanceWei.String(), weiToXGRString(balanceWei, p.decimals), + valAddr.String(), + )) + return nil + } + + if err := sendStake(relayer, ecdsaKey, stakeDeltaWei); err != nil { + outputter.SetError(err) + return nil + } + onchainStake = new(big.Int).Add(onchainStake, stakeDeltaWei) + } + + // 8) register BLS pubkey if needed + if !alreadyRegistered { + if err := sendRegisterBLS(relayer, ecdsaKey, blsPub); err != nil { + outputter.SetError(err) + return nil + } + } + + // 9) done + joinedAt, _ := queryJoinedAtBlock(relayer, valAddr) + outputter.SetCommandResult(&JoinResult{ + Validator: valAddr.String(), + JoinedAtBlock: joinedAt, + BLSRegistered: true, + StakeWei: requiredStakeWei.String(), + AccountStakeWei: onchainStake.String(), + Eligible: true, + Note: "Join requires >=200k self stake and becomes effective in next epoch. Eligibility in fetcher still additionally requires >=2M effective total support stake.", + }) + + return nil +} + +func normalizeJSONRPCAddress(raw string) string { + parsed, err := url.Parse(raw) + if err != nil { + return raw + } + + if parsed.Hostname() != "0.0.0.0" { + return raw + } + + port := parsed.Port() + if port == "" { + return raw + } + + parsed.Host = "127.0.0.1:" + port + + return parsed.String() +} + +func initSecretsManager() (secrets.SecretsManager, error) { + if p.config != "" { + cfg, err := secrets.ReadConfig(p.config) + if err != nil { + return nil, err + } + return secretsHelper.InitCloudSecretsManager(cfg) + } + + // local FS secrets manager + baseConfig := &secrets.SecretsManagerParams{ + Logger: hclog.NewNullLogger(), + Extra: map[string]interface{}{ + secrets.Path: p.dataDir, + }, + } + + return local.SecretsManagerFactory(nil, baseConfig) +} + +func ensureKeys(sm secrets.SecretsManager) error { + // ECDSA validator key + if !sm.HasSecret(secrets.ValidatorKey) { + if _, err := secretsHelper.InitECDSAValidatorKey(sm); err != nil { + return err + } + } + // BLS validator key + if !sm.HasSecret(secrets.ValidatorBLSKey) { + if _, err := secretsHelper.InitBLSValidatorKey(sm); err != nil { + return err + } + } + return nil +} + +func loadKeysFromSecrets(sm secrets.SecretsManager) (*ethgowallet.Key, []byte, error) { + ecdsaRaw, err := sm.GetSecret(secrets.ValidatorKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to load validator ecdsa key: %w", err) + } + ecdsaPriv, err := crypto.BytesToECDSAPrivateKey(ecdsaRaw) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse validator ecdsa key: %w", err) + } + ecdsaKey := ethgowallet.NewKey(ecdsaPriv) + + blsRaw, err := sm.GetSecret(secrets.ValidatorBLSKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to load validator bls key: %w", err) + } + blsKey, err := crypto.BytesToBLSSecretKey(blsRaw) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse validator bls key: %w", err) + } + blsPub, err := crypto.BLSSecretKeyToPubkeyBytes(blsKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to derive validator bls pubkey: %w", err) + } + + return ecdsaKey, blsPub, nil +} + +func weiToXGRString(wei *big.Int, decimals int) string { + if wei == nil { + return "0" + } + base := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil) + q := new(big.Int) + r := new(big.Int) + q.QuoRem(wei, base, r) + if r.Sign() == 0 { + return q.String() + } + + frac := r.Text(10) + if len(frac) < decimals { + frac = strings.Repeat("0", decimals-len(frac)) + frac + } + frac = strings.TrimRight(frac, "0") + + return q.String() + "." + frac +} + +func stakeToWei(amountStr string, decimals int) *big.Int { + a, _ := new(big.Int).SetString(amountStr, 10) + m := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil) + return new(big.Int).Mul(a, m) +} + +func queryAccountStake(relayer txrelayer.TxRelayer, addr types.Address) (*big.Int, error) { + method := abis.StakingABI.Methods["accountStake"] + if method == nil { + return nil, fmt.Errorf("staking ABI missing accountStake") + } + inp, err := method.Inputs.Encode(map[string]interface{}{ + "account": ethgo.Address(addr), + }) + if err != nil { + return nil, err + } + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), append(method.ID(), inp...)) + if err != nil { + return nil, err + } + b, err := hexToBytes(resp) + if err != nil { + return nil, err + } + decoded, err := method.Outputs.Decode(b) + if err != nil { + return nil, err + } + m, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode accountStake: unexpected type") + } + v, ok := m["0"].(*big.Int) + if !ok { + return nil, fmt.Errorf("decode accountStake: missing value") + } + return v, nil +} + +func queryJoinedAtBlock(relayer txrelayer.TxRelayer, addr types.Address) (uint64, error) { + selector := crypto.Keccak256([]byte("joinedAtBlock(address)"))[:4] + input := make([]byte, 4+32) + copy(input[:4], selector) + copy(input[4+12:], addr.Bytes()) + + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), input) + if err != nil { + return 0, err + } + b, err := hexToBytes(resp) + if err != nil { + return 0, err + } + if len(b) == 0 { + return 0, nil + } + + return new(big.Int).SetBytes(b).Uint64(), nil +} + +func queryValidatorMinSelfStake(relayer txrelayer.TxRelayer) (*big.Int, error) { + method := abis.StakingABI.Methods["VALIDATOR_MIN_SELF_STAKE"] + if method == nil { + return nil, fmt.Errorf("staking ABI missing VALIDATOR_MIN_SELF_STAKE") + } + + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), method.ID()) + if err != nil { + return nil, err + } + + b, err := hexToBytes(resp) + if err != nil { + return nil, err + } + + decoded, err := method.Outputs.Decode(b) + if err != nil { + return nil, err + } + mp, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode VALIDATOR_MIN_SELF_STAKE: unexpected type") + } + v, ok := mp["0"].(*big.Int) + if !ok { + return nil, fmt.Errorf("decode VALIDATOR_MIN_SELF_STAKE: missing value") + } + + return v, nil +} + +func isBLSRegistered(relayer txrelayer.TxRelayer, addr types.Address, pub []byte) (bool, error) { + mInfo := abis.StakingABI.Methods["validatorInfo"] + if mInfo == nil { + return false, fmt.Errorf("staking ABI missing validatorInfo") + } + + inp, err := mInfo.Inputs.Encode(map[string]interface{}{"account": ethgo.Address(addr)}) + if err != nil { + return false, err + } + + rawInfo, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), append(mInfo.ID(), inp...)) + if err != nil { + return false, err + } + + infoBytes, err := hexToBytes(rawInfo) + if err != nil { + return false, err + } + + decoded, err := mInfo.Outputs.Decode(infoBytes) + if err != nil { + return false, err + } + + mp, ok := decoded.(map[string]interface{}) + if !ok { + return false, fmt.Errorf("decode validatorInfo: unexpected type") + } + + blsRaw, ok := mp["blsPubKey"] + if !ok { + blsRaw = mp["4"] + } + + chainPub, err := toBytes(blsRaw) + if err != nil { + return false, fmt.Errorf("decode validatorInfo blsPubKey: %w", err) + } + + return bytes.Equal(chainPub, pub), nil +} + +func toBytes(raw interface{}) ([]byte, error) { + switch v := raw.(type) { + case []byte: + return v, nil + case string: + b, err := hexToBytes(v) + if err != nil { + return nil, fmt.Errorf("invalid hex value") + } + + return b, nil + } + + rv := reflect.ValueOf(raw) + if !rv.IsValid() { + return nil, fmt.Errorf("unexpected item type") + } + + kind := rv.Kind() + if kind == reflect.Array || kind == reflect.Slice { + if rv.Type().Elem().Kind() == reflect.Uint8 { + b := make([]byte, rv.Len()) + for i := 0; i < rv.Len(); i++ { + b[i] = byte(rv.Index(i).Uint()) + } + + return b, nil + } + } + + return nil, fmt.Errorf("unexpected item type") +} + +func methodSelector(signature string) []byte { + return crypto.Keccak256([]byte(signature))[:4] +} + +func ensureMethodSelector(methodName, signature string, id []byte) error { + expected := methodSelector(signature) + if !bytes.Equal(id, expected) { + return fmt.Errorf("staking ABI selector mismatch for %s: got 0x%s expected 0x%s", methodName, hex.EncodeToString(id), hex.EncodeToString(expected)) + } + + return nil +} + +func sendRegisterBLS(relayer txrelayer.TxRelayer, key ethgo.Key, pub []byte) error { + m := abis.StakingABI.Methods["registerBLSPublicKey"] + if m == nil { + return fmt.Errorf("staking ABI missing registerBLSPublicKey") + } + if err := ensureMethodSelector("registerBLSPublicKey", "registerBLSPublicKey(bytes)", m.ID()); err != nil { + return err + } + inp, err := m.Inputs.Encode(map[string]interface{}{ + "blsPubKey": pub, + }) + if err != nil { + return err + } + to := ethgo.Address(staking.AddrStakingContract) + tx := ðgo.Transaction{ + To: &to, + Input: append(m.ID(), inp...), + Type: ethgo.TransactionDynamicFee, + } + receipt, err := relayer.SendTransaction(tx, key) + if err != nil { + return fmt.Errorf("registerBLSPublicKey tx failed: %w", err) + } + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return fmt.Errorf("registerBLSPublicKey reverted") + } + return nil +} + +func sendStake(relayer txrelayer.TxRelayer, key ethgo.Key, stakeWei *big.Int) error { + m := abis.StakingABI.Methods["stake"] + if m == nil { + return fmt.Errorf("staking ABI missing stake") + } + if err := ensureMethodSelector("stake", "stake()", m.ID()); err != nil { + return err + } + to := ethgo.Address(staking.AddrStakingContract) + tx := ðgo.Transaction{ + To: &to, + Input: m.ID(), + Value: stakeWei, + Type: ethgo.TransactionDynamicFee, + } + receipt, err := relayer.SendTransaction(tx, key) + if err != nil { + return fmt.Errorf("stake tx failed: %w", err) + } + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return fmt.Errorf("stake reverted") + } + return nil +} + +// compile-time sanity: ensure we pull in crypto package (kept for future signature checks) +var _ = crypto.Keccak256 + +func hexToBytes(s string) ([]byte, error) { + s = strings.TrimSpace(s) + if s == "" || s == "0x" { + return []byte{}, nil + } + if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { + s = s[2:] + } + if len(s)%2 == 1 { + s = "0" + s + } + return hex.DecodeString(s) +} diff --git a/command/ibft/join/join_command_test.go b/command/ibft/join/join_command_test.go new file mode 100644 index 0000000..bec417e --- /dev/null +++ b/command/ibft/join/join_command_test.go @@ -0,0 +1,43 @@ +package join + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetCommand_DefaultStakeMatchesValidatorMinSelfStakeSemantics(t *testing.T) { + cmd := GetCommand() + + flag := cmd.Flags().Lookup(flagStakeXGR) + require.NotNil(t, flag) + require.Equal(t, "200000", flag.DefValue) + require.Contains(t, flag.Usage, "default 200,000") +} + +func TestGetCommand_StakeValidationRejectsNonInteger(t *testing.T) { + cmd := GetCommand() + require.NoError(t, cmd.Flags().Set("jsonrpc", "http://127.0.0.1:8545")) + require.NoError(t, cmd.Flags().Set(flagDataDir, "/tmp/data")) + require.NoError(t, cmd.Flags().Set(flagStakeXGR, "200000.5")) + + err := cmd.PreRunE(cmd, nil) + require.Error(t, err) + require.ErrorContains(t, err, "invalid --stake") +} + +func TestJoinResult_OutputExplicitlyMentionsTwoStageEligibility(t *testing.T) { + res := (&JoinResult{ + Validator: "0x1", + JoinedAtBlock: 123, + BLSRegistered: true, + StakeWei: "200000000000000000000000", + AccountStakeWei: "200000000000000000000000", + Eligible: true, // current CLI behavior: true after successful join flow + Note: "Join requires >=200k self stake and becomes effective in next epoch. Eligibility in fetcher still additionally requires >=2M effective total support stake.", + }).GetOutput() + + require.Contains(t, res, "Eligible = true") + require.Contains(t, res, ">=200k self stake") + require.Contains(t, res, ">=2M effective total support stake") +} diff --git a/command/ibft/join/join_decode_test.go b/command/ibft/join/join_decode_test.go new file mode 100644 index 0000000..0f06bcd --- /dev/null +++ b/command/ibft/join/join_decode_test.go @@ -0,0 +1,97 @@ +package join + +import ( + "math/big" + "reflect" + "testing" +) + +func TestToBytes(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + raw interface{} + want []byte + wantErr bool + }{ + {name: "native bytes", raw: []byte{0x01, 0x02}, want: []byte{0x01, 0x02}}, + {name: "hex string", raw: "0x0102", want: []byte{0x01, 0x02}}, + {name: "fixed byte array", raw: [2]byte{0x01, 0x02}, want: []byte{0x01, 0x02}}, + {name: "unexpected type", raw: 123, wantErr: true}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := toBytes(tt.raw) + if tt.wantErr { + if err == nil { + t.Fatalf("expected error") + } + return + } + if err != nil { + t.Fatalf("toBytes() error = %v", err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("toBytes() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNormalizeJSONRPCAddress(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in string + out string + }{ + {name: "rewrites all interfaces host", in: "http://0.0.0.0:8545", out: "http://127.0.0.1:8545"}, + {name: "keeps localhost", in: "http://127.0.0.1:8545", out: "http://127.0.0.1:8545"}, + {name: "keeps domain", in: "https://rpc.example.org:443", out: "https://rpc.example.org:443"}, + {name: "keeps invalid url", in: ":://broken", out: ":://broken"}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := normalizeJSONRPCAddress(tt.in); got != tt.out { + t.Fatalf("normalizeJSONRPCAddress() = %q, want %q", got, tt.out) + } + }) + } +} + +func TestWeiToXGRString(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + wei string + dec int + want string + }{ + {name: "whole", wei: "2000000000000000000000000", dec: 18, want: "2000000"}, + {name: "fraction", wei: "1500000000000000000", dec: 18, want: "1.5"}, + {name: "small", wei: "1", dec: 18, want: "0.000000000000000001"}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + v, ok := new(big.Int).SetString(tt.wei, 10) + if !ok { + t.Fatalf("invalid test wei") + } + if got := weiToXGRString(v, tt.dec); got != tt.want { + t.Fatalf("weiToXGRString() = %q, want %q", got, tt.want) + } + }) + } +} diff --git a/command/ibft/join/join_selector_test.go b/command/ibft/join/join_selector_test.go new file mode 100644 index 0000000..db4efb3 --- /dev/null +++ b/command/ibft/join/join_selector_test.go @@ -0,0 +1,18 @@ +package join + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEnsureMethodSelector_Ok(t *testing.T) { + err := ensureMethodSelector("stake", "stake()", methodSelector("stake()")) + require.NoError(t, err) +} + +func TestEnsureMethodSelector_Mismatch(t *testing.T) { + err := ensureMethodSelector("stake", "stake()", methodSelector("stake(uint256)")) + require.Error(t, err) + require.ErrorContains(t, err, "selector mismatch") +} diff --git a/command/ibft/join/result.go b/command/ibft/join/result.go new file mode 100644 index 0000000..bc061d6 --- /dev/null +++ b/command/ibft/join/result.go @@ -0,0 +1,33 @@ +package join + +import ( + "bytes" + "fmt" + + "github.com/xgr-network/xgr-node/command/helper" +) + +type JoinResult struct { + Validator string `json:"validator"` + JoinedAtBlock uint64 `json:"joinedAtBlock,omitempty"` + BLSRegistered bool `json:"blsRegistered"` + StakeWei string `json:"stakeWei"` + AccountStakeWei string `json:"accountStakeWei"` + Eligible bool `json:"eligible"` + Note string `json:"note"` +} + +func (r *JoinResult) GetOutput() string { + var b bytes.Buffer + b.WriteString("\n[IBFT JOIN]\n") + b.WriteString(helper.FormatKV([]string{ + fmt.Sprintf("Validator|%s", r.Validator), + fmt.Sprintf("Joined at block|%d", r.JoinedAtBlock), + fmt.Sprintf("BLS registered|%t", r.BLSRegistered), + fmt.Sprintf("Required stake (wei)|%s", r.StakeWei), + fmt.Sprintf("Account stake (wei)|%s", r.AccountStakeWei), + fmt.Sprintf("Eligible|%t", r.Eligible), + fmt.Sprintf("Note|%s", r.Note), + })) + return b.String() +} diff --git a/command/ibft/poolconfig/pool_config.go b/command/ibft/poolconfig/pool_config.go new file mode 100644 index 0000000..d18f7bc --- /dev/null +++ b/command/ibft/poolconfig/pool_config.go @@ -0,0 +1,241 @@ +package poolconfig + +import ( + "encoding/hex" + "fmt" + "math/big" + "strings" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + + "github.com/xgr-network/xgr-node/command" + ibftHelper "github.com/xgr-network/xgr-node/command/ibft/helper" + "github.com/xgr-network/xgr-node/contracts/abis" + "github.com/xgr-network/xgr-node/contracts/staking" + secretsHelper "github.com/xgr-network/xgr-node/helper/secrets" + "github.com/xgr-network/xgr-node/secrets" + "github.com/xgr-network/xgr-node/secrets/local" + "github.com/xgr-network/xgr-node/txrelayer" + "github.com/xgr-network/xgr-node/types" +) + +type params struct { + jsonRPC string + config string + dataDir string + initKeys bool + enabled bool + maxTotalDelegatedXGR string + minDelegatorXGR string + commissionBps uint64 +} + +func GetCommand() *cobra.Command { + p := ¶ms{} + + cmd := &cobra.Command{ + Use: "pool-config", + Short: "Configure validator delegation pool parameters via JSON-RPC", + Long: "Updates validator pool configuration by submitting a transaction over the configured JSON-RPC endpoint.", + RunE: func(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + if err := validateCLIParams(p); err != nil { + _ = cmd.Help() + outputter.SetError(err) + return nil + } + + sm, err := initSecretsManager(p) + if err != nil { + outputter.SetError(err) + return nil + } + if p.initKeys { + if err := ensureECDSAKey(sm); err != nil { + outputter.SetError(err) + return nil + } + } + + key, err := ibftHelper.GetECDSAKeyFromSecret(sm) + if err != nil { + outputter.SetError(err) + return nil + } + + maxTotalDelegatedWei, ok := new(big.Int).SetString(p.maxTotalDelegatedXGR, 10) + if !ok { + outputter.SetError(fmt.Errorf("invalid --max-total-delegated")) + return nil + } + maxTotalDelegatedWei.Mul(maxTotalDelegatedWei, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) + if p.enabled && maxTotalDelegatedWei.Sign() <= 0 { + outputter.SetError(fmt.Errorf("--max-total-delegated must be > 0 when --enabled=true")) + return nil + } + + relayer, err := txrelayer.NewTxRelayer( + txrelayer.WithIPAddress(p.jsonRPC), + txrelayer.WithReceiptTimeout(250*time.Millisecond), + ) + if err != nil { + outputter.SetError(err) + return nil + } + + validatorAddr := types.Address(key.Address()) + if err := ensureJoinedSelfValidator(relayer, validatorAddr); err != nil { + outputter.SetError(fmt.Errorf("%w (wallet=%s, jsonrpc=%s)", err, validatorAddr.String(), p.jsonRPC)) + return nil + } + + minDelegatorWei, ok := new(big.Int).SetString(p.minDelegatorXGR, 10) + if !ok { + outputter.SetError(fmt.Errorf("invalid --min-delegator-stake")) + return nil + } + minDelegatorWei.Mul(minDelegatorWei, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) + + if err := sendPoolConfigTx(relayer, key, p.enabled, maxTotalDelegatedWei, minDelegatorWei, p.commissionBps, p.jsonRPC); err != nil { + outputter.SetError(err) + return nil + } + + outputter.SetCommandResult(&Result{Validator: validatorAddr.String(), Enabled: p.enabled, MaxTotalDelegatedWei: maxTotalDelegatedWei.String(), MinDelegatorWei: minDelegatorWei.String(), CommissionBps: p.commissionBps}) + return nil + }, + } + + cmd.Flags().StringVar(&p.jsonRPC, "jsonrpc", "http://127.0.0.1:8545", "JSON-RPC endpoint") + cmd.Flags().StringVar(&p.dataDir, "data-dir", "", "Data directory for local secrets") + cmd.Flags().StringVar(&p.config, "config", "", "SecretsManager config file (optional)") + cmd.Flags().BoolVar(&p.initKeys, "init-keys", false, "Generate missing ECDSA key in data-dir/config") + cmd.Flags().BoolVar(&p.enabled, "enabled", true, "Enable or disable delegation pool") + cmd.Flags().StringVar(&p.maxTotalDelegatedXGR, "max-total-delegated", "0", "maximum total delegated stake accepted by this validator pool") + cmd.Flags().StringVar(&p.minDelegatorXGR, "min-delegator-stake", "0", "minimum stake required per delegator; 0 uses global DELEGATOR_MIN_STAKE") + cmd.Flags().Uint64Var(&p.commissionBps, "commission-bps", 0, "Validator commission in basis points") + + return cmd +} + +func validateCLIParams(p *params) error { + if p.dataDir == "" && p.config == "" { + return fmt.Errorf("either --data-dir or --config must be provided") + } + + return nil +} + +func sendPoolConfigTx(relayer txrelayer.TxRelayer, key ethgo.Key, enabled bool, maxTotalDelegatedWei *big.Int, minDelegatorWei *big.Int, commissionBps uint64, jsonRPC string) error { + if commissionBps > 10000 { + return fmt.Errorf("commission-bps must be <= 10000") + } + if enabled && maxTotalDelegatedWei.Sign() <= 0 { + return fmt.Errorf("--max-total-delegated must be > 0 when --enabled=true") + } + + method := abis.StakingABI.Methods["setValidatorPoolConfig"] + if method == nil { + return fmt.Errorf("staking ABI missing setValidatorPoolConfig") + } + + inputArgs, err := method.Inputs.Encode(map[string]interface{}{ + "delegationEnabled": enabled, + "maxTotalDelegatedStake": maxTotalDelegatedWei, + "minDelegatorStake": minDelegatorWei, + "commissionBps": uint16(commissionBps), + }) + if err != nil { + return err + } + + input := append(method.ID(), inputArgs...) + + to := ethgo.Address(staking.AddrStakingContract) + tx := ðgo.Transaction{To: &to, From: key.Address(), Input: input, Type: ethgo.TransactionDynamicFee} + receipt, err := relayer.SendTransaction(tx, key) + if err != nil { + return fmt.Errorf("pool-config transaction failed for wallet %s via jsonrpc %s: %w", key.Address(), jsonRPC, err) + } + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return fmt.Errorf("pool-config transaction reverted for wallet %s via jsonrpc %s", key.Address(), jsonRPC) + } + return nil +} + +func ensureJoinedSelfValidator(relayer txrelayer.TxRelayer, addr types.Address) error { + method := abis.StakingABI.Methods["validatorSelfStake"] + if method == nil { + return fmt.Errorf("staking ABI missing validatorSelfStake") + } + + inp, err := method.Inputs.Encode(map[string]interface{}{"validator": ethgo.Address(addr)}) + if err != nil { + return err + } + + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), append(method.ID(), inp...)) + if err != nil { + return err + } + + b, err := hexToBytes(resp) + if err != nil { + return err + } + + decoded, err := method.Outputs.Decode(b) + if err != nil { + return err + } + + outMap, ok := decoded.(map[string]interface{}) + if !ok { + return fmt.Errorf("decode validatorSelfStake: unexpected type") + } + + selfStake, ok := outMap["0"].(*big.Int) + if !ok { + return fmt.Errorf("decode validatorSelfStake: missing value") + } + + if selfStake.Sign() <= 0 { + return fmt.Errorf("wallet %s is not a joined validator self-position; run ibft join first or use the correct --data-dir", addr.String()) + } + + return nil +} + +func hexToBytes(s string) ([]byte, error) { + s = strings.TrimPrefix(s, "0x") + if s == "" { + return []byte{}, nil + } + + return hex.DecodeString(s) +} + +func initSecretsManager(p *params) (secrets.SecretsManager, error) { + if p.config != "" { + cfg, err := secrets.ReadConfig(p.config) + if err != nil { + return nil, err + } + return secretsHelper.InitCloudSecretsManager(cfg) + } + baseConfig := &secrets.SecretsManagerParams{Logger: hclog.NewNullLogger(), Extra: map[string]interface{}{secrets.Path: p.dataDir}} + return local.SecretsManagerFactory(nil, baseConfig) +} + +func ensureECDSAKey(sm secrets.SecretsManager) error { + if sm.HasSecret(secrets.ValidatorKey) { + return nil + } + _, err := secretsHelper.InitECDSAValidatorKey(sm) + return err +} diff --git a/command/ibft/poolconfig/pool_config_test.go b/command/ibft/poolconfig/pool_config_test.go new file mode 100644 index 0000000..41a6adf --- /dev/null +++ b/command/ibft/poolconfig/pool_config_test.go @@ -0,0 +1,93 @@ +package poolconfig + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" + "github.com/xgr-network/xgr-node/contracts/abis" + "github.com/xgr-network/xgr-node/types" +) + +type mockRelayer struct { + tx *ethgo.Transaction + callFn func(from ethgo.Address, to ethgo.Address, input []byte) (string, error) +} + +type mockKey struct { + addr ethgo.Address +} + +func (m mockKey) Address() ethgo.Address { + return m.addr +} + +func (m mockKey) Sign(_ []byte) ([]byte, error) { + return nil, nil +} + +func (m *mockRelayer) Call(from ethgo.Address, to ethgo.Address, input []byte) (string, error) { + if m.callFn != nil { + return m.callFn(from, to, input) + } + return "0x", nil +} + +func (m *mockRelayer) SendTransaction(txn *ethgo.Transaction, key ethgo.Key) (*ethgo.Receipt, error) { + m.tx = txn + return ðgo.Receipt{Status: uint64(types.ReceiptSuccess)}, nil +} + +func (m *mockRelayer) SendTransactionLocal(txn *ethgo.Transaction) (*ethgo.Receipt, error) { + m.tx = txn + return ðgo.Receipt{Status: uint64(types.ReceiptSuccess)}, nil +} + +func (m *mockRelayer) Client() *jsonrpc.Client { return nil } + +func TestSendPoolConfigTx_UsesStakingABI(t *testing.T) { + relayer := &mockRelayer{} + max := new(big.Int).Mul(big.NewInt(123), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) + key := mockKey{addr: ethgo.Address(types.StringToAddress("0x9999999999999999999999999999999999999999"))} + + require.NoError(t, sendPoolConfigTx(relayer, key, true, max, big.NewInt(77), 250, "http://127.0.0.1:8545")) + require.NotNil(t, relayer.tx) + + method := abis.StakingABI.Methods["setValidatorPoolConfig"] + require.NotNil(t, method) + require.GreaterOrEqual(t, len(relayer.tx.Input), 4) + require.Equal(t, method.ID(), relayer.tx.Input[:4]) + + decoded, err := method.Inputs.Decode(relayer.tx.Input[4:]) + require.NoError(t, err) + args, ok := decoded.(map[string]interface{}) + require.True(t, ok) + require.Equal(t, true, args["delegationEnabled"].(bool)) + require.Equal(t, 0, args["maxTotalDelegatedStake"].(*big.Int).Cmp(max)) + require.Equal(t, 0, args["minDelegatorStake"].(*big.Int).Cmp(big.NewInt(77))) + require.Equal(t, uint16(250), args["commissionBps"].(uint16)) + require.Equal(t, key.Address(), relayer.tx.From) +} + +func TestSendPoolConfigTx_RejectsTooHighCommission(t *testing.T) { + err := sendPoolConfigTx(&mockRelayer{}, mockKey{}, true, big.NewInt(1), big.NewInt(0), 10001, "http://127.0.0.1:8545") + require.Error(t, err) +} + +func TestGetCommand_DefaultsForPoolConfig(t *testing.T) { + cmd := GetCommand() + require.Equal(t, "0", cmd.Flags().Lookup("max-total-delegated").DefValue) + require.Equal(t, "0", cmd.Flags().Lookup("commission-bps").DefValue) + require.Equal(t, "0", cmd.Flags().Lookup("min-delegator-stake").DefValue) + require.Equal(t, "true", cmd.Flags().Lookup("enabled").DefValue) +} + +func TestResult_IncludesMinDelegatorWei(t *testing.T) { + r := &Result{Validator: "0x1", Enabled: true, MaxTotalDelegatedWei: "2", MinDelegatorWei: "3", CommissionBps: 4} + out := r.GetOutput() + require.Contains(t, out, "Max total delegated (wei)") + require.Contains(t, out, "Min delegator stake (wei)") + require.Contains(t, out, "3") +} diff --git a/command/ibft/poolconfig/result.go b/command/ibft/poolconfig/result.go new file mode 100644 index 0000000..0f69065 --- /dev/null +++ b/command/ibft/poolconfig/result.go @@ -0,0 +1,29 @@ +package poolconfig + +import ( + "bytes" + "fmt" + + "github.com/xgr-network/xgr-node/command/helper" +) + +type Result struct { + Validator string `json:"validator"` + Enabled bool `json:"enabled"` + MaxTotalDelegatedWei string `json:"maxTotalDelegatedWei"` + MinDelegatorWei string `json:"minDelegatorWei"` + CommissionBps uint64 `json:"commissionBps"` +} + +func (r *Result) GetOutput() string { + var b bytes.Buffer + b.WriteString("\n[IBFT POOL-CONFIG]\n") + b.WriteString(helper.FormatKV([]string{ + fmt.Sprintf("Validator|%s", r.Validator), + fmt.Sprintf("Enabled|%t", r.Enabled), + fmt.Sprintf("Max total delegated (wei)|%s", r.MaxTotalDelegatedWei), + fmt.Sprintf("Min delegator stake (wei)|%s", r.MinDelegatorWei), + fmt.Sprintf("Commission (bps)|%d", r.CommissionBps), + })) + return b.String() +} diff --git a/command/ibft/setactive/result.go b/command/ibft/setactive/result.go new file mode 100644 index 0000000..43699ee --- /dev/null +++ b/command/ibft/setactive/result.go @@ -0,0 +1,46 @@ +package setactive + +import ( + "bytes" + "fmt" + + "github.com/xgr-network/xgr-node/command/helper" +) + +type SetActiveResult struct { + Validator string `json:"validator"` + JoinedAtBlock uint64 `json:"joinedAtBlock,omitempty"` + Active bool `json:"active"` + AccountStakeWei string `json:"accountStakeWei"` + DeactivatedAtBlock uint64 `json:"deactivatedAtBlock,omitempty"` + UnstakeAvailableAtBlock uint64 `json:"unstakeAvailableAtBlock,omitempty"` +} + +func (r *SetActiveResult) GetOutput() string { + var b bytes.Buffer + b.WriteString("\n[IBFT SET-ACTIVE]\n") + rows := []string{ + fmt.Sprintf("Validator|%s", r.Validator), + fmt.Sprintf("Joined at block|%d", r.JoinedAtBlock), + fmt.Sprintf("Active|%t", r.Active), + fmt.Sprintf("Account stake (wei)|%s", r.AccountStakeWei), + } + + if r.DeactivatedAtBlock > 0 { + rows = append(rows, fmt.Sprintf("Deactivated at block|%d", r.DeactivatedAtBlock)) + } + if r.UnstakeAvailableAtBlock > 0 { + rows = append(rows, fmt.Sprintf("Unstake available at block|%d", r.UnstakeAvailableAtBlock)) + } + + b.WriteString(helper.FormatKV(rows)) + + if !r.Active && r.UnstakeAvailableAtBlock > 0 { + b.WriteString("\nNote: Unstake is available from the displayed block onward (next epoch).\n") + } + if r.Active && r.DeactivatedAtBlock == 0 { + b.WriteString("\nNote: deactivatedAtBlock=0 means 'never deactivated'. For withdraw/unstake, run set-active false first and wait for the next epoch.\n") + } + + return b.String() +} diff --git a/command/ibft/setactive/setactive.go b/command/ibft/setactive/setactive.go new file mode 100644 index 0000000..9943491 --- /dev/null +++ b/command/ibft/setactive/setactive.go @@ -0,0 +1,384 @@ +package setactive + +import ( + "encoding/hex" + "fmt" + "math/big" + "strings" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + + "github.com/xgr-network/xgr-node/command" + ibftHelper "github.com/xgr-network/xgr-node/command/ibft/helper" + "github.com/xgr-network/xgr-node/contracts/abis" + "github.com/xgr-network/xgr-node/contracts/staking" + "github.com/xgr-network/xgr-node/crypto" + secretsHelper "github.com/xgr-network/xgr-node/helper/secrets" + "github.com/xgr-network/xgr-node/secrets" + "github.com/xgr-network/xgr-node/secrets/local" + "github.com/xgr-network/xgr-node/txrelayer" + "github.com/xgr-network/xgr-node/types" +) + +type setActiveParams struct { + jsonRPC string + config string + dataDir string + insecure bool + initKeys bool + value bool +} + +func GetCommand() *cobra.Command { + p := &setActiveParams{} + + cmd := &cobra.Command{ + Use: "set-active", + Short: "Set validator active flag in staking contract (setActive(bool))", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + sm, err := initSecretsManager(p) + if err != nil { + outputter.SetError(err) + return nil + } + + if p.initKeys { + if err := ensureECDSAKey(sm); err != nil { + outputter.SetError(err) + return nil + } + } + + key, err := ibftHelper.GetECDSAKeyFromSecret(sm) + if err != nil { + outputter.SetError(err) + return nil + } + + relayer, err := txrelayer.NewTxRelayer( + txrelayer.WithIPAddress(p.jsonRPC), + txrelayer.WithReceiptTimeout(250*time.Millisecond), + ) + if err != nil { + outputter.SetError(err) + return nil + } + + if err := sendSetActive(relayer, key, p.value); err != nil { + outputter.SetError(err) + return nil + } + + validatorAddr := types.Address(key.Address()) + + validatorStatus, err := queryValidatorStatus(relayer, validatorAddr) + if err != nil { + outputter.SetError(err) + return nil + } + + epochSize, _ := queryEpochSize(relayer) + + onchainStake, err := queryAccountStake(relayer, validatorAddr) + if err != nil { + outputter.SetError(err) + return nil + } + + res := &SetActiveResult{ + Validator: validatorAddr.String(), + JoinedAtBlock: validatorStatus.JoinedAtBlock, + Active: validatorStatus.Active, + AccountStakeWei: onchainStake.String(), + } + + if validatorStatus.DeactivatedAtBlock > 0 { + res.DeactivatedAtBlock = validatorStatus.DeactivatedAtBlock + if epochSize > 0 { + deactEpoch := validatorStatus.DeactivatedAtBlock / epochSize + res.UnstakeAvailableAtBlock = (deactEpoch + 1) * epochSize + } + } + + outputter.SetCommandResult(res) + + return nil + }, + } + + setFlags(cmd, p) + return cmd +} + +func setFlags(cmd *cobra.Command, p *setActiveParams) { + cmd.Flags().StringVar(&p.jsonRPC, "jsonrpc", "http://127.0.0.1:8545", "JSON-RPC endpoint") + cmd.Flags().StringVar(&p.dataDir, "data-dir", "./data", "Data directory for local secrets") + cmd.Flags().StringVar(&p.config, "config", "", "SecretsManager config file (optional)") + cmd.Flags().BoolVar(&p.insecure, "insecure", false, "Allow insecure key storage") + cmd.Flags().BoolVar(&p.initKeys, "init-keys", false, "Generate missing ECDSA key in data-dir/config") + cmd.Flags().BoolVar(&p.value, "value", true, "Set active flag to true/false") +} + +func initSecretsManager(p *setActiveParams) (secrets.SecretsManager, error) { + if p.config != "" { + cfg, err := secrets.ReadConfig(p.config) + if err != nil { + return nil, err + } + return secretsHelper.InitCloudSecretsManager(cfg) + } + + baseConfig := &secrets.SecretsManagerParams{ + Logger: hclog.NewNullLogger(), + Extra: map[string]interface{}{ + secrets.Path: p.dataDir, + }, + } + + return local.SecretsManagerFactory(nil, baseConfig) +} + +func ensureECDSAKey(sm secrets.SecretsManager) error { + if sm.HasSecret(secrets.ValidatorKey) { + return nil + } + _, err := secretsHelper.InitECDSAValidatorKey(sm) + return err +} + +type validatorStatus struct { + Active bool + DeactivatedAtBlock uint64 + JoinedAtBlock uint64 +} + +func queryValidatorStatus(relayer txrelayer.TxRelayer, addr types.Address) (*validatorStatus, error) { + method := abis.StakingABI.Methods["validatorInfo"] + if method == nil { + return nil, fmt.Errorf("staking ABI missing validatorInfo") + } + + inp, err := method.Inputs.Encode(map[string]interface{}{ + "account": ethgo.Address(addr), + }) + if err != nil { + return nil, err + } + + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), append(method.ID(), inp...)) + if err != nil { + return nil, err + } + + b, err := hexToBytes(resp) + if err != nil { + return nil, err + } + + decoded, err := method.Outputs.Decode(b) + if err != nil { + return nil, err + } + + m, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode validatorInfo: unexpected type") + } + + active, ok := m["1"].(bool) + if !ok { + active, ok = m["active"].(bool) + if !ok { + return nil, fmt.Errorf("decode validatorInfo: missing active") + } + } + + deactivatedAt := uint64(0) + if deact, ok := m["3"].(*big.Int); ok && deact != nil { + deactivatedAt = deact.Uint64() + } else if deact, ok := m["deactivatedAtBlock"].(*big.Int); ok && deact != nil { + deactivatedAt = deact.Uint64() + } + + joinedAt, _ := queryJoinedAtBlock(relayer, addr) + + return &validatorStatus{ + Active: active, + DeactivatedAtBlock: deactivatedAt, + JoinedAtBlock: joinedAt, + }, nil +} + +func queryJoinedAtBlock(relayer txrelayer.TxRelayer, addr types.Address) (uint64, error) { + selector := crypto.Keccak256([]byte("joinedAtBlock(address)"))[:4] + input := make([]byte, 4+32) + copy(input[:4], selector) + copy(input[4+12:], addr.Bytes()) + + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), input) + if err != nil { + return 0, err + } + b, err := hexToBytes(resp) + if err != nil { + return 0, err + } + if len(b) == 0 { + return 0, nil + } + + return new(big.Int).SetBytes(b).Uint64(), nil +} + +func queryEpochSize(relayer txrelayer.TxRelayer) (uint64, error) { + method := abis.StakingABI.Methods["epochSize"] + if method == nil { + return 0, fmt.Errorf("staking ABI missing epochSize") + } + + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), method.ID()) + if err != nil { + return 0, err + } + + b, err := hexToBytes(resp) + if err != nil { + return 0, err + } + + decoded, err := method.Outputs.Decode(b) + if err != nil { + return 0, err + } + + m, ok := decoded.(map[string]interface{}) + if !ok { + return 0, fmt.Errorf("decode epochSize: unexpected type") + } + + if epoch, ok := uint64FromDecodedValue(m["0"]); ok { + return epoch, nil + } + if epoch, ok := uint64FromDecodedValue(m["epochSize"]); ok { + return epoch, nil + } + for _, value := range m { + if epoch, ok := uint64FromDecodedValue(value); ok { + return epoch, nil + } + } + + return 0, fmt.Errorf("decode epochSize: missing value") +} + +func epochOf(number, epochSize uint64) uint64 { + if number == 0 || epochSize == 0 { + return 0 + } + if number%epochSize == 0 { + return number / epochSize + } + return number/epochSize + 1 +} + +func uint64FromDecodedValue(v interface{}) (uint64, bool) { + switch x := v.(type) { + case *big.Int: + if x == nil { + return 0, false + } + return x.Uint64(), true + case uint64: + return x, true + case uint32: + return uint64(x), true + case int: + if x < 0 { + return 0, false + } + return uint64(x), true + } + + return 0, false +} + +func queryAccountStake(relayer txrelayer.TxRelayer, addr types.Address) (*big.Int, error) { + method := abis.StakingABI.Methods["accountStake"] + if method == nil { + return nil, fmt.Errorf("staking ABI missing accountStake") + } + inp, err := method.Inputs.Encode(map[string]interface{}{ + "account": ethgo.Address(addr), + }) + if err != nil { + return nil, err + } + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), append(method.ID(), inp...)) + if err != nil { + return nil, err + } + b, err := hexToBytes(resp) + if err != nil { + return nil, err + } + decoded, err := method.Outputs.Decode(b) + if err != nil { + return nil, err + } + m, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode accountStake: unexpected type") + } + v, ok := m["0"].(*big.Int) + if !ok { + return nil, fmt.Errorf("decode accountStake: missing value") + } + return v, nil +} + +func sendSetActive(relayer txrelayer.TxRelayer, key ethgo.Key, value bool) error { + m := abis.StakingABI.Methods["setActive"] + if m == nil { + return fmt.Errorf("staking ABI missing setActive") + } + inp, err := m.Inputs.Encode(map[string]interface{}{"active_": value}) + if err != nil { + return fmt.Errorf("encode setActive input failed: %w", err) + } + to := ethgo.Address(staking.AddrStakingContract) + tx := ðgo.Transaction{ + To: &to, + Input: append(m.ID(), inp...), + Type: ethgo.TransactionDynamicFee, + } + receipt, err := relayer.SendTransaction(tx, key) + if err != nil { + msg := err.Error() + if strings.Contains(msg, "0xa291f7f5") { + return nil + } + if strings.Contains(msg, "0x580e542f") { + return fmt.Errorf("set-active rejected: validator %s not found in staking set on this chain (check --data-dir/--config key and --jsonrpc endpoint)", key.Address()) + } + return fmt.Errorf("setActive tx failed: %w", err) + } + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return fmt.Errorf("setActive reverted") + } + return nil +} + +func hexToBytes(s string) ([]byte, error) { + s = strings.TrimPrefix(s, "0x") + if s == "" { + return []byte{}, nil + } + return hex.DecodeString(s) +} diff --git a/command/ibft/setactive/setactive_test.go b/command/ibft/setactive/setactive_test.go new file mode 100644 index 0000000..b42a71d --- /dev/null +++ b/command/ibft/setactive/setactive_test.go @@ -0,0 +1,123 @@ +package setactive + +import ( + "encoding/hex" + "errors" + "math/big" + "strings" + "testing" + + "github.com/xgr-network/xgr-node/contracts/abis" + + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" + "github.com/xgr-network/xgr-node/types" +) + +type mockRelayer struct { + callResp string + callErr error + sendReceipt *ethgo.Receipt + sendErr error +} + +func (m *mockRelayer) Call(ethgo.Address, ethgo.Address, []byte) (string, error) { + return m.callResp, m.callErr +} +func (m *mockRelayer) SendTransaction(*ethgo.Transaction, ethgo.Key) (*ethgo.Receipt, error) { + return m.sendReceipt, m.sendErr +} +func (m *mockRelayer) SendTransactionLocal(*ethgo.Transaction) (*ethgo.Receipt, error) { + return nil, nil +} +func (m *mockRelayer) Client() *jsonrpc.Client { return nil } + +type mockKey struct { + addr ethgo.Address +} + +func (m mockKey) Address() ethgo.Address { return m.addr } +func (m mockKey) Sign([]byte) ([]byte, error) { + return nil, nil +} + +func TestSendSetActive_AlreadyInRequestedState_IsNoop(t *testing.T) { + err := sendSetActive(&mockRelayer{sendErr: errors.New("execution reverted data:0xa291f7f5")}, mockKey{}, false) + if err != nil { + t.Fatalf("expected noop nil error, got %v", err) + } +} + +func TestSendSetActive_ValidatorNotFound(t *testing.T) { + key := mockKey{addr: ethgo.HexToAddress("0x1111111111111111111111111111111111111111")} + err := sendSetActive(&mockRelayer{sendErr: errors.New("execution reverted data:0x580e542f")}, key, false) + if err == nil { + t.Fatal("expected error") + } + + if !strings.Contains(err.Error(), "not found in staking set on this chain") { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(err.Error(), key.addr.String()) { + t.Fatalf("expected error to contain validator address, got %v", err) + } +} + +func TestSendSetActive_RevertedReceipt(t *testing.T) { + err := sendSetActive(&mockRelayer{sendReceipt: ðgo.Receipt{Status: uint64(types.ReceiptFailed)}}, mockKey{}, true) + if err == nil || err.Error() != "setActive reverted" { + t.Fatalf("expected setActive reverted, got %v", err) + } +} + +func TestQueryValidatorStatus_DecodeByIndex(t *testing.T) { + method := abis.StakingABI.Methods["validatorInfo"] + enc, err := method.Outputs.Encode(map[string]interface{}{ + "exists": false, + "active": true, + "stakedAmount": big.NewInt(0), + "deactivatedAtBlock": big.NewInt(7), + "blsPubKey": []byte{}, + }) + if err != nil { + t.Fatalf("encode failed: %v", err) + } + + status, err := queryValidatorStatus(&mockRelayer{callResp: "0x" + hex.EncodeToString(enc)}, types.ZeroAddress) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !status.Active { + t.Fatal("expected active=true") + } + if status.DeactivatedAtBlock != 7 { + t.Fatalf("expected deactivatedAtBlock=7, got %d", status.DeactivatedAtBlock) + } +} + +func TestQueryValidatorStatus_CallError(t *testing.T) { + _, err := queryValidatorStatus(&mockRelayer{callErr: errors.New("rpc down")}, types.ZeroAddress) + if err == nil || !strings.Contains(err.Error(), "rpc down") { + t.Fatalf("expected rpc error, got %v", err) + } +} + +func TestQueryEpochSize(t *testing.T) { + method := abis.StakingABI.Methods["epochSize"] + enc, err := method.Outputs.Encode(map[string]interface{}{ + "0": big.NewInt(100), + }) + if err != nil { + t.Fatalf("encode failed: %v", err) + } + + epochSize, err := queryEpochSize(&mockRelayer{callResp: "0x" + hex.EncodeToString(enc)}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if epochSize != 100 { + t.Fatalf("expected epoch size 100, got %d", epochSize) + } +} diff --git a/command/ibft/snapshot/result.go b/command/ibft/snapshot/result.go index 7487517..8738da4 100644 --- a/command/ibft/snapshot/result.go +++ b/command/ibft/snapshot/result.go @@ -16,19 +16,34 @@ type IBFTSnapshotVote struct { Vote ibftHelper.Vote `json:"vote"` } +type IBFTSnapshotValidator struct { + Validator validators.Validator `json:"validator"` + UptimeNominalWeight uint64 `json:"uptime_nominal_weight"` + UptimeEffectiveWeight uint64 `json:"uptime_effective_weight"` + UptimeInactivity uint64 `json:"uptime_inactivity"` +} + type IBFTSnapshotResult struct { - Number uint64 `json:"number"` - Hash string `json:"hash"` - Votes []IBFTSnapshotVote `json:"votes"` - Validators []validators.Validator `json:"validators"` + Number uint64 `json:"number"` + Hash string `json:"hash"` + MicroEpoch uint64 `json:"micro_epoch"` + UptimeEpoch uint64 `json:"uptime_epoch"` + UptimeTotalEffectiveWeight uint64 `json:"uptime_total_effective_weight"` + UptimeActiveEffective uint64 `json:"uptime_active_effective_weight"` + Votes []IBFTSnapshotVote `json:"votes"` + Validators []IBFTSnapshotValidator `json:"validators"` } func newIBFTSnapshotResult(resp *ibftOp.Snapshot) (*IBFTSnapshotResult, error) { res := &IBFTSnapshotResult{ - Number: resp.Number, - Hash: resp.Hash, - Votes: make([]IBFTSnapshotVote, len(resp.Votes)), - Validators: make([]validators.Validator, len(resp.Validators)), + Number: resp.Number, + Hash: resp.Hash, + MicroEpoch: resp.MicroEpoch, + UptimeEpoch: resp.UptimeEpoch, + UptimeTotalEffectiveWeight: resp.UptimeTotalEffectiveWeight, + UptimeActiveEffective: resp.UptimeActiveEffectiveWeight, + Votes: make([]IBFTSnapshotVote, len(resp.Votes)), + Validators: make([]IBFTSnapshotValidator, len(resp.Validators)), } for i, v := range resp.Votes { @@ -56,7 +71,12 @@ func newIBFTSnapshotResult(resp *ibftOp.Snapshot) (*IBFTSnapshotResult, error) { return nil, err } - res.Validators[i] = validator + res.Validators[i] = IBFTSnapshotValidator{ + Validator: validator, + UptimeNominalWeight: v.UptimeNominalWeight, + UptimeEffectiveWeight: v.UptimeEffectiveWeight, + UptimeInactivity: v.UptimeInactivity, + } } return res, nil @@ -74,10 +94,19 @@ func (r *IBFTSnapshotResult) GetOutput() string { } func (r *IBFTSnapshotResult) writeBlockData(buffer *bytes.Buffer) { - buffer.WriteString(helper.FormatKV([]string{ + lines := []string{ fmt.Sprintf("Block|%d", r.Number), fmt.Sprintf("Hash|%s", r.Hash), - })) + } + if r.uptimeFieldsVisible() { + lines = append(lines, + fmt.Sprintf("Micro-epoch|%d", r.MicroEpoch), + fmt.Sprintf("Uptime epoch|%d", r.UptimeEpoch), + fmt.Sprintf("Uptime total effective weight|%d", r.UptimeTotalEffectiveWeight), + fmt.Sprintf("Uptime active effective weight|%d", r.UptimeActiveEffective), + ) + } + buffer.WriteString(helper.FormatKV(lines)) buffer.WriteString("\n") } @@ -111,9 +140,17 @@ func (r *IBFTSnapshotResult) writeValidatorData(buffer *bytes.Buffer) { validators[0] = "No validators found" if numValidators > 0 { - validators[0] = "ADDRESS" + if r.uptimeFieldsVisible() { + validators[0] = "ADDRESS|UPTIME NOMINAL|UPTIME EFFECTIVE|UPTIME INACTIVITY" + } else { + validators[0] = "ADDRESS" + } for i, d := range r.Validators { - validators[i+1] = d.String() + if r.uptimeFieldsVisible() { + validators[i+1] = fmt.Sprintf("%s|%d|%d|%d", d.Validator.String(), d.UptimeNominalWeight, d.UptimeEffectiveWeight, d.UptimeInactivity) + } else { + validators[i+1] = d.Validator.String() + } } } @@ -121,3 +158,22 @@ func (r *IBFTSnapshotResult) writeValidatorData(buffer *bytes.Buffer) { buffer.WriteString(helper.FormatList(validators)) buffer.WriteString("\n") } + +func (r *IBFTSnapshotResult) uptimeFieldsVisible() bool { + if r.MicroEpoch != 0 || + r.UptimeEpoch != 0 || + r.UptimeTotalEffectiveWeight != 0 || + r.UptimeActiveEffective != 0 { + return true + } + + for _, v := range r.Validators { + if v.UptimeNominalWeight != 0 || + v.UptimeEffectiveWeight != 0 || + v.UptimeInactivity != 0 { + return true + } + } + + return false +} diff --git a/command/ibft/snapshot/result_test.go b/command/ibft/snapshot/result_test.go new file mode 100644 index 0000000..9d6d72a --- /dev/null +++ b/command/ibft/snapshot/result_test.go @@ -0,0 +1,76 @@ +package snapshot + +import ( + "testing" + + "github.com/stretchr/testify/require" + ibftOp "github.com/xgr-network/xgr-node/consensus/ibft/proto" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func TestIBFTSnapshotResult_GetOutput_HidesUptimeFieldsWhenInactive(t *testing.T) { + v := validators.NewECDSAValidator(types.StringToAddress("0x123")) + res, err := newIBFTSnapshotResult(&ibftOp.Snapshot{ + Number: 1, + Hash: "0x1", + Validators: []*ibftOp.Snapshot_Validator{{ + Type: "ecdsa", + Address: v.Addr().String(), + Data: v.Bytes(), + }}, + }) + require.NoError(t, err) + + out := res.GetOutput() + require.NotContains(t, out, "Micro-epoch") + require.Contains(t, out, "[VALIDATORS]") + require.Contains(t, out, "ADDRESS") + require.NotContains(t, out, "UPTIME NOMINAL") +} + +func TestIBFTSnapshotResult_GetOutput_ShowsUptimeFieldsWhenActive(t *testing.T) { + v := validators.NewECDSAValidator(types.StringToAddress("0x123")) + res, err := newIBFTSnapshotResult(&ibftOp.Snapshot{ + Number: 2, + Hash: "0x2", + MicroEpoch: 10, + UptimeEpoch: 3, + UptimeTotalEffectiveWeight: 100, + UptimeActiveEffectiveWeight: 90, + Validators: []*ibftOp.Snapshot_Validator{{ + Type: "ecdsa", + Address: v.Addr().String(), + Data: v.Bytes(), + UptimeNominalWeight: 100, + UptimeEffectiveWeight: 90, + UptimeInactivity: 1, + }}, + }) + require.NoError(t, err) + + out := res.GetOutput() + require.Contains(t, out, "Micro-epoch") + require.Contains(t, out, "UPTIME NOMINAL") +} + +func TestIBFTSnapshotResult_GetOutput_ShowsUptimeColumnsWhenWeightsAreZero(t *testing.T) { + v := validators.NewECDSAValidator(types.StringToAddress("0x123")) + res, err := newIBFTSnapshotResult(&ibftOp.Snapshot{ + Number: 329, + Hash: "0x329", + MicroEpoch: 329, + UptimeEpoch: 32, + Validators: []*ibftOp.Snapshot_Validator{{ + Type: "ecdsa", + Address: v.Addr().String(), + Data: v.Bytes(), + }}, + }) + require.NoError(t, err) + + out := res.GetOutput() + require.Contains(t, out, "Micro-epoch") + require.Contains(t, out, "UPTIME NOMINAL") + require.Contains(t, out, " 0 0 0") +} diff --git a/command/ibft/stake/result.go b/command/ibft/stake/result.go new file mode 100644 index 0000000..2bfdbc8 --- /dev/null +++ b/command/ibft/stake/result.go @@ -0,0 +1,26 @@ +package stake + +import ( + "bytes" + "fmt" + + "github.com/xgr-network/xgr-node/command/helper" +) + +type StakeResult struct { + Validator string `json:"validator"` + AmountWei string `json:"amountWei"` + AccountStakeWei string `json:"accountStakeWei"` +} + +func (r *StakeResult) GetOutput() string { + var b bytes.Buffer + b.WriteString("\n[IBFT STAKE]\n") + b.WriteString(helper.FormatKV([]string{ + fmt.Sprintf("Validator|%s", r.Validator), + fmt.Sprintf("Amount (wei)|%s", r.AmountWei), + fmt.Sprintf("Account stake (wei)|%s", r.AccountStakeWei), + })) + + return b.String() +} diff --git a/command/ibft/stake/stake.go b/command/ibft/stake/stake.go new file mode 100644 index 0000000..b133870 --- /dev/null +++ b/command/ibft/stake/stake.go @@ -0,0 +1,214 @@ +package stake + +import ( + "encoding/hex" + "fmt" + "math/big" + "strings" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + + "github.com/xgr-network/xgr-node/command" + ibftHelper "github.com/xgr-network/xgr-node/command/ibft/helper" + "github.com/xgr-network/xgr-node/contracts/abis" + "github.com/xgr-network/xgr-node/contracts/staking" + secretsHelper "github.com/xgr-network/xgr-node/helper/secrets" + "github.com/xgr-network/xgr-node/secrets" + "github.com/xgr-network/xgr-node/secrets/local" + "github.com/xgr-network/xgr-node/txrelayer" + "github.com/xgr-network/xgr-node/types" +) + +type stakeParams struct { + jsonRPC string + config string + dataDir string + insecure bool + initKeys bool + + amountStr string + decimals int +} + +func GetCommand() *cobra.Command { + p := &stakeParams{} + + cmd := &cobra.Command{ + Use: "stake", + Short: "Increase stake in the staking contract (payable stake())", + RunE: func(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + sm, err := initSecretsManager(p) + if err != nil { + outputter.SetError(err) + return nil + } + + if p.initKeys { + if err := ensureECDSAKey(sm); err != nil { + outputter.SetError(err) + return nil + } + } + + key, err := ibftHelper.GetECDSAKeyFromSecret(sm) + if err != nil { + outputter.SetError(err) + return nil + } + + relayer, err := txrelayer.NewTxRelayer( + txrelayer.WithIPAddress(p.jsonRPC), + txrelayer.WithReceiptTimeout(250*time.Millisecond), + ) + if err != nil { + outputter.SetError(err) + return nil + } + + amountWei := toWei(p.amountStr, p.decimals) + if amountWei.Sign() <= 0 { + outputter.SetError(fmt.Errorf("amount must be > 0")) + return nil + } + + // send stake() + if err := sendStake(relayer, key, amountWei); err != nil { + outputter.SetError(err) + return nil + } + + onchainStake, err := queryAccountStake(relayer, types.Address(key.Address())) + if err != nil { + outputter.SetError(err) + return nil + } + + outputter.SetCommandResult(&StakeResult{ + Validator: types.Address(key.Address()).String(), + AmountWei: amountWei.String(), + AccountStakeWei: onchainStake.String(), + }) + + return nil + }, + } + + setFlags(cmd, p) + return cmd +} + +func setFlags(cmd *cobra.Command, p *stakeParams) { + cmd.Flags().StringVar(&p.jsonRPC, "jsonrpc", "http://127.0.0.1:8545", "JSON-RPC endpoint") + cmd.Flags().StringVar(&p.dataDir, "data-dir", "./data", "Data directory for local secrets") + cmd.Flags().StringVar(&p.config, "config", "", "SecretsManager config file (optional)") + cmd.Flags().BoolVar(&p.insecure, "insecure", false, "Allow insecure key storage") + cmd.Flags().BoolVar(&p.initKeys, "init-keys", false, "Generate missing ECDSA key in data-dir/config") + + cmd.Flags().StringVar(&p.amountStr, "amount", "0", "Stake amount (integer, in token units)") + cmd.Flags().IntVar(&p.decimals, "decimals", 18, "Token decimals") + + _ = cmd.MarkFlagRequired("amount") +} + +func initSecretsManager(p *stakeParams) (secrets.SecretsManager, error) { + if p.config != "" { + cfg, err := secrets.ReadConfig(p.config) + if err != nil { + return nil, err + } + return secretsHelper.InitCloudSecretsManager(cfg) + } + + baseConfig := &secrets.SecretsManagerParams{ + Logger: hclog.NewNullLogger(), + Extra: map[string]interface{}{ + secrets.Path: p.dataDir, + }, + } + + return local.SecretsManagerFactory(nil, baseConfig) +} + +func ensureECDSAKey(sm secrets.SecretsManager) error { + if sm.HasSecret(secrets.ValidatorKey) { + return nil + } + _, err := secretsHelper.InitECDSAValidatorKey(sm) + return err +} + +func toWei(amountStr string, decimals int) *big.Int { + a, _ := new(big.Int).SetString(amountStr, 10) + m := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil) + return new(big.Int).Mul(a, m) +} + +func queryAccountStake(relayer txrelayer.TxRelayer, addr types.Address) (*big.Int, error) { + method := abis.StakingABI.Methods["accountStake"] + if method == nil { + return nil, fmt.Errorf("staking ABI missing accountStake") + } + inp, err := method.Inputs.Encode(map[string]interface{}{ + "account": ethgo.Address(addr), + }) + if err != nil { + return nil, err + } + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), append(method.ID(), inp...)) + if err != nil { + return nil, err + } + b, err := hexToBytes(resp) + if err != nil { + return nil, err + } + decoded, err := method.Outputs.Decode(b) + if err != nil { + return nil, err + } + m, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode accountStake: unexpected type") + } + v, ok := m["0"].(*big.Int) + if !ok { + return nil, fmt.Errorf("decode accountStake: missing value") + } + return v, nil +} + +func sendStake(relayer txrelayer.TxRelayer, key ethgo.Key, stakeWei *big.Int) error { + m := abis.StakingABI.Methods["stake"] + if m == nil { + return fmt.Errorf("staking ABI missing stake") + } + to := ethgo.Address(staking.AddrStakingContract) + tx := ðgo.Transaction{ + To: &to, + Input: m.ID(), + Value: stakeWei, + Type: ethgo.TransactionDynamicFee, + } + receipt, err := relayer.SendTransaction(tx, key) + if err != nil { + return fmt.Errorf("stake tx failed: %w", err) + } + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return fmt.Errorf("stake reverted") + } + return nil +} + +func hexToBytes(s string) ([]byte, error) { + s = strings.TrimPrefix(s, "0x") + if s == "" { + return []byte{}, nil + } + return hex.DecodeString(s) +} diff --git a/command/ibft/switch/params.go b/command/ibft/switch/params.go index cc6b529..a27df46 100644 --- a/command/ibft/switch/params.go +++ b/command/ibft/switch/params.go @@ -9,6 +9,7 @@ import ( "github.com/xgr-network/xgr-node/command" "github.com/xgr-network/xgr-node/command/helper" "github.com/xgr-network/xgr-node/consensus/ibft/fork" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" "github.com/xgr-network/xgr-node/helper/common" "github.com/xgr-network/xgr-node/validators" ) @@ -222,6 +223,16 @@ func (p *switchParams) initPoSConfig() error { return nil } + p.ibftValidators = validators.NewValidatorSetFromType(p.ibftValidatorType) + + if err := p.setValidatorSetFromPrefixPath(); err != nil { + return err + } + + if err := p.setValidatorSetFromCli(); err != nil { + return err + } + if p.minValidatorCountRaw != "" { value, err := common.ParseUint64orHex(&p.minValidatorCountRaw) if err != nil { @@ -420,6 +431,12 @@ func appendIBFTForks( case fork.PoA: newFork.Validators = validators case fork.PoS: + bootstrapValidators, err := resolvePoSBootstrapValidators(cc, lastFork, validators, validatorType) + if err != nil { + return err + } + newFork.Validators = bootstrapValidators + if deployment != nil { newFork.Deployment = &common.JSONNumber{Value: *deployment} } @@ -443,3 +460,112 @@ func appendIBFTForks( return nil } + +func resolvePoSBootstrapValidators( + cc *chain.Chain, + lastFork *fork.IBFTFork, + cliValidators validators.Validators, + validatorType validators.ValidatorType, +) (validators.Validators, error) { + if cliValidators != nil && cliValidators.Len() > 0 { + if err := validateBootstrapValidatorType(validatorType, cliValidators, "cli"); err != nil { + return nil, err + } + + return cliValidators, nil + } + + if lastFork != nil && lastFork.Validators != nil && lastFork.Validators.Len() > 0 { + if err := validateBootstrapValidatorType(validatorType, lastFork.Validators, "last-fork"); err != nil { + return nil, err + } + + return lastFork.Validators, nil + } + + bootstrapValidators, err := parseBootstrapValidatorsFromGenesisExtra(cc, validatorType) + if err != nil { + return nil, fmt.Errorf("unable to determine PoS bootstrap validators: %w", err) + } + + return bootstrapValidators, nil +} + +func validateBootstrapValidatorType( + expectedType validators.ValidatorType, + bootstrapValidators validators.Validators, + source string, +) error { + if bootstrapValidators == nil { + return nil + } + + actualType := bootstrapValidators.Type() + if actualType != expectedType { + return fmt.Errorf( + "bootstrap validators type mismatch: expected validator type %s, source %s, reason: type mismatch (got %s)", + expectedType, + source, + actualType, + ) + } + + return nil +} + +func parseBootstrapValidatorsFromGenesisExtra( + cc *chain.Chain, + validatorType validators.ValidatorType, +) (validators.Validators, error) { + if cc == nil || cc.Genesis == nil { + return nil, fmt.Errorf("missing chain genesis") + } + if len(cc.Genesis.ExtraData) < signer.IstanbulExtraVanity { + return nil, fmt.Errorf("genesis extra data is too short for IBFT validator extraction") + } + + bootstrapValidators, err := decodeValidatorsFromGenesisExtra(cc.Genesis.ExtraData, validatorType) + if err != nil { + otherType := validators.BLSValidatorType + if validatorType == validators.BLSValidatorType { + otherType = validators.ECDSAValidatorType + } + + if _, otherErr := decodeValidatorsFromGenesisExtra(cc.Genesis.ExtraData, otherType); otherErr == nil { + return nil, fmt.Errorf( + "bootstrap validators type mismatch: expected validator type %s, source genesis-extra, reason: type mismatch (got %s)", + validatorType, + otherType, + ) + } + + return nil, fmt.Errorf("failed to decode IBFT extra from genesis: %w", err) + } + + if bootstrapValidators == nil || bootstrapValidators.Len() == 0 { + return nil, fmt.Errorf("genesis IBFT extra does not contain validators") + } + + return bootstrapValidators, nil +} + +func decodeValidatorsFromGenesisExtra( + extraData []byte, + validatorType validators.ValidatorType, +) (validators.Validators, error) { + committedSeals := signer.Seals(&signer.SerializedSeal{}) + if validatorType == validators.BLSValidatorType { + committedSeals = &signer.AggregatedSeal{} + } + + extra := &signer.IstanbulExtra{ + Validators: validators.NewValidatorSetFromType(validatorType), + CommittedSeals: committedSeals, + } + + if err := extra.UnmarshalRLP(extraData[signer.IstanbulExtraVanity:]); err != nil { + return nil, err + } + + return extra.Validators, nil +} diff --git a/command/ibft/switch/params_test.go b/command/ibft/switch/params_test.go new file mode 100644 index 0000000..742c5e9 --- /dev/null +++ b/command/ibft/switch/params_test.go @@ -0,0 +1,300 @@ +package ibftswitch + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/chain" + "github.com/xgr-network/xgr-node/consensus/ibft/fork" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func TestAppendIBFTForks_PoSCarriesLastPoAValidatorsAsBootstrap(t *testing.T) { + t.Parallel() + + poaValidators := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000002")), + ) + + cc := &chain.Chain{ + Genesis: &chain.Genesis{ + ExtraData: mustGenesisExtraWithValidators(t, poaValidators), + }, + Params: &chain.Params{ + Engine: map[string]interface{}{ + "ibft": map[string]interface{}{ + fork.KeyTypes: []interface{}{ + map[string]interface{}{ + "type": "PoA", + "validator_type": "ecdsa", + "from": 0, + "validators": []interface{}{ + map[string]interface{}{"Address": "0x1000000000000000000000000000000000000001"}, + map[string]interface{}{"Address": "0x1000000000000000000000000000000000000002"}, + }, + }, + }, + }, + }, + }, + } + + require.NoError(t, appendIBFTForks( + cc, + fork.PoS, + validators.ECDSAValidatorType, + 11, + u64ptr(10), + nil, + u64ptr(20), + u64ptr(2), + )) + + ibftConfig := cc.Params.Engine["ibft"].(map[string]interface{}) + ibftForks := ibftConfig[fork.KeyTypes].(fork.IBFTForks) + require.Len(t, ibftForks, 2) + require.Equal(t, poaValidators.Len(), ibftForks[1].Validators.Len()) + require.Equal(t, poaValidators.At(0).Addr(), ibftForks[1].Validators.At(0).Addr()) +} + +func TestAppendIBFTForks_PoSFailsWhenLastForkValidatorTypeMismatches(t *testing.T) { + t.Parallel() + + poaValidators := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1100000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1100000000000000000000000000000000000002")), + ) + + cc := &chain.Chain{ + Genesis: &chain.Genesis{ + ExtraData: mustGenesisExtraWithValidators(t, poaValidators), + }, + Params: &chain.Params{ + Engine: map[string]interface{}{ + "ibft": map[string]interface{}{ + fork.KeyTypes: []interface{}{ + map[string]interface{}{ + "type": "PoA", + "validator_type": "ecdsa", + "from": 0, + "validators": []interface{}{ + map[string]interface{}{"Address": "0x1100000000000000000000000000000000000001"}, + map[string]interface{}{"Address": "0x1100000000000000000000000000000000000002"}, + }, + }, + }, + }, + }, + }, + } + + err := appendIBFTForks( + cc, + fork.PoS, + validators.BLSValidatorType, + 11, + u64ptr(10), + nil, + u64ptr(20), + u64ptr(2), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "expected validator type bls") + require.Contains(t, err.Error(), "source last-fork") + require.Contains(t, err.Error(), "type mismatch") +} + +func TestAppendIBFTForks_PoSFallsBackToGenesisExtraValidators(t *testing.T) { + t.Parallel() + + genesisValidators := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x2000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x2000000000000000000000000000000000000002")), + ) + + cc := &chain.Chain{ + Genesis: &chain.Genesis{ + ExtraData: mustGenesisExtraWithValidators(t, genesisValidators), + }, + Params: &chain.Params{ + Engine: map[string]interface{}{ + "ibft": map[string]interface{}{ + fork.KeyTypes: []interface{}{ + map[string]interface{}{ + "type": "PoA", + "validator_type": "ecdsa", + "from": 0, + }, + }, + }, + }, + }, + } + + require.NoError(t, appendIBFTForks( + cc, + fork.PoS, + validators.ECDSAValidatorType, + 5, + u64ptr(4), + nil, + u64ptr(10), + u64ptr(1), + )) + + ibftConfig := cc.Params.Engine["ibft"].(map[string]interface{}) + ibftForks := ibftConfig[fork.KeyTypes].(fork.IBFTForks) + require.Len(t, ibftForks, 2) + require.Equal(t, genesisValidators.Len(), ibftForks[1].Validators.Len()) + require.Equal(t, genesisValidators.At(1).Addr(), ibftForks[1].Validators.At(1).Addr()) +} + +func TestAppendIBFTForks_PoSWithCLIBLSValidators(t *testing.T) { + t.Parallel() + + cliValidators := validators.NewBLSValidatorSet( + validators.NewBLSValidator( + types.StringToAddress("0x3000000000000000000000000000000000000001"), + []byte{1, 2, 3}, + ), + validators.NewBLSValidator( + types.StringToAddress("0x3000000000000000000000000000000000000002"), + []byte{4, 5, 6}, + ), + ) + + cc := &chain.Chain{ + Genesis: &chain.Genesis{ + ExtraData: mustGenesisExtraWithValidators(t, validators.NewECDSAValidatorSet()), + }, + Params: &chain.Params{ + Engine: map[string]interface{}{ + "ibft": map[string]interface{}{ + fork.KeyTypes: []interface{}{ + map[string]interface{}{ + "type": "PoA", + "validator_type": "ecdsa", + "from": 0, + }, + }, + }, + }, + }, + } + + require.NoError(t, appendIBFTForks( + cc, + fork.PoS, + validators.BLSValidatorType, + 15, + u64ptr(14), + cliValidators, + u64ptr(50), + u64ptr(1), + )) + + ibftConfig := cc.Params.Engine["ibft"].(map[string]interface{}) + ibftForks := ibftConfig[fork.KeyTypes].(fork.IBFTForks) + require.Len(t, ibftForks, 2) + require.Equal(t, validators.BLSValidatorType, ibftForks[1].Validators.Type()) + require.Equal(t, cliValidators.Len(), ibftForks[1].Validators.Len()) +} + +func TestAppendIBFTForks_PoSFailsWhenGenesisExtraValidatorTypeMismatches(t *testing.T) { + t.Parallel() + + genesisECDSAValidators := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x4000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x4000000000000000000000000000000000000002")), + ) + + cc := &chain.Chain{ + Genesis: &chain.Genesis{ + ExtraData: mustGenesisExtraWithValidators(t, genesisECDSAValidators), + }, + Params: &chain.Params{ + Engine: map[string]interface{}{ + "ibft": map[string]interface{}{ + fork.KeyTypes: []interface{}{ + map[string]interface{}{ + "type": "PoA", + "validator_type": "ecdsa", + "from": 0, + }, + }, + }, + }, + }, + } + + err := appendIBFTForks( + cc, + fork.PoS, + validators.BLSValidatorType, + 7, + u64ptr(6), + nil, + u64ptr(20), + u64ptr(1), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "expected validator type bls") + require.Contains(t, err.Error(), "source genesis-extra") + require.Contains(t, err.Error(), "type mismatch") +} + +func TestAppendIBFTForks_PoSRequiresBootstrapValidators(t *testing.T) { + t.Parallel() + + cc := &chain.Chain{ + Genesis: &chain.Genesis{ + ExtraData: make([]byte, signer.IstanbulExtraVanity), + }, + Params: &chain.Params{ + Engine: map[string]interface{}{ + "ibft": map[string]interface{}{ + fork.KeyTypes: []interface{}{ + map[string]interface{}{ + "type": "PoA", + "validator_type": "ecdsa", + "from": 0, + }, + }, + }, + }, + }, + } + + err := appendIBFTForks( + cc, + fork.PoS, + validators.ECDSAValidatorType, + 5, + u64ptr(4), + nil, + u64ptr(10), + u64ptr(1), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "unable to determine PoS bootstrap validators") +} + +func mustGenesisExtraWithValidators(t *testing.T, vals validators.Validators) []byte { + t.Helper() + + extra := &signer.IstanbulExtra{ + Validators: vals, + ProposerSeal: []byte{}, + CommittedSeals: &signer.SerializedSeal{}, + } + + out := make([]byte, signer.IstanbulExtraVanity) + return extra.MarshalRLPTo(out) +} + +func u64ptr(v uint64) *uint64 { + return &v +} diff --git a/command/ibft/unstake/result.go b/command/ibft/unstake/result.go new file mode 100644 index 0000000..1a7ecd3 --- /dev/null +++ b/command/ibft/unstake/result.go @@ -0,0 +1,24 @@ +package unstake + +import ( + "bytes" + "fmt" + + "github.com/xgr-network/xgr-node/command/helper" +) + +type UnstakeResult struct { + Validator string `json:"validator"` + AccountStakeWei string `json:"accountStakeWei"` +} + +func (r *UnstakeResult) GetOutput() string { + var b bytes.Buffer + b.WriteString("\n[IBFT UNSTAKE]\n") + b.WriteString(helper.FormatKV([]string{ + fmt.Sprintf("Validator|%s", r.Validator), + fmt.Sprintf("Account stake (wei)|%s", r.AccountStakeWei), + })) + + return b.String() +} diff --git a/command/ibft/unstake/unstake.go b/command/ibft/unstake/unstake.go new file mode 100644 index 0000000..24f08c1 --- /dev/null +++ b/command/ibft/unstake/unstake.go @@ -0,0 +1,295 @@ +package unstake + +import ( + "encoding/hex" + "fmt" + "math/big" + "strings" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + + "github.com/xgr-network/xgr-node/command" + ibftHelper "github.com/xgr-network/xgr-node/command/ibft/helper" + "github.com/xgr-network/xgr-node/contracts/abis" + "github.com/xgr-network/xgr-node/contracts/staking" + secretsHelper "github.com/xgr-network/xgr-node/helper/secrets" + "github.com/xgr-network/xgr-node/secrets" + "github.com/xgr-network/xgr-node/secrets/local" + "github.com/xgr-network/xgr-node/txrelayer" + "github.com/xgr-network/xgr-node/types" +) + +type unstakeParams struct { + jsonRPC string + config string + dataDir string + insecure bool + initKeys bool +} + +func GetCommand() *cobra.Command { + p := &unstakeParams{} + + cmd := &cobra.Command{ + Use: "unstake", + Short: "Request unstake from staking contract (unstake())", + RunE: func(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + sm, err := initSecretsManager(p) + if err != nil { + outputter.SetError(err) + return nil + } + + if p.initKeys { + if err := ensureECDSAKey(sm); err != nil { + outputter.SetError(err) + return nil + } + } + + key, err := ibftHelper.GetECDSAKeyFromSecret(sm) + if err != nil { + outputter.SetError(err) + return nil + } + + relayer, err := txrelayer.NewTxRelayer( + txrelayer.WithIPAddress(p.jsonRPC), + txrelayer.WithReceiptTimeout(250*time.Millisecond), + ) + if err != nil { + outputter.SetError(err) + return nil + } + + epochSize, err := queryEpochSize(relayer) + if err != nil { + outputter.SetError(err) + return nil + } + if epochSize == 0 { + outputter.SetError(fmt.Errorf("staking contract returned epochSize=0; staking predeploy is invalid on this chain")) + return nil + } + + if err := sendUnstake(relayer, key); err != nil { + outputter.SetError(err) + return nil + } + + onchainStake, err := queryAccountStake(relayer, types.Address(key.Address())) + if err != nil { + outputter.SetError(err) + return nil + } + + outputter.SetCommandResult(&UnstakeResult{ + Validator: types.Address(key.Address()).String(), + AccountStakeWei: onchainStake.String(), + }) + + return nil + }, + } + + setFlags(cmd, p) + cmd.Example = `# You must deactivate before unstake +xgrchain ibft set-active --value false --jsonrpc http://127.0.0.1:8545 --data-dir ./data --insecure + +# Request unstake (withdraw is a separate step) +xgrchain ibft unstake --jsonrpc http://127.0.0.1:8545 --data-dir ./data --insecure` + return cmd +} + +func setFlags(cmd *cobra.Command, p *unstakeParams) { + cmd.Flags().StringVar(&p.jsonRPC, "jsonrpc", "http://127.0.0.1:8545", "JSON-RPC endpoint") + cmd.Flags().StringVar(&p.dataDir, "data-dir", "./data", "Data directory for local secrets") + cmd.Flags().StringVar(&p.config, "config", "", "SecretsManager config file (optional)") + cmd.Flags().BoolVar(&p.insecure, "insecure", false, "Allow insecure key storage") + cmd.Flags().BoolVar(&p.initKeys, "init-keys", false, "Generate missing ECDSA key in data-dir/config") +} + +func initSecretsManager(p *unstakeParams) (secrets.SecretsManager, error) { + if p.config != "" { + cfg, err := secrets.ReadConfig(p.config) + if err != nil { + return nil, err + } + return secretsHelper.InitCloudSecretsManager(cfg) + } + + baseConfig := &secrets.SecretsManagerParams{ + Logger: hclog.NewNullLogger(), + Extra: map[string]interface{}{ + secrets.Path: p.dataDir, + }, + } + + return local.SecretsManagerFactory(nil, baseConfig) +} + +func ensureECDSAKey(sm secrets.SecretsManager) error { + if sm.HasSecret(secrets.ValidatorKey) { + return nil + } + _, err := secretsHelper.InitECDSAValidatorKey(sm) + return err +} + +func queryAccountStake(relayer txrelayer.TxRelayer, addr types.Address) (*big.Int, error) { + method := abis.StakingABI.Methods["accountStake"] + if method == nil { + return nil, fmt.Errorf("staking ABI missing accountStake") + } + inp, err := method.Inputs.Encode(map[string]interface{}{ + "account": ethgo.Address(addr), + }) + if err != nil { + return nil, err + } + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), append(method.ID(), inp...)) + if err != nil { + return nil, err + } + b, err := hexToBytes(resp) + if err != nil { + return nil, err + } + decoded, err := method.Outputs.Decode(b) + if err != nil { + return nil, err + } + m, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode accountStake: unexpected type") + } + v, ok := m["0"].(*big.Int) + if !ok { + return nil, fmt.Errorf("decode accountStake: missing value") + } + return v, nil +} + +func queryEpochSize(relayer txrelayer.TxRelayer) (uint64, error) { + method := abis.StakingABI.Methods["epochSize"] + if method == nil { + return 0, fmt.Errorf("staking ABI missing epochSize") + } + + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), method.ID()) + if err != nil { + return 0, err + } + + b, err := hexToBytes(resp) + if err != nil { + return 0, err + } + + decoded, err := method.Outputs.Decode(b) + if err != nil { + return 0, err + } + + m, ok := decoded.(map[string]interface{}) + if !ok { + return 0, fmt.Errorf("decode epochSize: unexpected type") + } + + if epoch, ok := uint64FromDecodedValue(m["0"]); ok { + return epoch, nil + } + if epoch, ok := uint64FromDecodedValue(m["epochSize"]); ok { + return epoch, nil + } + for _, value := range m { + if epoch, ok := uint64FromDecodedValue(value); ok { + return epoch, nil + } + } + + return 0, fmt.Errorf("decode epochSize: missing value") +} + +func uint64FromDecodedValue(v interface{}) (uint64, bool) { + switch x := v.(type) { + case *big.Int: + if x == nil { + return 0, false + } + return x.Uint64(), true + case uint64: + return x, true + case uint32: + return uint64(x), true + case int: + if x < 0 { + return 0, false + } + return uint64(x), true + } + + return 0, false +} + +func sendUnstake(relayer txrelayer.TxRelayer, key ethgo.Key) error { + m := abis.StakingABI.Methods["unstake"] + if m == nil { + return fmt.Errorf("staking ABI missing unstake") + } + to := ethgo.Address(staking.AddrStakingContract) + + // Preflight call from validator address to surface deterministic revert reasons + // before gas estimation/sending wraps the node error. + if key != nil { + if _, err := relayer.Call(key.Address(), to, m.ID()); err != nil { + return mapUnstakeError(err) + } + } + + tx := ðgo.Transaction{ + To: &to, + Input: m.ID(), + Type: ethgo.TransactionDynamicFee, + } + receipt, err := relayer.SendTransaction(tx, key) + if err != nil { + return mapUnstakeError(err) + } + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return fmt.Errorf("unstake reverted") + } + return nil +} + +func mapUnstakeError(err error) error { + if err == nil { + return nil + } + msg := err.Error() + if strings.Contains(msg, "0x34637cc9") { + return fmt.Errorf("unstake rejected: validator must be deactivated first (run: xgrchain ibft set-active --value false)") + } + if strings.Contains(msg, "0x4e487b71") && strings.Contains(msg, "00000012") { + return fmt.Errorf("unstake rejected: staking contract panic 0x12 (division/modulo by zero). likely epochSize=0 in staking contract state") + } + if strings.Contains(msg, "0x4e487b71") { + return fmt.Errorf("unstake rejected: staking contract panic during unstake (panic selector 0x4e487b71): %w", err) + } + + return fmt.Errorf("unstake tx failed: %w", err) +} + +func hexToBytes(s string) ([]byte, error) { + s = strings.TrimPrefix(s, "0x") + if s == "" { + return []byte{}, nil + } + return hex.DecodeString(s) +} diff --git a/command/ibft/unstake/unstake_test.go b/command/ibft/unstake/unstake_test.go new file mode 100644 index 0000000..1129e4f --- /dev/null +++ b/command/ibft/unstake/unstake_test.go @@ -0,0 +1,72 @@ +package unstake + +import ( + "encoding/hex" + "errors" + "math/big" + "strings" + "testing" + + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" + "github.com/xgr-network/xgr-node/contracts/abis" +) + +type mockRelayer struct { + callResp string + callErr error + sendReceipt *ethgo.Receipt + sendErr error +} + +func (m *mockRelayer) Call(ethgo.Address, ethgo.Address, []byte) (string, error) { + return m.callResp, m.callErr +} + +func (m *mockRelayer) SendTransaction(*ethgo.Transaction, ethgo.Key) (*ethgo.Receipt, error) { + return m.sendReceipt, m.sendErr +} + +func (m *mockRelayer) SendTransactionLocal(*ethgo.Transaction) (*ethgo.Receipt, error) { + return nil, nil +} + +func (m *mockRelayer) Client() *jsonrpc.Client { return nil } + +func TestQueryEpochSize(t *testing.T) { + method := abis.StakingABI.Methods["epochSize"] + enc, err := method.Outputs.Encode(map[string]interface{}{"0": big.NewInt(500)}) + if err != nil { + t.Fatalf("encode failed: %v", err) + } + + epochSize, err := queryEpochSize(&mockRelayer{callResp: "0x" + hex.EncodeToString(enc)}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if epochSize != 500 { + t.Fatalf("expected epoch size 500, got %d", epochSize) + } +} + +func TestSendUnstake_Panic12Message(t *testing.T) { + err := sendUnstake(&mockRelayer{sendErr: errors.New("execution reverted data:0x4e487b710000000000000000000000000000000000000000000000000000000000000012")}, nil) + if err == nil { + t.Fatal("expected error") + } + + if !strings.Contains(err.Error(), "panic 0x12") { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestMapUnstakeError_PanicSelectorGeneric(t *testing.T) { + err := mapUnstakeError(errors.New("execution reverted: 0x4e487b71")) + if err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), "panic selector 0x4e487b71") { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/command/ibft/validators/result.go b/command/ibft/validators/result.go new file mode 100644 index 0000000..3b87d2d --- /dev/null +++ b/command/ibft/validators/result.go @@ -0,0 +1,46 @@ +package validators + +import ( + "bytes" + "fmt" + + "github.com/xgr-network/xgr-node/command/helper" +) + +type ValidatorRow struct { + Address string `json:"address"` + StakeWei string `json:"stakeWei"` + BLSPubKeyHex string `json:"blsPubKeyHex,omitempty"` +} + +type ValidatorsResult struct { + MinValidatorCount string `json:"minValidatorCount"` + MaxValidatorCount string `json:"maxValidatorCount"` + ValidatorThresholdWei string `json:"validatorThresholdWei"` + Validators []ValidatorRow `json:"validators"` +} + +func (r *ValidatorsResult) GetOutput() string { + var b bytes.Buffer + + b.WriteString("\n[IBFT VALIDATORS]\n") + b.WriteString(helper.FormatKV([]string{ + fmt.Sprintf("Min validator count|%s", r.MinValidatorCount), + fmt.Sprintf("Max validator count|%s", r.MaxValidatorCount), + fmt.Sprintf("Validator threshold (wei)|%s", r.ValidatorThresholdWei), + })) + + for i, v := range r.Validators { + rows := []string{ + fmt.Sprintf("Validator %d address|%s", i, v.Address), + fmt.Sprintf("Validator %d stake (wei)|%s", i, v.StakeWei), + } + if v.BLSPubKeyHex != "" { + rows = append(rows, fmt.Sprintf("Validator %d BLS pubkey|%s", i, v.BLSPubKeyHex)) + } + b.WriteString("\n") + b.WriteString(helper.FormatKV(rows)) + } + + return b.String() +} diff --git a/command/ibft/validators/validators.go b/command/ibft/validators/validators.go new file mode 100644 index 0000000..eec94e6 --- /dev/null +++ b/command/ibft/validators/validators.go @@ -0,0 +1,330 @@ +package validators + +import ( + "encoding/hex" + "fmt" + "math/big" + "reflect" + "strings" + + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/abi" + + "github.com/xgr-network/xgr-node/command" + "github.com/xgr-network/xgr-node/contracts/abis" + "github.com/xgr-network/xgr-node/contracts/staking" + "github.com/xgr-network/xgr-node/txrelayer" + "github.com/xgr-network/xgr-node/types" +) + +type params struct { + jsonRPC string +} + +func GetCommand() *cobra.Command { + p := ¶ms{} + + cmd := &cobra.Command{ + Use: "validators", + Short: "Show staking contract validator set and stake balances", + RunE: func(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + relayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(p.jsonRPC)) + if err != nil { + outputter.SetError(err) + return nil + } + + minV, err := callUint(relayer, "minNumValidators") + if err != nil { + outputter.SetError(fmt.Errorf("failed to query minNumValidators: %w", err)) + return nil + } + + maxV, err := callUint(relayer, "maxNumValidators") + if err != nil { + outputter.SetError(fmt.Errorf("failed to query maxNumValidators: %w", err)) + return nil + } + + thr, err := callUint(relayer, "VALIDATOR_THRESHOLD") + if err != nil { + outputter.SetError(fmt.Errorf("failed to query VALIDATOR_THRESHOLD: %w", err)) + return nil + } + + addrs, err := callAddresses(relayer, "validators") + if err != nil { + outputter.SetError(err) + return nil + } + + bls, _ := callBytesArray(relayer, "validatorBLSPublicKeys") + + rows := make([]ValidatorRow, 0, len(addrs)) + for i, a := range addrs { + stakeAmt, err := callAccountStake(relayer, a) + if err != nil { + outputter.SetError(err) + return nil + } + + row := ValidatorRow{ + Address: a.String(), + StakeWei: stakeAmt.String(), + } + if i < len(bls) { + row.BLSPubKeyHex = "0x" + hex.EncodeToString(bls[i]) + } + rows = append(rows, row) + } + + outputter.SetCommandResult(&ValidatorsResult{ + MinValidatorCount: minV.String(), + MaxValidatorCount: maxV.String(), + ValidatorThresholdWei: thr.String(), + Validators: rows, + }) + return nil + }, + } + + cmd.Flags().StringVar(&p.jsonRPC, "jsonrpc", "http://127.0.0.1:8545", "JSON-RPC endpoint") + return cmd +} + +func callBytes(relayer txrelayer.TxRelayer, method string, input []byte) ([]byte, error) { + m := abis.StakingABI.Methods[method] + if m == nil { + return nil, fmt.Errorf("staking ABI missing %s", method) + } + + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), input) + if err != nil { + return nil, err + } + + b, err := hexToBytes(resp) + if err != nil { + return nil, err + } + return b, nil +} + +func callUint(relayer txrelayer.TxRelayer, method string) (*big.Int, error) { + m := abis.StakingABI.Methods[method] + if m == nil { + return nil, fmt.Errorf("staking ABI missing %s", method) + } + b, err := callBytes(relayer, method, m.ID()) + if err != nil { + return nil, err + } + decoded, err := m.Outputs.Decode(b) + if err != nil { + return nil, err + } + mp, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode %s: unexpected type", method) + } + v, ok := asBigInt(mp["0"]) + if !ok { + return nil, fmt.Errorf("decode %s: missing value", method) + } + + return v, nil +} + +func asBigInt(val interface{}) (*big.Int, bool) { + switch v := val.(type) { + case *big.Int: + if v == nil { + return nil, false + } + + return v, true + case uint64: + return new(big.Int).SetUint64(v), true + case uint32: + return new(big.Int).SetUint64(uint64(v)), true + case uint16: + return new(big.Int).SetUint64(uint64(v)), true + case uint8: + return new(big.Int).SetUint64(uint64(v)), true + default: + return nil, false + } +} + +func callAddresses(relayer txrelayer.TxRelayer, method string) ([]types.Address, error) { + m := abis.StakingABI.Methods[method] + if m == nil { + return nil, fmt.Errorf("staking ABI missing %s", method) + } + b, err := callBytes(relayer, method, m.ID()) + if err != nil { + return nil, err + } + decoded, err := m.Outputs.Decode(b) + if err != nil { + return nil, err + } + mp, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode %s: unexpected type", method) + } + wa, ok := mp["0"].([]ethgo.Address) + if !ok { + return nil, fmt.Errorf("decode %s: unexpected array type", method) + } + out := make([]types.Address, len(wa)) + for i := range wa { + out[i] = types.Address(wa[i]) + } + return out, nil +} + +func callBytesArray(relayer txrelayer.TxRelayer, method string) ([][]byte, error) { + m := abis.StakingABI.Methods[method] + if m == nil { + return nil, fmt.Errorf("staking ABI missing %s", method) + } + b, err := callBytes(relayer, method, m.ID()) + if err != nil { + return nil, err + } + decoded, err := m.Outputs.Decode(b) + if err != nil { + return nil, err + } + arrRaw, err := firstOutputValue(m, decoded) + if err != nil { + return nil, fmt.Errorf("decode %s: %w", method, err) + } + arr, err := decodeBytesArray(arrRaw) + if err != nil { + return nil, fmt.Errorf("decode %s: %w", method, err) + } + return arr, nil +} + +func firstOutputValue(method *abi.Method, decoded interface{}) (interface{}, error) { + mp, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("unexpected type") + } + + if val, ok := mp["0"]; ok { + return val, nil + } + + outputs := method.Outputs.TupleElems() + if len(outputs) > 0 { + if name := outputs[0].Name; name != "" { + if val, ok := mp[name]; ok { + return val, nil + } + } + } + + if len(mp) == 1 { + for _, val := range mp { + return val, nil + } + } + + return nil, fmt.Errorf("missing output value") +} + +func decodeBytesArray(raw interface{}) ([][]byte, error) { + v := reflect.ValueOf(raw) + if !v.IsValid() { + return nil, fmt.Errorf("unexpected bytes[] type") + } + if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { + return nil, fmt.Errorf("unexpected bytes[] type") + } + + out := make([][]byte, 0, v.Len()) + for i := 0; i < v.Len(); i++ { + b, err := toBytes(v.Index(i).Interface()) + if err != nil { + return nil, err + } + out = append(out, b) + } + + return out, nil +} + +func toBytes(raw interface{}) ([]byte, error) { + switch v := raw.(type) { + case []byte: + return v, nil + case string: + b, err := hexToBytes(v) + if err != nil { + return nil, fmt.Errorf("invalid hex value") + } + + return b, nil + } + + rv := reflect.ValueOf(raw) + if !rv.IsValid() { + return nil, fmt.Errorf("unexpected item type") + } + + kind := rv.Kind() + if kind == reflect.Array || kind == reflect.Slice { + if rv.Type().Elem().Kind() == reflect.Uint8 { + b := make([]byte, rv.Len()) + for i := 0; i < rv.Len(); i++ { + b[i] = byte(rv.Index(i).Uint()) + } + + return b, nil + } + } + + return nil, fmt.Errorf("unexpected item type") +} +func callAccountStake(relayer txrelayer.TxRelayer, addr types.Address) (*big.Int, error) { + m := abis.StakingABI.Methods["accountStake"] + if m == nil { + return nil, fmt.Errorf("staking ABI missing accountStake") + } + inp, err := m.Inputs.Encode(map[string]interface{}{"account": ethgo.Address(addr)}) + if err != nil { + return nil, err + } + b, err := callBytes(relayer, "accountStake", append(m.ID(), inp...)) + if err != nil { + return nil, err + } + decoded, err := m.Outputs.Decode(b) + if err != nil { + return nil, err + } + mp, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode accountStake: unexpected type") + } + v, ok := mp["0"].(*big.Int) + if !ok { + return nil, fmt.Errorf("decode accountStake: missing value") + } + return v, nil +} + +func hexToBytes(s string) ([]byte, error) { + s = strings.TrimPrefix(s, "0x") + if s == "" { + return []byte{}, nil + } + return hex.DecodeString(s) +} diff --git a/command/ibft/validators/validators_decode_test.go b/command/ibft/validators/validators_decode_test.go new file mode 100644 index 0000000..7682809 --- /dev/null +++ b/command/ibft/validators/validators_decode_test.go @@ -0,0 +1,63 @@ +package validators + +import ( + "reflect" + "testing" + + "github.com/xgr-network/xgr-node/contracts/abis" +) + +func TestDecodeBytesArray(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + raw interface{} + want [][]byte + wantErr bool + }{ + {name: "native bytes", raw: [][]byte{{0x01}, {0x02, 0x03}}, want: [][]byte{{0x01}, {0x02, 0x03}}}, + {name: "hex string array", raw: []string{"0x01", "0203"}, want: [][]byte{{0x01}, {0x02, 0x03}}}, + {name: "fixed byte arrays", raw: [][2]byte{{0x01, 0x02}, {0x03, 0x04}}, want: [][]byte{{0x01, 0x02}, {0x03, 0x04}}}, + {name: "interface fixed array", raw: []interface{}{[2]byte{0x01, 0x02}}, want: [][]byte{{0x01, 0x02}}}, + {name: "unexpected item", raw: []interface{}{123}, wantErr: true}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := decodeBytesArray(tt.raw) + if tt.wantErr { + if err == nil { + t.Fatalf("expected error") + } + return + } + if err != nil { + t.Fatalf("decodeBytesArray() error = %v", err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("decodeBytesArray() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFirstOutputValue(t *testing.T) { + t.Parallel() + + m := abis.StakingABI.Methods["validatorBLSPublicKeys"] + if m == nil { + t.Fatal("missing method") + } + + val, err := firstOutputValue(m, map[string]interface{}{"keys": []string{"0x01"}}) + if err != nil { + t.Fatalf("firstOutputValue() error = %v", err) + } + + if _, ok := val.([]string); !ok { + t.Fatalf("firstOutputValue() returned unexpected type %T", val) + } +} diff --git a/command/ibft/withdraw/result.go b/command/ibft/withdraw/result.go new file mode 100644 index 0000000..099c1b3 --- /dev/null +++ b/command/ibft/withdraw/result.go @@ -0,0 +1,26 @@ +package withdraw + +import ( + "bytes" + "fmt" + + "github.com/xgr-network/xgr-node/command/helper" +) + +type WithdrawResult struct { + Validator string `json:"validator"` + AmountWei string `json:"amountWei"` + AccountStakeWei string `json:"accountStakeWei"` +} + +func (r *WithdrawResult) GetOutput() string { + var b bytes.Buffer + b.WriteString("\n[IBFT WITHDRAW]\n") + b.WriteString(helper.FormatKV([]string{ + fmt.Sprintf("Validator|%s", r.Validator), + fmt.Sprintf("Amount (wei)|%s", r.AmountWei), + fmt.Sprintf("Account stake (wei)|%s", r.AccountStakeWei), + })) + + return b.String() +} diff --git a/command/ibft/withdraw/withdraw.go b/command/ibft/withdraw/withdraw.go new file mode 100644 index 0000000..3e9d31c --- /dev/null +++ b/command/ibft/withdraw/withdraw.go @@ -0,0 +1,268 @@ +package withdraw + +import ( + "encoding/hex" + "fmt" + "math/big" + "strings" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + + "github.com/xgr-network/xgr-node/command" + ibftHelper "github.com/xgr-network/xgr-node/command/ibft/helper" + "github.com/xgr-network/xgr-node/contracts/abis" + "github.com/xgr-network/xgr-node/contracts/staking" + secretsHelper "github.com/xgr-network/xgr-node/helper/secrets" + "github.com/xgr-network/xgr-node/secrets" + "github.com/xgr-network/xgr-node/secrets/local" + "github.com/xgr-network/xgr-node/txrelayer" + "github.com/xgr-network/xgr-node/types" +) + +type withdrawParams struct { + jsonRPC string + config string + dataDir string + insecure bool + initKeys bool + + amountStr string + decimals int +} + +func GetCommand() *cobra.Command { + p := &withdrawParams{} + + cmd := &cobra.Command{ + Use: "withdraw", + Short: "Withdraw unstaked funds from staking contract (withdraw(uint256))", + RunE: func(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + sm, err := initSecretsManager(p) + if err != nil { + outputter.SetError(err) + return nil + } + + if p.initKeys { + if err := ensureECDSAKey(sm); err != nil { + outputter.SetError(err) + return nil + } + } + + key, err := ibftHelper.GetECDSAKeyFromSecret(sm) + if err != nil { + outputter.SetError(err) + return nil + } + + relayer, err := txrelayer.NewTxRelayer( + txrelayer.WithIPAddress(p.jsonRPC), + txrelayer.WithReceiptTimeout(250*time.Millisecond), + ) + if err != nil { + outputter.SetError(err) + return nil + } + + amountWei, err := toWei(p.amountStr, p.decimals) + if err != nil { + outputter.SetError(err) + return nil + } + if amountWei.Sign() <= 0 { + outputter.SetError(fmt.Errorf("amount must be > 0")) + return nil + } + + if err := sendWithdraw(relayer, key, amountWei); err != nil { + outputter.SetError(err) + return nil + } + + onchainStake, err := queryAccountStake(relayer, types.Address(key.Address())) + if err != nil { + outputter.SetError(err) + return nil + } + + outputter.SetCommandResult(&WithdrawResult{ + Validator: types.Address(key.Address()).String(), + AmountWei: amountWei.String(), + AccountStakeWei: onchainStake.String(), + }) + + return nil + }, + } + + setFlags(cmd, p) + + cmd.Example = `# Partial withdraw from stake (keeps validator in staking contract) +# Preconditions: validator is deactivated and remaining stake after withdraw stays >= MIN_STAKE +xgrchain ibft set-active --value false --jsonrpc http://127.0.0.1:8545 --data-dir ./data --insecure +xgrchain ibft withdraw --amount 1000000 --jsonrpc http://127.0.0.1:8545 --data-dir ./data --insecure + +# Full exit uses unstake (transfers full stake and removes validator metadata) +xgrchain ibft unstake --jsonrpc http://127.0.0.1:8545 --data-dir ./data --insecure` + + return cmd +} + +func setFlags(cmd *cobra.Command, p *withdrawParams) { + cmd.Flags().StringVar(&p.jsonRPC, "jsonrpc", "http://127.0.0.1:8545", "JSON-RPC endpoint") + cmd.Flags().StringVar(&p.dataDir, "data-dir", "./data", "Data directory for local secrets") + cmd.Flags().StringVar(&p.config, "config", "", "SecretsManager config file (optional)") + cmd.Flags().BoolVar(&p.insecure, "insecure", false, "Allow insecure key storage") + cmd.Flags().BoolVar(&p.initKeys, "init-keys", false, "Generate missing ECDSA key in data-dir/config") + + cmd.Flags().StringVar(&p.amountStr, "amount", "0", "Withdraw amount (integer, in token units)") + cmd.Flags().IntVar(&p.decimals, "decimals", 18, "Token decimals") + _ = cmd.MarkFlagRequired("amount") +} + +func initSecretsManager(p *withdrawParams) (secrets.SecretsManager, error) { + if p.config != "" { + cfg, err := secrets.ReadConfig(p.config) + if err != nil { + return nil, err + } + return secretsHelper.InitCloudSecretsManager(cfg) + } + + baseConfig := &secrets.SecretsManagerParams{ + Logger: hclog.NewNullLogger(), + Extra: map[string]interface{}{ + secrets.Path: p.dataDir, + }, + } + + return local.SecretsManagerFactory(nil, baseConfig) +} + +func ensureECDSAKey(sm secrets.SecretsManager) error { + if sm.HasSecret(secrets.ValidatorKey) { + return nil + } + _, err := secretsHelper.InitECDSAValidatorKey(sm) + return err +} + +func toWei(amountStr string, decimals int) (*big.Int, error) { + if decimals < 0 { + return nil, fmt.Errorf("decimals must be >= 0") + } + + a, ok := new(big.Int).SetString(amountStr, 10) + if !ok { + return nil, fmt.Errorf("invalid amount %q", amountStr) + } + + m := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil) + return new(big.Int).Mul(a, m), nil +} + +func queryAccountStake(relayer txrelayer.TxRelayer, addr types.Address) (*big.Int, error) { + method := abis.StakingABI.Methods["accountStake"] + if method == nil { + return nil, fmt.Errorf("staking ABI missing accountStake") + } + inp, err := method.Inputs.Encode(map[string]interface{}{ + "account": ethgo.Address(addr), + }) + if err != nil { + return nil, err + } + resp, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(staking.AddrStakingContract), append(method.ID(), inp...)) + if err != nil { + return nil, err + } + b, err := hexToBytes(resp) + if err != nil { + return nil, err + } + decoded, err := method.Outputs.Decode(b) + if err != nil { + return nil, err + } + m, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("decode accountStake: unexpected type") + } + v, ok := m["0"].(*big.Int) + if !ok { + return nil, fmt.Errorf("decode accountStake: missing value") + } + return v, nil +} + +func sendWithdraw(relayer txrelayer.TxRelayer, key ethgo.Key, amountWei *big.Int) error { + m := abis.StakingABI.Methods["withdraw"] + if m == nil { + return fmt.Errorf("staking ABI missing withdraw") + } + inp, err := m.Inputs.Encode(map[string]interface{}{"amount": amountWei}) + if err != nil { + return fmt.Errorf("encode withdraw input failed: %w", err) + } + + to := ethgo.Address(staking.AddrStakingContract) + + // Preflight call from validator address to surface deterministic revert reasons + // before gas estimation/sending wraps node-side execution errors. + if key != nil { + if _, err := relayer.Call(key.Address(), to, append(m.ID(), inp...)); err != nil { + return mapWithdrawError(err) + } + } + + tx := ðgo.Transaction{ + To: &to, + Input: append(m.ID(), inp...), + Type: ethgo.TransactionDynamicFee, + } + receipt, err := relayer.SendTransaction(tx, key) + if err != nil { + return mapWithdrawError(err) + } + if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { + return fmt.Errorf("withdraw reverted") + } + return nil +} + +func mapWithdrawError(err error) error { + if err == nil { + return nil + } + + msg := err.Error() + switch { + case strings.Contains(msg, "0x34637cc9"): + return fmt.Errorf("withdraw rejected: validator must be deactivated first and epoch transition must pass (run: xgrchain ibft set-active --value false, then wait next epoch)") + case strings.Contains(msg, "0x5c940c69"): + return fmt.Errorf("withdraw rejected: too early after deactivation (wait until next epoch)") + case strings.Contains(msg, "0x6eda21dd"): + return fmt.Errorf("withdraw rejected: remaining stake would fall below MIN_STAKE (2,000,000 XGR)") + case strings.Contains(msg, "0xdb73cdf0"): + return fmt.Errorf("withdraw rejected: invalid amount (must be > 0 and less than current stake)") + case strings.Contains(msg, "0x580e542f"): + return fmt.Errorf("withdraw rejected: validator not found in staking contract") + default: + return fmt.Errorf("withdraw tx failed: %w", err) + } +} + +func hexToBytes(s string) ([]byte, error) { + s = strings.TrimPrefix(s, "0x") + if s == "" { + return []byte{}, nil + } + return hex.DecodeString(s) +} diff --git a/command/ibft/withdraw/withdraw_test.go b/command/ibft/withdraw/withdraw_test.go new file mode 100644 index 0000000..2ee8581 --- /dev/null +++ b/command/ibft/withdraw/withdraw_test.go @@ -0,0 +1,80 @@ +package withdraw + +import ( + "errors" + "strings" + "testing" +) + +func TestToWei(t *testing.T) { + v, err := toWei("123", 2) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := v.String(); got != "12300" { + t.Fatalf("expected 12300, got %s", got) + } +} + +func TestToWei_InvalidAmount(t *testing.T) { + if _, err := toWei("abc", 18); err == nil { + t.Fatal("expected error for invalid amount") + } +} + +func TestToWei_InvalidDecimals(t *testing.T) { + if _, err := toWei("1", -1); err == nil { + t.Fatal("expected error for invalid decimals") + } +} + +func TestMapWithdrawError_KnownSelectors(t *testing.T) { + tests := []struct { + name string + err error + want string + }{ + { + name: "must deactivate", + err: errors.New(`execution reverted","data":"0x34637cc9"`), + want: "must be deactivated first", + }, + { + name: "too early", + err: errors.New(`execution reverted","data":"0x5c940c69"`), + want: "too early after deactivation", + }, + { + name: "below min stake", + err: errors.New(`execution reverted","data":"0x6eda21dd"`), + want: "below MIN_STAKE", + }, + { + name: "invalid amount", + err: errors.New(`execution reverted","data":"0xdb73cdf0"`), + want: "invalid amount", + }, + { + name: "validator missing", + err: errors.New(`execution reverted","data":"0x580e542f"`), + want: "validator not found", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := mapWithdrawError(tt.err) + if got == nil || !strings.Contains(got.Error(), tt.want) { + t.Fatalf("expected %q in error, got %v", tt.want, got) + } + }) + } +} + +func TestMapWithdrawError_Fallback(t *testing.T) { + err := errors.New("some other rpc failure") + got := mapWithdrawError(err) + if got == nil || !strings.Contains(got.Error(), "withdraw tx failed") { + t.Fatalf("expected wrapped withdraw tx failed error, got %v", got) + } +} diff --git a/command/polybft/polybft_command.go b/command/polybft/polybft_command.go deleted file mode 100644 index fc6d6b5..0000000 --- a/command/polybft/polybft_command.go +++ /dev/null @@ -1,48 +0,0 @@ -package polybft - -import ( - "github.com/spf13/cobra" - "github.com/xgr-network/xgr-node/command/rootchain/registration" - "github.com/xgr-network/xgr-node/command/rootchain/staking" - "github.com/xgr-network/xgr-node/command/rootchain/supernet" - "github.com/xgr-network/xgr-node/command/rootchain/supernet/stakemanager" - "github.com/xgr-network/xgr-node/command/rootchain/validators" - "github.com/xgr-network/xgr-node/command/rootchain/whitelist" - "github.com/xgr-network/xgr-node/command/rootchain/withdraw" - "github.com/xgr-network/xgr-node/command/sidechain/rewards" - "github.com/xgr-network/xgr-node/command/sidechain/unstaking" - sidechainWithdraw "github.com/xgr-network/xgr-node/command/sidechain/withdraw" -) - -func GetCommand() *cobra.Command { - polybftCmd := &cobra.Command{ - Use: "polybft", - Short: "Polybft command", - } - - polybftCmd.AddCommand( - // sidechain (validator set) command to unstake on child chain - unstaking.GetCommand(), - // sidechain (validator set) command to withdraw stake on child chain - sidechainWithdraw.GetCommand(), - // sidechain (reward pool) command to withdraw pending rewards - rewards.GetCommand(), - // rootchain (stake manager) command to withdraw stake - withdraw.GetCommand(), - // rootchain (supernet manager) command that queries validator info - validators.GetCommand(), - // rootchain (supernet manager) whitelist validator - whitelist.GetCommand(), - // rootchain (supernet manager) register validator - registration.GetCommand(), - // rootchain (stake manager) stake command - staking.GetCommand(), - // rootchain (supernet manager) command for finalizing genesis - // validator set and enabling staking - supernet.GetCommand(), - // rootchain command for deploying stake manager - stakemanager.GetCommand(), - ) - - return polybftCmd -} diff --git a/command/polybftsecrets/params.go b/command/polybftsecrets/params.go deleted file mode 100644 index b540f0b..0000000 --- a/command/polybftsecrets/params.go +++ /dev/null @@ -1,242 +0,0 @@ -package polybftsecrets - -import ( - "encoding/hex" - "fmt" - "strings" - - "github.com/spf13/cobra" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/secrets" - "github.com/xgr-network/xgr-node/secrets/helper" - "github.com/xgr-network/xgr-node/types" -) - -const ( - accountFlag = "account" - privateKeyFlag = "private" - insecureLocalStoreFlag = "insecure" - networkFlag = "network" - numFlag = "num" - outputFlag = "output" - - // maxInitNum is the maximum value for "num" flag - maxInitNum = 30 -) - -type initParams struct { - accountDir string - accountConfig string - - generatesAccount bool - generatesNetwork bool - - printPrivateKey bool - - numberOfSecrets int - - insecureLocalStore bool - - output bool -} - -func (ip *initParams) validateFlags() error { - if ip.numberOfSecrets < 1 || ip.numberOfSecrets > maxInitNum { - return ErrInvalidNum - } - - if ip.accountDir == "" && ip.accountConfig == "" { - return ErrInvalidParams - } - - return nil -} - -func (ip *initParams) setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - &ip.accountDir, - AccountDirFlag, - "", - AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - &ip.accountConfig, - AccountConfigFlag, - "", - AccountConfigFlagDesc, - ) - - cmd.Flags().IntVar( - &ip.numberOfSecrets, - numFlag, - 1, - "the flag indicating how many secrets should be created, only for the local FS", - ) - - // Don't accept data-dir and config flags because they are related to different secrets managers. - // data-dir is about the local FS as secrets storage, config is about remote secrets manager. - cmd.MarkFlagsMutuallyExclusive(AccountDirFlag, AccountConfigFlag) - - // num flag should be used with data-dir flag only so it should not be used with config flag. - cmd.MarkFlagsMutuallyExclusive(numFlag, AccountConfigFlag) - - cmd.Flags().BoolVar( - &ip.generatesAccount, - accountFlag, - true, - "the flag indicating whether new account is created", - ) - - cmd.Flags().BoolVar( - &ip.generatesNetwork, - networkFlag, - true, - "the flag indicating whether new Network key is created", - ) - - cmd.Flags().BoolVar( - &ip.printPrivateKey, - privateKeyFlag, - false, - "the flag indicating whether Private key is printed", - ) - - cmd.Flags().BoolVar( - &ip.insecureLocalStore, - insecureLocalStoreFlag, - false, - "the flag indicating should the secrets stored on the local storage be encrypted", - ) - - cmd.Flags().BoolVar( - &ip.output, - outputFlag, - false, - "the flag indicating to output existing secrets", - ) -} - -func (ip *initParams) Execute() (Results, error) { - results := make(Results, ip.numberOfSecrets) - - for i := 0; i < ip.numberOfSecrets; i++ { - configDir, dataDir := ip.accountConfig, ip.accountDir - - if ip.numberOfSecrets > 1 { - dataDir = fmt.Sprintf("%s%d", ip.accountDir, i+1) - } - - if configDir != "" && ip.numberOfSecrets > 1 { - configDir = fmt.Sprintf("%s%d", ip.accountConfig, i+1) - } - - secretManager, err := GetSecretsManager(dataDir, configDir, ip.insecureLocalStore) - if err != nil { - return results, err - } - - var gen []string - if !ip.output { - gen, err = ip.initKeys(secretManager) - if err != nil { - return results, err - } - } - - res, err := ip.getResult(secretManager, gen) - if err != nil { - return results, err - } - - results[i] = res - } - - return results, nil -} - -func (ip *initParams) initKeys(secretsManager secrets.SecretsManager) ([]string, error) { - var generated []string - - if ip.generatesNetwork { - if !secretsManager.HasSecret(secrets.NetworkKey) { - if _, err := helper.InitNetworkingPrivateKey(secretsManager); err != nil { - return generated, fmt.Errorf("error initializing network-key: %w", err) - } - - generated = append(generated, secrets.NetworkKey) - } - } - - if ip.generatesAccount { - var ( - a *wallet.Account - err error - ) - - if !secretsManager.HasSecret(secrets.ValidatorKey) && !secretsManager.HasSecret(secrets.ValidatorBLSKey) { - a, err = wallet.GenerateAccount() - if err != nil { - return generated, fmt.Errorf("error generating account: %w", err) - } - - if err = a.Save(secretsManager); err != nil { - return generated, fmt.Errorf("error saving account: %w", err) - } - - generated = append(generated, secrets.ValidatorKey, secrets.ValidatorBLSKey) - } - } - - return generated, nil -} - -// getResult gets keys from secret manager and return result to display -func (ip *initParams) getResult( - secretsManager secrets.SecretsManager, - generated []string, -) (command.CommandResult, error) { - var ( - res = &SecretsInitResult{} - err error - ) - - if ip.generatesAccount { - account, err := wallet.NewAccountFromSecret(secretsManager) - if err != nil { - return nil, err - } - - res.Address = types.Address(account.Ecdsa.Address()) - res.BLSPubkey = hex.EncodeToString(account.Bls.PublicKey().Marshal()) - - res.Generated = strings.Join(generated, ", ") - - if ip.printPrivateKey { - pk, err := account.Ecdsa.MarshallPrivateKey() - if err != nil { - return nil, err - } - - res.PrivateKey = hex.EncodeToString(pk) - - blspk, err := account.Bls.Marshal() - if err != nil { - return nil, err - } - - res.BLSPrivateKey = string(blspk) - } - } - - if ip.generatesNetwork { - if res.NodeID, err = helper.LoadNodeID(secretsManager); err != nil { - return nil, err - } - } - - res.Insecure = ip.insecureLocalStore - - return res, nil -} diff --git a/command/polybftsecrets/params_test.go b/command/polybftsecrets/params_test.go deleted file mode 100644 index 4abb389..0000000 --- a/command/polybftsecrets/params_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package polybftsecrets - -import ( - "encoding/hex" - "os" - "path" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo/wallet" - - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/secrets/helper" -) - -// Test initKeys -func Test_initKeys(t *testing.T) { - t.Parallel() - - // Creates test directory - dir, err := os.MkdirTemp("", "test") - require.NoError(t, err) - - defer os.RemoveAll(dir) - - sm, err := helper.SetupLocalSecretsManager(dir) - require.NoError(t, err) - - ip := &initParams{ - generatesAccount: false, - generatesNetwork: false, - } - - _, err = ip.initKeys(sm) - require.NoError(t, err) - - assert.False(t, fileExists(path.Join(dir, "consensus/validator.key"))) - assert.False(t, fileExists(path.Join(dir, "consensus/validator-bls.key"))) - assert.False(t, fileExists(path.Join(dir, "libp2p/libp2p.key"))) - - ip.generatesAccount = true - res, err := ip.initKeys(sm) - require.NoError(t, err) - assert.Len(t, res, 2) - - assert.True(t, fileExists(path.Join(dir, "consensus/validator.key"))) - assert.True(t, fileExists(path.Join(dir, "consensus/validator-bls.key"))) - assert.False(t, fileExists(path.Join(dir, "libp2p/libp2p.key"))) - - ip.generatesNetwork = true - res, err = ip.initKeys(sm) - require.NoError(t, err) - assert.Len(t, res, 1) - - assert.True(t, fileExists(path.Join(dir, "libp2p/libp2p.key"))) -} - -func fileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - - return !info.IsDir() -} - -func Test_getResult(t *testing.T) { - t.Parallel() - - // Creates test directory - dir, err := os.MkdirTemp("", "test") - require.NoError(t, err) - - defer os.RemoveAll(dir) - - sm, err := helper.SetupLocalSecretsManager(dir) - require.NoError(t, err) - - ip := &initParams{ - generatesAccount: true, - generatesNetwork: true, - printPrivateKey: true, - } - - _, err = ip.initKeys(sm) - require.NoError(t, err) - - res, err := ip.getResult(sm, []string{}) - require.NoError(t, err) - - sir := res.(*SecretsInitResult) //nolint:forcetypeassert - - // Test public key serialization - privKey, err := hex.DecodeString(sir.PrivateKey) - require.NoError(t, err) - k, err := wallet.NewWalletFromPrivKey(privKey) - require.NoError(t, err) - - pubKey := k.Address().String() - assert.Equal(t, sir.Address.String(), pubKey) - - // Test BLS public key serialization - blsPrivKey, err := bls.UnmarshalPrivateKey([]byte(sir.BLSPrivateKey)) - require.NoError(t, err) - - blsPubKey := hex.EncodeToString(blsPrivKey.PublicKey().Marshal()) - assert.Equal(t, sir.BLSPubkey, blsPubKey) -} diff --git a/command/polybftsecrets/result.go b/command/polybftsecrets/result.go deleted file mode 100644 index a30af05..0000000 --- a/command/polybftsecrets/result.go +++ /dev/null @@ -1,83 +0,0 @@ -package polybftsecrets - -import ( - "bytes" - "fmt" - - "github.com/xgr-network/xgr-node/command" - - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/types" -) - -type Results []command.CommandResult - -func (r Results) GetOutput() string { - var buffer bytes.Buffer - - for _, result := range r { - buffer.WriteString(result.GetOutput()) - } - - return buffer.String() -} - -type SecretsInitResult struct { - Address types.Address `json:"address"` - BLSPubkey string `json:"bls_pubkey"` - NodeID string `json:"node_id"` - PrivateKey string `json:"private_key"` - BLSPrivateKey string `json:"bls_private_key"` - Insecure bool `json:"insecure"` - Generated string `json:"generated"` -} - -func (r *SecretsInitResult) GetOutput() string { - var buffer bytes.Buffer - - vals := make([]string, 0, 3) - - vals = append( - vals, - fmt.Sprintf("Public key (address)|%s", r.Address.String()), - ) - - if r.PrivateKey != "" { - vals = append( - vals, - fmt.Sprintf("Private key|%s", r.PrivateKey), - ) - } - - if r.BLSPrivateKey != "" { - vals = append( - vals, - fmt.Sprintf("BLS Private key|%s", r.BLSPrivateKey), - ) - } - - if r.BLSPubkey != "" { - vals = append( - vals, - fmt.Sprintf("BLS Public key|%s", r.BLSPubkey), - ) - } - - vals = append(vals, fmt.Sprintf("Node ID|%s", r.NodeID)) - - if r.Insecure { - buffer.WriteString("\n[WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION]\n") - } - - if r.Generated != "" { - buffer.WriteString("\n[SECRETS GENERATED]\n") - buffer.WriteString(r.Generated) - buffer.WriteString("\n") - } - - buffer.WriteString("\n[SECRETS INIT]\n") - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/polybftsecrets/secrets_init.go b/command/polybftsecrets/secrets_init.go deleted file mode 100644 index acdfe83..0000000 --- a/command/polybftsecrets/secrets_init.go +++ /dev/null @@ -1,41 +0,0 @@ -package polybftsecrets - -import ( - "github.com/spf13/cobra" - - "github.com/xgr-network/xgr-node/command" -) - -var basicParams = &initParams{} - -func GetCommand() *cobra.Command { - secretsInitCmd := &cobra.Command{ - Use: "polybft-secrets", - Short: "Initializes private keys for the XGRChain (Validator + Networking) " + - "to the specified Secrets Manager", - PreRunE: runPreRun, - Run: runCommand, - } - - basicParams.setFlags(secretsInitCmd) - - return secretsInitCmd -} - -func runPreRun(_ *cobra.Command, _ []string) error { - return basicParams.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - results, err := basicParams.Execute() - if err != nil { - outputter.SetError(err) - - return - } - - outputter.SetCommandResult(results) -} diff --git a/command/polybftsecrets/utils.go b/command/polybftsecrets/utils.go deleted file mode 100644 index 7fddd6b..0000000 --- a/command/polybftsecrets/utils.go +++ /dev/null @@ -1,59 +0,0 @@ -package polybftsecrets - -import ( - "errors" - "fmt" - - "github.com/xgr-network/xgr-node/secrets" - "github.com/xgr-network/xgr-node/secrets/helper" -) - -// common flags for all polybft commands -const ( - AccountDirFlag = "data-dir" - AccountConfigFlag = "config" - PrivateKeyFlag = "private-key" - ChainIDFlag = "chain-id" - - AccountDirFlagDesc = "the directory for the XGRChain data if the local FS is used" - AccountConfigFlagDesc = "the path to the SecretsManager config file, if omitted, the local FS secrets manager is used" - PrivateKeyFlagDesc = "hex-encoded private key of the account which executes command" - ChainIDFlagDesc = "ID of child chain" -) - -// common errors for all polybft commands -var ( - ErrInvalidNum = fmt.Errorf("num flag value should be between 1 and %d", maxInitNum) - ErrInvalidParams = errors.New("no config file or data directory passed in") - ErrUnsupportedType = errors.New("unsupported secrets manager") - ErrSecureLocalStoreNotImplemented = errors.New( - "use a secrets backend, or supply an --insecure flag " + - "to store the private keys locally on the filesystem, " + - "avoid doing so in production") -) - -// GetSecretsManager function resolves secrets manager instance based on provided data or config paths. -// insecureLocalStore defines if utilization of local secrets manager is allowed. -func GetSecretsManager(dataPath, configPath string, insecureLocalStore bool) (secrets.SecretsManager, error) { - if configPath != "" { - secretsConfig, readErr := secrets.ReadConfig(configPath) - if readErr != nil { - return nil, fmt.Errorf("invalid secrets configuration: %w", readErr) - } - - if !secrets.SupportedServiceManager(secretsConfig.Type) { - return nil, ErrUnsupportedType - } - - return helper.InitCloudSecretsManager(secretsConfig) - } - - // Storing secrets on a local file system should only be allowed with --insecure flag, - // to raise awareness that it should be only used in development/testing environments. - // Production setups should use one of the supported secrets managers - if !insecureLocalStore { - return nil, ErrSecureLocalStoreNotImplemented - } - - return helper.SetupLocalSecretsManager(dataPath) -} diff --git a/command/regenesis/howtotest.md b/command/regenesis/howtotest.md deleted file mode 100644 index 87d6bdf..0000000 --- a/command/regenesis/howtotest.md +++ /dev/null @@ -1,179 +0,0 @@ -# Regenesis procedure - -This document outlines step necessary to perform a regenesis data migration. - -## Steps - -1. Create cluster - - ```bash - scripts/cluster ibft - ``` - -2. Check balance - - ```bash - curl -s -X POST --data '{"jsonrpc":"2.0", "method":"eth_getBalance", "params":["0x85da99c8a7c2c95964c8efd687e95e632fc533d6", "latest"], "id":1}' http://localhost:10002 - - {"jsonrpc":"2.0","id":1,"result":"0x3635c9adc5dea00000"} - ``` - -3. Get trie root - - ```bash - ./xgrchain regenesis getroot --rpc "http://localhost:10002" - - [Trie copy SUCCESS] - state root 0xf5ef1a28c82226effb90f4465180ec3469226747818579673f4be929f1cd8663 for block 38 - ``` - -4. Make trie snapshot - - ```bash - ./xgrchain regenesis --target-path ./trie_new --stateRoot 0xf5ef1a28c82226effb90f4465180ec3469226747818579673f4be929f1cd8663 --source-path ./test-chain-1/trie - - [Trie copy SUCCESS] - - ``` - -5. Remove old chain data - - ```bash - rm -rf test-chain-* - ``` - -6. Create new validators - - ```bash - ./xgrchain polybft-secrets --insecure --data-dir test-chain- --num 4 - - [WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION] - - [SECRETS INIT] - Public key (address. = 0x467CaA6185461E4c518597dCE7DE497Fb98a5680 - BLS Public key = 197059fdc3a78bd4001d802481c5ff1d84870c0b37aef851c83522323bd80f6429751ddae63cf38d180a8d45a5b4bf5519f380d60d6eedf9ccd22c3f95fc5e3a1193155af6ff3ab9e2aea5beab3f52e5e364d2cb410d6108c92f9a8375aac73110b8526407691c7e92e5cfab984e9011202b0606dc2be942808554b848cce67b - Node ID = 16Uiu2HAmTN2YAviWyyG4A56Zz8gsVJhmksdojymS4pk54SZqVbGV - - [WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION] - - [SECRETS INIT] - Public key (address. = 0x33177bBAebcB20F8545864a83b9b6EE334e4f94D - BLS Public key = 12d82d172646703d298453cef6f4415ceab2052267d9ec300fd4742fa46fd8db0168b5e98372ae3efd52d647f9b356043163fc4f76182f1ef685faf5e53b1dba15b7d0d9cb9b7868592e02179255775618dbacafd384cffba95b647a5d84de9a27995bb8cc0766194f370ad5b274d1a53b9b8ab21a3dee2f4ee4f177f63fb1f0 - Node ID = 16Uiu2HAkvtXkr1Hsct3UGn19ULNb7jgzPvEPu6Fpdakco8P45648 - - [WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION] - - [SECRETS INIT] - Public key (address. = 0xf308dF858dA25c2e40485FfA2c037D98105FD254 - BLS Public key = 220efa87e71744f44e286230cf4ac95a419abdd6d33d1a578fee21387b841fa32184dd132c077adb8f46db0eb66333672e36b5f363778030475672defce0b5622bb0974424dd7babfdf722fe9eab75ab6e5e34e2ecfcea89420f757bf8adfeb7020f0f961ca946cdb2dde40413c5c0d48aa9f13182ec35b58c4052de466c0e25 - Node ID = 16Uiu2HAmJqYRtWGbepPjQMgWfXnFEkXuX4GKH8AdD1voTwHpKuFa - - [WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION] - - [SECRETS INIT] - Public key (address. = 0x7eC6b7fc98D988472AD1AC0cFcaC6DA993d865B0 - BLS Public key = 2b8188f4bc99a19d5476fc97d14e17231cfb80b205a9fc45261725edefb0195209d972e9c1ad7d3c52b8fa129637738a88203c92fe8aa70fd0998d00f6251cb403ee077ddb4192fac270fe321468fe209308ea7597288e2505ed819f551ed00510c61f3da60f6a83d6017cbaeb7590c44bd354415178bbb160701b12a72a35a6 - Node ID = 16Uiu2HAmEuYYyzQKpyVr2HVCG8Gqx5e5DLCi8LWY4TkFYvHYcWAq - - ``` - -7. Generate genesis file - - ```bash - ./xgrchain genesis --consensus polybft \ - --block-gas-limit 10000000 \ - --proxy-contracts-admin 0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed \ - --epoch-size 10 --trieroot 0xf5ef1a28c82226effb90f4465180ec3469226747818579673f4be929f1cd8663 - - [GENESIS SUCCESS] - **** POLYBFT CONSENSUS PROTOCOL IS IN EXPERIMENTAL PHASE AND IS NOT FULLY PRODUCTION READY. YOU ARE USING IT AT YOUR OWN RISK. **** - Genesis written to ./genesis.json - - ``` - -8. Try to start a new v0.7 chain - - ```bash - ./xgrchain server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :10000 --libp2p :30301 --jsonrpc :10002 --seal --log-level DEBUG & - ./xgrchain server --data-dir ./test-chain-2 --chain genesis.json --grpc-address :20000 --libp2p :30302 --jsonrpc :20002 --seal --log-level DEBUG & - ./xgrchain server --data-dir ./test-chain-3 --chain genesis.json --grpc-address :30000 --libp2p :30303 --jsonrpc :30002 --seal --log-level DEBUG & - ./xgrchain server --data-dir ./test-chain-4 --chain genesis.json --grpc-address :40000 --libp2p :30304 --jsonrpc :40002 --seal --log-level DEBUG & - wait - - [1] 2615 - [2] 2616 - [3] 2617 - [4] 2618 - 2023-03-15T11:02:25.149+0400 [INFO] polygon.server: Data dir: path=./test-chain-1 - 2023-03-15T11:02:25.149+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. - 2023-03-15T11:02:25.233+0400 [INFO] polygon.server: Data dir: path=./test-chain-3 - 2023-03-15T11:02:25.251+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. - invalid initial state root - [1] exit 1 ./xgrchain server --data-dir ./test-chain-1 --chain genesis.json :10000 - 2023-03-15T11:02:25.299+0400 [INFO] polygon.server: Data dir: path=./test-chain-2 - 2023-03-15T11:02:25.302+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. - 2023-03-15T11:02:25.396+0400 [INFO] polygon.server: Data dir: path=./test-chain-4 - 2023-03-15T11:02:25.413+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. - invalid initial state root - [3] - exit 1 ./xgrchain server --data-dir ./test-chain-3 --chain genesis.json :30000 - invalid initial state root - [2] - exit 1 ./xgrchain server --data-dir ./test-chain-2 --chain genesis.json :20000 - invalid initial state root - [4] + exit 1 ./xgrchain server --data-dir ./test-chain-4 --chain genesis.json :40000 - ``` - - It fails, because we haven't provided trie database with correct state trie. - -9. Copy snapshot trie to our data directory - - ```bash - rm -rf ./test-chain-1/trie - rm -rf ./test-chain-2/trie - rm -rf ./test-chain-3/trie - rm -rf ./test-chain-4/trie - cp -fR ./trie_new/ ./test-chain-1/trie/ - cp -fR ./trie_new/ ./test-chain-2/trie/ - cp -fR ./trie_new/ ./test-chain-3/trie/ - cp -fR ./trie_new/ ./test-chain-4/trie/ - ``` - -10. Run chain again - - ```bash - ./xgrchain server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :10000 --libp2p :30301 --jsonrpc :10002 --seal --log-level DEBUG & - ./xgrchain server --data-dir ./test-chain-2 --chain genesis.json --grpc-address :20000 --libp2p :30302 --jsonrpc :20002 --seal --log-level DEBUG & - ./xgrchain server --data-dir ./test-chain-3 --chain genesis.json --grpc-address :30000 --libp2p :30303 --jsonrpc :30002 --seal --log-level DEBUG & - ./xgrchain server --data-dir ./test-chain-4 --chain genesis.json --grpc-address :40000 --libp2p :30304 --jsonrpc :40002 --seal --log-level DEBUG & - wait - - [1] 2721 - [2] 2722 - [3] 2723 - [4] 2724 - 2023-03-15T11:09:41.481+0400 [INFO] polygon.server: Data dir: path=./test-chain-2 - 2023-03-15T11:09:41.481+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. - 2023-03-15T11:09:41.597+0400 [INFO] polygon.server: Data dir: path=./test-chain-1 - 2023-03-15T11:09:41.597+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. - 2023-03-15T11:09:41.609+0400 [WARN] polygon: Initial state root checked and correct - 2023-03-15T11:09:41.661+0400 [INFO] polygon.server: Data dir: path=./test-chain-4 - 2023-03-15T11:09:41.661+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. - 2023-03-15T11:09:41.725+0400 [INFO] polygon.server: Data dir: path=./test-chain-3 - 2023-03-15T11:09:41.725+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. - 2023-03-15T11:09:41.844+0400 [INFO] polygon.blockchain: genesis: hash=0x627b70a8abc294324808d9820015faa5e0616afca5fdc75528b03b703a461acd - 2023-03-15T11:09:41.844+0400 [INFO] polygon.server.polybft: initializing polybft... - 2023-03-15T11:09:41.951+0400 [WARN] polygon: Initial state root checked and correct - 2023-03-15T11:09:42.101+0400 [WARN] polygon: Initial state root checked and correct - 2023-03-15T11:09:42.254+0400 [WARN] polygon: Initial state root checked and correct - 2023-03-15T11:09:42.445+0400 [INFO] polygon.blockchain: genesis: hash=0x627b70a8abc294324808d9820015faa5e0616afca5fdc75528b03b703a461acd - 2023-03-15T11:09:42.445+0400 [INFO] polygon.server.polybft: initializing polybft... - 2023-03-15T11:09:42.462+0400 [INFO] polygon.server.polybft.consensus_runtime: restartEpoch: block number=0 epoch=1 validators=4 firstBlockInEpoch=1 - ... - ``` - -11. Check that balance of account on v0.6 is not 0 - - ```bash - curl -s -X POST --data '{"jsonrpc":"2.0", "method":"eth_getBalance", "params":["0x85da99c8a7c2c95964c8efd687e95e632fc533d6", "latest"], "id":1}' http://localhost:10002 - - {"jsonrpc":"2.0","id":1,"result":"0x3635c9adc5dea00000"}% - ``` diff --git a/command/root/root.go b/command/root/root.go index 2f7c355..d641e5b 100644 --- a/command/root/root.go +++ b/command/root/root.go @@ -7,17 +7,13 @@ import ( "github.com/spf13/cobra" "github.com/xgr-network/xgr-node/command/backup" - "github.com/xgr-network/xgr-node/command/bridge" "github.com/xgr-network/xgr-node/command/genesis" "github.com/xgr-network/xgr-node/command/helper" "github.com/xgr-network/xgr-node/command/ibft" "github.com/xgr-network/xgr-node/command/license" "github.com/xgr-network/xgr-node/command/monitor" "github.com/xgr-network/xgr-node/command/peers" - "github.com/xgr-network/xgr-node/command/polybft" - "github.com/xgr-network/xgr-node/command/polybftsecrets" "github.com/xgr-network/xgr-node/command/regenesis" - "github.com/xgr-network/xgr-node/command/rootchain" "github.com/xgr-network/xgr-node/command/secrets" "github.com/xgr-network/xgr-node/command/server" "github.com/xgr-network/xgr-node/command/status" @@ -50,16 +46,12 @@ func (rc *RootCommand) registerSubCommands() { status.GetCommand(), secrets.GetCommand(), peers.GetCommand(), - rootchain.GetCommand(), monitor.GetCommand(), ibft.GetCommand(), backup.GetCommand(), genesis.GetCommand(), server.GetCommand(), license.GetCommand(), - polybftsecrets.GetCommand(), - polybft.GetCommand(), - bridge.GetCommand(), regenesis.GetCommand(), ) } diff --git a/command/rootchain/README.md b/command/rootchain/README.md deleted file mode 100644 index 2de8571..0000000 --- a/command/rootchain/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Rootchain helper command - -Top level command for manipulating rootchain server. - -## Start rootchain server - -This command starts `ethereum/client-go` container which is Geth node started in dev mode. - -```bash -$ xgrchain rootchain server -``` - -## Fund initialized accounts - -This command funds the initialized accounts via `xgrchain polybft-secrets` command. - -```bash -$ xgrchain rootchain fund --data-dir data-dir- --num 2 -``` - -or - -```bash -$ xgrchain rootchain fund --data-dir data-dir-1 -``` - -## Deploy and initialize contracts - -This command deploys and initializes rootchain contracts. Transactions are being sent to given `--json-rpc` endpoint and are signed by private key provided by `--adminKey` flag. - -```bash -$ xgrchain rootchain deploy \ - --genesis \ - --deployer-key \ - --stake-manager \ - --stake-token \ - --json-rpc \ - --proxy-contracts-admin
-``` - -**Note:** In case `test` flag is provided, it engages test mode, which uses predefined test account private key to send transactions to the rootchain. diff --git a/command/rootchain/deploy/deploy.go b/command/rootchain/deploy/deploy.go deleted file mode 100644 index 52ca402..0000000 --- a/command/rootchain/deploy/deploy.go +++ /dev/null @@ -1,788 +0,0 @@ -package deploy - -import ( - "context" - "errors" - "fmt" - "sync" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/jsonrpc" - "golang.org/x/sync/errgroup" - - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/command" - cmdHelper "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi/artifact" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -const ( - contractsDeploymentTitle = "[ROOTCHAIN - CONTRACTS DEPLOYMENT]" - ProxySufix = "Proxy" - - stateSenderName = "StateSender" - checkpointManagerName = "CheckpointManager" - blsName = "BLS" - bn256G2Name = "BN256G2" - exitHelperName = "ExitHelper" - rootERC20PredicateName = "RootERC20Predicate" - childERC20MintablePredicateName = "ChildERC20MintablePredicate" - rootERC20Name = "RootERC20" - erc20TemplateName = "ERC20Template" - rootERC721PredicateName = "RootERC721Predicate" - childERC721MintablePredicateName = "ChildERC721MintablePredicate" - erc721TemplateName = "ERC721Template" - rootERC1155PredicateName = "RootERC1155Predicate" - childERC1155MintablePredicateName = "ChildERC1155MintablePredicate" - erc1155TemplateName = "ERC1155Template" - customSupernetManagerName = "CustomSupernetManager" - stakeManagerName = "StakeManager" -) - -var ( - // params are the parameters of CLI command - params deployParams - - // consensusCfg contains consensus protocol configuration parameters - consensusCfg polybft.PolyBFTConfig - - // metadataPopulatorMap maps rootchain contract names to callback - // which populates appropriate field in the RootchainMetadata - metadataPopulatorMap = map[string]func(*polybft.RootchainConfig, types.Address){ - stateSenderName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.StateSenderAddress = addr - }, - getProxyNameForImpl(checkpointManagerName): func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.CheckpointManagerAddress = addr - }, - getProxyNameForImpl(blsName): func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.BLSAddress = addr - }, - getProxyNameForImpl(bn256G2Name): func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.BN256G2Address = addr - }, - getProxyNameForImpl(exitHelperName): func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.ExitHelperAddress = addr - }, - getProxyNameForImpl(rootERC20PredicateName): func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.RootERC20PredicateAddress = addr - }, - getProxyNameForImpl(childERC20MintablePredicateName): func( - rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.ChildMintableERC20PredicateAddress = addr - }, - rootERC20Name: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.RootNativeERC20Address = addr - }, - erc20TemplateName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.ChildERC20Address = addr - }, - getProxyNameForImpl(rootERC721PredicateName): func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.RootERC721PredicateAddress = addr - }, - getProxyNameForImpl(childERC721MintablePredicateName): func( - rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.ChildMintableERC721PredicateAddress = addr - }, - erc721TemplateName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.ChildERC721Address = addr - }, - getProxyNameForImpl(rootERC1155PredicateName): func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.RootERC1155PredicateAddress = addr - }, - getProxyNameForImpl(childERC1155MintablePredicateName): func( - rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.ChildMintableERC1155PredicateAddress = addr - }, - erc1155TemplateName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.ChildERC1155Address = addr - }, - getProxyNameForImpl(customSupernetManagerName): func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { - rootchainConfig.CustomSupernetManagerAddress = addr - }, - } - - // initializersMap maps rootchain contract names to initializer function callbacks - initializersMap = map[string]func(command.OutputFormatter, txrelayer.TxRelayer, - *polybft.RootchainConfig, ethgo.Key) error{ - getProxyNameForImpl(customSupernetManagerName): func(fmt command.OutputFormatter, - relayer txrelayer.TxRelayer, - config *polybft.RootchainConfig, - key ethgo.Key) error { - initParams := &contractsapi.InitializeCustomSupernetManagerFn{ - NewStakeManager: config.StakeManagerAddress, - NewBls: config.BLSAddress, - NewStateSender: config.StateSenderAddress, - NewMatic: types.StringToAddress(params.stakeTokenAddr), - NewChildValidatorSet: contracts.ValidatorSetContract, - NewExitHelper: config.ExitHelperAddress, - NewDomain: signer.DomainValidatorSetString, - NewRootERC20Predicate: config.RootERC20PredicateAddress, - } - - return initContract(fmt, relayer, initParams, - config.CustomSupernetManagerAddress, customSupernetManagerName, key) - }, - getProxyNameForImpl(exitHelperName): func(fmt command.OutputFormatter, - relayer txrelayer.TxRelayer, - config *polybft.RootchainConfig, - key ethgo.Key) error { - inputParams := &contractsapi.InitializeExitHelperFn{ - NewCheckpointManager: config.CheckpointManagerAddress, - } - - return initContract(fmt, relayer, inputParams, config.ExitHelperAddress, exitHelperName, key) - }, - getProxyNameForImpl(rootERC20PredicateName): func(fmt command.OutputFormatter, - relayer txrelayer.TxRelayer, - config *polybft.RootchainConfig, - key ethgo.Key) error { - - inputParams := &contractsapi.InitializeRootERC20PredicateFn{ - NewStateSender: config.StateSenderAddress, - NewExitHelper: config.ExitHelperAddress, - NewChildERC20Predicate: contracts.ChildERC20PredicateContract, - NewChildTokenTemplate: contracts.ChildERC20Contract, - // map root native token address should be non-zero only if native token is non-mintable on a childchain - NewNativeTokenRoot: config.RootNativeERC20Address, - } - - return initContract(fmt, relayer, inputParams, - config.RootERC20PredicateAddress, rootERC20PredicateName, key) - }, - getProxyNameForImpl(childERC20MintablePredicateName): func(fmt command.OutputFormatter, - relayer txrelayer.TxRelayer, - config *polybft.RootchainConfig, - key ethgo.Key) error { - initParams := &contractsapi.InitializeChildMintableERC20PredicateFn{ - NewStateSender: config.StateSenderAddress, - NewExitHelper: config.ExitHelperAddress, - NewRootERC20Predicate: contracts.RootMintableERC20PredicateContract, - NewChildTokenTemplate: config.ChildERC20Address, - } - - return initContract(fmt, relayer, initParams, - config.ChildMintableERC20PredicateAddress, childERC20MintablePredicateName, key) - }, - getProxyNameForImpl(rootERC721PredicateName): func(fmt command.OutputFormatter, - relayer txrelayer.TxRelayer, - config *polybft.RootchainConfig, - key ethgo.Key) error { - initParams := &contractsapi.InitializeRootERC721PredicateFn{ - NewStateSender: config.StateSenderAddress, - NewExitHelper: config.ExitHelperAddress, - NewChildERC721Predicate: contracts.ChildERC721PredicateContract, - NewChildTokenTemplate: contracts.ChildERC721Contract, - } - - return initContract(fmt, relayer, initParams, - config.RootERC721PredicateAddress, rootERC721PredicateName, key) - }, - getProxyNameForImpl(childERC721MintablePredicateName): func(fmt command.OutputFormatter, - relayer txrelayer.TxRelayer, - config *polybft.RootchainConfig, - key ethgo.Key) error { - initParams := &contractsapi.InitializeChildMintableERC721PredicateFn{ - NewStateSender: config.StateSenderAddress, - NewExitHelper: config.ExitHelperAddress, - NewRootERC721Predicate: contracts.RootMintableERC721PredicateContract, - NewChildTokenTemplate: config.ChildERC721Address, - } - - return initContract(fmt, relayer, initParams, - config.ChildMintableERC721PredicateAddress, childERC721MintablePredicateName, key) - }, - getProxyNameForImpl(rootERC1155PredicateName): func(fmt command.OutputFormatter, - relayer txrelayer.TxRelayer, - config *polybft.RootchainConfig, - key ethgo.Key) error { - initParams := &contractsapi.InitializeRootERC1155PredicateFn{ - NewStateSender: config.StateSenderAddress, - NewExitHelper: config.ExitHelperAddress, - NewChildERC1155Predicate: contracts.ChildERC1155PredicateContract, - NewChildTokenTemplate: contracts.ChildERC1155Contract, - } - - return initContract(fmt, relayer, initParams, - config.RootERC1155PredicateAddress, rootERC1155PredicateName, key) - }, - getProxyNameForImpl(childERC1155MintablePredicateName): func(fmt command.OutputFormatter, - relayer txrelayer.TxRelayer, - config *polybft.RootchainConfig, - key ethgo.Key) error { - initParams := &contractsapi.InitializeChildMintableERC1155PredicateFn{ - NewStateSender: config.StateSenderAddress, - NewExitHelper: config.ExitHelperAddress, - NewRootERC1155Predicate: contracts.RootMintableERC1155PredicateContract, - NewChildTokenTemplate: config.ChildERC1155Address, - } - - return initContract(fmt, relayer, initParams, - config.ChildMintableERC1155PredicateAddress, childERC1155MintablePredicateName, key) - }, - } -) - -type deploymentResultInfo struct { - RootchainCfg *polybft.RootchainConfig - SupernetID int64 - CommandResults []command.CommandResult -} - -// GetCommand returns the rootchain deploy command -func GetCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "deploy", - Short: "Deploys and initializes required smart contracts on the rootchain", - PreRunE: preRunCommand, - Run: runCommand, - } - - cmd.Flags().StringVar( - ¶ms.genesisPath, - helper.GenesisPathFlag, - helper.DefaultGenesisPath, - helper.GenesisPathFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.deployerKey, - deployerKeyFlag, - "", - "hex-encoded private key of the account which deploys rootchain contracts", - ) - - cmd.Flags().StringVar( - ¶ms.jsonRPCAddress, - jsonRPCFlag, - txrelayer.DefaultRPCAddress, - "the JSON RPC rootchain IP address", - ) - - cmd.Flags().StringVar( - ¶ms.rootERC20TokenAddr, - erc20AddrFlag, - "", - "existing root chain root native token address", - ) - - cmd.Flags().BoolVar( - ¶ms.isTestMode, - helper.TestModeFlag, - false, - "test indicates whether rootchain contracts deployer is hardcoded test account"+ - " (otherwise provided secrets are used to resolve deployer account)", - ) - - cmd.Flags().StringVar( - ¶ms.stakeTokenAddr, - helper.StakeTokenFlag, - "", - helper.StakeTokenFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.stakeManagerAddr, - helper.StakeManagerFlag, - "", - helper.StakeManagerFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.proxyContractsAdmin, - helper.ProxyContractsAdminFlag, - "", - helper.ProxyContractsAdminDesc, - ) - - cmd.MarkFlagsMutuallyExclusive(helper.TestModeFlag, deployerKeyFlag) - _ = cmd.MarkFlagRequired(helper.StakeManagerFlag) - _ = cmd.MarkFlagRequired(helper.StakeTokenFlag) - - return cmd -} - -func preRunCommand(_ *cobra.Command, _ []string) error { - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - outputter.WriteCommandResult(&helper.MessageResult{ - Message: fmt.Sprintf("%s started... Rootchain JSON RPC address %s.", contractsDeploymentTitle, params.jsonRPCAddress), - }) - - chainConfig, err := chain.ImportFromFile(params.genesisPath) - if err != nil { - outputter.SetError(fmt.Errorf("failed to read chain configuration: %w", err)) - - return - } - - client, err := jsonrpc.NewClient(params.jsonRPCAddress) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize JSON RPC client for provided IP address: %s: %w", - params.jsonRPCAddress, err)) - - return - } - - if consensusCfg.Bridge != nil { - code, err := client.Eth().GetCode(ethgo.Address(consensusCfg.Bridge.StateSenderAddr), ethgo.Latest) - if err != nil { - outputter.SetError(fmt.Errorf("failed to check if rootchain contracts are deployed: %w", err)) - - return - } else if code != "0x" { - outputter.SetCommandResult(&helper.MessageResult{ - Message: fmt.Sprintf("%s contracts are already deployed. Aborting.", contractsDeploymentTitle), - }) - - return - } - } - - // set event tracker start blocks for rootchain contract(s) of interest - // the block number should be queried before deploying contracts so that no events during deployment - // and initialization are missed - blockNum, err := client.Eth().BlockNumber() - if err != nil { - outputter.SetError(fmt.Errorf("failed to query rootchain latest block number: %w", err)) - - return - } - - deploymentResultInfo, err := deployContracts(outputter, client, - chainConfig.Params.ChainID, consensusCfg.InitialValidatorSet, cmd.Context()) - if err != nil { - outputter.SetError(fmt.Errorf("failed to deploy rootchain contracts: %w", err)) - outputter.SetCommandResult(command.Results(deploymentResultInfo.CommandResults)) - - return - } - - // populate bridge configuration - bridgeConfig := deploymentResultInfo.RootchainCfg.ToBridgeConfig() - if consensusCfg.Bridge != nil { - // only true if stake-manager-deploy command was executed - // users can still deploy stake manager manually - // only used for e2e tests - bridgeConfig.StakeTokenAddr = consensusCfg.Bridge.StakeTokenAddr - } - - consensusCfg.Bridge = bridgeConfig - - consensusCfg.Bridge.EventTrackerStartBlocks = map[types.Address]uint64{ - deploymentResultInfo.RootchainCfg.StateSenderAddress: blockNum, - } - consensusCfg.SupernetID = deploymentResultInfo.SupernetID - - // write updated consensus configuration - chainConfig.Params.Engine[polybft.ConsensusName] = consensusCfg - - if err := cmdHelper.WriteGenesisConfigToDisk(chainConfig, params.genesisPath); err != nil { - outputter.SetError(fmt.Errorf("failed to save chain configuration bridge data: %w", err)) - - return - } - - deploymentResultInfo.CommandResults = append(deploymentResultInfo.CommandResults, &helper.MessageResult{ - Message: fmt.Sprintf("%s finished. All contracts are successfully deployed and initialized.", - contractsDeploymentTitle), - }) - outputter.SetCommandResult(command.Results(deploymentResultInfo.CommandResults)) -} - -// deployContracts deploys and initializes rootchain smart contracts -func deployContracts(outputter command.OutputFormatter, client *jsonrpc.Client, chainID int64, - initialValidators []*validator.GenesisValidator, cmdCtx context.Context) (deploymentResultInfo, error) { - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(client), txrelayer.WithWriter(outputter)) - if err != nil { - return deploymentResultInfo{RootchainCfg: nil, SupernetID: 0, CommandResults: nil}, - fmt.Errorf("failed to initialize tx relayer: %w", err) - } - - deployerKey, err := helper.DecodePrivateKey(params.deployerKey) - if err != nil { - return deploymentResultInfo{RootchainCfg: nil, SupernetID: 0, CommandResults: nil}, - fmt.Errorf("failed to initialize deployer key: %w", err) - } - - if params.isTestMode { - deployerAddr := deployerKey.Address() - - txn := helper.CreateTransaction(ethgo.ZeroAddress, &deployerAddr, nil, ethgo.Ether(1), true) - if _, err = txRelayer.SendTransactionLocal(txn); err != nil { - return deploymentResultInfo{RootchainCfg: nil, SupernetID: 0, CommandResults: nil}, err - } - } - - type contractInfo struct { - name string - artifact *artifact.Artifact - hasProxy bool - byteCodeBuilder func() ([]byte, error) - } - - rootchainConfig := &polybft.RootchainConfig{ - JSONRPCAddr: params.jsonRPCAddress, - // update stake manager address in genesis in case if stake manager was deployed manually - StakeManagerAddress: types.StringToAddress(params.stakeManagerAddr), - } - - tokenContracts := []*contractInfo{} - - // deploy root ERC20 token only if non-mintable native token flavor is used on a child chain - if !consensusCfg.NativeTokenConfig.IsMintable { - if params.rootERC20TokenAddr != "" { - // use existing root chain ERC20 token - if err := populateExistingTokenAddr(client.Eth(), - params.rootERC20TokenAddr, rootERC20Name, rootchainConfig); err != nil { - return deploymentResultInfo{RootchainCfg: nil, SupernetID: 0, CommandResults: nil}, err - } - } else { - // deploy MockERC20 as a root chain root native token - tokenContracts = append(tokenContracts, - &contractInfo{name: rootERC20Name, artifact: contractsapi.RootERC20}) - } - } - - allContracts := []*contractInfo{ - { - name: stateSenderName, - artifact: contractsapi.StateSender, - }, - { - name: checkpointManagerName, - artifact: contractsapi.CheckpointManager, - hasProxy: true, - byteCodeBuilder: func() ([]byte, error) { - constructorFn := &contractsapi.CheckpointManagerConstructorFn{ - Initiator: types.Address(deployerKey.Address()), - } - - input, err := constructorFn.EncodeAbi() - if err != nil { - return nil, err - } - - return append(contractsapi.CheckpointManager.Bytecode, input...), nil - }, - }, - { - name: blsName, - artifact: contractsapi.BLS, - hasProxy: true, - }, - { - name: bn256G2Name, - artifact: contractsapi.BLS256, - hasProxy: true, - }, - { - name: exitHelperName, - artifact: contractsapi.ExitHelper, - hasProxy: true, - }, - { - name: rootERC20PredicateName, - artifact: contractsapi.RootERC20Predicate, - hasProxy: true, - }, - { - name: childERC20MintablePredicateName, - artifact: contractsapi.ChildMintableERC20Predicate, - hasProxy: true, - }, - { - name: erc20TemplateName, - artifact: contractsapi.ChildERC20, - }, - { - name: rootERC721PredicateName, - artifact: contractsapi.RootERC721Predicate, - hasProxy: true, - }, - { - name: childERC721MintablePredicateName, - artifact: contractsapi.ChildMintableERC721Predicate, - hasProxy: true, - }, - { - name: erc721TemplateName, - artifact: contractsapi.ChildERC721, - }, - { - name: rootERC1155PredicateName, - artifact: contractsapi.RootERC1155Predicate, - hasProxy: true, - }, - { - name: childERC1155MintablePredicateName, - artifact: contractsapi.ChildMintableERC1155Predicate, - hasProxy: true, - }, - { - name: erc1155TemplateName, - artifact: contractsapi.ChildERC1155, - }, - { - name: customSupernetManagerName, - artifact: contractsapi.CustomSupernetManager, - hasProxy: true, - }, - } - - allContracts = append(tokenContracts, allContracts...) - - g, ctx := errgroup.WithContext(cmdCtx) - results := make(map[string]*deployContractResult, len(allContracts)) - resultsLock := sync.Mutex{} - proxyAdmin := types.StringToAddress(params.proxyContractsAdmin) - - for _, contract := range allContracts { - contract := contract - - g.Go(func() error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - bytecode := contract.artifact.Bytecode - if contract.byteCodeBuilder != nil { - bytecode, err = contract.byteCodeBuilder() - if err != nil { - return err - } - } - - txn := helper.CreateTransaction(ethgo.ZeroAddress, nil, bytecode, nil, true) - - receipt, err := txRelayer.SendTransaction(txn, deployerKey) - if err != nil { - return fmt.Errorf("failed sending %s contract deploy transaction: %w", contract.name, err) - } - - if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { - return fmt.Errorf("deployment of %s contract failed", contract.name) - } - - deployResults := make([]*deployContractResult, 0, 2) - implementationAddress := types.Address(receipt.ContractAddress) - - deployResults = append(deployResults, newDeployContractsResult(contract.name, - implementationAddress, - receipt.TransactionHash, - receipt.GasUsed)) - - if contract.hasProxy { - proxyContractName := getProxyNameForImpl(contract.name) - - receipt, err := helper.DeployProxyContract( - txRelayer, deployerKey, proxyContractName, proxyAdmin, implementationAddress) - if err != nil { - return err - } - - if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { - return fmt.Errorf("deployment of %s contract failed", proxyContractName) - } - - deployResults = append(deployResults, newDeployContractsResult(proxyContractName, - types.Address(receipt.ContractAddress), - receipt.TransactionHash, - receipt.GasUsed)) - } - - resultsLock.Lock() - defer resultsLock.Unlock() - - for _, deployResult := range deployResults { - results[deployResult.Name] = deployResult - } - - return nil - } - }) - } - - if err := g.Wait(); err != nil { - return collectResultsOnError(results), err - } - - commandResults := make([]command.CommandResult, 0, len(results)) - - for _, result := range results { - commandResults = append(commandResults, result) - - populatorFn, exists := metadataPopulatorMap[result.Name] - if !exists { - continue - } - - populatorFn(rootchainConfig, result.Address) - } - - g, ctx = errgroup.WithContext(cmdCtx) - - for contractName := range results { - contractName := contractName - - initializer, exists := initializersMap[contractName] - if !exists { - continue - } - - g.Go(func() error { - select { - case <-cmdCtx.Done(): - return cmdCtx.Err() - default: - return initializer(outputter, txRelayer, rootchainConfig, deployerKey) - } - }) - } - - if err := g.Wait(); err != nil { - return deploymentResultInfo{RootchainCfg: nil, SupernetID: 0, CommandResults: nil}, err - } - - // register supernets manager on stake manager - supernetID, err := registerChainOnStakeManager(txRelayer, rootchainConfig, deployerKey) - if err != nil { - return deploymentResultInfo{RootchainCfg: nil, SupernetID: 0, CommandResults: nil}, err - } - - return deploymentResultInfo{ - RootchainCfg: rootchainConfig, - SupernetID: supernetID, - CommandResults: commandResults}, nil -} - -// populateExistingTokenAddr checks whether given token is deployed on the provided address. -// If it is, then its address is set to the rootchain config, otherwise an error is returned -func populateExistingTokenAddr(eth *jsonrpc.Eth, tokenAddr, tokenName string, - rootchainCfg *polybft.RootchainConfig) error { - addr := types.StringToAddress(tokenAddr) - - code, err := eth.GetCode(ethgo.Address(addr), ethgo.Latest) - if err != nil { - return fmt.Errorf("failed to check is %s token deployed: %w", tokenName, err) - } else if code == "0x" { - return fmt.Errorf("%s token is not deployed on provided address %s", tokenName, tokenAddr) - } - - populatorFn, ok := metadataPopulatorMap[tokenName] - if !ok { - return fmt.Errorf("root chain metadata populator not registered for contract '%s'", tokenName) - } - - populatorFn(rootchainCfg, addr) - - return nil -} - -// registerChainOnStakeManager registers child chain and its supernet manager on rootchain -func registerChainOnStakeManager(txRelayer txrelayer.TxRelayer, - rootchainCfg *polybft.RootchainConfig, deployerKey ethgo.Key) (int64, error) { - registerChainFn := &contractsapi.RegisterChildChainStakeManagerFn{ - Manager: rootchainCfg.CustomSupernetManagerAddress, - } - - encoded, err := registerChainFn.EncodeAbi() - if err != nil { - return 0, fmt.Errorf("failed to encode parameters for registering child chain on supernets. error: %w", err) - } - - receipt, err := helper.SendTransaction(txRelayer, ethgo.Address(rootchainCfg.StakeManagerAddress), - encoded, stakeManagerName, deployerKey) - if err != nil { - return 0, err - } - - var ( - childChainRegisteredEvent contractsapi.ChildManagerRegisteredEvent - found bool - supernetID int64 - ) - - for _, log := range receipt.Logs { - doesMatch, err := childChainRegisteredEvent.ParseLog(log) - if err != nil { - return 0, err - } - - if !doesMatch { - continue - } - - supernetID = childChainRegisteredEvent.ID.Int64() - found = true - - break - } - - if !found { - return 0, errors.New("could not find a log that child chain was registered on stake manager") - } - - return supernetID, nil -} - -// initContract initializes arbitrary contract with given parameters deployed on a given address -func initContract(cmdOutput command.OutputFormatter, txRelayer txrelayer.TxRelayer, - initInputFn contractsapi.StateTransactionInput, contractAddr types.Address, - contractName string, deployerKey ethgo.Key) error { - input, err := initInputFn.EncodeAbi() - if err != nil { - return fmt.Errorf("failed to encode initialization params for %s.initialize. error: %w", - contractName, err) - } - - if _, err := helper.SendTransaction(txRelayer, ethgo.Address(contractAddr), - input, contractName, deployerKey); err != nil { - return err - } - - cmdOutput.WriteCommandResult( - &helper.MessageResult{ - Message: fmt.Sprintf("%s %s contract is initialized", contractsDeploymentTitle, contractName), - }) - - return nil -} - -func collectResultsOnError(results map[string]*deployContractResult) deploymentResultInfo { - commandResults := make([]command.CommandResult, 0, len(results)+1) - messageResult := helper.MessageResult{Message: "[ROOTCHAIN - DEPLOY] Successfully deployed the following contracts\n"} - - for _, result := range results { - if result != nil { - // In case an error happened, some of the indices may not be populated. - // Filter those out. - commandResults = append(commandResults, result) - } - } - - commandResults = append([]command.CommandResult{messageResult}, commandResults...) - - return deploymentResultInfo{ - RootchainCfg: nil, - SupernetID: 0, - CommandResults: commandResults} -} - -func getProxyNameForImpl(input string) string { - return input + ProxySufix -} diff --git a/command/rootchain/deploy/deploy_test.go b/command/rootchain/deploy/deploy_test.go deleted file mode 100644 index 9e141f0..0000000 --- a/command/rootchain/deploy/deploy_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package deploy - -import ( - "context" - "os" - "testing" - - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/jsonrpc" - "github.com/umbracle/ethgo/testutil" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/types" -) - -func TestDeployContracts_NoPanics(t *testing.T) { - t.Parallel() - - server := testutil.DeployTestServer(t, nil) - t.Cleanup(func() { - if err := os.RemoveAll(params.genesisPath); err != nil { - t.Fatal(err) - } - }) - - client, err := jsonrpc.NewClient(server.HTTPAddr()) - require.NoError(t, err) - - testKey, err := helper.DecodePrivateKey("") - require.NoError(t, err) - - receipt, err := server.Fund(testKey.Address()) - require.NoError(t, err) - require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) - - txn := ðgo.Transaction{ - To: nil, // contract deployment - Input: contractsapi.StakeManager.Bytecode, - } - - receipt, err = server.SendTxn(txn) - require.NoError(t, err) - require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) - - outputter := command.InitializeOutputter(GetCommand()) - params.stakeManagerAddr = receipt.ContractAddress.String() - params.stakeTokenAddr = types.StringToAddress("0x123456789").String() - params.proxyContractsAdmin = "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" - consensusCfg = polybft.PolyBFTConfig{ - NativeTokenConfig: &polybft.TokenConfig{ - Name: "Test", - Symbol: "TST", - Decimals: 18, - IsMintable: false, - }, - } - - require.NotPanics(t, func() { - _, err = deployContracts(outputter, client, 1, []*validator.GenesisValidator{}, context.Background()) - }) - require.NoError(t, err) -} diff --git a/command/rootchain/deploy/params.go b/command/rootchain/deploy/params.go deleted file mode 100644 index 2381e6b..0000000 --- a/command/rootchain/deploy/params.go +++ /dev/null @@ -1,55 +0,0 @@ -package deploy - -import ( - "errors" - "fmt" - "os" - - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/consensus/polybft" -) - -const ( - deployerKeyFlag = "deployer-key" - jsonRPCFlag = "json-rpc" - erc20AddrFlag = "erc20-token" -) - -type deployParams struct { - genesisPath string - deployerKey string - jsonRPCAddress string - stakeTokenAddr string - rootERC20TokenAddr string - stakeManagerAddr string - proxyContractsAdmin string - isTestMode bool -} - -func (ip *deployParams) validateFlags() error { - var err error - - if _, err = os.Stat(ip.genesisPath); err != nil { - return fmt.Errorf("provided genesis path '%s' is invalid. Error: %w ", ip.genesisPath, err) - } - - consensusCfg, err = polybft.LoadPolyBFTConfig(ip.genesisPath) - if err != nil { - return err - } - - if consensusCfg.NativeTokenConfig == nil { - return errors.New("native token configuration is undefined") - } - - // when using mintable native token, child native token on root chain gets mapped automatically - if consensusCfg.NativeTokenConfig.IsMintable && ip.rootERC20TokenAddr != "" { - return errors.New("if child chain native token is mintable, root native token must not pre-exist on root chain") - } - - if params.stakeTokenAddr == "" { - return errors.New("stake token address is not provided") - } - - return helper.ValidateProxyContractsAdmin(ip.proxyContractsAdmin) -} diff --git a/command/rootchain/deploy/result.go b/command/rootchain/deploy/result.go deleted file mode 100644 index c3ffc2a..0000000 --- a/command/rootchain/deploy/result.go +++ /dev/null @@ -1,45 +0,0 @@ -package deploy - -import ( - "bytes" - "fmt" - - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/types" -) - -type deployContractResult struct { - Name string `json:"name"` - Address types.Address `json:"address"` - Hash types.Hash `json:"hash"` - GasUsed uint64 `json:"gasUsed"` -} - -func newDeployContractsResult(name string, - address types.Address, - hash ethgo.Hash, gasUsed uint64) *deployContractResult { - return &deployContractResult{ - Name: name, - Address: address, - Hash: types.BytesToHash(hash.Bytes()), - GasUsed: gasUsed, - } -} - -func (r deployContractResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[ROOTCHAIN - DEPLOY CONTRACT]\n") - - vals := make([]string, 0, 4) - vals = append(vals, fmt.Sprintf("Name|%s", r.Name)) - vals = append(vals, fmt.Sprintf("Contract (address)|%s", r.Address)) - vals = append(vals, fmt.Sprintf("Transaction (hash)|%s", r.Hash)) - vals = append(vals, fmt.Sprintf("Transaction (gas used)|%d", r.GasUsed)) - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/fund/fund.go b/command/rootchain/fund/fund.go deleted file mode 100644 index c4bc1fa..0000000 --- a/command/rootchain/fund/fund.go +++ /dev/null @@ -1,187 +0,0 @@ -package fund - -import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "golang.org/x/sync/errgroup" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var ( - params fundParams -) - -// GetCommand returns the rootchain fund command -func GetCommand() *cobra.Command { - rootchainFundCmd := &cobra.Command{ - Use: "fund", - Short: "Fund validator account with given tokens amount", - PreRunE: preRunCommand, - Run: runCommand, - } - - setFlags(rootchainFundCmd) - - return rootchainFundCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringSliceVar( - ¶ms.addresses, - helper.AddressesFlag, - nil, - "validator addresses", - ) - - cmd.Flags().StringSliceVar( - ¶ms.amounts, - helper.AmountsFlag, - nil, - "token amounts which is funded to validator on a root chain", - ) - - cmd.Flags().StringVar( - ¶ms.jsonRPCAddress, - jsonRPCFlag, - txrelayer.DefaultRPCAddress, - "the rootchain JSON RPC endpoint", - ) - - cmd.Flags().StringVar( - ¶ms.stakeTokenAddr, - helper.StakeTokenFlag, - "", - helper.StakeTokenFlagDesc, - ) - - cmd.Flags().BoolVar( - ¶ms.mintStakeToken, - mintStakeTokenFlag, - false, - "indicates if stake token deployer should mint root tokens to given validators", - ) - - cmd.Flags().StringVar( - ¶ms.deployerPrivateKey, - polybftsecrets.PrivateKeyFlag, - "", - polybftsecrets.PrivateKeyFlagDesc, - ) -} - -func preRunCommand(_ *cobra.Command, _ []string) error { - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPCAddress)) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize tx relayer: %w", err)) - - return - } - - deployerKey, err := helper.DecodePrivateKey(params.deployerPrivateKey) - if err != nil { - outputter.SetError(fmt.Errorf("failed to initialize deployer private key: %w", err)) - - return - } - - var stakeTokenAddr types.Address - - if params.mintStakeToken { - stakeTokenAddr = types.StringToAddress(params.stakeTokenAddr) - } - - results := make([]command.CommandResult, len(params.addresses)) - g, ctx := errgroup.WithContext(cmd.Context()) - - for i := 0; i < len(params.addresses); i++ { - i := i - - g.Go(func() error { - select { - case <-ctx.Done(): - return ctx.Err() - - default: - validatorAddr := types.StringToAddress(params.addresses[i]) - fundAddr := ethgo.Address(validatorAddr) - txn := helper.CreateTransaction(ethgo.ZeroAddress, &fundAddr, nil, params.amountValues[i], true) - - var ( - receipt *ethgo.Receipt - err error - ) - - if params.deployerPrivateKey != "" { - receipt, err = txRelayer.SendTransaction(txn, deployerKey) - } else { - receipt, err = txRelayer.SendTransactionLocal(txn) - } - - if err != nil { - return fmt.Errorf("failed to send fund validator '%s' transaction: %w", validatorAddr, err) - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("failed to fund validator '%s'", validatorAddr) - } - - if params.mintStakeToken { - // mint tokens to validator, so he is able to send them - mintTxn, err := helper.CreateMintTxn(validatorAddr, stakeTokenAddr, params.amountValues[i], true) - if err != nil { - return fmt.Errorf("failed to create mint native tokens transaction for validator '%s'. err: %w", - validatorAddr, err) - } - - receipt, err := txRelayer.SendTransaction(mintTxn, deployerKey) - if err != nil { - return fmt.Errorf("failed to send mint native tokens transaction to validator '%s'. err: %w", validatorAddr, err) - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("failed to mint native tokens to validator '%s'", validatorAddr) - } - } - - results[i] = &result{ - ValidatorAddr: validatorAddr, - TxHash: types.Hash(receipt.TransactionHash), - IsMinted: params.mintStakeToken, - } - } - - return nil - }) - } - - if err := g.Wait(); err != nil { - outputter.SetError(err) - _, _ = outputter.Write([]byte("[ROOTCHAIN FUND] Successfully funded following accounts\n")) - - for _, result := range results { - if result != nil { - // In case an error happened, some of the indices may not be populated. - // Filter those out. - outputter.SetCommandResult(result) - } - } - - return - } - - outputter.SetCommandResult(command.Results(results)) -} diff --git a/command/rootchain/fund/params.go b/command/rootchain/fund/params.go deleted file mode 100644 index 70b1fcc..0000000 --- a/command/rootchain/fund/params.go +++ /dev/null @@ -1,70 +0,0 @@ -package fund - -import ( - "errors" - "fmt" - "math/big" - - cmdhelper "github.com/xgr-network/xgr-node/command/helper" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/types" -) - -const ( - jsonRPCFlag = "json-rpc" - mintStakeTokenFlag = "mint" -) - -var ( - errNoAddressesProvided = errors.New("no addresses provided") - errInconsistentLength = errors.New("addresses and amounts must be equal length") -) - -type fundParams struct { - addresses []string - amounts []string - stakeTokenAddr string - deployerPrivateKey string - mintStakeToken bool - jsonRPCAddress string - - amountValues []*big.Int -} - -func (fp *fundParams) validateFlags() error { - if len(fp.addresses) == 0 { - return rootHelper.ErrNoAddressesProvided - } - - if len(fp.amounts) != len(fp.addresses) { - return rootHelper.ErrInconsistentLength - } - - for _, addr := range fp.addresses { - if err := types.IsValidAddress(addr); err != nil { - return err - } - } - - fp.amountValues = make([]*big.Int, len(fp.amounts)) - for i, amountRaw := range fp.amounts { - amountValue, err := cmdhelper.ParseAmount(amountRaw) - if err != nil { - return err - } - - fp.amountValues[i] = amountValue - } - - if fp.mintStakeToken { - if fp.stakeTokenAddr == "" { - return rootHelper.ErrMandatoryStakeToken - } - - if err := types.IsValidAddress(fp.stakeTokenAddr); err != nil { - return fmt.Errorf("invalid stake token address is provided: %w", err) - } - } - - return nil -} diff --git a/command/rootchain/fund/params_test.go b/command/rootchain/fund/params_test.go deleted file mode 100644 index 11f0dec..0000000 --- a/command/rootchain/fund/params_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package fund - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/types" -) - -func Test_validateFlags(t *testing.T) { - t.Parallel() - - cases := []struct { - buildParamsFn func() *fundParams - err string - }{ - { - // no addresses provided - buildParamsFn: func() *fundParams { - return &fundParams{} - }, - err: errNoAddressesProvided.Error(), - }, - { - // inconsistent length (addresses vs amounts) - buildParamsFn: func() *fundParams { - return &fundParams{ - addresses: []string{"0x1", "0x2"}, - amounts: []string{"10"}, - } - }, - err: errInconsistentLength.Error(), - }, - { - // address contains invalid characters - buildParamsFn: func() *fundParams { - return &fundParams{ - addresses: []string{"0x10", "0x20"}, - amounts: []string{"10", "20"}, - } - }, - err: "address \x10 has invalid length", - }, - { - // stake token address omitted - buildParamsFn: func() *fundParams { - return &fundParams{ - mintStakeToken: true, - addresses: []string{ - types.StringToAddress("0x10").String(), - types.StringToAddress("0x20").String()}, - amounts: []string{"10", "20"}, - } - }, - err: rootHelper.ErrMandatoryStakeToken.Error(), - }, - { - // stake token address omitted - buildParamsFn: func() *fundParams { - return &fundParams{ - mintStakeToken: true, - stakeTokenAddr: "0xA", - addresses: []string{ - types.StringToAddress("0x10").String(), - types.StringToAddress("0x20").String()}, - amounts: []string{"10", "20"}, - } - }, - err: "invalid stake token address is provided", - }, - { - // valid scenario - buildParamsFn: func() *fundParams { - return &fundParams{ - addresses: []string{ - types.StringToAddress("0x10").String(), - types.StringToAddress("0x20").String()}, - amounts: []string{"10", "20"}, - } - }, - err: "", - }, - } - - for i, c := range cases { - c := c - i := i - - t.Run(fmt.Sprintf("case#%d", i+1), func(t *testing.T) { - t.Parallel() - - fp := c.buildParamsFn() - - err := fp.validateFlags() - if c.err != "" { - require.ErrorContains(t, err, c.err) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/command/rootchain/fund/result.go b/command/rootchain/fund/result.go deleted file mode 100644 index 07a5647..0000000 --- a/command/rootchain/fund/result.go +++ /dev/null @@ -1,30 +0,0 @@ -package fund - -import ( - "bytes" - "fmt" - - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/types" -) - -type result struct { - ValidatorAddr types.Address `json:"address"` - TxHash types.Hash `json:"tx_hash"` - IsMinted bool `json:"mint"` -} - -func (r *result) GetOutput() string { - var buffer bytes.Buffer - - vals := make([]string, 0, 3) - vals = append(vals, fmt.Sprintf("Validator (address)|%s", r.ValidatorAddr)) - vals = append(vals, fmt.Sprintf("Transaction (hash)|%s", r.TxHash)) - vals = append(vals, fmt.Sprintf("Is minted|%v", r.IsMinted)) - - buffer.WriteString("\n[ROOTCHAIN FUND]\n") - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/helper/utils.go b/command/rootchain/helper/utils.go deleted file mode 100644 index bfe7602..0000000 --- a/command/rootchain/helper/utils.go +++ /dev/null @@ -1,330 +0,0 @@ -package helper - -import ( - "bytes" - "context" - "errors" - "fmt" - "math/big" - - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/client" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/wallet" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - "github.com/xgr-network/xgr-node/consensus/polybft" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - polybftWallet "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/helper/hex" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -//nolint:gosec -const ( - TestAccountPrivKey = "aa75e9a7d427efc732f8e4f1a5b7646adcc61fd5bae40f80d13c8419c9f43d6d" - TestModeFlag = "test" - SupernetManagerFlag = "supernet-manager" - SupernetManagerFlagDesc = "address of supernet manager contract" - StakeManagerFlag = "stake-manager" - StakeManagerFlagDesc = "address of stake manager contract" - NativeRootTokenFlag = "native-root-token" - NativeRootTokenFlagDesc = "address of native root token" - GenesisPathFlag = "genesis" - GenesisPathFlagDesc = "genesis file path, which contains chain configuration" - DefaultGenesisPath = "./genesis.json" - StakeTokenFlag = "stake-token" - StakeTokenFlagDesc = "address of ERC20 token used for staking on rootchain" - ProxyContractsAdminFlag = "proxy-contracts-admin" - ProxyContractsAdminDesc = "admin for proxy contracts" - AddressesFlag = "addresses" - AmountsFlag = "amounts" - Erc20TokenFlag = "erc20-token" //nolint:gosec -) - -var ( - ErrRootchainNotFound = errors.New("rootchain not found") - ErrRootchainPortBind = errors.New("port 8545 is not bind with localhost") - ErrMandatoryStakeToken = errors.New("stake token address is mandatory") - errTestModeSecrets = errors.New("rootchain test mode does not imply specifying secrets parameters") - - ErrNoAddressesProvided = errors.New("no addresses provided") - ErrInconsistentLength = errors.New("addresses and amounts must be equal length") - - ErrMandatoryERC20Token = errors.New("erc20 token address is mandatory") - - rootchainAccountKey *wallet.Key -) - -type MessageResult struct { - Message string `json:"message"` -} - -func (r MessageResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString(r.Message) - buffer.WriteString("\n") - - return buffer.String() -} - -// DecodePrivateKey decodes a private key from provided raw private key -func DecodePrivateKey(rawKey string) (ethgo.Key, error) { - privateKeyRaw := TestAccountPrivKey - if rawKey != "" { - privateKeyRaw = rawKey - } - - dec, err := hex.DecodeString(privateKeyRaw) - if err != nil { - return nil, fmt.Errorf("failed to decode private key string '%s': %w", privateKeyRaw, err) - } - - rootchainAccountKey, err = wallet.NewWalletFromPrivKey(dec) - if err != nil { - return nil, fmt.Errorf("failed to initialize key from provided private key '%s': %w", privateKeyRaw, err) - } - - return rootchainAccountKey, nil -} - -func GetRootchainID() (string, error) { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return "", fmt.Errorf("rootchain id error: %w", err) - } - - containers, err := cli.ContainerList(context.Background(), dockertypes.ContainerListOptions{}) - if err != nil { - return "", fmt.Errorf("rootchain id error: %w", err) - } - - for _, c := range containers { - if c.Labels["edge-type"] == "rootchain" { - return c.ID, nil - } - } - - return "", ErrRootchainNotFound -} - -func ReadRootchainIP() (string, error) { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - return "", fmt.Errorf("rootchain id error: %w", err) - } - - contID, err := GetRootchainID() - if err != nil { - return "", err - } - - inspect, err := cli.ContainerInspect(context.Background(), contID) - if err != nil { - return "", fmt.Errorf("rootchain ip error: %w", err) - } - - ports, ok := inspect.HostConfig.PortBindings["8545/tcp"] - if !ok || len(ports) == 0 { - return "", ErrRootchainPortBind - } - - return fmt.Sprintf("http://%s:%s", ports[0].HostIP, ports[0].HostPort), nil -} - -// GetECDSAKey returns the key based on provided parameters -// If private key is provided, it will return that key -// if not, it will return the key from the secrets manager -func GetECDSAKey(privateKey, accountDir, accountConfig string) (ethgo.Key, error) { - if privateKey != "" { - key, err := DecodePrivateKey(privateKey) - if err != nil { - return nil, fmt.Errorf("failed to initialize private key: %w", err) - } - - return key, nil - } - - secretsManager, err := polybftsecrets.GetSecretsManager(accountDir, accountConfig, true) - if err != nil { - return nil, err - } - - return polybftWallet.GetEcdsaFromSecret(secretsManager) -} - -// GetValidatorInfo queries SupernetManager smart contract on root -// and retrieves validator info for given address -func GetValidatorInfo(validatorAddr ethgo.Address, supernetManagerAddr, stakeManagerAddr types.Address, - chainID int64, txRelayer txrelayer.TxRelayer) (*polybft.ValidatorInfo, error) { - caller := ethgo.Address(contracts.SystemCaller) - getValidatorMethod := contractsapi.CustomSupernetManager.Abi.GetMethod("getValidator") - - encode, err := getValidatorMethod.Encode([]interface{}{validatorAddr}) - if err != nil { - return nil, err - } - - response, err := txRelayer.Call(caller, ethgo.Address(supernetManagerAddr), encode) - if err != nil { - return nil, err - } - - byteResponse, err := hex.DecodeHex(response) - if err != nil { - return nil, fmt.Errorf("unable to decode hex response, %w", err) - } - - decoded, err := getValidatorMethod.Outputs.Decode(byteResponse) - if err != nil { - return nil, err - } - - decodedOutputsMap, ok := decoded.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("could not convert decoded outputs to map") - } - - innerMap, ok := decodedOutputsMap["0"].(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("could not convert decoded outputs map to inner map") - } - - //nolint:forcetypeassert - validatorInfo := &polybft.ValidatorInfo{ - Address: validatorAddr, - IsActive: innerMap["isActive"].(bool), - IsWhitelisted: innerMap["isWhitelisted"].(bool), - } - - stakeOfFn := &contractsapi.StakeOfStakeManagerFn{ - ID: new(big.Int).SetInt64(chainID), - Validator: types.Address(validatorAddr), - } - - encode, err = stakeOfFn.EncodeAbi() - if err != nil { - return nil, err - } - - response, err = txRelayer.Call(caller, ethgo.Address(stakeManagerAddr), encode) - if err != nil { - return nil, err - } - - stake, err := common.ParseUint256orHex(&response) - if err != nil { - return nil, err - } - - validatorInfo.Stake = stake - - return validatorInfo, nil -} - -// CreateMintTxn encodes parameters for mint function on rootchain token contract -func CreateMintTxn(receiver, erc20TokenAddr types.Address, - amount *big.Int, rootchainTx bool) (*ethgo.Transaction, error) { - mintFn := &contractsapi.MintRootERC20Fn{ - To: receiver, - Amount: amount, - } - - input, err := mintFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode provided parameters: %w", err) - } - - addr := ethgo.Address(erc20TokenAddr) - txn := CreateTransaction(ethgo.ZeroAddress, &addr, input, nil, rootchainTx) - - return txn, nil -} - -// CreateApproveERC20Txn sends approve transaction -// to ERC20 token for spender so that it is able to spend given tokens -func CreateApproveERC20Txn(amount *big.Int, - spender, erc20TokenAddr types.Address, rootchainTx bool) (*ethgo.Transaction, error) { - approveFnParams := &contractsapi.ApproveRootERC20Fn{ - Spender: spender, - Amount: amount, - } - - input, err := approveFnParams.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode parameters for RootERC20.approve. error: %w", err) - } - - addr := ethgo.Address(erc20TokenAddr) - - return CreateTransaction(ethgo.ZeroAddress, &addr, input, nil, rootchainTx), nil -} - -// SendTransaction sends provided transaction -func SendTransaction(txRelayer txrelayer.TxRelayer, addr ethgo.Address, input []byte, contractName string, - deployerKey ethgo.Key) (*ethgo.Receipt, error) { - txn := CreateTransaction(ethgo.ZeroAddress, &addr, input, nil, true) - - receipt, err := txRelayer.SendTransaction(txn, deployerKey) - if err != nil { - return nil, fmt.Errorf("failed to send transaction to %s contract (%s). error: %w", - contractName, txn.To.Address(), err) - } - - if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { - return nil, fmt.Errorf("transaction execution failed on %s contract", contractName) - } - - return receipt, nil -} - -// CreateTransaction is a helper function that creates either dynamic fee or legacy transaction based on provided flag -func CreateTransaction(sender ethgo.Address, receiver *ethgo.Address, - input []byte, value *big.Int, isDynamicFee bool) *ethgo.Transaction { - txn := ðgo.Transaction{ - From: sender, - To: receiver, - Input: input, - Value: value, - } - - if isDynamicFee { - txn.Type = ethgo.TransactionDynamicFee - } else { - txn.Type = ethgo.TransactionLegacy - } - - return txn -} - -func DeployProxyContract(txRelayer txrelayer.TxRelayer, deployerKey ethgo.Key, proxyContractName string, - proxyAdmin, logicAddress types.Address) (*ethgo.Receipt, error) { - proxyConstructorFn := contractsapi.TransparentUpgradeableProxyConstructorFn{ - Logic: logicAddress, - Admin_: proxyAdmin, - Data: []byte{}, - } - - constructorInput, err := proxyConstructorFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode proxy constructor function for %s contract. error: %w", - proxyContractName, err) - } - - var proxyDeployInput []byte - - proxyDeployInput = append(proxyDeployInput, contractsapi.TransparentUpgradeableProxy.Bytecode...) - proxyDeployInput = append(proxyDeployInput, constructorInput...) - - txn := CreateTransaction(ethgo.ZeroAddress, nil, proxyDeployInput, nil, true) - - receipt, err := txRelayer.SendTransaction(txn, deployerKey) - if err != nil { - return nil, fmt.Errorf("failed sending %s contract deploy transaction: %w", proxyContractName, err) - } - - return receipt, nil -} diff --git a/command/rootchain/premine/params.go b/command/rootchain/premine/params.go deleted file mode 100644 index 6c249de..0000000 --- a/command/rootchain/premine/params.go +++ /dev/null @@ -1,94 +0,0 @@ -package premine - -import ( - "bytes" - "errors" - "fmt" - "math/big" - - "github.com/xgr-network/xgr-node/command/helper" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" - "github.com/xgr-network/xgr-node/types" -) - -var ( - errMandatorySupernetManagerAddr = errors.New("custom supernet manager address not defined") - errMandatoryRootPredicateAddr = errors.New("root erc20 predicate address not defined") -) - -const rootERC20PredicateFlag = "root-erc20-predicate" - -type premineParams struct { - accountDir string - accountConfig string - privateKey string - customSupernetManager string - rootERC20Predicate string - nativeTokenRoot string - jsonRPC string - amount string - - amountValue *big.Int -} - -func (p *premineParams) validateFlags() (err error) { - if p.nativeTokenRoot == "" { - return rootHelper.ErrMandatoryERC20Token - } - - if err := types.IsValidAddress(p.nativeTokenRoot); err != nil { - return fmt.Errorf("invalid erc20 token address is provided: %w", err) - } - - if p.customSupernetManager == "" { - return errMandatorySupernetManagerAddr - } - - if err := types.IsValidAddress(p.customSupernetManager); err != nil { - return fmt.Errorf("invalid supernet manager address is provided: %w", err) - } - - if p.rootERC20Predicate == "" { - return errMandatoryRootPredicateAddr - } - - if err := types.IsValidAddress(p.rootERC20Predicate); err != nil { - return fmt.Errorf("invalid root erc20 predicate address is provided: %w", err) - } - - if p.amountValue, err = helper.ParseAmount(p.amount); err != nil { - return err - } - - // validate jsonrpc address - if _, err := helper.ParseJSONRPCAddress(p.jsonRPC); err != nil { - return fmt.Errorf("failed to parse json rpc address. Error: %w", err) - } - - if p.privateKey == "" { - return sidechainHelper.ValidateSecretFlags(p.accountDir, p.accountConfig) - } - - return nil -} - -type premineResult struct { - Address string `json:"address"` - Amount *big.Int `json:"amount"` -} - -func (p premineResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[NATIVE ROOT TOKEN PREMINE]\n") - - vals := make([]string, 0, 2) - vals = append(vals, fmt.Sprintf("Address|%s", p.Address)) - vals = append(vals, fmt.Sprintf("Amount Premined|%d", p.Amount)) - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/premine/premine.go b/command/rootchain/premine/premine.go deleted file mode 100644 index 44a03cc..0000000 --- a/command/rootchain/premine/premine.go +++ /dev/null @@ -1,158 +0,0 @@ -package premine - -import ( - "fmt" - "time" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var ( - params premineParams -) - -func GetCommand() *cobra.Command { - premineCmd := &cobra.Command{ - Use: "premine", - Short: "Premine native root token to the caller, which determines genesis balances. " + - "This command is used in case Supernets native token is rootchain originated.", - PreRunE: runPreRun, - RunE: runCommand, - } - - helper.RegisterJSONRPCFlag(premineCmd) - setFlags(premineCmd) - - return premineCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.privateKey, - polybftsecrets.PrivateKeyFlag, - "", - polybftsecrets.PrivateKeyFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.customSupernetManager, - rootHelper.SupernetManagerFlag, - "", - rootHelper.SupernetManagerFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.nativeTokenRoot, - rootHelper.Erc20TokenFlag, - "", - "address of root erc20 native token", - ) - - cmd.Flags().StringVar( - ¶ms.rootERC20Predicate, - rootERC20PredicateFlag, - "", - "address of root erc20 predicate", - ) - - cmd.Flags().StringVar( - ¶ms.amount, - sidechainHelper.AmountFlag, - "", - "amount to premine", - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountConfigFlag) - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountDirFlag) -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - ownerKey, err := rootHelper.GetECDSAKey(params.privateKey, params.accountDir, params.accountConfig) - if err != nil { - return err - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), - txrelayer.WithReceiptTimeout(150*time.Millisecond)) - if err != nil { - return err - } - - approveTxn, err := rootHelper.CreateApproveERC20Txn( - params.amountValue, - types.StringToAddress(params.customSupernetManager), - types.StringToAddress(params.nativeTokenRoot), true) - if err != nil { - return err - } - - receipt, err := txRelayer.SendTransaction(approveTxn, ownerKey) - if err != nil { - return err - } - - if receipt == nil || receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("approve transaction failed on block %d", receipt.BlockNumber) - } - - premineFn := &contractsapi.AddGenesisBalanceCustomSupernetManagerFn{ - Amount: params.amountValue, - } - - premineInput, err := premineFn.EncodeAbi() - if err != nil { - return err - } - - supernetManagerAddr := ethgo.Address(types.StringToAddress(params.customSupernetManager)) - txn := rootHelper.CreateTransaction(ownerKey.Address(), &supernetManagerAddr, premineInput, nil, true) - - receipt, err = txRelayer.SendTransaction(txn, ownerKey) - if err != nil { - return err - } - - if receipt == nil || receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("premine transaction failed on block %d", receipt.BlockNumber) - } - - outputter.WriteCommandResult(&premineResult{ - Address: ownerKey.Address().String(), - Amount: params.amountValue, - }) - - return nil -} diff --git a/command/rootchain/registration/params.go b/command/rootchain/registration/params.go deleted file mode 100644 index 8b12c01..0000000 --- a/command/rootchain/registration/params.go +++ /dev/null @@ -1,45 +0,0 @@ -package registration - -import ( - "bytes" - "fmt" - - "github.com/xgr-network/xgr-node/command/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" -) - -type registerParams struct { - accountDir string - accountConfig string - supernetManagerAddress string - jsonRPC string -} - -func (rp *registerParams) validateFlags() error { - // validate jsonrpc address - _, err := helper.ParseJSONRPCAddress(rp.jsonRPC) - if err != nil { - return fmt.Errorf("failed to parse json rpc address. Error: %w", err) - } - - return sidechainHelper.ValidateSecretFlags(rp.accountDir, rp.accountConfig) -} - -type registerResult struct { - validatorAddress string - koskSignature string -} - -func (rr registerResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[VALIDATOR REGISTRATION]\n") - - vals := make([]string, 0, 2) - vals = append(vals, fmt.Sprintf("Validator Address|%s", rr.validatorAddress)) - vals = append(vals, fmt.Sprintf("KOSK Signature|%s", rr.koskSignature)) - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/registration/register_validator.go b/command/rootchain/registration/register_validator.go deleted file mode 100644 index 23941da..0000000 --- a/command/rootchain/registration/register_validator.go +++ /dev/null @@ -1,166 +0,0 @@ -package registration - -import ( - "encoding/hex" - "errors" - "fmt" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var params registerParams - -func GetCommand() *cobra.Command { - registerCmd := &cobra.Command{ - Use: "register-validator", - Short: "registers a whitelisted validator to supernet manager on rootchain", - PreRunE: runPreRun, - RunE: runCommand, - } - - setFlags(registerCmd) - - return registerCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.supernetManagerAddress, - rootHelper.SupernetManagerFlag, - "", - rootHelper.SupernetManagerFlagDesc, - ) - - helper.RegisterJSONRPCFlag(cmd) - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountConfigFlag, polybftsecrets.AccountDirFlag) -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - secretsManager, err := polybftsecrets.GetSecretsManager(params.accountDir, params.accountConfig, true) - if err != nil { - return err - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC)) - if err != nil { - return err - } - - rootChainID, err := txRelayer.Client().Eth().ChainID() - if err != nil { - return err - } - - newValidatorAccount, err := wallet.NewAccountFromSecret(secretsManager) - if err != nil { - return err - } - - koskSignature, err := signer.MakeKOSKSignature( - newValidatorAccount.Bls, newValidatorAccount.Address(), - rootChainID.Int64(), signer.DomainValidatorSet, types.StringToAddress(params.supernetManagerAddress)) - if err != nil { - return err - } - - receipt, err := registerValidator(txRelayer, newValidatorAccount, koskSignature) - if err != nil { - return err - } - - if receipt.Status != uint64(types.ReceiptSuccess) { - return errors.New("register validator transaction failed") - } - - result := ®isterResult{} - foundLog := false - - var validatorRegisteredEvent contractsapi.ValidatorRegisteredEvent - for _, log := range receipt.Logs { - doesMatch, err := validatorRegisteredEvent.ParseLog(log) - if err != nil { - return err - } - - if !doesMatch { - continue - } - - koskSignatureRaw, err := koskSignature.Marshal() - if err != nil { - return err - } - - result.koskSignature = hex.EncodeToString(koskSignatureRaw) - result.validatorAddress = validatorRegisteredEvent.Validator.String() - - foundLog = true - - break - } - - if !foundLog { - return fmt.Errorf("could not find an appropriate log in receipt that registration happened") - } - - outputter.WriteCommandResult(result) - - return nil -} - -func registerValidator(sender txrelayer.TxRelayer, account *wallet.Account, - signature *bls.Signature) (*ethgo.Receipt, error) { - sigMarshal, err := signature.ToBigInt() - if err != nil { - return nil, fmt.Errorf("register validator failed: %w", err) - } - - registerFn := &contractsapi.RegisterCustomSupernetManagerFn{ - Signature: sigMarshal, - Pubkey: account.Bls.PublicKey().ToBigInt(), - } - - input, err := registerFn.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("register validator failed: %w", err) - } - - supernetAddr := ethgo.Address(types.StringToAddress(params.supernetManagerAddress)) - txn := rootHelper.CreateTransaction(ethgo.ZeroAddress, &supernetAddr, input, nil, true) - - return sender.SendTransaction(txn, account.Ecdsa) -} diff --git a/command/rootchain/rootchain.go b/command/rootchain/rootchain.go deleted file mode 100644 index 4377fa5..0000000 --- a/command/rootchain/rootchain.go +++ /dev/null @@ -1,31 +0,0 @@ -package rootchain - -import ( - "github.com/spf13/cobra" - - "github.com/xgr-network/xgr-node/command/rootchain/deploy" - "github.com/xgr-network/xgr-node/command/rootchain/fund" - "github.com/xgr-network/xgr-node/command/rootchain/premine" - "github.com/xgr-network/xgr-node/command/rootchain/server" -) - -// GetCommand creates "rootchain" helper command -func GetCommand() *cobra.Command { - rootchainCmd := &cobra.Command{ - Use: "rootchain", - Short: "Top level rootchain helper command.", - } - - rootchainCmd.AddCommand( - // rootchain server - server.GetCommand(), - // rootchain deploy - deploy.GetCommand(), - // rootchain fund - fund.GetCommand(), - // rootchain premine - premine.GetCommand(), - ) - - return rootchainCmd -} diff --git a/command/rootchain/server/params.go b/command/rootchain/server/params.go deleted file mode 100644 index 5a20d7b..0000000 --- a/command/rootchain/server/params.go +++ /dev/null @@ -1,11 +0,0 @@ -package server - -const ( - dataDirFlag = "data-dir" - noConsole = "no-console" -) - -type serverParams struct { - dataDir string - noConsole bool -} diff --git a/command/rootchain/server/result.go b/command/rootchain/server/result.go deleted file mode 100644 index b44b070..0000000 --- a/command/rootchain/server/result.go +++ /dev/null @@ -1,41 +0,0 @@ -package server - -import ( - "bytes" - "fmt" - - "github.com/docker/docker/api/types/container" - - "github.com/xgr-network/xgr-node/command/helper" -) - -type containerStopResult struct { - Status int64 `json:"status"` - Err string `json:"err"` -} - -func newContainerStopResult(status container.WaitResponse) *containerStopResult { - errMsg := "" - if status.Error != nil { - errMsg = status.Error.Message - } - - return &containerStopResult{ - Status: status.StatusCode, - Err: errMsg, - } -} - -func (r containerStopResult) GetOutput() string { - var buffer bytes.Buffer - - vals := make([]string, 0, 2) - vals = append(vals, fmt.Sprintf("Status|%d", r.Status)) - vals = append(vals, fmt.Sprintf("Error|%s", r.Err)) - - buffer.WriteString("\n[ROOTCHAIN SERVER - STOP]\n") - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/server/server.go b/command/rootchain/server/server.go deleted file mode 100644 index 9709083..0000000 --- a/command/rootchain/server/server.go +++ /dev/null @@ -1,299 +0,0 @@ -package server - -import ( - "context" - "errors" - "fmt" - "io" - "log" - "net/http" - "os" - "os/signal" - "path/filepath" - "strings" - "syscall" - "time" - - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - dockerclient "github.com/docker/docker/client" - "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/go-connections/nat" - "github.com/spf13/cobra" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/helper/common" -) - -const ( - gethConsoleImage = "ghcr.io/0xpolygon/go-ethereum-console:latest" - gethImage = "ethereum/client-go:v1.9.25" - - defaultHostIP = "127.0.0.1" - defaultHostPort = "8545" -) - -var ( - params serverParams - dockerClient *dockerclient.Client - dockerContainerID string -) - -// GetCommand returns the rootchain server command -func GetCommand() *cobra.Command { - rootchainServerCmd := &cobra.Command{ - Use: "server", - Short: "Start the rootchain command", - PreRunE: runPreRun, - Run: runCommand, - } - - setFlags(rootchainServerCmd) - - return rootchainServerCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.dataDir, - dataDirFlag, - "test-rootchain", - "target directory for the chain", - ) - - cmd.Flags().BoolVar( - ¶ms.noConsole, - noConsole, - false, - "use the official geth image instead of the console fork", - ) -} - -func runPreRun(_ *cobra.Command, _ []string) error { - return nil -} - -func runCommand(cmd *cobra.Command, _ []string) { - ctx := cmd.Context() - - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - closeCh := make(chan struct{}) - - // Check if the client is already running - if cid, err := helper.GetRootchainID(); !errors.Is(err, helper.ErrRootchainNotFound) { - if err != nil { - outputter.SetError(err) - } else if cid != "" { - outputter.SetError(fmt.Errorf("rootchain already running: %s", cid)) - } - - return - } - - // Start the client - if err := runRootchain(ctx, outputter, closeCh); err != nil { - outputter.SetError(fmt.Errorf("failed to run rootchain: %w", err)) - - return - } - - // Ping geth server to make sure everything is up and running - if err := PingServer(closeCh); err != nil { - close(closeCh) - - if ip, err := helper.ReadRootchainIP(); err != nil { - outputter.SetError(fmt.Errorf("failed to ping rootchain server: %w", err)) - } else { - outputter.SetError(fmt.Errorf("failed to ping rootchain server at address %s: %w", ip, err)) - } - - return - } - - // Gather the logs - go func() { - if err := gatherLogs(ctx, outputter); err != nil { - outputter.SetError(fmt.Errorf("failed to gether logs: %w", err)) - - return - } - }() - - if err := handleSignals(ctx, closeCh); err != nil { - outputter.SetError(fmt.Errorf("failed to handle signals: %w", err)) - } -} - -func runRootchain(ctx context.Context, outputter command.OutputFormatter, closeCh chan struct{}) error { - var err error - if dockerClient, err = dockerclient.NewClientWithOpts(dockerclient.FromEnv, - dockerclient.WithAPIVersionNegotiation()); err != nil { - return err - } - - // target directory for the chain - if err = common.CreateDirSafe(params.dataDir, 0700); err != nil { - return err - } - - image := gethConsoleImage - if params.noConsole { - image = gethImage - } - - // try to pull the image - reader, err := dockerClient.ImagePull(ctx, image, dockertypes.ImagePullOptions{}) - if err != nil { - return err - } - defer reader.Close() - - if _, err = io.Copy(outputter, reader); err != nil { - return fmt.Errorf("cannot copy: %w", err) - } - - // create the client - args := []string{"--dev"} - - // add period of 2 seconds - args = append(args, "--dev.period", "2") - - // add data dir - args = append(args, "--datadir", "/eth1data") - - // add ipcpath - args = append(args, "--ipcpath", "/eth1data/geth.ipc") - - // enable rpc - args = append(args, "--http", "--http.addr", "0.0.0.0", "--http.api", "eth,net,web3,debug") - - // enable ws - args = append(args, "--ws", "--ws.addr", "0.0.0.0") - - config := &container.Config{ - Image: image, - Cmd: args, - Labels: map[string]string{ - "edge-type": "rootchain", - }, - } - - mountDir := params.dataDir - - // we need to use the full path - if !strings.HasPrefix(params.dataDir, "/") { - // if the path is not absolute, assume we want to create it locally - // in current folder - pwdDir, err := os.Getwd() - if err != nil { - log.Fatal(err) - } else { - mountDir = filepath.Join(pwdDir, params.dataDir) - } - } - - port := nat.Port(fmt.Sprintf("%s/tcp", defaultHostPort)) - hostConfig := &container.HostConfig{ - Binds: []string{ - mountDir + ":/eth1data", - }, - PortBindings: nat.PortMap{ - port: []nat.PortBinding{ - { - HostIP: defaultHostIP, - HostPort: defaultHostPort, - }, - }, - }, - } - - resp, err := dockerClient.ContainerCreate(ctx, config, hostConfig, nil, nil, "") - if err != nil { - return err - } - - // start the client - if err = dockerClient.ContainerStart(ctx, resp.ID, dockertypes.ContainerStartOptions{}); err != nil { - return err - } - - dockerContainerID = resp.ID - - // wait for it to finish - go func() { - statusCh, errCh := dockerClient.ContainerWait(ctx, dockerContainerID, container.WaitConditionNotRunning) - select { - case err = <-errCh: - outputter.SetError(err) - case status := <-statusCh: - outputter.SetCommandResult(newContainerStopResult(status)) - } - close(closeCh) - }() - - return nil -} - -func gatherLogs(ctx context.Context, outputter command.OutputFormatter) error { - opts := dockertypes.ContainerLogsOptions{ - ShowStderr: true, - ShowStdout: true, - Follow: true, - } - - out, err := dockerClient.ContainerLogs(ctx, dockerContainerID, opts) - if err != nil { - return fmt.Errorf("failed to retrieve container logs: %w", err) - } - - if _, err = stdcopy.StdCopy(outputter, outputter, out); err != nil { - return fmt.Errorf("failed to write container logs to the stdout: %w", err) - } - - return nil -} - -func PingServer(closeCh <-chan struct{}) error { - httpTimer := time.NewTimer(30 * time.Second) - httpClient := http.Client{ - Timeout: 5 * time.Second, - } - - for { - select { - case <-time.After(500 * time.Millisecond): - resp, err := httpClient.Post(fmt.Sprintf("http://%s:%s", defaultHostIP, defaultHostPort), "application/json", nil) - if err == nil { - return resp.Body.Close() - } - case <-httpTimer.C: - return fmt.Errorf("timeout to start http") - case <-closeCh: - return fmt.Errorf("closed before connecting with http. Is there any other process running and using rootchain dir?") - } - } -} - -func handleSignals(ctx context.Context, closeCh <-chan struct{}) error { - signalCh := make(chan os.Signal, 4) - signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) - - stop := true - select { - case <-signalCh: - case <-closeCh: - stop = false - } - - // close the container if possible - if stop { - if err := dockerClient.ContainerStop(ctx, dockerContainerID, container.StopOptions{}); err != nil { - return fmt.Errorf("failed to stop container: %w", err) - } - } - - return nil -} diff --git a/command/rootchain/staking/params.go b/command/rootchain/staking/params.go deleted file mode 100644 index 68cd93d..0000000 --- a/command/rootchain/staking/params.go +++ /dev/null @@ -1,57 +0,0 @@ -package staking - -import ( - "bytes" - "fmt" - "math/big" - - "github.com/xgr-network/xgr-node/command/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" -) - -var supernetIDFlag = "supernet-id" - -type stakeParams struct { - accountDir string - accountConfig string - stakeManagerAddr string - stakeTokenAddr string - jsonRPC string - supernetID int64 - amount string - - amountValue *big.Int -} - -func (sp *stakeParams) validateFlags() (err error) { - if sp.amountValue, err = helper.ParseAmount(sp.amount); err != nil { - return err - } - - // validate jsonrpc address - if _, err := helper.ParseJSONRPCAddress(sp.jsonRPC); err != nil { - return fmt.Errorf("failed to parse json rpc address. Error: %w", err) - } - - return sidechainHelper.ValidateSecretFlags(sp.accountDir, sp.accountConfig) -} - -type stakeResult struct { - ValidatorAddress string `json:"validatorAddress"` - Amount *big.Int `json:"amount"` -} - -func (sr stakeResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[VALIDATOR STAKE]\n") - - vals := make([]string, 0, 2) - vals = append(vals, fmt.Sprintf("Validator Address|%s", sr.ValidatorAddress)) - vals = append(vals, fmt.Sprintf("Amount Staked|%d", sr.Amount)) - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/staking/stake.go b/command/rootchain/staking/stake.go deleted file mode 100644 index 860b3f8..0000000 --- a/command/rootchain/staking/stake.go +++ /dev/null @@ -1,177 +0,0 @@ -package staking - -import ( - "fmt" - "math/big" - "time" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var ( - params stakeParams -) - -func GetCommand() *cobra.Command { - stakeCmd := &cobra.Command{ - Use: "stake", - Short: "Stakes the amount sent for validator on rootchain", - PreRunE: runPreRun, - RunE: runCommand, - } - - helper.RegisterJSONRPCFlag(stakeCmd) - setFlags(stakeCmd) - - return stakeCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.stakeManagerAddr, - rootHelper.StakeManagerFlag, - "", - rootHelper.StakeManagerFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.amount, - sidechainHelper.AmountFlag, - "", - "amount to stake", - ) - - cmd.Flags().Int64Var( - ¶ms.supernetID, - supernetIDFlag, - 0, - "ID of supernet provided by stake manager on supernet registration", - ) - - cmd.Flags().StringVar( - ¶ms.stakeTokenAddr, - rootHelper.StakeTokenFlag, - "", - rootHelper.StakeTokenFlagDesc, - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - validatorAccount, err := sidechainHelper.GetAccount(params.accountDir, params.accountConfig) - if err != nil { - return err - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), - txrelayer.WithReceiptTimeout(150*time.Millisecond)) - if err != nil { - return err - } - - approveTxn, err := rootHelper.CreateApproveERC20Txn(params.amountValue, - types.StringToAddress(params.stakeManagerAddr), types.StringToAddress(params.stakeTokenAddr), true) - if err != nil { - return err - } - - receipt, err := txRelayer.SendTransaction(approveTxn, validatorAccount.Ecdsa) - if err != nil { - return err - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("approve transaction failed on block %d", receipt.BlockNumber) - } - - stakeFn := contractsapi.StakeForStakeManagerFn{ - ID: new(big.Int).SetInt64(params.supernetID), - Amount: params.amountValue, - } - - encoded, err := stakeFn.EncodeAbi() - if err != nil { - return err - } - - stakeManagerAddr := ethgo.Address(types.StringToAddress(params.stakeManagerAddr)) - - txn := rootHelper.CreateTransaction(validatorAccount.Ecdsa.Address(), &stakeManagerAddr, encoded, nil, true) - - receipt, err = txRelayer.SendTransaction(txn, validatorAccount.Ecdsa) - if err != nil { - return err - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("staking transaction failed on block %d", receipt.BlockNumber) - } - - result := &stakeResult{ - ValidatorAddress: validatorAccount.Ecdsa.Address().String(), - } - - var ( - stakeAddedEvent contractsapi.StakeAddedEvent - foundLog bool - ) - - // check the logs to check for the result - for _, log := range receipt.Logs { - doesMatch, err := stakeAddedEvent.ParseLog(log) - if err != nil { - return err - } - - if !doesMatch { - continue - } - - result.Amount = stakeAddedEvent.Amount - result.ValidatorAddress = stakeAddedEvent.Validator.String() - foundLog = true - - break - } - - if !foundLog { - return fmt.Errorf("could not find an appropriate log in receipt that stake happened") - } - - outputter.WriteCommandResult(result) - - return nil -} diff --git a/command/rootchain/supernet/params.go b/command/rootchain/supernet/params.go deleted file mode 100644 index e566fff..0000000 --- a/command/rootchain/supernet/params.go +++ /dev/null @@ -1,69 +0,0 @@ -package supernet - -import ( - "bytes" - "fmt" - "os" - - "github.com/xgr-network/xgr-node/command/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" -) - -const ( - finalizeGenesisSetFlag = "finalize-genesis-set" - enableStakingFlag = "enable-staking" -) - -type supernetParams struct { - accountDir string - accountConfig string - privateKey string - jsonRPC string - supernetManagerAddress string - genesisPath string - stakeManagerAddress string - finalizeGenesisSet bool - enableStaking bool -} - -func (sp *supernetParams) validateFlags() error { - if sp.privateKey == "" { - return sidechainHelper.ValidateSecretFlags(sp.accountDir, sp.accountConfig) - } - - if _, err := os.Stat(sp.genesisPath); err != nil { - return fmt.Errorf("provided genesis path '%s' is invalid. Error: %w ", sp.genesisPath, err) - } - - // validate jsonrpc address - _, err := helper.ParseJSONRPCAddress(sp.jsonRPC) - - return err -} - -type supernetResult struct { - IsGenesisSetFinalized bool `json:"isGenesisSetFinalized"` - IsStakingEnabled bool `json:"isStakingEnabled"` -} - -func (sr supernetResult) GetOutput() string { - var ( - buffer bytes.Buffer - vals []string - ) - - buffer.WriteString("\n[SUPERNET COMMAND]\n") - - if sr.IsGenesisSetFinalized { - vals = append(vals, "Genesis validator set finalized on supernet manager") - } - - if sr.IsStakingEnabled { - vals = append(vals, "Staking enabled on supernet manager") - } - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/supernet/stakemanager/params.go b/command/rootchain/supernet/stakemanager/params.go deleted file mode 100644 index 60d0a17..0000000 --- a/command/rootchain/supernet/stakemanager/params.go +++ /dev/null @@ -1,53 +0,0 @@ -package stakemanager - -import ( - "fmt" - "os" - - "github.com/xgr-network/xgr-node/command/helper" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" - "github.com/xgr-network/xgr-node/types" -) - -type stakeManagerDeployParams struct { - accountDir string - accountConfig string - privateKey string - jsonRPC string - genesisPath string - stakeTokenAddress string - proxyContractsAdmin string - isTestMode bool -} - -func (s *stakeManagerDeployParams) validateFlags() error { - if !s.isTestMode { - // private key is mandatory - if s.privateKey == "" { - return sidechainHelper.ValidateSecretFlags(s.accountDir, s.accountConfig) - } - - // stake token address is mandatory - if s.stakeTokenAddress == "" { - return rootHelper.ErrMandatoryStakeToken - } - - if err := types.IsValidAddress(s.stakeTokenAddress); err != nil { - return fmt.Errorf("invalid stake token address is provided: %w", err) - } - } - - // check if provided genesis path is valid - if _, err := os.Stat(s.genesisPath); err != nil { - return fmt.Errorf("provided genesis path '%s' is invalid. Error: %w ", s.genesisPath, err) - } - - // validate jsonrpc address - _, err := helper.ParseJSONRPCAddress(s.jsonRPC) - if err != nil { - return err - } - - return helper.ValidateProxyContractsAdmin(s.proxyContractsAdmin) -} diff --git a/command/rootchain/supernet/stakemanager/params_test.go b/command/rootchain/supernet/stakemanager/params_test.go deleted file mode 100644 index 421bac4..0000000 --- a/command/rootchain/supernet/stakemanager/params_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package stakemanager - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" -) - -func TestValidateFlags(t *testing.T) { - t.Parallel() - - testCases := []struct { - params *stakeManagerDeployParams - errMsg string - }{ - { - params: &stakeManagerDeployParams{ - isTestMode: false, - privateKey: rootHelper.TestAccountPrivKey, - stakeTokenAddress: "", - }, - errMsg: rootHelper.ErrMandatoryStakeToken.Error(), - }, - { - params: &stakeManagerDeployParams{ - isTestMode: false, - privateKey: rootHelper.TestAccountPrivKey, - stakeTokenAddress: "0x1B", - }, - errMsg: "invalid stake token address is provided", - }, - } - - for i, tc := range testCases { - i := i - tc := tc - - t.Run(fmt.Sprintf("case#%d", i+1), func(t *testing.T) { - t.Parallel() - - err := tc.params.validateFlags() - require.ErrorContains(t, err, tc.errMsg) - }) - } -} diff --git a/command/rootchain/supernet/stakemanager/stake_manager_deploy.go b/command/rootchain/supernet/stakemanager/stake_manager_deploy.go deleted file mode 100644 index 4c13ce9..0000000 --- a/command/rootchain/supernet/stakemanager/stake_manager_deploy.go +++ /dev/null @@ -1,282 +0,0 @@ -package stakemanager - -import ( - "errors" - "fmt" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi/artifact" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" - "golang.org/x/sync/errgroup" -) - -var params stakeManagerDeployParams - -func GetCommand() *cobra.Command { - stakeMgrDeployCmd := &cobra.Command{ - Use: "stake-manager-deploy", - Short: "Command for deploying stake manager contract on rootchain", - PreRunE: preRunCommand, - RunE: runCommand, - } - - setFlags(stakeMgrDeployCmd) - - return stakeMgrDeployCmd -} - -func preRunCommand(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.privateKey, - polybftsecrets.PrivateKeyFlag, - "", - polybftsecrets.PrivateKeyFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.genesisPath, - rootHelper.GenesisPathFlag, - rootHelper.DefaultGenesisPath, - rootHelper.GenesisPathFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.stakeTokenAddress, - rootHelper.StakeTokenFlag, - "", - rootHelper.StakeTokenFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.proxyContractsAdmin, - rootHelper.ProxyContractsAdminFlag, - "", - rootHelper.ProxyContractsAdminDesc, - ) - - cmd.Flags().BoolVar( - ¶ms.isTestMode, - rootHelper.TestModeFlag, - false, - "indicates if command is run in test mode. If test mode is used contract will be deployed using test account "+ - "and a test stake ERC20 token will be deployed to be used for staking", - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountConfigFlag) - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountDirFlag) - cmd.MarkFlagsMutuallyExclusive(rootHelper.TestModeFlag, polybftsecrets.PrivateKeyFlag) - cmd.MarkFlagsMutuallyExclusive(rootHelper.TestModeFlag, polybftsecrets.AccountConfigFlag) - cmd.MarkFlagsMutuallyExclusive(rootHelper.TestModeFlag, polybftsecrets.AccountDirFlag) - cmd.MarkFlagsMutuallyExclusive(rootHelper.TestModeFlag, rootHelper.StakeTokenFlag) - - helper.RegisterJSONRPCFlag(cmd) -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - var ( - deployerKey ethgo.Key - err error - ) - - if params.isTestMode { - deployerKey, err = rootHelper.DecodePrivateKey("") - } else { - deployerKey, err = rootHelper.GetECDSAKey(params.privateKey, params.accountDir, params.accountConfig) - } - - if err != nil { - return fmt.Errorf("failed to get deployer key: %w", err) - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC)) - if err != nil { - return fmt.Errorf("deploying stake manager failed: %w", err) - } - - if params.isTestMode { - // fund deployer so that he can deploy contracts - deployerAddr := deployerKey.Address() - txn := rootHelper.CreateTransaction(ethgo.ZeroAddress, &deployerAddr, nil, ethgo.Ether(1), true) - - if _, err = txRelayer.SendTransactionLocal(txn); err != nil { - return fmt.Errorf("failed to send local transaction: %w", err) - } - } - - var ( - stakeManagerAddress types.Address - stakeTokenAddress = types.StringToAddress(params.stakeTokenAddress) - g, ctx = errgroup.WithContext(cmd.Context()) - ) - - g.Go(func() error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - // deploy stake manager - contractAddress, err := deployContract(txRelayer, deployerKey, - contractsapi.StakeManager, "StakeManager") - if err != nil { - return err - } - - // deploy stake manager proxy - receipt, err := rootHelper.DeployProxyContract(txRelayer, deployerKey, "StakeManagerProxy", - types.StringToAddress(params.proxyContractsAdmin), contractAddress) - if err != nil { - return err - } - - if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { - return errors.New("deployment of StakeManagerProxy contract failed") - } - - outputter.WriteCommandResult(&rootHelper.MessageResult{ - Message: "[STAKEMANAGER - DEPLOY] Successfully deployed StakeManager contract", - }) - - stakeManagerAddress = types.Address(receipt.ContractAddress) - - return nil - } - }) - - if params.isTestMode { - g.Go(func() error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - // this is only used for testing, we are deploying a test ERC20 token to use for staking - // this should not be used in production - // deploy stake manager - contractAddress, err := deployContract(txRelayer, deployerKey, - contractsapi.RootERC20, "MockERC20StakeToken") - if err != nil { - return err - } - - outputter.WriteCommandResult(&rootHelper.MessageResult{ - Message: "[STAKEMANAGER - DEPLOY] Successfully deployed MockERC20StakeToken contract", - }) - - stakeTokenAddress = contractAddress - - return nil - } - }) - } - - if err := g.Wait(); err != nil { - return err - } - - // initialize stake manager - if err := initializeStakeManager(outputter, txRelayer, - stakeManagerAddress, stakeTokenAddress, deployerKey); err != nil { - return fmt.Errorf("could not initialize stake manager contract. Error: %w", err) - } - - // update stake manager address in genesis - chainConfig, err := chain.ImportFromFile(params.genesisPath) - if err != nil { - return fmt.Errorf("failed to read chain configuration: %w", err) - } - - consensusConfig, err := polybft.GetPolyBFTConfig(chainConfig) - if err != nil { - return fmt.Errorf("failed to retrieve consensus configuration: %w", err) - } - - if consensusConfig.Bridge == nil { - consensusConfig.Bridge = &polybft.BridgeConfig{} - } - - consensusConfig.Bridge.StakeManagerAddr = stakeManagerAddress - consensusConfig.Bridge.StakeTokenAddr = stakeTokenAddress - - // write updated chain configuration - chainConfig.Params.Engine[polybft.ConsensusName] = consensusConfig - - if err = helper.WriteGenesisConfigToDisk(chainConfig, params.genesisPath); err != nil { - return fmt.Errorf("failed to save chain configuration bridge data: %w", err) - } - - return nil -} - -// initializeStakeManager invokes initialize function on StakeManager contract -func initializeStakeManager(cmdOutput command.OutputFormatter, - txRelayer txrelayer.TxRelayer, - stakeManagerAddress types.Address, - stakeTokenAddress types.Address, - deployerKey ethgo.Key) error { - initFn := &contractsapi.InitializeStakeManagerFn{NewStakingToken: stakeTokenAddress} - - input, err := initFn.EncodeAbi() - if err != nil { - return err - } - - if _, err := rootHelper.SendTransaction(txRelayer, ethgo.Address(stakeManagerAddress), - input, "StakeManager", deployerKey); err != nil { - return err - } - - cmdOutput.WriteCommandResult(&rootHelper.MessageResult{ - Message: "[STAKEMANAGER - DEPLOY] StakeManager contract is initialized", - }) - - return nil -} - -func deployContract(txRelayer txrelayer.TxRelayer, deployerKey ethgo.Key, - artifact *artifact.Artifact, contractName string) (types.Address, error) { - txn := rootHelper.CreateTransaction(ethgo.ZeroAddress, nil, artifact.Bytecode, nil, true) - - receipt, err := txRelayer.SendTransaction(txn, deployerKey) - if err != nil { - return types.ZeroAddress, - fmt.Errorf("failed sending %s contract deploy transaction: %w", contractName, err) - } - - if receipt == nil || receipt.Status != uint64(types.ReceiptSuccess) { - return types.ZeroAddress, fmt.Errorf("deployment of %s contract failed", contractName) - } - - return types.Address(receipt.ContractAddress), nil -} diff --git a/command/rootchain/supernet/supernet.go b/command/rootchain/supernet/supernet.go deleted file mode 100644 index 475a0e4..0000000 --- a/command/rootchain/supernet/supernet.go +++ /dev/null @@ -1,400 +0,0 @@ -package supernet - -import ( - "errors" - "fmt" - "math/big" - "strings" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/genesis" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/helper/hex" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var ( - params supernetParams - genesisSetABIFn = contractsapi.CustomSupernetManager.Abi.Methods["genesisSet"] - genesisBalancesABIFn = contractsapi.CustomSupernetManager.Abi.Methods["genesisBalances"] -) - -func GetCommand() *cobra.Command { - registerCmd := &cobra.Command{ - Use: "supernet", - Short: "Supernet initialization & finalization command", - PreRunE: runPreRun, - RunE: runCommand, - } - - setFlags(registerCmd) - - return registerCmd -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.privateKey, - polybftsecrets.PrivateKeyFlag, - "", - polybftsecrets.PrivateKeyFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.genesisPath, - rootHelper.GenesisPathFlag, - rootHelper.DefaultGenesisPath, - rootHelper.GenesisPathFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.supernetManagerAddress, - rootHelper.SupernetManagerFlag, - "", - rootHelper.SupernetManagerFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.stakeManagerAddress, - rootHelper.StakeManagerFlag, - "", - fmt.Sprintf("[DEPRECATED] %s", rootHelper.StakeManagerFlagDesc), - ) - - cmd.Flags().BoolVar( - ¶ms.finalizeGenesisSet, - finalizeGenesisSetFlag, - false, - "indicates if genesis validator set should be finalized on rootchain", - ) - - cmd.Flags().BoolVar( - ¶ms.enableStaking, - enableStakingFlag, - false, - "indicates if staking will be enabled after finalization of genesis validators on rootchain", - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountConfigFlag) - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountDirFlag) - - helper.RegisterJSONRPCFlag(cmd) -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - ownerKey, err := rootHelper.GetECDSAKey(params.privateKey, params.accountDir, params.accountConfig) - if err != nil { - return err - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC)) - if err != nil { - return fmt.Errorf("enlist validator failed: %w", err) - } - - supernetAddr := ethgo.Address(types.StringToAddress(params.supernetManagerAddress)) - - if params.finalizeGenesisSet { - encoded, err := contractsapi.CustomSupernetManager.Abi.Methods["finalizeGenesis"].Encode([]interface{}{}) - if err != nil { - return err - } - - txn := rootHelper.CreateTransaction(ownerKey.Address(), &supernetAddr, encoded, nil, true) - - if _, err = txRelayer.Call(ownerKey.Address(), supernetAddr, encoded); err == nil { - receipt, err := txRelayer.SendTransaction(txn, ownerKey) - if err != nil { - return fmt.Errorf("finalizing genesis validator set failed. Error: %w", err) - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("finalizing genesis validator set transaction failed on block %d", receipt.BlockNumber) - } - } else if !strings.Contains(err.Error(), "execution reverted: GenesisLib: already finalized") { - return err - } - - chainConfig, err := chain.ImportFromFile(params.genesisPath) - if err != nil { - return fmt.Errorf("failed to read chain configuration: %w", err) - } - - consensusConfig, err := polybft.GetPolyBFTConfig(chainConfig) - if err != nil { - return fmt.Errorf("failed to retrieve consensus configuration: %w", err) - } - - validatorMetadata := make([]*validator.ValidatorMetadata, len(consensusConfig.InitialValidatorSet)) - - genesisSetInput, err := genesisSetABIFn.Encode([]interface{}{}) - if err != nil { - return fmt.Errorf("failed to encode genesis set input: %w", err) - } - - genesisSetHexOut, err := txRelayer.Call(ethgo.ZeroAddress, supernetAddr, genesisSetInput) - if err != nil { - return fmt.Errorf("failed to retrieve genesis set: %w", err) - } - - genesisAccounts, err := decodeGenesisAccounts(genesisSetHexOut) - if err != nil { - return err - } - - genesisAccsMap := make(map[types.Address]*validator.GenesisValidator, len(genesisAccounts)) - - for _, genesisAcc := range genesisAccounts { - genesisBalanceInput, err := genesisBalancesABIFn.Encode([]interface{}{genesisAcc.Address}) - if err != nil { - return err - } - - genesisBalanceRaw, err := txRelayer.Call(ethgo.ZeroAddress, supernetAddr, genesisBalanceInput) - if err != nil { - return err - } - - genesisBalance, err := common.ParseUint256orHex(&genesisBalanceRaw) - if err != nil { - return fmt.Errorf("failed to convert genesis balance '%s' to number: %w", - genesisBalanceRaw, err) - } - - genesisAccsMap[genesisAcc.Address] = genesisAcc - - if genesisBalance.Sign() > 0 { - // premine genesis accounts - chainConfig.Genesis.Alloc[genesisAcc.Address] = - &chain.GenesisAccount{Balance: genesisBalance} - } - } - - // update Stake field of validators in genesis file - // based on their finalized stake on rootchain - for i, v := range consensusConfig.InitialValidatorSet { - genesisAcc, exists := genesisAccsMap[v.Address] - if !exists { - return fmt.Errorf("validator with address %s not found among genesis accounts", v.Address) - } - - v.Stake = genesisAcc.Stake - - metadata, err := v.ToValidatorMetadata() - if err != nil { - return err - } - - validatorMetadata[i] = metadata - } - - // update the voting power in genesis block extra - // based on finalized stake on rootchain - genesisExtraData, err := genesis.GenerateExtraDataPolyBft(validatorMetadata) - if err != nil { - return err - } - - chainConfig.Genesis.ExtraData = genesisExtraData - chainConfig.Params.Engine[polybft.ConsensusName] = consensusConfig - - // save updated stake and genesis extra to genesis file on disk - if err := helper.WriteGenesisConfigToDisk(chainConfig, params.genesisPath); err != nil { - return fmt.Errorf("failed to save chain configuration bridge data: %w", err) - } - - // initialize CheckpointManager contract since it needs to have a valid VotingPowers of validators - if err := initializeCheckpointManager(outputter, txRelayer, - consensusConfig, chainConfig.Params.ChainID, ownerKey); err != nil { - return fmt.Errorf("could not initialize CheckpointManager with finalized genesis validator set: %w", err) - } - } - - if params.enableStaking { - encoded, err := contractsapi.CustomSupernetManager.Abi.Methods["enableStaking"].Encode([]interface{}{}) - if err != nil { - return err - } - - txn := rootHelper.CreateTransaction(ownerKey.Address(), &supernetAddr, encoded, nil, true) - - receipt, err := txRelayer.SendTransaction(txn, ownerKey) - if err != nil { - return fmt.Errorf("enabling staking on supernet manager failed. Error: %w", err) - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("enable staking transaction failed on block %d", receipt.BlockNumber) - } - } - - result := &supernetResult{ - IsGenesisSetFinalized: params.finalizeGenesisSet, - IsStakingEnabled: params.enableStaking, - } - - outputter.WriteCommandResult(result) - - return nil -} - -// decodeGenesisAccounts decodes genesis set retrieved from CustomSupernetManager contract -func decodeGenesisAccounts(genesisSetRaw string) ([]*validator.GenesisValidator, error) { - decodeAccount := func(rawAccount map[string]interface{}) (*validator.GenesisValidator, error) { - addr, ok := rawAccount["addr"].(ethgo.Address) - if !ok { - return nil, errors.New("failed to retrieve genesis account address") - } - - stake, ok := rawAccount["initialStake"].(*big.Int) - if !ok { - return nil, errors.New("failed to retrieve genesis account stake") - } - - return &validator.GenesisValidator{ - Address: types.Address(addr), - Stake: stake, - }, nil - } - - genesisSetRawOut, err := hex.DecodeHex(genesisSetRaw) - if err != nil { - return nil, fmt.Errorf("failed to decode genesis set from hex format: %w", err) - } - - decodedGenesisSet, err := genesisSetABIFn.Outputs.Decode(genesisSetRawOut) - if err != nil { - return nil, fmt.Errorf("failed to decode genesis set from raw format: %w", err) - } - - decodedGenesisSetMap, ok := decodedGenesisSet.(map[string]interface{}) - if !ok { - return nil, errors.New("failed to convert genesis set to map") - } - - decodedGenesisSetSliceMap, ok := decodedGenesisSetMap["0"].([]map[string]interface{}) - if !ok { - return nil, errors.New("failed to convert genesis set to slice") - } - - genesisAccounts := make([]*validator.GenesisValidator, len(decodedGenesisSetSliceMap)) - for i, rawGenesisAccount := range decodedGenesisSetSliceMap { - genesisAccounts[i], err = decodeAccount(rawGenesisAccount) - if err != nil { - return nil, err - } - } - - return genesisAccounts, nil -} - -// validatorSetToABISlice converts given validators to generic map -// which is used for ABI encoding validator set being sent to the rootchain contract -func validatorSetToABISlice(o command.OutputFormatter, - validators []*validator.GenesisValidator) ([]*contractsapi.Validator, error) { - accSet := make(validator.AccountSet, len(validators)) - - if _, err := o.Write([]byte("[VALIDATORS - CHECKPOINT MANAGER] \n")); err != nil { - return nil, err - } - - for i, val := range validators { - if _, err := o.Write([]byte(fmt.Sprintf("%v\n", val))); err != nil { - return nil, err - } - - blsKey, err := val.UnmarshalBLSPublicKey() - if err != nil { - return nil, err - } - - accSet[i] = &validator.ValidatorMetadata{ - Address: val.Address, - BlsKey: blsKey, - VotingPower: new(big.Int).Set(val.Stake), - } - } - - hash, err := accSet.Hash() - if err != nil { - return nil, err - } - - if _, err := o.Write([]byte( - fmt.Sprintf("[VALIDATORS - CHECKPOINT MANAGER] Validators hash: %s\n", hash))); err != nil { - return nil, err - } - - return accSet.ToAPIBinding(), nil -} - -// initializeCheckpointManager initializes CheckpointManager contract on rootchain -// based on finalized stake (voting power) of genesis validators on root -func initializeCheckpointManager(outputter command.OutputFormatter, - txRelayer txrelayer.TxRelayer, - consensusConfig polybft.PolyBFTConfig, chainID int64, - deployerKey ethgo.Key) error { - validatorSet, err := validatorSetToABISlice(outputter, consensusConfig.InitialValidatorSet) - if err != nil { - return fmt.Errorf("failed to convert validators to map: %w", err) - } - - initParams := &contractsapi.InitializeCheckpointManagerFn{ - ChainID_: big.NewInt(chainID), - NewBls: consensusConfig.Bridge.BLSAddress, - NewBn256G2: consensusConfig.Bridge.BN256G2Address, - NewValidatorSet: validatorSet, - } - - input, err := initParams.EncodeAbi() - if err != nil { - return fmt.Errorf("failed to encode initialization params for CheckpointManager.initialize. error: %w", err) - } - - if _, err := rootHelper.SendTransaction(txRelayer, ethgo.Address(consensusConfig.Bridge.CheckpointManagerAddr), - input, "CheckpointManager", deployerKey); err != nil { - return err - } - - outputter.WriteCommandResult( - &rootHelper.MessageResult{ - Message: fmt.Sprintf("CheckpointManager contract is initialized"), - }) - - return nil -} diff --git a/command/rootchain/validators/params.go b/command/rootchain/validators/params.go deleted file mode 100644 index 936b770..0000000 --- a/command/rootchain/validators/params.go +++ /dev/null @@ -1,50 +0,0 @@ -package validators - -import ( - "bytes" - "fmt" - - "github.com/xgr-network/xgr-node/command/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" -) - -type validatorInfoParams struct { - accountDir string - accountConfig string - jsonRPC string - supernetManagerAddress string - stakeManagerAddress string - chainID int64 -} - -func (v *validatorInfoParams) validateFlags() error { - if _, err := helper.ParseJSONRPCAddress(v.jsonRPC); err != nil { - return fmt.Errorf("failed to parse json rpc address. Error: %w", err) - } - - return sidechainHelper.ValidateSecretFlags(v.accountDir, v.accountConfig) -} - -type validatorsInfoResult struct { - Address string `json:"address"` - Stake uint64 `json:"stake"` - Active bool `json:"active"` - Whitelisted bool `json:"whitelisted"` -} - -func (vr validatorsInfoResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[VALIDATOR INFO]\n") - - vals := make([]string, 4) - vals[0] = fmt.Sprintf("Validator Address|%s", vr.Address) - vals[1] = fmt.Sprintf("Stake|%v", vr.Stake) - vals[2] = fmt.Sprintf("Is Whitelisted|%v", vr.Whitelisted) - vals[3] = fmt.Sprintf("Is Active|%v", vr.Active) - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/validators/validator_info.go b/command/rootchain/validators/validator_info.go deleted file mode 100644 index 8df0adb..0000000 --- a/command/rootchain/validators/validator_info.go +++ /dev/null @@ -1,111 +0,0 @@ -package validators - -import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var ( - params validatorInfoParams -) - -func GetCommand() *cobra.Command { - validatorInfoCmd := &cobra.Command{ - Use: "validator-info", - Short: "Gets validator info", - PreRunE: runPreRun, - RunE: runCommand, - } - - helper.RegisterJSONRPCFlag(validatorInfoCmd) - setFlags(validatorInfoCmd) - - return validatorInfoCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.supernetManagerAddress, - rootHelper.SupernetManagerFlag, - "", - rootHelper.SupernetManagerFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.stakeManagerAddress, - rootHelper.StakeManagerFlag, - "", - rootHelper.StakeManagerFlagDesc, - ) - - cmd.Flags().Int64Var( - ¶ms.chainID, - polybftsecrets.ChainIDFlag, - 0, - polybftsecrets.ChainIDFlagDesc, - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - validatorAccount, err := sidechainHelper.GetAccount(params.accountDir, params.accountConfig) - if err != nil { - return err - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC)) - if err != nil { - return err - } - - validatorAddr := validatorAccount.Ecdsa.Address() - supernetManagerAddr := types.StringToAddress(params.supernetManagerAddress) - stakeManagerAddr := types.StringToAddress(params.stakeManagerAddress) - - validatorInfo, err := rootHelper.GetValidatorInfo(validatorAddr, - supernetManagerAddr, stakeManagerAddr, params.chainID, txRelayer) - if err != nil { - return fmt.Errorf("failed to get validator info for %s: %w", validatorAddr, err) - } - - outputter.WriteCommandResult(&validatorsInfoResult{ - Address: validatorInfo.Address.String(), - Stake: validatorInfo.Stake.Uint64(), - Active: validatorInfo.IsActive, - Whitelisted: validatorInfo.IsWhitelisted, - }) - - return nil -} diff --git a/command/rootchain/whitelist/params.go b/command/rootchain/whitelist/params.go deleted file mode 100644 index 81e9c49..0000000 --- a/command/rootchain/whitelist/params.go +++ /dev/null @@ -1,62 +0,0 @@ -package whitelist - -import ( - "bytes" - "errors" - "fmt" - - "github.com/xgr-network/xgr-node/command/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" -) - -const ( - newValidatorAddressesFlag = "addresses" -) - -var ( - errNoNewValidatorsProvided = errors.New("no new validators addresses provided") -) - -type whitelistParams struct { - accountDir string - accountConfig string - privateKey string - jsonRPC string - newValidatorAddresses []string - supernetManagerAddress string -} - -func (ep *whitelistParams) validateFlags() error { - if len(ep.newValidatorAddresses) == 0 { - return errNoNewValidatorsProvided - } - - if ep.privateKey == "" { - return sidechainHelper.ValidateSecretFlags(ep.accountDir, ep.accountConfig) - } - - // validate jsonrpc address - _, err := helper.ParseJSONRPCAddress(ep.jsonRPC) - - return err -} - -type whitelistResult struct { - NewValidatorAddresses []string `json:"newValidatorAddresses"` -} - -func (wr whitelistResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[WHITELIST VALIDATORS]\n") - - vals := make([]string, len(wr.NewValidatorAddresses)) - for i, addr := range wr.NewValidatorAddresses { - vals[i] = fmt.Sprintf("Validator address|%s", addr) - } - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/whitelist/whitelist_validators.go b/command/rootchain/whitelist/whitelist_validators.go deleted file mode 100644 index d7f43fd..0000000 --- a/command/rootchain/whitelist/whitelist_validators.go +++ /dev/null @@ -1,152 +0,0 @@ -package whitelist - -import ( - "fmt" - "time" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var params whitelistParams - -func GetCommand() *cobra.Command { - registerCmd := &cobra.Command{ - Use: "whitelist-validators", - Short: "whitelist new validators", - PreRunE: runPreRun, - RunE: runCommand, - } - - setFlags(registerCmd) - - return registerCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.privateKey, - polybftsecrets.PrivateKeyFlag, - "", - polybftsecrets.PrivateKeyFlagDesc, - ) - - cmd.Flags().StringSliceVar( - ¶ms.newValidatorAddresses, - newValidatorAddressesFlag, - []string{}, - "account addresses of a possible validators", - ) - - cmd.Flags().StringVar( - ¶ms.supernetManagerAddress, - rootHelper.SupernetManagerFlag, - "", - rootHelper.SupernetManagerFlagDesc, - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountConfigFlag) - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.PrivateKeyFlag, polybftsecrets.AccountDirFlag) - - helper.RegisterJSONRPCFlag(cmd) -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - ecdsaKey, err := rootHelper.GetECDSAKey(params.privateKey, params.accountDir, params.accountConfig) - if err != nil { - return err - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), - txrelayer.WithReceiptTimeout(150*time.Millisecond)) - if err != nil { - return fmt.Errorf("whitelist validator failed. Could not create tx relayer: %w", err) - } - - whitelistFn := &contractsapi.WhitelistValidatorsCustomSupernetManagerFn{ - Validators_: stringSliceToAddressSlice(params.newValidatorAddresses), - } - - encoded, err := whitelistFn.EncodeAbi() - if err != nil { - return fmt.Errorf("whitelist validator failed. Could not abi encode whitelist function: %w", err) - } - - supernetAddr := ethgo.Address(types.StringToAddress(params.supernetManagerAddress)) - txn := rootHelper.CreateTransaction(ecdsaKey.Address(), &supernetAddr, encoded, nil, true) - - receipt, err := txRelayer.SendTransaction(txn, ecdsaKey) - if err != nil { - return fmt.Errorf("whitelist validator failed %w", err) - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("whitelist validator transaction failed on block %d", receipt.BlockNumber) - } - - var ( - whitelistEvent contractsapi.AddedToWhitelistEvent - result = &whitelistResult{} - ) - - for _, log := range receipt.Logs { - doesMatch, err := whitelistEvent.ParseLog(log) - if !doesMatch { - continue - } - - if err != nil { - return err - } - - result.NewValidatorAddresses = append(result.NewValidatorAddresses, whitelistEvent.Validator.String()) - } - - if len(result.NewValidatorAddresses) != len(params.newValidatorAddresses) { - return fmt.Errorf("whitelist of validators did not pass successfully") - } - - outputter.WriteCommandResult(result) - - return nil -} - -func stringSliceToAddressSlice(addrs []string) []ethgo.Address { - res := make([]ethgo.Address, len(addrs)) - for indx, addr := range addrs { - res[indx] = ethgo.Address(types.StringToAddress(addr)) - } - - return res -} diff --git a/command/rootchain/withdraw/params.go b/command/rootchain/withdraw/params.go deleted file mode 100644 index 84b0a89..0000000 --- a/command/rootchain/withdraw/params.go +++ /dev/null @@ -1,59 +0,0 @@ -package withdraw - -import ( - "bytes" - "fmt" - "math/big" - - "github.com/xgr-network/xgr-node/command/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" -) - -var ( - addressToFlag = "to" -) - -type withdrawParams struct { - accountDir string - accountConfig string - jsonRPC string - stakeManagerAddr string - addressTo string - amount string - - amountValue *big.Int -} - -func (v *withdrawParams) validateFlags() (err error) { - if v.amountValue, err = helper.ParseAmount(v.amount); err != nil { - return err - } - - if _, err = helper.ParseJSONRPCAddress(v.jsonRPC); err != nil { - return fmt.Errorf("failed to parse json rpc address. Error: %w", err) - } - - return sidechainHelper.ValidateSecretFlags(v.accountDir, v.accountConfig) -} - -type withdrawResult struct { - ValidatorAddress string `json:"validatorAddress"` - Amount uint64 `json:"amount"` - WithdrawnTo string `json:"withdrawnTo"` -} - -func (wr withdrawResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[WITHDRAWN AMOUNT]\n") - - vals := make([]string, 0, 3) - vals = append(vals, fmt.Sprintf("Validator Address|%s", wr.ValidatorAddress)) - vals = append(vals, fmt.Sprintf("Amount Withdrawn|%v", wr.Amount)) - vals = append(vals, fmt.Sprintf("Withdrawn To|%s", wr.WithdrawnTo)) - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/rootchain/withdraw/withdraw.go b/command/rootchain/withdraw/withdraw.go deleted file mode 100644 index fe22441..0000000 --- a/command/rootchain/withdraw/withdraw.go +++ /dev/null @@ -1,150 +0,0 @@ -package withdraw - -import ( - "fmt" - "time" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var params withdrawParams - -func GetCommand() *cobra.Command { - withdrawCmd := &cobra.Command{ - Use: "withdraw-root", - Short: "Withdraws sender's withdrawable amount to specified address on the root chain", - PreRunE: runPreRun, - RunE: runCommand, - } - - setFlags(withdrawCmd) - - return withdrawCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.addressTo, - addressToFlag, - "", - "address where to withdraw withdrawable amount", - ) - - cmd.Flags().StringVar( - ¶ms.stakeManagerAddr, - rootHelper.StakeManagerFlag, - "", - rootHelper.StakeManagerFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.amount, - sidechainHelper.AmountFlag, - "", - "amount to withdraw", - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) - helper.RegisterJSONRPCFlag(cmd) -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - validatorAccount, err := sidechainHelper.GetAccount(params.accountDir, params.accountConfig) - if err != nil { - return err - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), - txrelayer.WithReceiptTimeout(150*time.Millisecond)) - if err != nil { - return err - } - - withdrawFn := &contractsapi.WithdrawStakeStakeManagerFn{ - To: types.StringToAddress(params.addressTo), - Amount: params.amountValue, - } - - encoded, err := withdrawFn.EncodeAbi() - if err != nil { - return err - } - - stakeManagerAddr := ethgo.Address(types.StringToAddress(params.stakeManagerAddr)) - txn := rootHelper.CreateTransaction(validatorAccount.Ecdsa.Address(), &stakeManagerAddr, encoded, nil, true) - - receipt, err := txRelayer.SendTransaction(txn, validatorAccount.Ecdsa) - if err != nil { - return err - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("withdraw transaction failed on block %d", receipt.BlockNumber) - } - - result := &withdrawResult{ - ValidatorAddress: validatorAccount.Ecdsa.Address().String(), - } - - var ( - withdrawalEvent contractsapi.StakeWithdrawnEvent - foundLog bool - ) - - for _, log := range receipt.Logs { - doesMatch, err := withdrawalEvent.ParseLog(log) - if !doesMatch { - continue - } - - if err != nil { - return err - } - - result.Amount = withdrawalEvent.Amount.Uint64() - result.WithdrawnTo = withdrawalEvent.Recipient.String() - foundLog = true - - break - } - - if !foundLog { - return fmt.Errorf("could not find an appropriate log in receipt that withdrawal happened") - } - - outputter.WriteCommandResult(result) - - return nil -} diff --git a/command/server/server.go b/command/server/server.go index 6a47e32..ccc6f34 100644 --- a/command/server/server.go +++ b/command/server/server.go @@ -218,7 +218,7 @@ func setFlags(cmd *cobra.Command) { ¶ms.rawConfig.Relayer, relayerFlag, defaultConfig.Relayer, - "start the state sync relayer service (PolyBFT only)", + "start the state sync relayer service", ) cmd.Flags().Uint64Var( diff --git a/command/sidechain/helper.go b/command/sidechain/helper.go deleted file mode 100644 index 9e2e33a..0000000 --- a/command/sidechain/helper.go +++ /dev/null @@ -1,90 +0,0 @@ -package sidechain - -import ( - "errors" - "fmt" - "os" - - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - "github.com/xgr-network/xgr-node/consensus/polybft" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -const ( - AmountFlag = "amount" -) - -func CheckIfDirectoryExist(dir string) error { - if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("provided directory '%s' doesn't exist", dir) - } - - return nil -} - -func ValidateSecretFlags(dataDir, config string) error { - if config == "" { - if dataDir == "" { - return polybftsecrets.ErrInvalidParams - } else { - return CheckIfDirectoryExist(dataDir) - } - } - - return nil -} - -// GetAccount resolves secrets manager and returns an account object -func GetAccount(accountDir, accountConfig string) (*wallet.Account, error) { - // resolve secrets manager instance and allow usage of insecure local secrets manager - secretsManager, err := polybftsecrets.GetSecretsManager(accountDir, accountConfig, true) - if err != nil { - return nil, err - } - - return wallet.NewAccountFromSecret(secretsManager) -} - -// GetAccountFromDir returns an account object from local secrets manager -func GetAccountFromDir(accountDir string) (*wallet.Account, error) { - return GetAccount(accountDir, "") -} - -// GetValidatorInfo queries CustomSupernetManager, StakeManager and RewardPool smart contracts -// to retrieve validator info for given address -func GetValidatorInfo(validatorAddr ethgo.Address, supernetManager, stakeManager types.Address, - chainID int64, rootRelayer, childRelayer txrelayer.TxRelayer) (*polybft.ValidatorInfo, error) { - validatorInfo, err := rootHelper.GetValidatorInfo(validatorAddr, supernetManager, stakeManager, - chainID, rootRelayer) - if err != nil { - return nil, err - } - - withdrawableFn := contractsapi.RewardPool.Abi.GetMethod("pendingRewards") - - encode, err := withdrawableFn.Encode([]interface{}{validatorAddr}) - if err != nil { - return nil, err - } - - response, err := childRelayer.Call(ethgo.ZeroAddress, ethgo.Address(contracts.RewardPoolContract), encode) - if err != nil { - return nil, err - } - - withdrawableRewards, err := common.ParseUint256orHex(&response) - if err != nil { - return nil, err - } - - validatorInfo.WithdrawableRewards = withdrawableRewards - - return validatorInfo, nil -} diff --git a/command/sidechain/rewards/params.go b/command/sidechain/rewards/params.go deleted file mode 100644 index cc0def8..0000000 --- a/command/sidechain/rewards/params.go +++ /dev/null @@ -1,43 +0,0 @@ -package rewards - -import ( - "bytes" - "fmt" - - "github.com/xgr-network/xgr-node/command/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" -) - -type withdrawRewardsParams struct { - accountDir string - accountConfig string - jsonRPC string -} - -type withdrawRewardResult struct { - ValidatorAddress string `json:"validatorAddress"` - RewardAmount uint64 `json:"rewardAmount"` -} - -func (w *withdrawRewardsParams) validateFlags() error { - if _, err := helper.ParseJSONRPCAddress(w.jsonRPC); err != nil { - return fmt.Errorf("failed to parse json rpc address. Error: %w", err) - } - - return sidechainHelper.ValidateSecretFlags(w.accountDir, w.accountConfig) -} - -func (wr withdrawRewardResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[WITHDRAW REWARDS]\n") - - vals := make([]string, 0, 2) - vals = append(vals, fmt.Sprintf("Validator Address|%s", wr.ValidatorAddress)) - vals = append(vals, fmt.Sprintf("Amount Withdrawn|%v", wr.RewardAmount)) - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/sidechain/rewards/rewards.go b/command/sidechain/rewards/rewards.go deleted file mode 100644 index bbf97a9..0000000 --- a/command/sidechain/rewards/rewards.go +++ /dev/null @@ -1,119 +0,0 @@ -package rewards - -import ( - "fmt" - "time" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var params withdrawRewardsParams - -func GetCommand() *cobra.Command { - unstakeCmd := &cobra.Command{ - Use: "withdraw-rewards", - Short: "Withdraws pending rewards on child chain for given validator", - PreRunE: runPreRun, - RunE: runCommand, - } - - helper.RegisterJSONRPCFlag(unstakeCmd) - setFlags(unstakeCmd) - - return unstakeCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - validatorAccount, err := sidechainHelper.GetAccount(params.accountDir, params.accountConfig) - if err != nil { - return err - } - - validatorAddr := validatorAccount.Ecdsa.Address() - rewardPoolAddr := ethgo.Address(contracts.RewardPoolContract) - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), - txrelayer.WithReceiptTimeout(150*time.Millisecond)) - if err != nil { - return err - } - - encoded, err := contractsapi.RewardPool.Abi.Methods["pendingRewards"].Encode([]interface{}{validatorAddr}) - if err != nil { - return err - } - - response, err := txRelayer.Call(validatorAddr, rewardPoolAddr, encoded) - if err != nil { - return err - } - - amount, err := common.ParseUint256orHex(&response) - if err != nil { - return err - } - - encoded, err = contractsapi.RewardPool.Abi.Methods["withdrawReward"].Encode([]interface{}{}) - if err != nil { - return err - } - - txn := rootHelper.CreateTransaction(validatorAddr, &rewardPoolAddr, encoded, nil, false) - - receipt, err := txRelayer.SendTransaction(txn, validatorAccount.Ecdsa) - if err != nil { - return err - } - - if receipt.Status != uint64(types.ReceiptSuccess) { - return fmt.Errorf("withdraw transaction failed on block: %d", receipt.BlockNumber) - } - - result := &withdrawRewardResult{ - ValidatorAddress: validatorAccount.Ecdsa.Address().String(), - RewardAmount: amount.Uint64(), - } - - outputter.WriteCommandResult(result) - - return nil -} diff --git a/command/sidechain/unstaking/params.go b/command/sidechain/unstaking/params.go deleted file mode 100644 index d209186..0000000 --- a/command/sidechain/unstaking/params.go +++ /dev/null @@ -1,55 +0,0 @@ -package unstaking - -import ( - "bytes" - "fmt" - "math/big" - - "github.com/xgr-network/xgr-node/command/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" -) - -var ( - undelegateAddressFlag = "undelegate" -) - -type unstakeParams struct { - accountDir string - accountConfig string - jsonRPC string - amount string - - amountValue *big.Int -} - -func (v *unstakeParams) validateFlags() (err error) { - if v.amountValue, err = helper.ParseAmount(v.amount); err != nil { - return err - } - - if _, err = helper.ParseJSONRPCAddress(v.jsonRPC); err != nil { - return fmt.Errorf("failed to parse json rpc address. Error: %w", err) - } - - return sidechainHelper.ValidateSecretFlags(v.accountDir, v.accountConfig) -} - -type unstakeResult struct { - ValidatorAddress string `json:"validatorAddress"` - Amount uint64 `json:"amount"` -} - -func (ur unstakeResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[UNSTAKE]\n") - - vals := make([]string, 0, 2) - vals = append(vals, fmt.Sprintf("Validator Address|%s", ur.ValidatorAddress)) - vals = append(vals, fmt.Sprintf("Amount Unstaked|%v", ur.Amount)) - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/sidechain/unstaking/unstake.go b/command/sidechain/unstaking/unstake.go deleted file mode 100644 index d6412d7..0000000 --- a/command/sidechain/unstaking/unstake.go +++ /dev/null @@ -1,136 +0,0 @@ -package unstaking - -import ( - "fmt" - "time" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var params unstakeParams - -func GetCommand() *cobra.Command { - unstakeCmd := &cobra.Command{ - Use: "unstake", - Short: "Unstakes the amount sent for validator or undelegates amount from validator", - PreRunE: runPreRun, - RunE: runCommand, - } - - helper.RegisterJSONRPCFlag(unstakeCmd) - setFlags(unstakeCmd) - - return unstakeCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.amount, - sidechainHelper.AmountFlag, - "", - "amount to unstake from validator", - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - validatorAccount, err := sidechainHelper.GetAccount(params.accountDir, params.accountConfig) - if err != nil { - return err - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), - txrelayer.WithReceiptTimeout(150*time.Millisecond)) - if err != nil { - return err - } - - unstakeFn := &contractsapi.UnstakeValidatorSetFn{ - Amount: params.amountValue, - } - - encoded, err := unstakeFn.EncodeAbi() - if err != nil { - return err - } - - txn := ðgo.Transaction{ - From: validatorAccount.Ecdsa.Address(), - Input: encoded, - To: (*ethgo.Address)(&contracts.ValidatorSetContract), - } - - receipt, err := txRelayer.SendTransaction(txn, validatorAccount.Ecdsa) - if err != nil { - return err - } - - if receipt.Status != uint64(types.ReceiptSuccess) { - return fmt.Errorf("unstake transaction failed on block: %d", receipt.BlockNumber) - } - - var ( - withdrawalRegisteredEvent contractsapi.WithdrawalRegisteredEvent - foundLog bool - ) - - result := &unstakeResult{ - ValidatorAddress: validatorAccount.Ecdsa.Address().String(), - } - - // check the logs to check for the result - for _, log := range receipt.Logs { - doesMatch, err := withdrawalRegisteredEvent.ParseLog(log) - if err != nil { - return err - } - - if doesMatch { - foundLog = true - result.Amount = withdrawalRegisteredEvent.Amount.Uint64() - - break - } - } - - if !foundLog { - return fmt.Errorf("could not find an appropriate log in receipt that unstake happened (withdrawal registered)") - } - - outputter.WriteCommandResult(result) - - return nil -} diff --git a/command/sidechain/withdraw/params.go b/command/sidechain/withdraw/params.go deleted file mode 100644 index 2a77307..0000000 --- a/command/sidechain/withdraw/params.go +++ /dev/null @@ -1,48 +0,0 @@ -package withdraw - -import ( - "bytes" - "fmt" - "math/big" - - "github.com/xgr-network/xgr-node/command/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" -) - -type withdrawParams struct { - accountDir string - accountConfig string - jsonRPC string -} - -func (w *withdrawParams) validateFlags() error { - if _, err := helper.ParseJSONRPCAddress(w.jsonRPC); err != nil { - return fmt.Errorf("failed to parse json rpc address. Error: %w", err) - } - - return sidechainHelper.ValidateSecretFlags(w.accountDir, w.accountConfig) -} - -type withdrawResult struct { - ValidatorAddress string `json:"validatorAddress"` - Amount *big.Int `json:"amount"` - ExitEventIDs []*big.Int `json:"exitEventIDs"` - BlockNumber uint64 `json:"blockNumber"` -} - -func (r *withdrawResult) GetOutput() string { - var buffer bytes.Buffer - - buffer.WriteString("\n[WITHDRAWAL]\n") - - vals := make([]string, 0, 4) - vals = append(vals, fmt.Sprintf("Validator Address|%s", r.ValidatorAddress)) - vals = append(vals, fmt.Sprintf("Amount Withdrawn|%d", r.Amount)) - vals = append(vals, fmt.Sprintf("Exit Event IDs|%d", r.ExitEventIDs)) - vals = append(vals, fmt.Sprintf("Inclusion Block Number|%d", r.BlockNumber)) - - buffer.WriteString(helper.FormatKV(vals)) - buffer.WriteString("\n") - - return buffer.String() -} diff --git a/command/sidechain/withdraw/withdraw.go b/command/sidechain/withdraw/withdraw.go deleted file mode 100644 index 30e161a..0000000 --- a/command/sidechain/withdraw/withdraw.go +++ /dev/null @@ -1,131 +0,0 @@ -package withdraw - -import ( - "fmt" - "time" - - "github.com/spf13/cobra" - "github.com/umbracle/ethgo" - - "github.com/xgr-network/xgr-node/command" - "github.com/xgr-network/xgr-node/command/bridge/common" - "github.com/xgr-network/xgr-node/command/helper" - "github.com/xgr-network/xgr-node/command/polybftsecrets" - rootHelper "github.com/xgr-network/xgr-node/command/rootchain/helper" - sidechainHelper "github.com/xgr-network/xgr-node/command/sidechain" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -var params withdrawParams - -func GetCommand() *cobra.Command { - unstakeCmd := &cobra.Command{ - Use: "withdraw-child", - Short: "Withdraws pending withdrawals on child chain for given validator", - PreRunE: runPreRun, - RunE: runCommand, - } - - helper.RegisterJSONRPCFlag(unstakeCmd) - setFlags(unstakeCmd) - - return unstakeCmd -} - -func setFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - ¶ms.accountDir, - polybftsecrets.AccountDirFlag, - "", - polybftsecrets.AccountDirFlagDesc, - ) - - cmd.Flags().StringVar( - ¶ms.accountConfig, - polybftsecrets.AccountConfigFlag, - "", - polybftsecrets.AccountConfigFlagDesc, - ) - - cmd.MarkFlagsMutuallyExclusive(polybftsecrets.AccountDirFlag, polybftsecrets.AccountConfigFlag) -} - -func runPreRun(cmd *cobra.Command, _ []string) error { - params.jsonRPC = helper.GetJSONRPCAddress(cmd) - - return params.validateFlags() -} - -func runCommand(cmd *cobra.Command, _ []string) error { - outputter := command.InitializeOutputter(cmd) - defer outputter.WriteOutput() - - validatorAccount, err := sidechainHelper.GetAccount(params.accountDir, params.accountConfig) - if err != nil { - return err - } - - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), - txrelayer.WithReceiptTimeout(150*time.Millisecond)) - if err != nil { - return err - } - - encoded, err := contractsapi.ValidatorSet.Abi.Methods["withdraw"].Encode([]interface{}{}) - if err != nil { - return err - } - - receiver := (*ethgo.Address)(&contracts.ValidatorSetContract) - txn := rootHelper.CreateTransaction(validatorAccount.Ecdsa.Address(), receiver, encoded, nil, false) - - receipt, err := txRelayer.SendTransaction(txn, validatorAccount.Ecdsa) - if err != nil { - return err - } - - if receipt.Status != uint64(types.ReceiptSuccess) { - return fmt.Errorf("withdraw transaction failed on block: %d", receipt.BlockNumber) - } - - var ( - withdrawalEvent contractsapi.WithdrawalEvent - foundLog bool - ) - - // check the logs to check for the result - for _, log := range receipt.Logs { - doesMatch, err := withdrawalEvent.ParseLog(log) - if err != nil { - return err - } - - if doesMatch { - foundLog = true - - break - } - } - - if !foundLog { - return fmt.Errorf("could not find an appropriate log in receipt that withdraw happened on ValidatorSet") - } - - exitEventIDs, err := common.ExtractExitEventIDs(receipt) - if err != nil { - return fmt.Errorf("withdrawal failed: %w", err) - } - - outputter.WriteCommandResult( - &withdrawResult{ - ValidatorAddress: validatorAccount.Ecdsa.Address().String(), - Amount: withdrawalEvent.Amount, - ExitEventIDs: exitEventIDs, - BlockNumber: receipt.BlockNumber, - }) - - return nil -} diff --git a/consensus/ibft/consensus.go b/consensus/ibft/consensus.go index 74bcd03..5170212 100644 --- a/consensus/ibft/consensus.go +++ b/consensus/ibft/consensus.go @@ -3,6 +3,7 @@ package ibft import ( "context" "sync" + "time" "github.com/0xPolygon/go-ibft/core" ) @@ -13,6 +14,7 @@ type IBFTConsensus struct { wg sync.WaitGroup + sequenceMu sync.RWMutex cancelSequence context.CancelFunc } @@ -27,15 +29,21 @@ func newIBFT( } } +// ExtendRoundTimeout extends each round timeout. +func (c *IBFTConsensus) ExtendRoundTimeout(amount time.Duration) { + c.IBFT.ExtendRoundTimeout(amount) +} + // runSequence starts the underlying consensus mechanism for the given height. // It may be called by a single thread at any given time func (c *IBFTConsensus) runSequence(height uint64) <-chan struct{} { done := make(chan struct{}) ctx, cancel := context.WithCancel(context.Background()) + c.sequenceMu.Lock() c.cancelSequence = cancel - c.wg.Add(1) + c.sequenceMu.Unlock() go func() { defer func() { @@ -52,6 +60,12 @@ func (c *IBFTConsensus) runSequence(height uint64) <-chan struct{} { // stopSequence terminates the running IBFT sequence gracefully and waits for it to return func (c *IBFTConsensus) stopSequence() { - c.cancelSequence() + c.sequenceMu.Lock() + cancel := c.cancelSequence + if cancel != nil { + cancel() + } c.wg.Wait() + c.cancelSequence = nil + c.sequenceMu.Unlock() } diff --git a/consensus/ibft/consensus_backend.go b/consensus/ibft/consensus_backend.go index b435c23..f1c5565 100644 --- a/consensus/ibft/consensus_backend.go +++ b/consensus/ibft/consensus_backend.go @@ -9,6 +9,7 @@ import ( "github.com/0xPolygon/go-ibft/messages" "github.com/0xPolygon/go-ibft/messages/proto" "github.com/xgr-network/xgr-node/consensus" + "github.com/xgr-network/xgr-node/consensus/ibft/pos" "github.com/xgr-network/xgr-node/consensus/ibft/signer" "github.com/xgr-network/xgr-node/helper/hex" "github.com/xgr-network/xgr-node/state" @@ -16,11 +17,19 @@ import ( ) func (i *backendIBFT) BuildProposal(view *proto.View) []byte { + + if view == nil { + if i.logger != nil { + i.logger.Error("build proposal requested with nil view") + } + + return nil + } + var ( latestHeader = i.blockchain.Header() latestBlockNumber = latestHeader.Number ) - if latestBlockNumber+1 != view.Height { i.logger.Error( "unable to build block, due to lack of parent block", @@ -104,7 +113,6 @@ func (i *backendIBFT) InsertProposal( return } - i.updateMetrics(newBlock) // i.logger.Info( @@ -140,24 +148,42 @@ func (i *backendIBFT) MaximumFaultyNodes() uint64 { return uint64(CalcMaxFaultyNodes(i.currentValidators)) } -// DISCLAIMER: IBFT will be deprecated so we set 1 as a voting power to all validators func (i *backendIBFT) GetVotingPowers(height uint64) (map[string]*big.Int, error) { + if height == 0 { + return nil, fmt.Errorf("invalid height 0") + } + validators, err := i.forkManager.GetValidators(height) if err != nil { return nil, err } + parentHeader, ok := i.blockchain.GetHeaderByNumber(height - 1) + if !ok { + return nil, fmt.Errorf("parent header not found for height %d", height) + } - result := make(map[string]*big.Int, validators.Len()) - - for index := 0; index < validators.Len(); index++ { - strAddress := types.AddressToString(validators.At(uint64(index)).Addr()) - result[strAddress] = big.NewInt(1) // set 1 as voting power to everyone + result, snapshot, err := i.effectiveVotingPowerSnapshot(height, validators, parentHeader) + if err != nil { + return nil, err + } + if i.logger != nil && i.forkManager.IsPosActive(height) { + i.logger.Info( + "ibft voting powers", + "height", height, + "parentNumber", parentHeader.Number, + "parentHash", parentHeader.Hash, + "parentStateRoot", parentHeader.StateRoot, + "stakeWeightedActive", i.forkManager.IsPosActive(parentHeader.Number), + "totalVotingPower", snapshot.totalVotingPower, + "quorumThreshold", snapshot.quorumThreshold, + "votingPowers", snapshot.powers, + ) } return result, nil } -// buildBlock builds the block, based on the passed in snapshot and parent header +// buildBlock builds the block, based on the passed in snapshot and parent header. func (i *backendIBFT) buildBlock(parent *types.Header) (*types.Block, error) { header := &types.Header{ ParentHash: parent.Hash, @@ -187,8 +213,13 @@ func (i *backendIBFT) buildBlock(parent *types.Header) (*types.Block, error) { return nil, err } - // Set the header timestamp - potentialTimestamp := i.calcHeaderTimestamp(parent.Timestamp, time.Now().UTC()) + // Set the header timestamp. + // PoS mode may still use round-bound logical timestamps in the header, + // but runtime transaction writing / pacing should stay aligned with normal + // IBFT wall-clock scheduling. + var potentialTimestamp time.Time + runtimeDeadline := i.calcHeaderTimestamp(parent.Timestamp, time.Now().UTC()) + potentialTimestamp = runtimeDeadline header.Timestamp = uint64(potentialTimestamp.Unix()) parentCommittedSeals, err := i.extractParentCommittedSeals(parent) @@ -203,7 +234,7 @@ func (i *backendIBFT) buildBlock(parent *types.Header) (*types.Block, error) { return nil, err } // Get the block transactions - writeCtx, cancelFn := context.WithDeadline(context.Background(), potentialTimestamp) + writeCtx, cancelFn := context.WithDeadline(context.Background(), runtimeDeadline) defer cancelFn() txs := i.writeTransactions( @@ -212,11 +243,19 @@ func (i *backendIBFT) buildBlock(parent *types.Header) (*types.Block, error) { header.Number, transition, ) - - // provide dummy block instance to the PreCommitState - // (for the IBFT consensus, it is correct to have just a header, as only it is used) - if err := i.PreCommitState(&types.Block{Header: header}, transition); err != nil { - return nil, err + // Provide the in-progress block body to PreCommitState. PoS epoch boundary + // finalization writes a deterministic internal system receipt; when that + // happens, append the matching system transaction to the otherwise user-tx-free + // block so receipt count, receipt root, and tx root remain deterministic. + preCommitBlock := &types.Block{Header: header, Transactions: txs} + if err := i.PreCommitState(preCommitBlock, transition); err != nil { + return nil, fmt.Errorf("pre-commit state failed at block %d: %w", header.Number, err) + } + if len(transition.Receipts()) == len(txs)+1 { + finalizationTx := pos.EpochFinalizationSystemTx(header.Number) + if transition.Receipts()[len(transition.Receipts())-1].TxHash == finalizationTx.Hash { + txs = append(txs, finalizationTx) + } } _, root, err := transition.Commit() if err != nil { @@ -246,7 +285,6 @@ func (i *backendIBFT) buildBlock(parent *types.Header) (*types.Block, error) { block.Header.ComputeHash() i.logger.Info("build block", "number", header.Number, "txs", len(txs)) - return block, nil } @@ -353,7 +391,8 @@ write: } } - // wait for the timer to expire + // wait for the timer to expire so the built block is aligned with the + // configured block time schedule <-writeCtx.Done() return diff --git a/consensus/ibft/consensus_backend_test.go b/consensus/ibft/consensus_backend_test.go deleted file mode 100644 index 7566443..0000000 --- a/consensus/ibft/consensus_backend_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package ibft - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -// TestIBFTBackend_CalculateHeaderTimestamp verifies that the header timestamp -// is successfully calculated -func TestIBFTBackend_CalculateHeaderTimestamp(t *testing.T) { - t.Parallel() - - // Reference time - now := time.Unix(time.Now().UTC().Unix(), 0) // Round down - - testTable := []struct { - name string - parentTimestamp int64 - currentTime time.Time - blockTime uint64 - - expectedTimestamp time.Time - }{ - { - "Valid clock block timestamp", - now.Add(time.Duration(-1) * time.Second).Unix(), // 1s before - now, - 1, - now, // 1s after - }, - { - "Next multiple block clock", - now.Add(time.Duration(-4) * time.Second).Unix(), // 4s before - now, - 3, - roundUpTime(now, 3*time.Second), - }, - } - - for _, testCase := range testTable { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - i := &backendIBFT{ - blockTime: time.Duration(testCase.blockTime) * time.Second, - } - - assert.Equal( - t, - testCase.expectedTimestamp.Unix(), - i.calcHeaderTimestamp( - uint64(testCase.parentTimestamp), - testCase.currentTime, - ).Unix(), - ) - }) - } -} - -// TestIBFTBackend_RoundUpTime verifies time is rounded up correctly -func TestIBFTBackend_RoundUpTime(t *testing.T) { - t.Parallel() - - // Reference time - now := time.Now().UTC() - - calcExpected := func(time int64, multiple int64) int64 { - if time%multiple == 0 { - return time + multiple - } - - return ((time + multiple/2) / multiple) * multiple - } - - testTable := []struct { - name string - time time.Time - multiple time.Duration - - expectedTime int64 - }{ - { - "No rounding needed", - now, - 0 * time.Second, - now.Unix(), - }, - { - "Rounded up time even", - now, - 2 * time.Second, - calcExpected(now.Unix(), 2), - }, - } - - for _, testCase := range testTable { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - assert.Equal( - t, - testCase.expectedTime, - roundUpTime(testCase.time, testCase.multiple).Unix(), - ) - }) - } -} diff --git a/consensus/ibft/consensus_no_unsafe_test.go b/consensus/ibft/consensus_no_unsafe_test.go new file mode 100644 index 0000000..538b1e0 --- /dev/null +++ b/consensus/ibft/consensus_no_unsafe_test.go @@ -0,0 +1,19 @@ +package ibft + +import ( + "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestConsensusWrapper_DoesNotUseReflectOrUnsafe(t *testing.T) { + data, err := os.ReadFile("consensus.go") + require.NoError(t, err) + + source := string(data) + require.NotContains(t, source, "\"reflect\"") + require.NotContains(t, source, "\"unsafe\"") + require.False(t, strings.Contains(source, "Unsafe")) +} diff --git a/consensus/ibft/effective_power.go b/consensus/ibft/effective_power.go new file mode 100644 index 0000000..958a4e9 --- /dev/null +++ b/consensus/ibft/effective_power.go @@ -0,0 +1,183 @@ +package ibft + +import ( + "encoding/binary" + "fmt" + "math/big" + + "github.com/xgr-network/xgr-node/consensus/ibft/pos" + xcrypto "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" + contractstore "github.com/xgr-network/xgr-node/validators/store/contract" +) + +func (i *backendIBFT) effectiveVotingPowerSnapshot( + height uint64, + validatorSet validators.Validators, + parentHeader *types.Header, +) (map[string]*big.Int, effectivePowerSnapshot, error) { + var ( + stateTx *state.Transition + err error + ) + if parentHeader == nil { + return nil, effectivePowerSnapshot{}, fmt.Errorf("parent header required for stake-weighted snapshot at height %d", height) + } + stateTx, err = i.executor.BeginTxn(parentHeader.StateRoot, parentHeader, types.ZeroAddress) + if err != nil { + return nil, effectivePowerSnapshot{}, err + } + + // Block 1 in direct-PoS chains has parent=genesis(0). Genesis bootstrap stakes + // are marked joinedAtBlock=1, so snapshot(0) is intentionally not mature yet. + // Keep block 1 in unit voting mode and enable stake-weighted mode from block 2. + parentPoSActive := parentHeader != nil && parentHeader.Number > 0 && i.forkManager.IsPosActive(parentHeader.Number) + heightPoSActive := i.forkManager.IsPosActive(height) + stakeWeightedActive := parentPoSActive + snapshotHeight, err := stakeSnapshotHeight(height, parentHeader, stakeWeightedActive) + if err != nil { + return nil, effectivePowerSnapshot{}, err + } + + result, snapshot, err := i.snapshotVotingPowers(height, snapshotHeight, validatorSet, stateTx, stakeWeightedActive) + if err != nil { + return nil, effectivePowerSnapshot{}, err + } + if stakeWeightedActive && stateTx != nil && !contractstore.IsEmergencyModeActive(stateTx) && i.uptimeCfg.MicroEpochNominalWeight > 1 && validatorSet.Len() > 1 { + total := uint64(0) + allUnit := true + for idx := 0; idx < validatorSet.Len(); idx++ { + addr := validatorSet.At(uint64(idx)).Addr() + p := result[types.AddressToString(addr)] + if p == nil { + p = big.NewInt(0) + } + v := p.Uint64() + total += v + if v != 1 { + allUnit = false + } + } + if total == uint64(validatorSet.Len()) && allUnit { + parentNumber := uint64(0) + parentHash := types.ZeroHash.String() + parentStateRoot := types.ZeroHash.String() + if parentHeader != nil { + parentNumber = parentHeader.Number + parentHash = parentHeader.Hash.String() + parentStateRoot = parentHeader.StateRoot.String() + } + return nil, effectivePowerSnapshot{}, fmt.Errorf( + "stake-weighted verify rejected classic-unit fallback: height=%d parentNumber=%d parentHash=%s parentStateRoot=%s isPosActiveAtParent=%t isPosActiveAtHeight=%t nominalWeightUnits=%d total=%d validatorCount=%d votingPowers=%v", + height, + parentNumber, + parentHash, + parentStateRoot, + parentPoSActive, + heightPoSActive, + i.uptimeCfg.MicroEpochNominalWeight, + total, + validatorSet.Len(), + snapshot.powers, + ) + } + } + + return result, snapshot, nil +} + +func (i *backendIBFT) snapshotVotingPowers( + height uint64, + snapshotHeight uint64, + validatorSet validators.Validators, + stateTx *state.Transition, + stakeWeightedActive bool, +) (map[string]*big.Int, effectivePowerSnapshot, error) { + result := make(map[string]*big.Int, validatorSet.Len()) + snapshotPowers := make(map[string]string, validatorSet.Len()) + total := big.NewInt(0) + emergency := stakeWeightedActive && stateTx != nil && contractstore.IsEmergencyModeActive(stateTx) + for index := 0; index < validatorSet.Len(); index++ { + addr := validatorSet.At(uint64(index)).Addr() + if !stakeWeightedActive || stateTx == nil { + result[types.AddressToString(addr)] = big.NewInt(1) + snapshotPowers[addr.String()] = "1" + total.Add(total, big.NewInt(1)) + continue + } + weight := pos.EffectiveWeight(stateTx.Txn(), addr, i.uptimeCfg) + nominal := pos.UptimeNominalWeight(stateTx.Txn(), addr) + if nominal == 0 { + nominal = i.uptimeCfg.MicroEpochNominalWeight + } + stake := contractstore.ReadValidatorVotingStakeAt(stateTx, addr, snapshotHeight) + + if stake == nil || stake.Sign() <= 0 { + return nil, effectivePowerSnapshot{}, fmt.Errorf("missing effective stake for stake-weighted validator %s at height %d", addr, height) + } + + w := big.NewInt(1) + if !emergency { + w = pos.WeightedStake(stake, weight, nominal) + if stake.Sign() > 0 && weight > 0 && nominal > 0 && w.Sign() == 0 { + w = big.NewInt(1) + } + } + result[types.AddressToString(addr)] = w + snapshotPowers[addr.String()] = w.String() + total.Add(total, w) + } + + quorum := weightedQuorumThreshold(total) + + return result, effectivePowerSnapshot{ + hash: hashEffectiveWeights(result, validatorSet, total, quorum), + powers: snapshotPowers, + totalVotingPower: total.String(), + quorumThreshold: quorum.String(), + }, nil +} + +func stakeSnapshotHeight(height uint64, parentHeader *types.Header, stakeWeightedActive bool) (uint64, error) { + if !stakeWeightedActive { + // Unit voting mode ignores stake snapshots entirely. + return 0, nil + } + + if parentHeader != nil { + return parentHeader.Number, nil + } + + if height == 0 { + return 0, fmt.Errorf("stake-weighted voting power snapshot requires parent state (height=0)") + } + + return height - 1, nil +} +func hashEffectiveWeights(weights map[string]*big.Int, set validators.Validators, total, quorum *big.Int) types.Hash { + hasher := make([]byte, 0, set.Len()*64+64) + for idx := 0; idx < set.Len(); idx++ { + addr := set.At(uint64(idx)).Addr() + hasher = append(hasher, addr.Bytes()...) + w := weights[types.AddressToString(addr)] + if w == nil { + w = big.NewInt(0) + } + hasher = append(hasher, w.FillBytes(make([]byte, 32))...) + } + + hasher = append(hasher, total.FillBytes(make([]byte, 32))...) + hasher = append(hasher, quorum.FillBytes(make([]byte, 32))...) + + var size [8]byte + binary.BigEndian.PutUint64(size[:], uint64(set.Len())) + hasher = append(hasher, size[:]...) + + if len(hasher) == 0 { + return types.ZeroHash + } + + return types.BytesToHash(xcrypto.Keccak256(hasher)) +} diff --git a/consensus/ibft/effective_power_snapshot_height_test.go b/consensus/ibft/effective_power_snapshot_height_test.go new file mode 100644 index 0000000..28d04d8 --- /dev/null +++ b/consensus/ibft/effective_power_snapshot_height_test.go @@ -0,0 +1,32 @@ +package ibft + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/types" +) + +func TestStakeSnapshotHeight_UnitModeReturnsZero(t *testing.T) { + t.Parallel() + + h, err := stakeSnapshotHeight(12, nil, false) + require.NoError(t, err) + require.Equal(t, uint64(0), h) +} + +func TestStakeSnapshotHeight_PosUsesParentHeaderNumber(t *testing.T) { + t.Parallel() + + h, err := stakeSnapshotHeight(100, &types.Header{Number: 77}, true) + require.NoError(t, err) + require.Equal(t, uint64(77), h) +} + +func TestStakeSnapshotHeight_PosHeightZeroWithoutParentFails(t *testing.T) { + t.Parallel() + + _, err := stakeSnapshotHeight(0, nil, true) + require.Error(t, err) + require.Contains(t, err.Error(), "requires parent state") +} diff --git a/consensus/ibft/effective_power_stake_test.go b/consensus/ibft/effective_power_stake_test.go new file mode 100644 index 0000000..7b62445 --- /dev/null +++ b/consensus/ibft/effective_power_stake_test.go @@ -0,0 +1,1552 @@ +package ibft + +import ( + "encoding/binary" + "math/big" + "os" + "path/filepath" + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/chain" + "github.com/xgr-network/xgr-node/consensus/ibft/fork" + "github.com/xgr-network/xgr-node/consensus/ibft/pos" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + "github.com/xgr-network/xgr-node/contracts/staking" + "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/helper/common" + "github.com/xgr-network/xgr-node/helper/keccak" + stakingHelper "github.com/xgr-network/xgr-node/helper/staking" + "github.com/xgr-network/xgr-node/state" + itrie "github.com/xgr-network/xgr-node/state/immutable-trie" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" + contractstore "github.com/xgr-network/xgr-node/validators/store/contract" +) + +func posKeyLastProposer() types.Hash { + return types.BytesToHash(crypto.Keccak256([]byte("xgr.pos.last.proposer"))) +} +func addressToHash(addr types.Address) types.Hash { + var out types.Hash + copy(out[12:], addr[:]) + return out +} + +type sequenceSigner struct { + extra *signer.IstanbulExtra + proposers map[uint64]types.Address +} + +func (m *sequenceSigner) Type() validators.ValidatorType { return validators.ECDSAValidatorType } +func (m *sequenceSigner) Address() types.Address { return types.ZeroAddress } +func (m *sequenceSigner) InitIBFTExtra(*types.Header, validators.Validators, signer.Seals) {} +func (m *sequenceSigner) GetIBFTExtra(*types.Header) (*signer.IstanbulExtra, error) { + return m.extra, nil +} +func (m *sequenceSigner) GetValidators(*types.Header) (validators.Validators, error) { + return m.extra.Validators, nil +} +func (m *sequenceSigner) WriteProposerSeal(h *types.Header) (*types.Header, error) { return h, nil } +func (m *sequenceSigner) EcrecoverFromHeader(h *types.Header) (types.Address, error) { + return m.proposers[h.Number], nil +} +func (m *sequenceSigner) CreateCommittedSeal([]byte) ([]byte, error) { return nil, nil } +func (m *sequenceSigner) VerifyCommittedSeal(validators.Validators, types.Address, []byte, []byte) error { + return nil +} +func (m *sequenceSigner) WriteCommittedSeals(*types.Header, uint64, map[types.Address][]byte) (*types.Header, error) { + return nil, nil +} +func (m *sequenceSigner) VerifyCommittedSeals(types.Hash, signer.Seals, validators.Validators, int) error { + return nil +} +func (m *sequenceSigner) VerifyParentCommittedSeals(types.Hash, *types.Header, validators.Validators, int, bool) error { + return nil +} +func (m *sequenceSigner) SignIBFTMessage([]byte) ([]byte, error) { return nil, nil } +func (m *sequenceSigner) EcrecoverFromIBFTMessage([]byte, []byte) (types.Address, error) { + return types.ZeroAddress, nil +} +func (m *sequenceSigner) CalculateHeaderHash(*types.Header) (types.Hash, error) { + return types.ZeroHash, nil +} +func (m *sequenceSigner) FilterHeaderForHash(h *types.Header) (*types.Header, error) { return h, nil } + +func TestSnapshotVotingPowers_PrePoS_UnitBasedWithoutStakeState(t *testing.T) { + t.Parallel() + pool := newTesterAccountPool(t, 3) + backend := &backendIBFT{} + + powers, snap, err := backend.snapshotVotingPowers(11, 10, pool.ValidatorSet(), nil, false) + require.NoError(t, err) + require.Equal(t, "3", snap.totalVotingPower) + for i := 0; i < pool.ValidatorSet().Len(); i++ { + addr := pool.ValidatorSet().At(uint64(i)).Addr() + require.Equal(t, uint64(1), powers[types.AddressToString(addr)].Uint64()) + } +} + +func TestSnapshotVotingPowers_StakeWeightedInactive_DoesNotReadStakeState(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001"))) + stateTx := newBareVotingPowerTestTransition(t, 10) + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochNominalWeight: 10_000}} + + powers, snap, err := backend.snapshotVotingPowers(11, 0, valSet, stateTx, false) + require.NoError(t, err) + require.Equal(t, "1", snap.totalVotingPower) + require.Equal(t, uint64(1), powers[types.AddressToString(valSet.At(0).Addr())].Uint64()) +} + +func TestSnapshotVotingPowers_BootstrapStakeDecayFloorsToOne(t *testing.T) { + t.Parallel() + val := types.StringToAddress("0x10000000000000000000000000000000000000f1") + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(val)) + stateTx := newVotingPowerTestTransition(t, valSet, 10) + require.False(t, contractstore.IsEmergencyModeActive(stateTx)) + setNominalAndEffectiveWeight(stateTx, val, 10_000, 1) + + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 1, MicroEpochNominalWeight: 10_000}} + powers, snap, err := backend.snapshotVotingPowers(11, 10, valSet, stateTx, true) + require.NoError(t, err) + require.Equal(t, uint64(1), powers[types.AddressToString(val)].Uint64()) + require.Equal(t, "1", snap.totalVotingPower) +} + +func TestSnapshotVotingPowers_BootstrapStakeDecayTotalVotingPowerNonZero(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x10000000000000000000000000000000000000f2")), + validators.NewECDSAValidator(types.StringToAddress("0x10000000000000000000000000000000000000f3")), + ) + stateTx := newVotingPowerTestTransition(t, valSet, 10) + setStakingConfigForVotingPowerTest(t, stateTx, 2, 1, uint64(valSet.Len())) + for i := 0; i < valSet.Len(); i++ { + setNominalAndEffectiveWeight(stateTx, valSet.At(uint64(i)).Addr(), 10_000, 1) + } + + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 1, MicroEpochNominalWeight: 10_000}} + powers, snap, err := backend.snapshotVotingPowers(11, 10, valSet, stateTx, true) + require.NoError(t, err) + require.Equal(t, "2", snap.totalVotingPower) + for i := 0; i < valSet.Len(); i++ { + require.Equal(t, uint64(1), powers[types.AddressToString(valSet.At(uint64(i)).Addr())].Uint64()) + } +} + +func TestSnapshotVotingPowers_EmergencyModeUsesDeterministicUnitVotingPower(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x10000000000000000000000000000000000000f4")), + validators.NewECDSAValidator(types.StringToAddress("0x10000000000000000000000000000000000000f5")), + ) + stateTx := newVotingPowerTestTransition(t, valSet, 10) + setStakingConfigForVotingPowerTest(t, stateTx, 2, 1, uint64(valSet.Len())) + setValidatorSelfStake(t, stateTx, valSet.At(0).Addr(), 10) + setValidatorSelfStake(t, stateTx, valSet.At(1).Addr(), 1_000_000) + for i := 0; i < valSet.Len(); i++ { + setNominalAndEffectiveWeight(stateTx, valSet.At(uint64(i)).Addr(), 10_000, 5_000) + } + require.True(t, contractstore.IsEmergencyModeActive(stateTx)) + + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 1, MicroEpochNominalWeight: 10_000}} + powers, snap, err := backend.snapshotVotingPowers(11, 10, valSet, stateTx, true) + require.NoError(t, err) + require.Equal(t, "2", snap.totalVotingPower) + for i := 0; i < valSet.Len(); i++ { + require.Equal(t, uint64(1), powers[types.AddressToString(valSet.At(uint64(i)).Addr())].Uint64()) + } +} + +func TestSnapshotVotingPowers_FirstPoSBlock_UsesParentStateStake(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000002")), + ) + stateTx := newVotingPowerTestTransition(t, valSet, 9) + a := valSet.At(0).Addr() + b := valSet.At(1).Addr() + setValidatorSelfStake(t, stateTx, a, 10_000) + setValidatorSelfStake(t, stateTx, b, 30_000) + setNominalAndEffectiveWeight(stateTx, a, 10_000, 10_000) + setNominalAndEffectiveWeight(stateTx, b, 10_000, 10_000) + + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(h uint64) bool { return h >= 10 }}} + res, snap, err := backend.snapshotVotingPowers(10, 9, valSet, stateTx, true) + require.NoError(t, err) + require.Equal(t, uint64(10_000), res[types.AddressToString(a)].Uint64()) + require.Equal(t, uint64(30_000), res[types.AddressToString(b)].Uint64()) + require.Equal(t, "40000", snap.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(big.NewInt(40_000)).String(), snap.quorumThreshold) +} + +func TestSnapshotVotingPowers_PoS_MissingStakeIsHardError(t *testing.T) { + t.Parallel() + val := types.StringToAddress("0x1000000000000000000000000000000000000001") + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(val)) + stateTx := newBareVotingPowerTestTransition(t, 10) + setValidatorSelfStakeRaw(t, stateTx, val, 0, 0, types.ZeroAddress) + setNominalAndEffectiveWeight(stateTx, val, 10_000, 10_000) + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + + _, _, err := backend.snapshotVotingPowers(10, 9, valSet, stateTx, true) + require.Error(t, err) + require.Contains(t, err.Error(), "missing effective stake") +} + +func TestSnapshotVotingPowers_UsesRawSelfStake_WithoutJoinMaturity(t *testing.T) { + t.Parallel() + val := types.StringToAddress("0x1000000000000000000000000000000000000001") + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(val)) + stateTx := newBareVotingPowerTestTransition(t, 10) + setValidatorSelfStakeRaw(t, stateTx, val, 10_000, 10, val) + setNominalAndEffectiveWeight(stateTx, val, 10_000, 10_000) + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + + res, _, err := backend.snapshotVotingPowers(10, 9, valSet, stateTx, true) + require.NoError(t, err) + require.Equal(t, uint64(10_000), res[types.AddressToString(val)].Uint64()) +} + +func TestSnapshotVotingPowers_UsesRawSelfStake_WithoutJoinMetadata(t *testing.T) { + t.Parallel() + val := types.StringToAddress("0x1000000000000000000000000000000000000001") + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(val)) + stateTx := newBareVotingPowerTestTransition(t, 10) + setValidatorSelfStakeRaw(t, stateTx, val, 42, 0, val) + setNominalAndEffectiveWeight(stateTx, val, 10_000, 10_000) + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + + res, _, err := backend.snapshotVotingPowers(10, 9, valSet, stateTx, true) + require.NoError(t, err) + require.Equal(t, uint64(42), res[types.AddressToString(val)].Uint64()) +} + +func TestSnapshotVotingPowers_ActiveDelegationIncludedInactiveExcluded(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000002")), + ) + stateTx := newVotingPowerTestTransition(t, valSet, 10) + validatorA := valSet.At(0).Addr() + validatorB := valSet.At(1).Addr() + activeDelegator := types.StringToAddress("0x2000000000000000000000000000000000000001") + inactiveDelegator := types.StringToAddress("0x2000000000000000000000000000000000000002") + + setValidatorSelfStake(t, stateTx, validatorA, 200) + setValidatorSelfStake(t, stateTx, validatorB, 500) + setValidatorDelegatedStakeTotals(t, stateTx, validatorA, 700, 300) + setDelegatorStakeForValidator(t, stateTx, validatorA, activeDelegator, 300, 1, 0, true) + // Raw-only delegated stake is present in the validator's delegation snapshot, + // but it joined at the snapshot height and is therefore not epoch-effective yet. + setDelegatorStakeForValidator(t, stateTx, validatorA, inactiveDelegator, 400, 9, 0, false) + setNominalAndEffectiveWeight(stateTx, validatorA, 10_000, 10_000) + setNominalAndEffectiveWeight(stateTx, validatorB, 10_000, 10_000) + + backend := &backendIBFT{ + uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, + forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}, + } + powers, snap, err := backend.snapshotVotingPowers(10, 9, valSet, stateTx, true) + require.NoError(t, err) + + validatorAPower := powers[types.AddressToString(validatorA)] + validatorBPower := powers[types.AddressToString(validatorB)] + require.Equal(t, uint64(500), validatorAPower.Uint64(), "validatorA must include self stake plus active delegated stake") + require.Equal(t, validatorBPower, validatorAPower, "validatorA self 200 + active delegation 300 must equal validatorB self 500") + require.NotEqual(t, uint64(900), validatorAPower.Uint64(), "inactive/raw-only delegated stake must not contribute") + require.Equal(t, uint64(500), validatorBPower.Uint64()) + require.Equal(t, "1000", snap.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(big.NewInt(1_000)).String(), snap.quorumThreshold) + + setValidatorDelegatedStakeTotals(t, stateTx, validatorA, 700, 0) + setStakerStakeForValidator(t, stateTx, activeDelegator, validatorA, 300, 9, 0, false) + powers, snap, err = backend.snapshotVotingPowers(10, 9, valSet, stateTx, true) + require.NoError(t, err) + + validatorAPower = powers[types.AddressToString(validatorA)] + validatorBPower = powers[types.AddressToString(validatorB)] + require.Equal(t, uint64(200), validatorAPower.Uint64(), "validatorA must fall back to self stake when all raw delegation is inactive") + require.Equal(t, uint64(500), validatorBPower.Uint64(), "validatorB must be unchanged") + require.Equal(t, "700", snap.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(big.NewInt(700)).String(), snap.quorumThreshold) +} + +func TestSnapshotVotingPowers_MidEpochStakeChangesDoNotAffectCurrentPower(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000002")), + ) + validatorA := valSet.At(0).Addr() + validatorB := valSet.At(1).Addr() + delegatorA := types.StringToAddress("0x2000000000000000000000000000000000000001") + + st := itrie.NewState(itrie.NewMemoryStorage()) + ex := state.NewExecutor(&chain.Params{Forks: chain.AllForksEnabled, BurnContract: map[uint64]types.Address{0: types.ZeroAddress}}, st, hclog.NewNullLogger()) + genesisRoot, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { return genesisRoot } + } + beginTxn := func(root types.Hash, block uint64) *state.Transition { + t.Helper() + tx, err := ex.BeginTxn(root, &types.Header{Number: block}, types.ZeroAddress) + require.NoError(t, err) + return tx + } + commitTxn := func(tx *state.Transition) types.Hash { + t.Helper() + _, root, err := tx.Commit() + require.NoError(t, err) + return root + } + + currentTx := beginTxn(genesisRoot, 15) + contractState, err := stakingHelper.PredeployStakingSC(valSet, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: uint64(valSet.Len()), EpochSize: 20}) + require.NoError(t, err) + require.NoError(t, currentTx.SetAccountDirectly(staking.AddrStakingContract, contractState)) + setValidatorSelfStake(t, currentTx, validatorA, 500) + setValidatorSelfStake(t, currentTx, validatorB, 500) + setNominalAndEffectiveWeight(currentTx, validatorA, 10_000, 10_000) + setNominalAndEffectiveWeight(currentTx, validatorB, 10_000, 10_000) + currentRoot := commitTxn(currentTx) + currentParent := &types.Header{Number: 15, StateRoot: currentRoot} + + backend := &backendIBFT{ + executor: ex, + uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, + forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}, + } + assertVotingPower := func(parent *types.Header, height uint64, wantA uint64, wantB uint64, wantTotal uint64) { + t.Helper() + powers, snap, err := backend.effectiveVotingPowerSnapshot(height, valSet, parent) + require.NoError(t, err) + require.Equal(t, wantA, powers[types.AddressToString(validatorA)].Uint64()) + require.Equal(t, wantB, powers[types.AddressToString(validatorB)].Uint64()) + require.Equal(t, new(big.Int).SetUint64(wantTotal).String(), snap.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(new(big.Int).SetUint64(wantTotal)).String(), snap.quorumThreshold) + } + + assertVotingPower(currentParent, 16, 500, 500, 1_000) + + // Commit live StakingV2 state changes on top of the current parent root. The + // current voting-power path must keep using currentParent.StateRoot, while the + // next parent root models the first snapshot after the epoch boundary. + nextTx := beginTxn(currentRoot, 20) + setValidatorSelfStakeRaw(t, nextTx, validatorA, 900, 1, validatorA) + setValidatorDelegatedStakeTotals(t, nextTx, validatorA, 600, 600) + setDelegatorStakeForValidator(t, nextTx, validatorA, delegatorA, 600, 15, 0, true) + nextRoot := commitTxn(nextTx) + nextParent := &types.Header{Number: 20, StateRoot: nextRoot} + + assertVotingPower(currentParent, 16, 500, 500, 1_000) + assertVotingPower(nextParent, 21, 1_500, 500, 2_000) +} + +func TestPoS_Slashing_ReducesValidatorSelfStakeAndVotingPowerWithoutDelegationDrift(t *testing.T) { + validatorA := types.StringToAddress("0x1000000000000000000000000000000000000101") + validatorB := types.StringToAddress("0x1000000000000000000000000000000000000102") + delegatorA := types.StringToAddress("0x2000000000000000000000000000000000000101") + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(validatorA), + validators.NewECDSAValidator(validatorB), + ) + const epochSize uint64 = 10 + const slashEpoch uint64 = 2 + + tx := newVotingPowerTestTransitionWithFeePoolSplit(t, valSet, 20) + setStakingConfigForVotingPowerTest(t, tx, epochSize, 0, uint64(valSet.Len())) + setValidatorSelfStake(t, tx, validatorA, 1_000) + setValidatorSelfStake(t, tx, validatorB, 1_000) + setDelegatorStakeForValidator(t, tx, validatorA, delegatorA, 500, 1, 0, true) + setValidatorDelegatedStakeTotals(t, tx, validatorA, 500, 500) + setNominalAndEffectiveWeight(tx, validatorA, 10_000, 10_000) + setNominalAndEffectiveWeight(tx, validatorB, 10_000, 10_000) + tx.Txn().SetBalance(staking.AddrStakingContract, big.NewInt(2_500)) + + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + assertSnapshot := func(height, parent uint64, wantA, wantB, wantTotal uint64) { + t.Helper() + powers, snap, err := backend.snapshotVotingPowers(height, parent, valSet, tx, true) + require.NoError(t, err) + require.Equal(t, wantA, powers[types.AddressToString(validatorA)].Uint64()) + require.Equal(t, wantB, powers[types.AddressToString(validatorB)].Uint64()) + require.Equal(t, new(big.Int).SetUint64(wantTotal).String(), snap.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(new(big.Int).SetUint64(wantTotal)).String(), snap.quorumThreshold) + } + + require.Equal(t, uint64(1_000), readStakerAmountForVotingPowerTest(tx, validatorA)) + require.Equal(t, uint64(500), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, uint64(500), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9)) + require.Equal(t, uint64(500), readStakerAmountForVotingPowerTest(tx, delegatorA)) + assertSnapshot(20, 19, 1_500, 1_000, 2_500) + + setPoSEpochValidatorsSnapshotForVotingPowerTest(t, tx, slashEpoch, valSet) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.stake.snapshot", slashEpoch, validatorA, big.NewInt(1_500)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.stake.snapshot", slashEpoch, validatorB, big.NewInt(1_000)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, validatorA, big.NewInt(1_000)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, delegatorA, big.NewInt(500)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, validatorB, big.NewInt(1_000)) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.slots", slashEpoch, validatorA, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.miss", slashEpoch, validatorA, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.slots", slashEpoch, validatorB, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.miss", slashEpoch, validatorB, 0) + + header := &types.Header{Number: 20} + headerSigner := &sequenceSigner{extra: &signer.IstanbulExtra{Validators: valSet}} + require.NoError(t, pos.FinalizeEpoch(header, epochSize, pos.UptimeConfig{}, headerSigner, tx)) + + logs := tx.Txn().Logs() + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, validatorA, 1, 10, 1_000, 1_000, 990) + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, delegatorA, 2, 5, 500, 500, 495) + require.Zero(t, readPoSU64StateForVotingPowerTest(tx, "xgr.pos.slashed", slashEpoch, validatorA)) + require.Zero(t, readPoSBigStateForVotingPowerTest(tx, "xgr.pos.slash.amount", slashEpoch, validatorA).Sign()) + require.Equal(t, uint64(990), readStakerAmountForVotingPowerTest(tx, validatorA)) + require.Equal(t, uint64(1_000), readStakerAmountForVotingPowerTest(tx, validatorB)) + + // Current production slashing intentionally allocates the epoch slash across + // effective self and delegated positions; these assertions ensure the raw and + // active delegation aggregates track the delegator staker amount exactly. + require.Equal(t, uint64(495), readStakerAmountForVotingPowerTest(tx, delegatorA)) + require.Equal(t, uint64(495), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, uint64(495), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9)) + require.Positive(t, readStakerAmountForVotingPowerTest(tx, validatorA)) + require.Positive(t, readStakerAmountForVotingPowerTest(tx, delegatorA)) + require.False(t, isStakerActiveForVotingPowerTest(tx, validatorA), "zero-uptime validator is deactivated by finalize") + assertSnapshot(21, 20, 1_485, 1_000, 2_485) +} + +func TestPoS_Slashing_InactiveDelegationAccounting(t *testing.T) { + validatorA := types.StringToAddress("0x1000000000000000000000000000000000000121") + validatorB := types.StringToAddress("0x1000000000000000000000000000000000000122") + activeDelegator := types.StringToAddress("0x2000000000000000000000000000000000000121") + inactiveDelegator := types.StringToAddress("0x2000000000000000000000000000000000000122") + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(validatorA), + validators.NewECDSAValidator(validatorB), + ) + const epochSize uint64 = 10 + const slashEpoch uint64 = 2 + + tx := newVotingPowerTestTransitionWithFeePoolSplit(t, valSet, 20) + setStakingConfigForVotingPowerTest(t, tx, epochSize, 0, uint64(valSet.Len())) + setValidatorSelfStake(t, tx, validatorA, 1_000) + setValidatorSelfStake(t, tx, validatorB, 1_000) + setDelegatorStakeForValidator(t, tx, validatorA, activeDelegator, 500, 1, 0, true) + setDelegatorStakeForValidator(t, tx, validatorA, inactiveDelegator, 300, 1, 9, false) + setValidatorDelegatedStakeTotals(t, tx, validatorA, 800, 500) + setNominalAndEffectiveWeight(tx, validatorA, 10_000, 10_000) + setNominalAndEffectiveWeight(tx, validatorB, 10_000, 10_000) + tx.Txn().SetBalance(staking.AddrStakingContract, big.NewInt(2_800)) + + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + assertSnapshot := func(height, parent uint64, wantA, wantB, wantTotal uint64) { + t.Helper() + powers, snap, err := backend.snapshotVotingPowers(height, parent, valSet, tx, true) + require.NoError(t, err) + require.Equal(t, wantA, powers[types.AddressToString(validatorA)].Uint64()) + require.Equal(t, wantB, powers[types.AddressToString(validatorB)].Uint64()) + require.Equal(t, new(big.Int).SetUint64(wantTotal).String(), snap.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(new(big.Int).SetUint64(wantTotal)).String(), snap.quorumThreshold) + } + + require.Equal(t, uint64(800), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, uint64(500), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9)) + assertSnapshot(20, 19, 1_500, 1_000, 2_500) + + setPoSEpochValidatorsSnapshotForVotingPowerTest(t, tx, slashEpoch, valSet) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.stake.snapshot", slashEpoch, validatorA, big.NewInt(1_500)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.stake.snapshot", slashEpoch, validatorB, big.NewInt(1_000)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, validatorA, big.NewInt(1_000)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, activeDelegator, big.NewInt(500)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, inactiveDelegator, big.NewInt(300)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, validatorB, big.NewInt(1_000)) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.slots", slashEpoch, validatorA, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.miss", slashEpoch, validatorA, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.slots", slashEpoch, validatorB, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.miss", slashEpoch, validatorB, 0) + + header := &types.Header{Number: 20} + headerSigner := &sequenceSigner{extra: &signer.IstanbulExtra{Validators: valSet}} + var finalizeErr error + require.NotPanics(t, func() { finalizeErr = pos.FinalizeEpoch(header, epochSize, pos.UptimeConfig{}, headerSigner, tx) }) + require.NoError(t, finalizeErr) + + logs := tx.Txn().Logs() + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, validatorA, 1, 9, 1_000, 1_000, 991) + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, activeDelegator, 2, 4, 500, 500, 496) + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, inactiveDelegator, 2, 2, 300, 300, 298) + require.Zero(t, readPoSU64StateForVotingPowerTest(tx, "xgr.pos.slashed", slashEpoch, validatorA)) + require.Zero(t, readPoSBigStateForVotingPowerTest(tx, "xgr.pos.slash.amount", slashEpoch, validatorA).Sign()) + require.Equal(t, uint64(991), readStakerAmountForVotingPowerTest(tx, validatorA)) + require.Equal(t, uint64(496), readStakerAmountForVotingPowerTest(tx, activeDelegator)) + require.Equal(t, uint64(298), readStakerAmountForVotingPowerTest(tx, inactiveDelegator), "inactive delegator with an epoch stake snapshot is slashed, but does not contribute to active delegated aggregate") + require.Equal(t, uint64(794), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, uint64(496), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9)) + require.Equal(t, readStakerAmountForVotingPowerTest(tx, activeDelegator)+readStakerAmountForVotingPowerTest(tx, inactiveDelegator), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, readStakerAmountForVotingPowerTest(tx, activeDelegator), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9)) + require.Equal(t, uint64(1_000), readStakerAmountForVotingPowerTest(tx, validatorB)) + require.Equal(t, uint64(2_785), txnBalanceUint64ForVotingPowerTest(tx, staking.AddrStakingContract)) + require.False(t, isStakerActiveForVotingPowerTest(tx, validatorA)) + assertSnapshot(21, 20, 1_487, 1_000, 2_487) +} + +func TestPoS_Slashing_MultipleDelegators_ProportionalAndDustAccounting(t *testing.T) { + validatorA := types.StringToAddress("0x1000000000000000000000000000000000000131") + validatorB := types.StringToAddress("0x1000000000000000000000000000000000000132") + delegator1 := types.StringToAddress("0x2000000000000000000000000000000000000131") + delegator2 := types.StringToAddress("0x2000000000000000000000000000000000000132") + delegator3 := types.StringToAddress("0x2000000000000000000000000000000000000133") + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validatorA), validators.NewECDSAValidator(validatorB)) + const epochSize uint64 = 10 + const slashEpoch uint64 = 2 + + tx := newVotingPowerTestTransitionWithFeePoolSplit(t, valSet, 20) + setStakingConfigForVotingPowerTest(t, tx, epochSize, 0, uint64(valSet.Len())) + setValidatorSelfStake(t, tx, validatorA, 1_000) + setValidatorSelfStake(t, tx, validatorB, 2_000) + setDelegatorStakeForValidator(t, tx, validatorA, delegator1, 333, 1, 0, true) + setDelegatorStakeForValidator(t, tx, validatorA, delegator2, 777, 1, 0, true) + setDelegatorStakeForValidator(t, tx, validatorA, delegator3, 111, 1, 0, true) + setValidatorDelegatedStakeTotals(t, tx, validatorA, 1_221, 1_221) + setNominalAndEffectiveWeight(tx, validatorA, 10_000, 10_000) + setNominalAndEffectiveWeight(tx, validatorB, 10_000, 10_000) + tx.Txn().SetBalance(staking.AddrStakingContract, big.NewInt(4_221)) + + setPoSEpochValidatorsSnapshotForVotingPowerTest(t, tx, slashEpoch, valSet) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.stake.snapshot", slashEpoch, validatorA, big.NewInt(2_221)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.stake.snapshot", slashEpoch, validatorB, big.NewInt(2_000)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, validatorA, big.NewInt(1_000)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, delegator1, big.NewInt(333)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, delegator2, big.NewInt(777)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, delegator3, big.NewInt(111)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, validatorB, big.NewInt(2_000)) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.slots", slashEpoch, validatorA, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.miss", slashEpoch, validatorA, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.slots", slashEpoch, validatorB, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.miss", slashEpoch, validatorB, 0) + + require.NoError(t, pos.FinalizeEpoch(&types.Header{Number: 20}, epochSize, pos.UptimeConfig{}, &sequenceSigner{extra: &signer.IstanbulExtra{Validators: valSet}}, tx)) + + const wantSlash uint64 = 22 + reductions := (uint64(1_000) - readStakerAmountForVotingPowerTest(tx, validatorA)) + + (uint64(333) - readStakerAmountForVotingPowerTest(tx, delegator1)) + + (uint64(777) - readStakerAmountForVotingPowerTest(tx, delegator2)) + + (uint64(111) - readStakerAmountForVotingPowerTest(tx, delegator3)) + logs := tx.Txn().Logs() + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, validatorA, 1, 10, 1_000, 1_000, 990) + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, delegator1, 2, 4, 333, 333, 329) + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, delegator2, 2, 7, 777, 777, 770) + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, delegator3, 2, 1, 111, 111, 110) + require.Zero(t, readPoSU64StateForVotingPowerTest(tx, "xgr.pos.slashed", slashEpoch, validatorA)) + require.Zero(t, readPoSBigStateForVotingPowerTest(tx, "xgr.pos.slash.amount", slashEpoch, validatorA).Sign()) + require.Equal(t, wantSlash, reductions) + require.Equal(t, uint64(990), readStakerAmountForVotingPowerTest(tx, validatorA), "two wei of rounding dust are assigned to the first slashable positions in deterministic staker order") + require.Equal(t, uint64(329), readStakerAmountForVotingPowerTest(tx, delegator1)) + require.Equal(t, uint64(770), readStakerAmountForVotingPowerTest(tx, delegator2)) + require.Equal(t, uint64(110), readStakerAmountForVotingPowerTest(tx, delegator3)) + require.Equal(t, uint64(1_209), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, uint64(1_209), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9)) + require.Equal(t, readStakerAmountForVotingPowerTest(tx, delegator1)+readStakerAmountForVotingPowerTest(tx, delegator2)+readStakerAmountForVotingPowerTest(tx, delegator3), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, uint64(2_000), readStakerAmountForVotingPowerTest(tx, validatorB)) + require.Equal(t, uint64(4_199), txnBalanceUint64ForVotingPowerTest(tx, staking.AddrStakingContract)) + + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + powers, snap, err := backend.snapshotVotingPowers(21, 20, valSet, tx, true) + require.NoError(t, err) + require.Equal(t, uint64(2_199), powers[types.AddressToString(validatorA)].Uint64()) + require.Equal(t, uint64(2_000), powers[types.AddressToString(validatorB)].Uint64()) + require.Equal(t, "4199", snap.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(big.NewInt(4_199)).String(), snap.quorumThreshold) +} + +func TestPoS_Slashing_BelowMinimumStakeValidatorStateAndVotingPower(t *testing.T) { + validatorA := types.StringToAddress("0x1000000000000000000000000000000000000111") + validatorB := types.StringToAddress("0x1000000000000000000000000000000000000112") + delegatorA := types.StringToAddress("0x2000000000000000000000000000000000000111") + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(validatorA), + validators.NewECDSAValidator(validatorB), + ) + const epochSize uint64 = 10 + const slashEpoch uint64 = 2 + const validatorASelfStake uint64 = 2 + const validatorADelegatedStake uint64 = 199 + const validatorBStake uint64 = 1_000 + + tx := newVotingPowerTestTransitionWithFeePoolSplit(t, valSet, 20) + setStakingConfigForVotingPowerTest(t, tx, epochSize, 0, uint64(valSet.Len())) + setValidatorSelfStake(t, tx, validatorA, validatorASelfStake) + setValidatorSelfStake(t, tx, validatorB, validatorBStake) + setDelegatorStakeForValidator(t, tx, validatorA, delegatorA, validatorADelegatedStake, 1, 0, true) + setValidatorDelegatedStakeTotals(t, tx, validatorA, validatorADelegatedStake, validatorADelegatedStake) + setNominalAndEffectiveWeight(tx, validatorA, 10_000, 10_000) + setNominalAndEffectiveWeight(tx, validatorB, 10_000, 10_000) + tx.Txn().SetBalance(staking.AddrStakingContract, new(big.Int).SetUint64(validatorASelfStake+validatorADelegatedStake+validatorBStake)) + + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + assertSnapshot := func(height, parent uint64, wantA, wantB, wantTotal uint64) { + t.Helper() + powers, snap, err := backend.snapshotVotingPowers(height, parent, valSet, tx, true) + require.NoError(t, err) + require.Equal(t, wantA, powers[types.AddressToString(validatorA)].Uint64()) + require.Equal(t, wantB, powers[types.AddressToString(validatorB)].Uint64()) + require.Equal(t, new(big.Int).SetUint64(wantTotal).String(), snap.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(new(big.Int).SetUint64(wantTotal)).String(), snap.quorumThreshold) + } + + require.Equal(t, validatorASelfStake, readStakerAmountForVotingPowerTest(tx, validatorA)) + require.Equal(t, validatorADelegatedStake, readStakerAmountForVotingPowerTest(tx, delegatorA)) + require.Equal(t, validatorADelegatedStake, readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, validatorADelegatedStake, readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9)) + require.Equal(t, validatorBStake, readStakerAmountForVotingPowerTest(tx, validatorB)) + assertSnapshot(20, 19, validatorASelfStake+validatorADelegatedStake, validatorBStake, validatorASelfStake+validatorADelegatedStake+validatorBStake) + + setPoSEpochValidatorsSnapshotForVotingPowerTest(t, tx, slashEpoch, valSet) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.stake.snapshot", slashEpoch, validatorA, new(big.Int).SetUint64(validatorASelfStake+validatorADelegatedStake)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.stake.snapshot", slashEpoch, validatorB, new(big.Int).SetUint64(validatorBStake)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, validatorA, new(big.Int).SetUint64(validatorASelfStake)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, delegatorA, new(big.Int).SetUint64(validatorADelegatedStake)) + setPoSBigStateForVotingPowerTest(tx, "xgr.pos.staker.stake.snapshot", slashEpoch, validatorB, new(big.Int).SetUint64(validatorBStake)) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.slots", slashEpoch, validatorA, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.miss", slashEpoch, validatorA, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.slots", slashEpoch, validatorB, 1) + setPoSU64StateForVotingPowerTest(tx, "xgr.pos.prop.miss", slashEpoch, validatorB, 0) + + header := &types.Header{Number: 20} + headerSigner := &sequenceSigner{extra: &signer.IstanbulExtra{Validators: valSet}} + var finalizeErr error + require.NotPanics(t, func() { + finalizeErr = pos.FinalizeEpoch(header, epochSize, pos.UptimeConfig{}, headerSigner, tx) + }) + require.NoError(t, finalizeErr) + + const wantSlashAmount uint64 = 2 + const wantValidatorASelfStake uint64 = 1 + const wantDelegatorAStake uint64 = 198 + const wantValidatorAPower uint64 = wantValidatorASelfStake + wantDelegatorAStake + const wantTotalPower uint64 = wantValidatorAPower + validatorBStake + + logs := tx.Txn().Logs() + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, validatorA, 1, 1, validatorASelfStake, validatorASelfStake, wantValidatorASelfStake) + assertStakerSlashedLogForVotingPowerTest(t, logs, slashEpoch, validatorA, delegatorA, 2, 1, validatorADelegatedStake, validatorADelegatedStake, wantDelegatorAStake) + require.Zero(t, readPoSU64StateForVotingPowerTest(tx, "xgr.pos.slashed", slashEpoch, validatorA)) + require.Zero(t, readPoSBigStateForVotingPowerTest(tx, "xgr.pos.slash.amount", slashEpoch, validatorA).Sign()) + require.LessOrEqual(t, wantSlashAmount, validatorASelfStake+validatorADelegatedStake) + require.Equal(t, wantValidatorASelfStake, readStakerAmountForVotingPowerTest(tx, validatorA)) + require.Equal(t, wantDelegatorAStake, readStakerAmountForVotingPowerTest(tx, delegatorA)) + require.Equal(t, wantDelegatorAStake, readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, wantDelegatorAStake, readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9)) + require.Equal(t, readStakerAmountForVotingPowerTest(tx, delegatorA), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8)) + require.Equal(t, readStakerAmountForVotingPowerTest(tx, delegatorA), readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9)) + require.Equal(t, validatorBStake, readStakerAmountForVotingPowerTest(tx, validatorB)) + require.Equal(t, validatorASelfStake+validatorADelegatedStake+validatorBStake-wantSlashAmount, txnBalanceUint64ForVotingPowerTest(tx, staking.AddrStakingContract)) + require.Positive(t, readStakerAmountForVotingPowerTest(tx, validatorA)) + require.Positive(t, readStakerAmountForVotingPowerTest(tx, delegatorA)) + require.False(t, isStakerActiveForVotingPowerTest(tx, validatorA), "zero-uptime validator is deactivated by finalize") + require.Equal(t, uint64(20), readStakerDeactivatedAtForVotingPowerTest(tx, validatorA)) + assertSnapshot(21, 20, wantValidatorAPower, validatorBStake, wantTotalPower) + + t.Logf("slash amount=%d final validatorA self=%d delegatorA=%d rawDelegated=%d activeDelegated=%d validatorB=%d totalVotingPower=%d", + wantSlashAmount, + readStakerAmountForVotingPowerTest(tx, validatorA), + readStakerAmountForVotingPowerTest(tx, delegatorA), + readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 8), + readValidatorDelegatedStakeTotalForVotingPowerTest(tx, validatorA, 9), + readStakerAmountForVotingPowerTest(tx, validatorB), + wantTotalPower, + ) +} + +func TestWeightedCommittedSeals_StakeAndUptimePowerThreshold(t *testing.T) { + pool := newTesterAccountPool(t) + pool.add("A", "B", "C", "D") + valSet := pool.ValidatorSet() + hash := types.BytesToHash(crypto.Keccak256([]byte("weighted committed seals power threshold"))) + round := uint64(1) + proposal := &types.Header{Number: 11} + + type votingPowerConfig struct { + stake uint64 + nominal uint64 + effective uint64 + } + + newWeightedSealBackend := func(powers map[string]votingPowerConfig) (*backendIBFT, *types.Header) { + t.Helper() + + st := itrie.NewState(itrie.NewMemoryStorage()) + ex := state.NewExecutor(&chain.Params{Forks: chain.AllForksEnabled, BurnContract: map[uint64]types.Address{0: types.ZeroAddress}}, st, hclog.NewNullLogger()) + genesisRoot, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { return genesisRoot } + } + + tx, err := ex.BeginTxn(genesisRoot, &types.Header{Number: 10}, types.ZeroAddress) + require.NoError(t, err) + contractState, err := stakingHelper.PredeployStakingSC(valSet, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: uint64(valSet.Len()), EpochSize: 2}) + require.NoError(t, err) + require.NoError(t, tx.SetAccountDirectly(staking.AddrStakingContract, contractState)) + tx.Txn().SetNonce(pos.PosSysAddr, 1) + + for name, power := range powers { + addr := pool.get(name).Address() + setValidatorSelfStake(t, tx, addr, power.stake) + setNominalAndEffectiveWeight(tx, addr, power.nominal, power.effective) + } + + _, root, err := tx.Commit() + require.NoError(t, err) + + parent := &types.Header{Number: 10, StateRoot: root} + parent.ComputeHash() + + backend := &backendIBFT{ + executor: ex, + uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, + forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}, + } + + return backend, parent + } + + committedSealsFrom := func(names ...string) signer.Seals { + t.Helper() + + seals := signer.SerializedSeal{} + for _, name := range names { + acc := pool.get(name) + s := signer.NewSigner( + signer.NewECDSAKeyManagerFromKey(acc.priv), + signer.NewECDSAKeyManagerFromKey(acc.priv), + ) + seal, err := s.CreateCommittedSeal(hash.Bytes()) + require.NoError(t, err) + seals = append(seals, seal) + } + + return &seals + } + + fullPowerBackend, fullPowerParent := newWeightedSealBackend(map[string]votingPowerConfig{ + "A": {stake: 700, nominal: 10_000, effective: 10_000}, + "B": {stake: 100, nominal: 10_000, effective: 10_000}, + "C": {stake: 100, nominal: 10_000, effective: 10_000}, + "D": {stake: 100, nominal: 10_000, effective: 10_000}, + }) + require.Equal(t, "667", weightedQuorumThreshold(big.NewInt(1_000)).String()) + require.NoError(t, fullPowerBackend.verifyWeightedCommittedPower(11, hash, committedSealsFrom("A"), valSet, proposal, fullPowerParent, &round), "one high-power signer must satisfy weighted quorum even though signer count is low") + err := fullPowerBackend.verifyWeightedCommittedPower(11, hash, committedSealsFrom("B", "C", "D"), valSet, proposal, fullPowerParent, &round) + require.ErrorIs(t, err, signer.ErrNotEnoughCommittedSeals, "three low-power signers must not satisfy stake-weighted quorum by validator count alone") + require.Contains(t, err.Error(), "collected=300") + require.Contains(t, err.Error(), "quorum=667") + + decayedBackend, decayedParent := newWeightedSealBackend(map[string]votingPowerConfig{ + "A": {stake: 700, nominal: 10_000, effective: 2_858}, + "B": {stake: 100, nominal: 10_000, effective: 10_000}, + "C": {stake: 100, nominal: 10_000, effective: 10_000}, + "D": {stake: 100, nominal: 10_000, effective: 10_000}, + }) + decayedPowers, decayedSnap, err := decayedBackend.effectiveVotingPowerSnapshot(11, valSet, decayedParent) + require.NoError(t, err) + require.Equal(t, uint64(200), decayedPowers[types.AddressToString(pool.get("A").Address())].Uint64()) + require.Equal(t, "500", decayedSnap.totalVotingPower) + require.Equal(t, "334", weightedQuorumThreshold(big.NewInt(500)).String()) + err = decayedBackend.verifyWeightedCommittedPower(11, hash, committedSealsFrom("A", "B"), valSet, proposal, decayedParent, &round) + require.ErrorIs(t, err, signer.ErrNotEnoughCommittedSeals, "nominally high stake must not count after uptime decay lowers effective power") + require.Contains(t, err.Error(), "collected=300") + require.Contains(t, err.Error(), "quorum=334") + require.NoError(t, decayedBackend.verifyWeightedCommittedPower(11, hash, committedSealsFrom("A", "B", "C", "D"), valSet, proposal, decayedParent, &round), "additional effective power must satisfy weighted quorum after uptime decay") +} + +func TestWeightedCommittedSeals_RejectsDuplicateUnknownAndWrongSealPower(t *testing.T) { + pool := newTesterAccountPool(t) + pool.add("A", "B", "C", "D", "E") + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(pool.get("A").Address()), + validators.NewECDSAValidator(pool.get("B").Address()), + validators.NewECDSAValidator(pool.get("C").Address()), + validators.NewECDSAValidator(pool.get("D").Address()), + ) + hash := types.BytesToHash(crypto.Keccak256([]byte("weighted committed seals duplicate unknown wrong hash"))) + wrongHash := types.BytesToHash(crypto.Keccak256([]byte("wrong committed hash"))) + round := uint64(1) + wrongRound := uint64(2) + proposal := &types.Header{Number: 11} + + type votingPowerConfig struct{ stake, nominal, effective uint64 } + newWeightedSealBackend := func(powers map[string]votingPowerConfig) (*backendIBFT, *types.Header) { + t.Helper() + st := itrie.NewState(itrie.NewMemoryStorage()) + ex := state.NewExecutor(&chain.Params{Forks: chain.AllForksEnabled, BurnContract: map[uint64]types.Address{0: types.ZeroAddress}}, st, hclog.NewNullLogger()) + genesisRoot, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + ex.GetHash = func(h *types.Header) state.GetHashByNumber { return func(i uint64) types.Hash { return genesisRoot } } + tx, err := ex.BeginTxn(genesisRoot, &types.Header{Number: 10}, types.ZeroAddress) + require.NoError(t, err) + contractState, err := stakingHelper.PredeployStakingSC(valSet, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: uint64(valSet.Len()), EpochSize: 2}) + require.NoError(t, err) + require.NoError(t, tx.SetAccountDirectly(staking.AddrStakingContract, contractState)) + for name, power := range powers { + addr := pool.get(name).Address() + setValidatorSelfStake(t, tx, addr, power.stake) + setNominalAndEffectiveWeight(tx, addr, power.nominal, power.effective) + } + _, root, err := tx.Commit() + require.NoError(t, err) + parent := &types.Header{Number: 10, StateRoot: root} + parent.ComputeHash() + return &backendIBFT{executor: ex, uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}}, parent + } + + sealFor := func(name string, sealHash types.Hash) []byte { + t.Helper() + acc := pool.get(name) + s := signer.NewSigner(signer.NewECDSAKeyManagerFromKey(acc.priv), signer.NewECDSAKeyManagerFromKey(acc.priv)) + seal, err := s.CreateCommittedSeal(sealHash.Bytes()) + require.NoError(t, err) + return seal + } + seals := func(items ...[]byte) signer.Seals { + t.Helper() + out := signer.SerializedSeal{} + for _, item := range items { + out = append(out, item) + } + return &out + } + + fullBackend, fullParent := newWeightedSealBackend(map[string]votingPowerConfig{ + "A": {stake: 700, nominal: 10_000, effective: 10_000}, + "B": {stake: 100, nominal: 10_000, effective: 10_000}, + "C": {stake: 100, nominal: 10_000, effective: 10_000}, + "D": {stake: 100, nominal: 10_000, effective: 10_000}, + }) + require.NoError(t, fullBackend.verifyWeightedCommittedPower(11, hash, seals(sealFor("A", hash), sealFor("A", hash)), valSet, proposal, fullParent, &round), "duplicate high-power signer is deduplicated; A alone legitimately satisfies quorum") + require.ErrorIs(t, fullBackend.verifyWeightedCommittedPower(11, hash, seals(sealFor("E", hash)), valSet, proposal, fullParent, &round), signer.ErrNonValidatorCommittedSeal) + require.Error(t, fullBackend.verifyWeightedCommittedPower(11, hash, seals(sealFor("A", wrongHash)), valSet, proposal, fullParent, &round), "a validator seal over the wrong commit hash must not be accepted for this hash") + require.Error(t, fullBackend.verifyWeightedCommittedPower(11, hash, seals([]byte{0x01, 0x02, 0x03}), valSet, proposal, fullParent, &round), "malformed ECDSA seal must fail recovery") + + thresholdBackend, thresholdParent := newWeightedSealBackend(map[string]votingPowerConfig{ + "A": {stake: 400, nominal: 10_000, effective: 10_000}, + "B": {stake: 300, nominal: 10_000, effective: 10_000}, + "C": {stake: 200, nominal: 10_000, effective: 10_000}, + "D": {stake: 100, nominal: 10_000, effective: 10_000}, + }) + err := thresholdBackend.verifyWeightedCommittedPower(11, hash, seals(sealFor("B", hash), sealFor("B", hash), sealFor("C", hash)), valSet, proposal, thresholdParent, &round) + require.ErrorIs(t, err, signer.ErrNotEnoughCommittedSeals, "duplicate B must not be double-counted to cross quorum") + require.Contains(t, err.Error(), "collected=500") + require.Contains(t, err.Error(), "quorum=667") + + ordered := seals(sealFor("A", hash), sealFor("B", hash)) + reversed := seals(sealFor("B", hash), sealFor("A", hash)) + require.NoError(t, thresholdBackend.verifyWeightedCommittedPower(11, hash, ordered, valSet, proposal, thresholdParent, &round)) + require.NoError(t, thresholdBackend.verifyWeightedCommittedPower(11, hash, reversed, valSet, proposal, thresholdParent, &round), "signer ordering does not change collected power") + require.NoError(t, thresholdBackend.verifyWeightedCommittedPower(11, hash, ordered, valSet, proposal, thresholdParent, &wrongRound), "current ECDSA committed-seal power path does not encode round in the seal digest") +} + +func TestSnapshotVotingPowers_MicroDecayAsStakeMultiplier_AndRestore(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000002")), + ) + stateTx := newVotingPowerTestTransition(t, valSet, 10) + a := valSet.At(0).Addr() + b := valSet.At(1).Addr() + setValidatorSelfStake(t, stateTx, a, 10_000) + setValidatorSelfStake(t, stateTx, b, 30_000) + setNominalAndEffectiveWeight(stateTx, a, 10_000, 10_000) + setNominalAndEffectiveWeight(stateTx, b, 10_000, 5_000) + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + + res, _, err := backend.snapshotVotingPowers(10, 9, valSet, stateTx, true) + require.NoError(t, err) + require.Equal(t, uint64(10_000), res[types.AddressToString(a)].Uint64()) + require.Equal(t, uint64(15_000), res[types.AddressToString(b)].Uint64()) + + setNominalAndEffectiveWeight(stateTx, b, 10_000, 10_000) + res, _, err = backend.snapshotVotingPowers(10, 9, valSet, stateTx, true) + require.NoError(t, err) + require.Equal(t, uint64(30_000), res[types.AddressToString(b)].Uint64()) +} + +func TestSnapshotVotingPowers_FirstPoSBlock_UsesUnitWeights_WhenParentPrePoS(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000002")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000003")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000004")), + ) + backend := &backendIBFT{} + powers, snap, err := backend.snapshotVotingPowers(50, 49, valSet, nil, false) + require.NoError(t, err) + require.Equal(t, "4", snap.totalVotingPower) + require.Equal(t, "3", snap.quorumThreshold) + for i := 0; i < valSet.Len(); i++ { + addr := valSet.At(uint64(i)).Addr() + require.Equal(t, uint64(1), powers[types.AddressToString(addr)].Uint64()) + } +} + +func TestSnapshotVotingPowers_SecondPoSBlock_MissingStakeFails(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001"))) + stateTx := newBareVotingPowerTestTransition(t, 50) + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}} + _, _, err := backend.snapshotVotingPowers(51, 50, valSet, stateTx, true) + require.Error(t, err) + require.Contains(t, err.Error(), "missing effective stake") +} + +func TestSnapshotVotingPowers_UsesConsensusStake_WhenValidatorDeactivated(t *testing.T) { + t.Parallel() + val := types.StringToAddress("0x1000000000000000000000000000000000000001") + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(val)) + stateTx := newVotingPowerTestTransition(t, valSet, 250) + setValidatorSelfStake(t, stateTx, val, 10) + setStakerDeactivatedAt(t, stateTx, val, 200) + setNominalAndEffectiveWeight(stateTx, val, 10_000, 10_000) + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + + res, _, err := backend.snapshotVotingPowers(251, 250, valSet, stateTx, true) + require.NoError(t, err) + require.Equal(t, uint64(10), res[types.AddressToString(val)].Uint64()) +} + +func TestSnapshotVotingPowers_EpochBoundaryDeactivationRegression(t *testing.T) { + t.Parallel() + val := types.StringToAddress("0x1000000000000000000000000000000000000001") + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(val)) + stateTx := newVotingPowerTestTransition(t, valSet, 250) + setEpochSizeForVotingPowerTest(t, stateTx, 50) + setValidatorSelfStake(t, stateTx, val, 10) + setStakerDeactivatedAt(t, stateTx, val, 200) + setNominalAndEffectiveWeight(stateTx, val, 10_000, 10_000) + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + + _, _, err := backend.snapshotVotingPowers(251, 250, valSet, stateTx, true) + require.NoError(t, err) +} + +func TestConsensusDoesNotReferenceRemovedPoSWrappers(t *testing.T) { + t.Parallel() + content, err := os.ReadFile(filepath.Join("..", "..", "consensus", "ibft", "ibft.go")) + require.NoError(t, err) + require.NotContains(t, string(content), "is"+"PoSActiveAt(") + require.NotContains(t, string(content), "is"+"StakeWeightedVotingActive(") + require.NotContains(t, string(content), "is"+"BeaconActiveAt(") +} + +func TestPoAToPoSBoundary_DoesNotDeadlockVotingPowerInit(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000002")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000003")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000004")), + ) + backend := &backendIBFT{} + powers, snap, err := backend.snapshotVotingPowers(50, 49, valSet, nil, false) + require.NoError(t, err) + require.Equal(t, "4", snap.totalVotingPower) + for i := 0; i < valSet.Len(); i++ { + addr := valSet.At(uint64(i)).Addr() + require.Equal(t, uint64(1), powers[types.AddressToString(addr)].Uint64()) + } +} + +func TestSnapshotVotingPowers_MicroEpochDutyWithoutSeen_DecaysOnlyAfterBoundary(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000002")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000003")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000004")), + ) + tx := newVotingPowerTestTransitionWithFeePoolSplit(t, valSet, 110) + setEpochSizeForVotingPowerTest(t, tx, 100) + cfg := pos.UptimeConfig{MicroEpochSize: 4, MicroEpochNominalWeight: 10_000, MicroEpochInactivityDecayBps: 9_000} + backend := &backendIBFT{uptimeCfg: cfg, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + for i := 0; i < valSet.Len(); i++ { + addr := valSet.At(uint64(i)).Addr() + setValidatorSelfStake(t, tx, addr, 10) + setNominalAndEffectiveWeight(tx, addr, 10_000, 10_000) + } + + last := valSet.At(0).Addr() + tx.Txn().SetState(pos.PosSysAddr, posKeyLastProposer(), addressToHash(last)) + s := &sequenceSigner{proposers: map[uint64]types.Address{ + 101: valSet.At(0).Addr(), + 102: valSet.At(0).Addr(), + 103: valSet.At(0).Addr(), + 104: valSet.At(0).Addr(), + 105: valSet.At(0).Addr(), + }} + for _, block := range []uint64{101, 102, 103, 104} { + r := uint64(0) + extra := &signer.IstanbulExtra{Validators: valSet, RoundNumber: &r, CommittedSeals: &signer.SerializedSeal{}, ParentCommittedSeals: &signer.SerializedSeal{}} + header := &types.Header{Number: block, ExtraData: extra.MarshalRLPTo(make([]byte, signer.IstanbulExtraVanity))} + s.extra = extra + require.NoError(t, pos.RecordBlockUptime(header, 100, valSet, s, cfg, tx)) + } + target := valSet.At(1).Addr() + require.Equal(t, uint64(10_000), pos.UptimeEffectiveWeight(tx.Txn(), target)) + + powersBefore, snapBefore, err := backend.snapshotVotingPowers(105, 104, valSet, tx, true) + require.NoError(t, err) + require.Equal(t, uint64(10), powersBefore[types.AddressToString(target)].Uint64()) + require.Equal(t, "40", snapBefore.totalVotingPower) + + r := uint64(0) + extra := &signer.IstanbulExtra{Validators: valSet, RoundNumber: &r, CommittedSeals: &signer.SerializedSeal{}, ParentCommittedSeals: &signer.SerializedSeal{}} + header := &types.Header{Number: 105, ExtraData: extra.MarshalRLPTo(make([]byte, signer.IstanbulExtraVanity))} + s.extra = extra + require.NoError(t, pos.RecordBlockUptime(header, 100, valSet, s, cfg, tx)) + + require.Equal(t, uint64(9_000), pos.UptimeEffectiveWeight(tx.Txn(), target)) + powersAfter, snapAfter, err := backend.snapshotVotingPowers(106, 105, valSet, tx, true) + require.NoError(t, err) + require.Equal(t, uint64(9), powersAfter[types.AddressToString(target)].Uint64()) + require.Equal(t, "39", snapAfter.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(big.NewInt(39)).String(), snapAfter.quorumThreshold) +} + +func TestSnapshotVotingPowers_Height201DeactivatedValidatorSelfStakeRegression(t *testing.T) { + t.Parallel() + valSet := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000002")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000003")), + validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000004")), + ) + tx := newVotingPowerTestTransition(t, valSet, 200) + setEpochSizeForVotingPowerTest(t, tx, 50) + backend := &backendIBFT{uptimeCfg: pos.UptimeConfig{MicroEpochSize: 4, MicroEpochNominalWeight: 10_000}, forkManager: &votingPowerForkManager{active: func(uint64) bool { return true }}} + + for i := 0; i < valSet.Len(); i++ { + addr := valSet.At(uint64(i)).Addr() + setValidatorSelfStake(t, tx, addr, 10) + setNominalAndEffectiveWeight(tx, addr, 10_000, 10_000) + } + + deactivated := valSet.At(0).Addr() + setStakerDeactivatedAt(t, tx, deactivated, 55) + + powers, snap, err := backend.snapshotVotingPowers(201, 200, valSet, tx, true) + require.NoError(t, err) + require.Equal(t, uint64(10), powers[types.AddressToString(deactivated)].Uint64()) + require.Equal(t, "40", snap.totalVotingPower) + require.Equal(t, weightedQuorumThreshold(big.NewInt(40)).String(), snap.quorumThreshold) + + effective := contractstore.ReadValidatorEffectiveTotalStakeAt(tx, deactivated, 200) + voting := contractstore.ReadValidatorVotingStakeAt(tx, deactivated, 200) + require.Equal(t, int64(0), effective.Int64()) + require.Equal(t, int64(10), voting.Int64()) +} + +func slashEventTopicForVotingPowerTest() types.Hash { + return types.BytesToHash(crypto.Keccak256([]byte("StakerSlashed(uint256,address,address,uint8,uint256,uint256,uint256,uint256,uint256,uint256,uint256,address)"))) +} + +func abiHashU64ForVotingPowerTest(v uint64) types.Hash { + return types.BytesToHash(logWordBigForVotingPowerTest(new(big.Int).SetUint64(v))) +} + +func abiHashAddressForVotingPowerTest(addr types.Address) types.Hash { + return types.BytesToHash(abiWordAddressForVotingPowerTest(addr)) +} + +func abiWordAddressForVotingPowerTest(addr types.Address) []byte { + var out [32]byte + copy(out[12:], addr[:]) + return out[:] +} + +func logWordBigForVotingPowerTest(v *big.Int) []byte { + var out [32]byte + if v == nil || v.Sign() == 0 { + return out[:] + } + b := v.Bytes() + if len(b) > 32 { + b = b[len(b)-32:] + } + copy(out[32-len(b):], b) + return out[:] +} + +func assertStakerSlashedLogForVotingPowerTest( + t *testing.T, + logs []*types.Log, + epoch uint64, + validator types.Address, + staker types.Address, + role uint8, + amount uint64, + stakeSnapshot uint64, + stakeBefore uint64, + stakeAfter uint64, +) { + t.Helper() + + wantTopic := slashEventTopicForVotingPowerTest() + wantEpoch := abiHashU64ForVotingPowerTest(epoch) + wantValidator := abiHashAddressForVotingPowerTest(validator) + wantStaker := abiHashAddressForVotingPowerTest(staker) + + for _, log := range logs { + if log.Address != pos.PosSysAddr || len(log.Topics) != 4 || log.Topics[0] != wantTopic || log.Topics[1] != wantEpoch || log.Topics[2] != wantValidator || log.Topics[3] != wantStaker { + continue + } + + require.Len(t, log.Data, 9*32) + require.Equal(t, new(big.Int).SetUint64(uint64(role)), logWordBigIntForVotingPowerTest(log.Data, 0)) + require.Equal(t, new(big.Int).SetUint64(amount), logWordBigIntForVotingPowerTest(log.Data, 1)) + require.Equal(t, new(big.Int).SetUint64(stakeSnapshot), logWordBigIntForVotingPowerTest(log.Data, 2)) + require.Equal(t, new(big.Int).SetUint64(stakeBefore), logWordBigIntForVotingPowerTest(log.Data, 3)) + require.Equal(t, new(big.Int).SetUint64(stakeAfter), logWordBigIntForVotingPowerTest(log.Data, 4)) + require.Equal(t, new(big.Int).SetUint64(1), logWordBigIntForVotingPowerTest(log.Data, 5), "slots") + require.Equal(t, new(big.Int).SetUint64(1), logWordBigIntForVotingPowerTest(log.Data, 6), "missed") + require.Equal(t, new(big.Int).SetUint64(100), logWordBigIntForVotingPowerTest(log.Data, 7), "slashBps") + require.Equal(t, abiWordAddressForVotingPowerTest(chain.DefaultBurnedAddress), log.Data[8*32:9*32], "destination") + return + } + + require.Failf(t, "missing StakerSlashed log", "epoch=%d validator=%s staker=%s", epoch, validator, staker) +} + +func logWordBigIntForVotingPowerTest(data []byte, idx int) *big.Int { + return new(big.Int).SetBytes(data[idx*32 : (idx+1)*32]) +} + +func posStateKeyForVotingPowerTest(prefix string, epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(prefix), eb[:], addr.Bytes())) +} + +func posEpochValidatorsLenKeyForVotingPowerTest(epoch uint64) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte("xgr.pos.epoch.validators.len"), eb[:])) +} + +func posEpochValidatorKeyForVotingPowerTest(epoch, idx uint64) types.Hash { + var eb [8]byte + var ib [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + binary.BigEndian.PutUint64(ib[:], idx) + + return types.BytesToHash(crypto.Keccak256([]byte("xgr.pos.epoch.validators.val"), eb[:], ib[:])) +} + +func setPoSEpochValidatorsSnapshotForVotingPowerTest(t *testing.T, txn *state.Transition, epoch uint64, vals validators.Validators) { + t.Helper() + txn.Txn().SetState(pos.PosSysAddr, posEpochValidatorsLenKeyForVotingPowerTest(epoch), types.BytesToHash(new(big.Int).SetUint64(uint64(vals.Len())).Bytes())) + for i := 0; i < vals.Len(); i++ { + txn.Txn().SetState(pos.PosSysAddr, posEpochValidatorKeyForVotingPowerTest(epoch, uint64(i)), addressToHash(vals.At(uint64(i)).Addr())) + } +} + +func setPoSU64StateForVotingPowerTest(txn *state.Transition, prefix string, epoch uint64, addr types.Address, value uint64) { + var b [32]byte + binary.BigEndian.PutUint64(b[24:], value) + txn.Txn().SetState(pos.PosSysAddr, posStateKeyForVotingPowerTest(prefix, epoch, addr), types.BytesToHash(b[:])) +} + +func readPoSU64StateForVotingPowerTest(txn *state.Transition, prefix string, epoch uint64, addr types.Address) uint64 { + h := txn.Txn().GetState(pos.PosSysAddr, posStateKeyForVotingPowerTest(prefix, epoch, addr)) + return binary.BigEndian.Uint64(h[24:]) +} + +func setPoSBigStateForVotingPowerTest(txn *state.Transition, prefix string, epoch uint64, addr types.Address, value *big.Int) { + txn.Txn().SetState(pos.PosSysAddr, posStateKeyForVotingPowerTest(prefix, epoch, addr), types.BytesToHash(value.Bytes())) +} + +func readPoSBigStateForVotingPowerTest(txn *state.Transition, prefix string, epoch uint64, addr types.Address) *big.Int { + h := txn.Txn().GetState(pos.PosSysAddr, posStateKeyForVotingPowerTest(prefix, epoch, addr)) + return new(big.Int).SetBytes(h[:]) +} + +func readStakerAmountForVotingPowerTest(txn *state.Transition, staker types.Address) uint64 { + h := txn.Txn().GetState(staking.AddrStakingContract, stakingTestStakerSlotWithOffset(staker, 1)) + return new(big.Int).SetBytes(h[:]).Uint64() +} + +func readValidatorDelegatedStakeTotalForVotingPowerTest(txn *state.Transition, validator types.Address, slot int64) uint64 { + base := keccak.Keccak256(nil, + append(common.PadLeftOrTrim(validator.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(slot).Bytes(), 32)...), + ) + h := txn.Txn().GetState(staking.AddrStakingContract, types.BytesToHash(base)) + return new(big.Int).SetBytes(h[:]).Uint64() +} + +func txnBalanceUint64ForVotingPowerTest(txn *state.Transition, addr types.Address) uint64 { + return new(big.Int).Set(txn.Txn().GetBalance(addr)).Uint64() +} + +func isStakerActiveForVotingPowerTest(txn *state.Transition, staker types.Address) bool { + h := txn.Txn().GetState(staking.AddrStakingContract, stakingTestStakerSlotWithOffset(staker, 0)) + packed := new(big.Int).SetBytes(h[:]) + active := new(big.Int).Rsh(packed, 8) + active.And(active, new(big.Int).SetUint64(0xff)) + + return active.Sign() != 0 +} + +func readStakerDeactivatedAtForVotingPowerTest(txn *state.Transition, staker types.Address) uint64 { + h := txn.Txn().GetState(staking.AddrStakingContract, stakingTestStakerSlotWithOffset(staker, 3)) + return new(big.Int).SetBytes(h[:]).Uint64() +} + +func newVotingPowerTestTransition(t *testing.T, vals validators.Validators, block uint64) *state.Transition { + t.Helper() + st := itrie.NewState(itrie.NewMemoryStorage()) + ex := state.NewExecutor(&chain.Params{Forks: chain.AllForksEnabled, BurnContract: map[uint64]types.Address{0: types.ZeroAddress}}, st, hclog.NewNullLogger()) + root, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { return root } + } + txn, err := ex.BeginTxn(root, &types.Header{Number: block}, types.ZeroAddress) + require.NoError(t, err) + contractState, err := stakingHelper.PredeployStakingSC(vals, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: uint64(vals.Len()), EpochSize: 2}) + require.NoError(t, err) + require.NoError(t, txn.SetAccountDirectly(staking.AddrStakingContract, contractState)) + return txn +} + +func newVotingPowerTestTransitionWithFeePoolSplit(t *testing.T, vals validators.Validators, block uint64) *state.Transition { + t.Helper() + st := itrie.NewState(itrie.NewMemoryStorage()) + forks := chain.AllForksEnabled.Copy() + forks.SetFork(chain.FeePoolSplit, chain.NewFork(0)) + ex := state.NewExecutor(&chain.Params{Forks: forks, BurnContract: map[uint64]types.Address{0: types.ZeroAddress}}, st, hclog.NewNullLogger()) + root, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { return root } + } + txn, err := ex.BeginTxn(root, &types.Header{Number: block}, types.ZeroAddress) + require.NoError(t, err) + contractState, err := stakingHelper.PredeployStakingSC(vals, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: uint64(vals.Len()), EpochSize: 2}) + require.NoError(t, err) + require.NoError(t, txn.SetAccountDirectly(staking.AddrStakingContract, contractState)) + return txn +} + +func newBareVotingPowerTestTransition(t *testing.T, block uint64) *state.Transition { + t.Helper() + st := itrie.NewState(itrie.NewMemoryStorage()) + ex := state.NewExecutor(&chain.Params{Forks: chain.AllForksEnabled, BurnContract: map[uint64]types.Address{0: types.ZeroAddress}}, st, hclog.NewNullLogger()) + root, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { return root } + } + txn, err := ex.BeginTxn(root, &types.Header{Number: block}, types.ZeroAddress) + require.NoError(t, err) + return txn +} + +func setNominalAndEffectiveWeight(txn *state.Transition, addr types.Address, nominal, effective uint64) { + toHash := func(v uint64) types.Hash { + var b [32]byte + binary.BigEndian.PutUint64(b[24:], v) + return types.BytesToHash(b[:]) + } + txn.Txn().SetState(pos.PosSysAddr, types.BytesToHash(crypto.Keccak256([]byte("xgr.uptime.nominal.weight"), addr.Bytes())), toHash(nominal)) + txn.Txn().SetState(pos.PosSysAddr, types.BytesToHash(crypto.Keccak256([]byte("xgr.uptime.effective.weight"), addr.Bytes())), toHash(effective)) +} + +func setValidatorSelfStake(t *testing.T, txn *state.Transition, validator types.Address, stake uint64) { + t.Helper() + stakerSlotWithOffset := func(addr types.Address, offset int64) types.Hash { + base := keccak.Keccak256(nil, + append(common.PadLeftOrTrim(addr.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(3).Bytes(), 32)...), + ) + i := new(big.Int).SetBytes(base) + i.Add(i, big.NewInt(offset)) + return types.BytesToHash(i.Bytes()) + } + toHashBig := func(v *big.Int) types.Hash { return types.BytesToHash(v.Bytes()) } + // staker self amount + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(validator, 1), toHashBig(new(big.Int).SetUint64(stake))) + // staker validator pointer = self + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(validator, 4), types.BytesToHash(validator.Bytes())) + // active from genesis, not deactivated + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(validator, 2), types.BytesToHash(new(big.Int).SetUint64(1).Bytes())) + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(validator, 3), types.Hash{}) + + base := keccak.Keccak256(nil, append(common.PadLeftOrTrim(validator.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(6).Bytes(), 32)...)) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(base), types.BytesToHash(new(big.Int).SetUint64(1).Bytes())) + slot := new(big.Int).SetBytes(keccak.Keccak256(nil, common.PadLeftOrTrim(base, 32))) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(slot.Bytes()), types.BytesToHash(validator.Bytes())) +} + +func setValidatorSelfStakeRaw(t *testing.T, txn *state.Transition, validator types.Address, stake uint64, joinedAt uint64, mappedValidator types.Address) { + t.Helper() + stakerSlotWithOffset := func(addr types.Address, offset int64) types.Hash { + base := keccak.Keccak256(nil, + append(common.PadLeftOrTrim(addr.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(3).Bytes(), 32)...), + ) + idx := new(big.Int).SetBytes(base) + idx.Add(idx, big.NewInt(offset)) + return types.BytesToHash(idx.Bytes()) + } + toHashBig := func(v *big.Int) types.Hash { return types.BytesToHash(v.Bytes()) } + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(validator, 1), toHashBig(new(big.Int).SetUint64(stake))) + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(validator, 4), types.BytesToHash(mappedValidator.Bytes())) + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(validator, 2), types.BytesToHash(new(big.Int).SetUint64(joinedAt).Bytes())) + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(validator, 3), types.Hash{}) +} + +func setDelegatorStakeForValidator( + t *testing.T, + txn *state.Transition, + validator types.Address, + delegator types.Address, + amount uint64, + joinedAt uint64, + deactivatedAt uint64, + active bool, +) { + t.Helper() + setStakerStakeForValidator(t, txn, delegator, validator, amount, joinedAt, deactivatedAt, active) + appendStakerForValidator(t, txn, validator, delegator) +} + +func setStakerStakeForValidator( + t *testing.T, + txn *state.Transition, + staker types.Address, + validator types.Address, + amount uint64, + joinedAt uint64, + deactivatedAt uint64, + active bool, +) { + t.Helper() + packed := big.NewInt(1) + if active { + packed.SetBit(packed, 8, 1) + } + txn.Txn().SetState(staking.AddrStakingContract, stakingTestStakerSlotWithOffset(staker, 0), types.BytesToHash(packed.Bytes())) + txn.Txn().SetState(staking.AddrStakingContract, stakingTestStakerSlotWithOffset(staker, 1), types.BytesToHash(new(big.Int).SetUint64(amount).Bytes())) + txn.Txn().SetState(staking.AddrStakingContract, stakingTestStakerSlotWithOffset(staker, 2), types.BytesToHash(new(big.Int).SetUint64(joinedAt).Bytes())) + txn.Txn().SetState(staking.AddrStakingContract, stakingTestStakerSlotWithOffset(staker, 3), types.BytesToHash(new(big.Int).SetUint64(deactivatedAt).Bytes())) + txn.Txn().SetState(staking.AddrStakingContract, stakingTestStakerSlotWithOffset(staker, 4), types.BytesToHash(validator.Bytes())) +} + +func appendStakerForValidator(t *testing.T, txn *state.Transition, validator types.Address, staker types.Address) { + t.Helper() + stakersByValidatorBase := keccak.Keccak256(nil, + append(common.PadLeftOrTrim(validator.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(6).Bytes(), 32)...), + ) + lengthKey := types.BytesToHash(stakersByValidatorBase) + lengthRaw := txn.Txn().GetState(staking.AddrStakingContract, lengthKey) + length := new(big.Int).SetBytes(lengthRaw[:]).Uint64() + + arrayBase := new(big.Int).SetBytes(keccak.Keccak256(nil, common.PadLeftOrTrim(stakersByValidatorBase, 32))) + arraySlot := new(big.Int).Add(arrayBase, new(big.Int).SetUint64(length)) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(arraySlot.Bytes()), types.BytesToHash(staker.Bytes())) + txn.Txn().SetState(staking.AddrStakingContract, lengthKey, types.BytesToHash(new(big.Int).SetUint64(length+1).Bytes())) + + stakerIndexOuter := keccak.Keccak256(nil, + append(common.PadLeftOrTrim(validator.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(7).Bytes(), 32)...), + ) + stakerIndexInner := append(common.PadLeftOrTrim(staker.Bytes(), 32), common.PadLeftOrTrim(stakerIndexOuter, 32)...) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(keccak.Keccak256(nil, stakerIndexInner)), types.BytesToHash(new(big.Int).SetUint64(length+1).Bytes())) +} + +func setValidatorDelegatedStakeTotals(t *testing.T, txn *state.Transition, validator types.Address, raw uint64, active uint64) { + t.Helper() + setValidatorDelegatedStakeTotal(t, txn, validator, 8, raw) + setValidatorDelegatedStakeTotal(t, txn, validator, 9, active) +} + +func setValidatorDelegatedStakeTotal(t *testing.T, txn *state.Transition, validator types.Address, slot int64, amount uint64) { + t.Helper() + base := keccak.Keccak256(nil, + append(common.PadLeftOrTrim(validator.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(slot).Bytes(), 32)...), + ) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(base), types.BytesToHash(new(big.Int).SetUint64(amount).Bytes())) +} + +func stakingTestStakerSlotWithOffset(addr types.Address, offset int64) types.Hash { + base := keccak.Keccak256(nil, + append(common.PadLeftOrTrim(addr.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(3).Bytes(), 32)...), + ) + idx := new(big.Int).SetBytes(base) + idx.Add(idx, big.NewInt(offset)) + return types.BytesToHash(idx.Bytes()) +} + +func setStakerDeactivatedAt(t *testing.T, txn *state.Transition, staker types.Address, block uint64) { + t.Helper() + stakerSlotWithOffset := func(addr types.Address, offset int64) types.Hash { + base := keccak.Keccak256(nil, + append(common.PadLeftOrTrim(addr.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(3).Bytes(), 32)...), + ) + i := new(big.Int).SetBytes(base) + i.Add(i, big.NewInt(offset)) + return types.BytesToHash(i.Bytes()) + } + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(staker, 3), types.BytesToHash(new(big.Int).SetUint64(block).Bytes())) +} + +func setStakingConfigForVotingPowerTest(t *testing.T, txn *state.Transition, epochSize, minValidators, maxValidators uint64) { + t.Helper() + v := new(big.Int).SetUint64(maxValidators) + v.Lsh(v, 64) + v.Or(v, new(big.Int).SetUint64(minValidators)) + v.Lsh(v, 64) + v.Or(v, new(big.Int).SetUint64(epochSize)) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(big.NewInt(1).Bytes()), types.BytesToHash(v.Bytes())) +} + +func setEpochSizeForVotingPowerTest(t *testing.T, txn *state.Transition, epochSize uint64) { + t.Helper() + raw := txn.Txn().GetState(staking.AddrStakingContract, types.BytesToHash(big.NewInt(0).Bytes())) + v := new(big.Int).SetBytes(raw[:]) + mask64 := new(big.Int).SetUint64(^uint64(0)) + clearMask := new(big.Int).Not(mask64) + v.And(v, clearMask) + v.Or(v, new(big.Int).SetUint64(epochSize)) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(big.NewInt(0).Bytes()), types.BytesToHash(v.Bytes())) +} + +type votingPowerForkManager struct{ active func(uint64) bool } + +func (m *votingPowerForkManager) Initialize() error { return nil } +func (m *votingPowerForkManager) Close() error { return nil } +func (m *votingPowerForkManager) GetSigner(uint64) (signer.Signer, error) { return nil, nil } +func (m *votingPowerForkManager) GetValidatorStore(uint64) (fork.ValidatorStore, error) { + return nil, nil +} +func (m *votingPowerForkManager) GetValidators(uint64) (validators.Validators, error) { + return nil, nil +} +func (m *votingPowerForkManager) GetHooks(uint64) fork.HooksInterface { return nil } +func (m *votingPowerForkManager) IsPosActive(h uint64) bool { return m.active != nil && m.active(h) } + +func TestPoAToPoSBoundaryVotingPowerSemantics(t *testing.T) { + pool := newTesterAccountPool(t) + pool.add("A", "B", "C") + valSet := pool.ValidatorSet() + const forkHeight uint64 = 10 + + st := itrie.NewState(itrie.NewMemoryStorage()) + ex := state.NewExecutor(&chain.Params{Forks: chain.AllForksEnabled, BurnContract: map[uint64]types.Address{0: types.ZeroAddress}}, st, hclog.NewNullLogger()) + genesisRoot, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + ex.GetHash = func(*types.Header) state.GetHashByNumber { return func(uint64) types.Hash { return genesisRoot } } + + tx, err := ex.BeginTxn(genesisRoot, &types.Header{Number: forkHeight - 1}, types.ZeroAddress) + require.NoError(t, err) + contractState, err := stakingHelper.PredeployStakingSC(valSet, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: uint64(valSet.Len()), EpochSize: 20}) + require.NoError(t, err) + require.NoError(t, tx.SetAccountDirectly(staking.AddrStakingContract, contractState)) + setValidatorSelfStake(t, tx, pool.get("A").Address(), 700) + setValidatorSelfStake(t, tx, pool.get("B").Address(), 200) + setValidatorSelfStake(t, tx, pool.get("C").Address(), 100) + for _, name := range []string{"A", "B", "C"} { + setNominalAndEffectiveWeight(tx, pool.get(name).Address(), 10_000, 10_000) + } + _, root, err := tx.Commit() + require.NoError(t, err) + + parentBeforeFork := &types.Header{Number: forkHeight - 1, StateRoot: root} + parentBeforeFork.ComputeHash() + parentAtFork := &types.Header{Number: forkHeight, StateRoot: root, ParentHash: parentBeforeFork.Hash} + parentAtFork.ComputeHash() + + backend := &backendIBFT{ + executor: ex, + uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, + forkManager: &votingPowerForkManager{active: func(h uint64) bool { return h >= forkHeight }}, + } + + powersAtFork, snapAtFork, err := backend.effectiveVotingPowerSnapshot(forkHeight, valSet, parentBeforeFork) + require.NoError(t, err) + require.Equal(t, "3", snapAtFork.totalVotingPower) + require.Equal(t, "2", snapAtFork.quorumThreshold) + for _, name := range []string{"A", "B", "C"} { + require.Equal(t, uint64(1), powersAtFork[types.AddressToString(pool.get(name).Address())].Uint64(), "height F must not read weighted stake from a non-PoS parent") + } + + powersAfterFork, snapAfterFork, err := backend.effectiveVotingPowerSnapshot(forkHeight+1, valSet, parentAtFork) + require.NoError(t, err) + require.Equal(t, uint64(700), powersAfterFork[types.AddressToString(pool.get("A").Address())].Uint64()) + require.Equal(t, uint64(200), powersAfterFork[types.AddressToString(pool.get("B").Address())].Uint64()) + require.Equal(t, uint64(100), powersAfterFork[types.AddressToString(pool.get("C").Address())].Uint64()) + require.Equal(t, "1000", snapAfterFork.totalVotingPower) + require.Equal(t, "667", snapAfterFork.quorumThreshold) +} + +func TestPoAToPoSBoundaryParentCommittedSealWeightedRules(t *testing.T) { + pool := newTesterAccountPool(t) + pool.add("A", "B", "C") + valSet := pool.ValidatorSet() + const forkHeight uint64 = 10 + + st := itrie.NewState(itrie.NewMemoryStorage()) + ex := state.NewExecutor(&chain.Params{Forks: chain.AllForksEnabled, BurnContract: map[uint64]types.Address{0: types.ZeroAddress}}, st, hclog.NewNullLogger()) + genesisRoot, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + ex.GetHash = func(*types.Header) state.GetHashByNumber { return func(uint64) types.Hash { return genesisRoot } } + + tx, err := ex.BeginTxn(genesisRoot, &types.Header{Number: forkHeight}, types.ZeroAddress) + require.NoError(t, err) + contractState, err := stakingHelper.PredeployStakingSC(valSet, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: uint64(valSet.Len()), EpochSize: 20}) + require.NoError(t, err) + require.NoError(t, tx.SetAccountDirectly(staking.AddrStakingContract, contractState)) + setValidatorSelfStake(t, tx, pool.get("A").Address(), 700) + setValidatorSelfStake(t, tx, pool.get("B").Address(), 200) + setValidatorSelfStake(t, tx, pool.get("C").Address(), 100) + for _, name := range []string{"A", "B", "C"} { + setNominalAndEffectiveWeight(tx, pool.get(name).Address(), 10_000, 10_000) + } + _, root, err := tx.Commit() + require.NoError(t, err) + posParent := &types.Header{Number: forkHeight, StateRoot: root} + posParent.ComputeHash() + + backend := &backendIBFT{ + executor: ex, + uptimeCfg: pos.UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10_000}, + forkManager: &votingPowerForkManager{active: func(h uint64) bool { return h >= forkHeight }}, + } + hash := types.BytesToHash(crypto.Keccak256([]byte("pos boundary committed seals"))) + proposal := &types.Header{Number: forkHeight + 1, ParentHash: posParent.Hash} + round := uint64(0) + sealsFrom := func(names ...string) signer.Seals { + out := signer.SerializedSeal{} + for _, name := range names { + s := signer.NewSigner(signer.NewECDSAKeyManagerFromKey(pool.get(name).priv), signer.NewECDSAKeyManagerFromKey(pool.get(name).priv)) + seal, err := s.CreateCommittedSeal(hash.Bytes()) + require.NoError(t, err) + out = append(out, seal) + } + return &out + } + + require.NoError(t, backend.verifyWeightedCommittedPower(forkHeight+1, hash, sealsFrom("A"), valSet, proposal, posParent, &round)) + err = backend.verifyWeightedCommittedPower(forkHeight+1, hash, sealsFrom("B", "C"), valSet, proposal, posParent, &round) + require.ErrorIs(t, err, signer.ErrNotEnoughCommittedSeals) + require.Contains(t, err.Error(), "collected=300") + require.Contains(t, err.Error(), "quorum=667") + + // Height F is PoS, but its parent is still PoA, so parent committed-seal voting power remains unit-based. + require.NoError(t, backend.verifyWeightedCommittedPower(forkHeight, hash, sealsFrom("B", "C"), valSet, &types.Header{Number: forkHeight}, &types.Header{Number: forkHeight - 1, StateRoot: root}, &round)) +} diff --git a/consensus/ibft/fork/hookregister.go b/consensus/ibft/fork/hookregister.go index 1e0c793..b6172b5 100644 --- a/consensus/ibft/fork/hookregister.go +++ b/consensus/ibft/fork/hookregister.go @@ -2,6 +2,8 @@ package fork import ( "github.com/xgr-network/xgr-node/consensus/ibft/hook" + "github.com/xgr-network/xgr-node/consensus/ibft/pos" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" ) // PoAHookRegisterer that registers hooks for PoA mode @@ -61,19 +63,34 @@ func (r *PoAHookRegister) RegisterHooks(hooks *hook.Hooks, height uint64) { type PoSHookRegister struct { posForks IBFTForks epochSize uint64 + firstPoSFrom uint64 + hasPoS bool deployContractForks map[uint64]*IBFTFork + uptimeCfg pos.UptimeConfig + getSigner func(uint64) (signer.Signer, error) } // NewPoSHookRegister is a constructor of PoSHookRegister func NewPoSHookRegister( forks IBFTForks, epochSize uint64, + uptimeCfg pos.UptimeConfig, + getSigner func(uint64) (signer.Signer, error), ) *PoSHookRegister { posForks := forks.filterByType(PoS) deployContractForks := make(map[uint64]*IBFTFork) + var ( + firstPoSFrom uint64 + hasPoS bool + ) for _, fork := range posForks { + if !hasPoS || fork.From.Value < firstPoSFrom { + firstPoSFrom = fork.From.Value + hasPoS = true + } + if fork.Deployment == nil { continue } @@ -84,7 +101,11 @@ func NewPoSHookRegister( return &PoSHookRegister{ posForks: posForks, epochSize: epochSize, + firstPoSFrom: firstPoSFrom, + hasPoS: hasPoS, deployContractForks: deployContractForks, + uptimeCfg: uptimeCfg, + getSigner: getSigner, } } @@ -92,11 +113,11 @@ func NewPoSHookRegister( func (r *PoSHookRegister) RegisterHooks(hooks *hook.Hooks, height uint64) { if currentFork := r.posForks.getFork(height); currentFork != nil { // in PoS mode currently - registerTxInclusionGuardHooks(hooks, r.epochSize) + registerTxInclusionGuardHooks(hooks, r.epochSize, r.uptimeCfg, r.getSigner, r.firstPoSFrom) } if deploymentFork, ok := r.deployContractForks[height]; ok { // deploy or update staking contract in deployment height - registerStakingContractDeploymentHooks(hooks, deploymentFork) + registerStakingContractDeploymentHooks(hooks, deploymentFork, r.epochSize) } } diff --git a/consensus/ibft/fork/hooks.go b/consensus/ibft/fork/hooks.go index 56bd479..0e32de3 100644 --- a/consensus/ibft/fork/hooks.go +++ b/consensus/ibft/fork/hooks.go @@ -2,10 +2,12 @@ package fork import ( "errors" + "fmt" "github.com/xgr-network/xgr-node/consensus/ibft/hook" + "github.com/xgr-network/xgr-node/consensus/ibft/pos" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" "github.com/xgr-network/xgr-node/contracts/staking" - "github.com/xgr-network/xgr-node/helper/hex" stakingHelper "github.com/xgr-network/xgr-node/helper/staking" "github.com/xgr-network/xgr-node/state" "github.com/xgr-network/xgr-node/types" @@ -17,6 +19,67 @@ var ( ErrTxInLastEpochOfBlock = errors.New("block must not have transactions in the last of epoch") ) +func chainShouldWriteTransactions(a, b hook.ShouldWriteTransactionsFunc) hook.ShouldWriteTransactionsFunc { + if a == nil { + return b + } + if b == nil { + return a + } + + return func(height uint64) bool { + return a(height) && b(height) + } +} + +func chainVerifyBlock(a, b hook.VerifyBlockFunc) hook.VerifyBlockFunc { + if a == nil { + return b + } + if b == nil { + return a + } + + return func(block *types.Block) error { + if err := a(block); err != nil { + return err + } + return b(block) + } +} + +func chainPreCommitState(a, b hook.PreCommitStateFunc) hook.PreCommitStateFunc { + if a == nil { + return b + } + if b == nil { + return a + } + + return func(header *types.Header, txn *state.Transition) error { + if err := a(header, txn); err != nil { + return err + } + return b(header, txn) + } +} + +func chainPostInsertBlock(a, b hook.PostInsertBlockFunc) hook.PostInsertBlockFunc { + if a == nil { + return b + } + if b == nil { + return a + } + + return func(block *types.Block) error { + if err := a(block); err != nil { + return err + } + return b(block) + } +} + // HeaderModifier is an interface for the struct that modifies block header for additional process type HeaderModifier interface { ModifyHeader(*types.Header, types.Address) error @@ -51,7 +114,7 @@ func registerUpdateValidatorsHooks( fromHeight uint64, ) { if us, ok := validatorStore.(Updatable); ok { - hooks.PostInsertBlockFunc = func(b *types.Block) error { + next := func(b *types.Block) error { if fromHeight != b.Number()+1 { return nil } @@ -59,26 +122,85 @@ func registerUpdateValidatorsHooks( // update validators if the block height is the one before beginning height return us.UpdateValidatorSet(validators, fromHeight) } + + hooks.PostInsertBlockFunc = chainPostInsertBlock(hooks.PostInsertBlockFunc, next) } } // registerPoSVerificationHooks registers that hooks to prevent the last epoch block from having transactions -func registerTxInclusionGuardHooks(hooks *hook.Hooks, epochSize uint64) { +func registerTxInclusionGuardHooks( + hooks *hook.Hooks, + epochSize uint64, + uptimeCfg pos.UptimeConfig, + getSigner func(uint64) (signer.Signer, error), + firstPoSFrom uint64, +) { isLastEpoch := func(height uint64) bool { return height > 0 && height%epochSize == 0 } - hooks.ShouldWriteTransactionFunc = func(height uint64) bool { - return !isLastEpoch(height) + guardShouldWrite := func(height uint64) bool { + return !isLastEpoch(height) || shouldSkipEpochFinalizationBeforePoS(height, firstPoSFrom) } - hooks.VerifyBlockFunc = func(block *types.Block) error { - if isLastEpoch(block.Number()) && len(block.Transactions) > 0 { + guardVerify := func(block *types.Block) error { + if !isLastEpoch(block.Number()) { + return nil + } + + if shouldSkipEpochFinalizationBeforePoS(block.Number(), firstPoSFrom) { + return nil + } + + if len(block.Transactions) == 0 { return ErrTxInLastEpochOfBlock } - return nil + if len(block.Transactions) == 1 && pos.IsEpochFinalizationSystemTx(block.Transactions[0], block.Number()) { + return nil + } + + return ErrTxInLastEpochOfBlock + } + + // 1) user-tx-free last epoch block once PoS finalization is active + hooks.ShouldWriteTransactionFunc = chainShouldWriteTransactions(hooks.ShouldWriteTransactionFunc, guardShouldWrite) + hooks.VerifyBlockFunc = chainVerifyBlock(hooks.VerifyBlockFunc, guardVerify) + + // 2) deterministic native epoch-finalize on the epoch boundary block + hooks.PreCommitStateFunc = chainPreCommitState(hooks.PreCommitStateFunc, func(header *types.Header, txn *state.Transition) error { + if !isLastEpoch(header.Number) { + return nil + } + if shouldSkipEpochFinalizationBeforePoS(header.Number, firstPoSFrom) { + return nil + } + if getSigner == nil { + return fmt.Errorf("missing signer getter") + } + headerSigner, err := getSigner(header.Number) + if err != nil { + return err + } + if headerSigner == nil { + return fmt.Errorf("missing signer") + } + finalizationTx := pos.EpochFinalizationSystemTx(header.Number) + + if err := pos.FinalizeEpoch(header, epochSize, uptimeCfg, headerSigner, txn); err != nil { + return err + } + + return txn.WriteSystemReceipt(finalizationTx) + }) +} + +func shouldSkipEpochFinalizationBeforePoS(boundaryBlock, firstPoSFrom uint64) bool { + if boundaryBlock == 0 { + return false } + + return boundaryBlock-1 < firstPoSFrom } // registerStakingContractDeploymentHooks registers hooks @@ -86,51 +208,190 @@ func registerTxInclusionGuardHooks(hooks *hook.Hooks, epochSize uint64) { func registerStakingContractDeploymentHooks( hooks *hook.Hooks, fork *IBFTFork, + epochSize uint64, ) { - hooks.PreCommitStateFunc = func(header *types.Header, txn *state.Transition) error { + deployOrUpdate := func(header *types.Header, txn *state.Transition) error { // safe check if header.Number != fork.Deployment.Value { return nil } - if txn.AccountExists(staking.AddrStakingContract) { - // update bytecode of deployed contract - codeBytes, err := hex.DecodeHex(stakingHelper.StakingSCBytecode) + // Bootstrap validator set: + // At PoA -> PoS transition, validators may not have staked yet. + // The contract must still contain the current validator list (with BLS keys) + // to keep the chain live. Stake filtering is applied later by fetcher. + bootstrapVals := fork.Validators + if bootstrapVals == nil || bootstrapVals.Len() == 0 { + parsedBootstrapVals, err := parseValidatorsFromHeader(header, fork.ValidatorType) if err != nil { - return err + return fmt.Errorf( + "staking predeploy bootstrap validator fallback failed at block %d (validatorType=%s): %w", + header.Number, + fork.ValidatorType, + err, + ) + } + bootstrapVals = parsedBootstrapVals + + if bootstrapVals == nil || bootstrapVals.Len() == 0 { + return fmt.Errorf( + "staking predeploy bootstrap validator fallback returned empty validator set at block %d", + header.Number, + ) } + } - return txn.SetCodeDirectly(staking.AddrStakingContract, codeBytes) - } else { - // deploy contract - contractState, err := stakingHelper.PredeployStakingSC( - fork.Validators, - getPreDeployParams(fork), + params, err := getPreDeployParams(fork, epochSize, uint64(bootstrapVals.Len())) + if err != nil { + return fmt.Errorf( + "staking predeploy params invalid at block %d (bootstrapValidators=%d): %w", + header.Number, + bootstrapVals.Len(), + err, ) + } - if err != nil { - return err + contractState, err := stakingHelper.PredeployStakingSCBootstrap( + bootstrapVals, + params, + ) + if err != nil { + return fmt.Errorf( + "staking predeploy bootstrap failed at block %d (validators=%d min=%d max=%d epochSize=%d): %w", + header.Number, + bootstrapVals.Len(), + params.MinValidatorCount, + params.MaxValidatorCount, + params.EpochSize, + err, + ) + } + + if txn.AccountExists(staking.AddrStakingContract) { + // Update bytecode (if needed) and ensure storage is bootstrapped exactly once. + // Use the precomputed runtime code (contractState.Code), not creation bytecode. + if err := txn.SetCodeDirectly(staking.AddrStakingContract, contractState.Code); err != nil { + return fmt.Errorf("staking predeploy code update failed at block %d: %w", header.Number, err) + } + + // If the contract storage is not initialized yet (e.g. maxNumValidators == 0), + // apply the bootstrap storage map. + // When uninitialized, stale bootstrap slots can still exist from a previous + // partial recovery and must be overwritten to match the deterministic + // bootstrap snapshot. + if stakingHelper.IsUninitializedStakingStorage(txn, staking.AddrStakingContract) { + for k, v := range contractState.Storage { + txn.Txn().SetState(staking.AddrStakingContract, k, v) + } } - return txn.SetAccountDirectly(staking.AddrStakingContract, contractState) + return nil + } + + // Deploy contract with fully bootstrapped storage. + if err := txn.SetAccountDirectly(staking.AddrStakingContract, contractState); err != nil { + return fmt.Errorf("staking predeploy account deployment failed at block %d: %w", header.Number, err) } + + return nil } + // Deploy/update must run first in the deployment block, before any other pre-commit hooks. + hooks.PreCommitStateFunc = chainPreCommitState(deployOrUpdate, hooks.PreCommitStateFunc) +} + +func parseValidatorsFromHeader( + header *types.Header, + validatorType validators.ValidatorType, +) (validators.Validators, error) { + if header == nil { + return nil, fmt.Errorf("header is nil") + } + + if len(header.ExtraData) < signer.IstanbulExtraVanity { + return nil, fmt.Errorf( + "extra-data shorter than vanity length (%d < %d)", + len(header.ExtraData), + signer.IstanbulExtraVanity, + ) + } + + extra := &signer.IstanbulExtra{ + Validators: validators.NewValidatorSetFromType(validatorType), + } + + switch validatorType { + case validators.ECDSAValidatorType: + extra.CommittedSeals = &signer.SerializedSeal{} + extra.ParentCommittedSeals = &signer.SerializedSeal{} + case validators.BLSValidatorType: + extra.CommittedSeals = &signer.AggregatedSeal{} + extra.ParentCommittedSeals = &signer.AggregatedSeal{} + default: + return nil, fmt.Errorf("unsupported validator type: %s", validatorType) + } + + if err := extra.UnmarshalRLP(header.ExtraData[signer.IstanbulExtraVanity:]); err != nil { + return nil, fmt.Errorf("unable to decode IBFT extra: %w", err) + } + + if extra.Validators == nil || extra.Validators.Len() == 0 { + return nil, fmt.Errorf("decoded validator set is empty") + } + + return extra.Validators, nil } // getPreDeployParams returns PredeployParams for Staking Contract from IBFTFork -func getPreDeployParams(fork *IBFTFork) stakingHelper.PredeployParams { - params := stakingHelper.PredeployParams{ - MinValidatorCount: stakingHelper.MinValidatorCount, - MaxValidatorCount: stakingHelper.MaxValidatorCount, +func getPreDeployParams( + fork *IBFTFork, + epochSize uint64, + bootstrapValidatorCount uint64, +) (stakingHelper.PredeployParams, error) { + if fork == nil { + return stakingHelper.PredeployParams{}, fmt.Errorf("PoS staking predeploy requires fork configuration") } + if bootstrapValidatorCount == 0 { + return stakingHelper.PredeployParams{}, fmt.Errorf("PoS staking predeploy requires at least one bootstrap validator") + } + + minValidatorCount := bootstrapValidatorCount if fork.MinValidatorCount != nil { - params.MinValidatorCount = fork.MinValidatorCount.Value + minValidatorCount = fork.MinValidatorCount.Value } + maxValidatorCount := uint64(0) if fork.MaxValidatorCount != nil { - params.MaxValidatorCount = fork.MaxValidatorCount.Value + maxValidatorCount = fork.MaxValidatorCount.Value + } else { + maxValidatorCount = bootstrapValidatorCount + } + + if maxValidatorCount > pos.MaxEpochValidatorsSnapshot { + return stakingHelper.PredeployParams{}, fmt.Errorf( + "invalid staking predeploy params: max validator count (%d) exceeds epoch snapshot max (%d)", + maxValidatorCount, + pos.MaxEpochValidatorsSnapshot, + ) + } + if minValidatorCount > maxValidatorCount { + return stakingHelper.PredeployParams{}, fmt.Errorf( + "invalid staking predeploy params: min validator count (%d) exceeds max (%d)", + minValidatorCount, + maxValidatorCount, + ) + } + if minValidatorCount > bootstrapValidatorCount { + return stakingHelper.PredeployParams{}, fmt.Errorf( + "invalid staking predeploy params: min validator count (%d) exceeds bootstrap validator count (%d)", + minValidatorCount, + bootstrapValidatorCount, + ) } - return params + return stakingHelper.PredeployParams{ + MinValidatorCount: minValidatorCount, + MaxValidatorCount: maxValidatorCount, + EpochSize: epochSize, + }, nil } diff --git a/consensus/ibft/fork/hooks_test.go b/consensus/ibft/fork/hooks_test.go index 5d6090e..6070f0b 100644 --- a/consensus/ibft/fork/hooks_test.go +++ b/consensus/ibft/fork/hooks_test.go @@ -2,12 +2,15 @@ package fork import ( "errors" + "math/big" "testing" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/assert" "github.com/xgr-network/xgr-node/chain" "github.com/xgr-network/xgr-node/consensus/ibft/hook" + "github.com/xgr-network/xgr-node/consensus/ibft/pos" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" "github.com/xgr-network/xgr-node/contracts/staking" "github.com/xgr-network/xgr-node/crypto" "github.com/xgr-network/xgr-node/helper/common" @@ -27,6 +30,145 @@ type mockHeaderModifierStore struct { ProcessHeaderFunc func(*types.Header) error } +func Test_registerStakingContractDeploymentHooks_overwritesStaleBootstrapSlotsWhenUninitialized(t *testing.T) { + t.Parallel() + + oldVals := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1111")), + ) + newVals := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x2222")), + ) + + params := stakingHelper.PredeployParams{ + MinValidatorCount: 1, + MaxValidatorCount: 10, + EpochSize: 10, + } + + oldState, err := stakingHelper.PredeployStakingSCBootstrap(oldVals, params) + assert.NoError(t, err) + + newState, err := stakingHelper.PredeployStakingSCBootstrap(newVals, params) + assert.NoError(t, err) + + // Find the validators[0] storage slot (same key, different value between old/new bootstrap sets). + var validators0Key types.Hash + for k, oldV := range oldState.Storage { + newV, ok := newState.Storage[k] + if !ok || oldV == newV || oldV == (types.Hash{}) || newV == (types.Hash{}) { + continue + } + + validators0Key = k + + break + } + assert.NotEqual(t, types.Hash{}, validators0Key) + + maxValidatorsKey := types.BytesToHash(big.NewInt(6).Bytes()) + validatorsLengthKey := types.BytesToHash(big.NewInt(0).Bytes()) + + txn := newTestTransition(t) + // Existing contract account with partial recovery state: + // - capacity is set (slot 6 non-zero) + // - validator length is zero (slot 0 => uninitialized) + // - stale validators[0] slot is still populated from old state + err = txn.SetAccountDirectly(staking.AddrStakingContract, &chain.GenesisAccount{ + Code: oldState.Code, + Balance: big.NewInt(0), + Storage: map[types.Hash]types.Hash{ + maxValidatorsKey: oldState.Storage[maxValidatorsKey], + validatorsLengthKey: {}, + validators0Key: oldState.Storage[validators0Key], + }, + }) + assert.NoError(t, err) + + hooks := &hook.Hooks{} + fork := &IBFTFork{ + Deployment: &common.JSONNumber{Value: 10}, + MinValidatorCount: &common.JSONNumber{Value: params.MinValidatorCount}, + MaxValidatorCount: &common.JSONNumber{Value: params.MaxValidatorCount}, + ValidatorType: validators.ECDSAValidatorType, + Validators: newVals, + } + registerStakingContractDeploymentHooks(hooks, fork, 10) + + assert.NoError(t, hooks.PreCommitState(&types.Header{Number: 10}, txn)) + + assert.Equal( + t, + newState.Storage[validators0Key], + txn.Txn().GetState(staking.AddrStakingContract, validators0Key), + ) +} + +func Test_registerStakingContractDeploymentHooks_bootstrapsWhenEpochSizeSlotMissing(t *testing.T) { + t.Parallel() + + oldVals := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1111")), + ) + newVals := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x2222")), + ) + + params := stakingHelper.PredeployParams{ + MinValidatorCount: 1, + MaxValidatorCount: 10, + EpochSize: 10, + } + + oldState, err := stakingHelper.PredeployStakingSCBootstrap(oldVals, params) + assert.NoError(t, err) + + newState, err := stakingHelper.PredeployStakingSCBootstrap(newVals, params) + assert.NoError(t, err) + + var validators0Key types.Hash + for k, oldV := range oldState.Storage { + newV, ok := newState.Storage[k] + if !ok || oldV == newV || oldV == (types.Hash{}) || newV == (types.Hash{}) { + continue + } + validators0Key = k + break + } + assert.NotEqual(t, types.Hash{}, validators0Key) + + capKey := types.BytesToHash(big.NewInt(0).Bytes()) + lenKey := types.BytesToHash(big.NewInt(1).Bytes()) + epochKey := types.BytesToHash(big.NewInt(8).Bytes()) + + txn := newTestTransition(t) + err = txn.SetAccountDirectly(staking.AddrStakingContract, &chain.GenesisAccount{ + Code: oldState.Code, + Balance: big.NewInt(0), + Storage: map[types.Hash]types.Hash{ + capKey: oldState.Storage[capKey], + lenKey: oldState.Storage[lenKey], + epochKey: {}, // missing epoch size must trigger bootstrap rewrite + validators0Key: oldState.Storage[validators0Key], + }, + }) + assert.NoError(t, err) + + hooks := &hook.Hooks{} + fork := &IBFTFork{ + Deployment: &common.JSONNumber{Value: 10}, + MinValidatorCount: &common.JSONNumber{Value: params.MinValidatorCount}, + MaxValidatorCount: &common.JSONNumber{Value: params.MaxValidatorCount}, + ValidatorType: validators.ECDSAValidatorType, + Validators: newVals, + } + registerStakingContractDeploymentHooks(hooks, fork, 10) + + assert.NoError(t, hooks.PreCommitState(&types.Header{Number: 10}, txn)) + assert.Equal(t, newState.Storage[epochKey], txn.Txn().GetState(staking.AddrStakingContract, epochKey)) + assert.Equal(t, newState.Storage[validators0Key], txn.Txn().GetState(staking.AddrStakingContract, validators0Key)) +} + func (m *mockHeaderModifierStore) ModifyHeader(header *types.Header, addr types.Address) error { return m.ModifyHeaderFunc(header, addr) } @@ -216,14 +358,23 @@ func Test_registerTxInclusionGuardHooks(t *testing.T) { epochSize := uint64(10) hooks := &hook.Hooks{} - registerTxInclusionGuardHooks(hooks, epochSize) + key, err := crypto.GenerateECDSAKey() + assert.NoError(t, err) + keyManager := signer.NewECDSAKeyManagerFromKey(key) + headerSigner := signer.NewSigner(keyManager, keyManager) + + registerTxInclusionGuardHooks(hooks, epochSize, pos.DefaultUptimeConfig(), func(uint64) (signer.Signer, error) { + return headerSigner, nil + }, 1) assert.Nil(t, hooks.ModifyHeaderFunc) assert.Nil(t, hooks.VerifyHeaderFunc) assert.Nil(t, hooks.ProcessHeaderFunc) - assert.Nil(t, hooks.PreCommitStateFunc) + assert.NotNil(t, hooks.PreCommitStateFunc) assert.Nil(t, hooks.PostInsertBlockFunc) + assert.NoError(t, hooks.PreCommitStateFunc(&types.Header{Number: 1}, newTestTransition(t))) + var ( cases = map[uint64]bool{ 0: true, @@ -259,17 +410,127 @@ func Test_registerTxInclusionGuardHooks(t *testing.T) { blockWithTransactions.Header.Number = h blockWithoutTransactions.Header.Number = h + blockWithSystemTransaction := &types.Block{ + Header: &types.Header{Number: h}, + Transactions: []*types.Transaction{pos.EpochFinalizationSystemTx(h)}, + } if ok { assert.NoError(t, hooks.VerifyBlock(blockWithoutTransactions)) assert.NoError(t, hooks.VerifyBlock(blockWithTransactions)) } else { - assert.NoError(t, hooks.VerifyBlock(blockWithoutTransactions)) + assert.ErrorIs(t, ErrTxInLastEpochOfBlock, hooks.VerifyBlock(blockWithoutTransactions)) + assert.NoError(t, hooks.VerifyBlock(blockWithSystemTransaction)) assert.ErrorIs(t, ErrTxInLastEpochOfBlock, hooks.VerifyBlock(blockWithTransactions)) } } } +func Test_registerTxInclusionGuardHooks_PrePoAForkSafety(t *testing.T) { + t.Parallel() + + epochSize := uint64(10) + firstPoSFrom := uint64(100) + hooks := &hook.Hooks{} + + registerTxInclusionGuardHooks(hooks, epochSize, pos.DefaultUptimeConfig(), func(uint64) (signer.Signer, error) { + return nil, errors.New("signer must not be requested before PoS activation") + }, firstPoSFrom) + + boundary := epochSize + assert.True(t, hooks.ShouldWriteTransactions(boundary), "PoA boundary block production must still allow normal transactions") + assert.NoError(t, hooks.VerifyBlock(&types.Block{Header: &types.Header{Number: boundary}}), "empty PoA boundary block remains valid") + assert.NoError(t, hooks.VerifyBlock(&types.Block{ + Header: &types.Header{Number: boundary}, + Transactions: []*types.Transaction{{Nonce: 1}}, + }), "user transactions remain valid before PoS activation") + + txn := newTestTransition(t) + assert.NoError(t, hooks.PreCommitStateFunc(&types.Header{Number: boundary}, txn)) + assert.Empty(t, txn.Receipts(), "no epoch-finalization system receipt is produced before PoS activation") +} + +func Test_registerTxInclusionGuardHooks_RejectsMalformedEpochFinalizationSystemTx(t *testing.T) { + t.Parallel() + + epochSize := uint64(10) + hooks := &hook.Hooks{} + + registerTxInclusionGuardHooks(hooks, epochSize, pos.DefaultUptimeConfig(), func(uint64) (signer.Signer, error) { + return nil, nil + }, 1) + + boundary := epochSize + valid := pos.EpochFinalizationSystemTx(boundary) + assert.NoError(t, hooks.VerifyBlock(&types.Block{ + Header: &types.Header{Number: boundary}, + Transactions: []*types.Transaction{valid}, + })) + + cases := map[string]func(*types.Transaction){ + "wrong nonce": func(tx *types.Transaction) { tx.Nonce = boundary + 1 }, + "wrong hash": func(tx *types.Transaction) { tx.Hash[0] ^= 0x01 }, + "wrong from": func(tx *types.Transaction) { tx.From = types.ZeroAddress }, + "wrong input": func(tx *types.Transaction) { tx.Input = []byte("bad-input") }, + } + + for name, mutate := range cases { + t.Run(name, func(t *testing.T) { + tx := valid.Copy() + mutate(tx) + + assert.ErrorIs(t, ErrTxInLastEpochOfBlock, hooks.VerifyBlock(&types.Block{ + Header: &types.Header{Number: boundary}, + Transactions: []*types.Transaction{tx}, + })) + }) + } +} + +func Test_shouldSkipEpochFinalizationBeforePoS(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + boundary uint64 + firstPoSFrom uint64 + skip bool + }{ + { + name: "skip when finalized epoch is fully pre-PoS at cutover boundary", + boundary: 50, + firstPoSFrom: 50, + skip: true, + }, + { + name: "do not skip direct PoS from genesis", + boundary: 50, + firstPoSFrom: 1, + skip: false, + }, + { + name: "do not skip when PoS starts mid-previous epoch", + boundary: 50, + firstPoSFrom: 45, + skip: false, + }, + { + name: "do not skip next boundary after cutover", + boundary: 100, + firstPoSFrom: 50, + skip: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.skip, shouldSkipEpochFinalizationBeforePoS(tt.boundary, tt.firstPoSFrom)) + }) + } +} + func newTestTransition( t *testing.T, ) *state.Transition { @@ -311,9 +572,21 @@ func Test_registerStakingContractDeploymentHooks(t *testing.T) { Deployment: &common.JSONNumber{ Value: 10, }, + MinValidatorCount: &common.JSONNumber{Value: 1}, + MaxValidatorCount: &common.JSONNumber{Value: 10}, + ValidatorType: validators.ECDSAValidatorType, + Validators: validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1234")), + ), } - registerStakingContractDeploymentHooks(hooks, fork) + registerStakingContractDeploymentHooks(hooks, fork, 10) + + expectedState, err := stakingHelper.PredeployStakingSCBootstrap( + fork.Validators, + stakingHelper.PredeployParams{MinValidatorCount: 1, MaxValidatorCount: 10, EpochSize: 10}, + ) + assert.NoError(t, err) assert.Nil(t, hooks.ShouldWriteTransactionFunc) assert.Nil(t, hooks.ModifyHeaderFunc) @@ -344,6 +617,7 @@ func Test_registerStakingContractDeploymentHooks(t *testing.T) { t, txn.AccountExists(staking.AddrStakingContract), ) + assert.Equal(t, expectedState.Code, txn.GetCode(staking.AddrStakingContract)) // should update only bytecode (if contract is deployed again, it returns error) assert.NoError( @@ -355,34 +629,72 @@ func Test_registerStakingContractDeploymentHooks(t *testing.T) { t, txn.AccountExists(staking.AddrStakingContract), ) + assert.Equal(t, expectedState.Code, txn.GetCode(staking.AddrStakingContract)) } func Test_getPreDeployParams(t *testing.T) { t.Parallel() tests := []struct { - name string - fork *IBFTFork - params stakingHelper.PredeployParams + name string + fork *IBFTFork + bootstrapValidatorCount uint64 + params stakingHelper.PredeployParams + wantErr bool }{ { - name: "should use the given heights", + name: "uses the given values", fork: &IBFTFork{ MinValidatorCount: &common.JSONNumber{Value: 10}, MaxValidatorCount: &common.JSONNumber{Value: 20}, }, + bootstrapValidatorCount: 10, params: stakingHelper.PredeployParams{ MinValidatorCount: 10, MaxValidatorCount: 20, + EpochSize: 10, }, }, { - name: "should use the default values", - fork: &IBFTFork{}, + name: "missing values default to bootstrap validator count", + fork: &IBFTFork{}, + bootstrapValidatorCount: 4, params: stakingHelper.PredeployParams{ - MinValidatorCount: stakingHelper.MinValidatorCount, - MaxValidatorCount: stakingHelper.MaxValidatorCount, + MinValidatorCount: 4, + MaxValidatorCount: 4, + EpochSize: 10, + }, + }, + { + name: "errors when bootstrap validator count is empty", + fork: &IBFTFork{}, + bootstrapValidatorCount: 0, + wantErr: true, + }, + { + name: "errors when min exceeds max", + fork: &IBFTFork{ + MinValidatorCount: &common.JSONNumber{Value: 5}, + MaxValidatorCount: &common.JSONNumber{Value: 4}, + }, + wantErr: true, + }, + { + name: "errors when max exceeds epoch snapshot max", + fork: &IBFTFork{ + MaxValidatorCount: &common.JSONNumber{Value: pos.MaxEpochValidatorsSnapshot + 1}, }, + bootstrapValidatorCount: 4, + wantErr: true, + }, + { + name: "errors when min exceeds bootstrap validator count", + fork: &IBFTFork{ + MinValidatorCount: &common.JSONNumber{Value: 5}, + MaxValidatorCount: &common.JSONNumber{Value: 10}, + }, + bootstrapValidatorCount: 4, + wantErr: true, }, } @@ -392,11 +704,71 @@ func Test_getPreDeployParams(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - assert.Equal( - t, - test.params, - getPreDeployParams(test.fork), - ) + params, err := getPreDeployParams(test.fork, 10, test.bootstrapValidatorCount) + if test.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, test.params, params) }) } } + +func Test_registerStakingContractDeploymentHooks_UsesHeaderValidatorsWhenForkValidatorsEmpty(t *testing.T) { + t.Parallel() + + headerValidators := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1111")), + validators.NewECDSAValidator(types.StringToAddress("0x2222")), + ) + + hooks := &hook.Hooks{} + fork := &IBFTFork{ + Deployment: &common.JSONNumber{Value: 10}, + MinValidatorCount: &common.JSONNumber{Value: 1}, + MaxValidatorCount: &common.JSONNumber{Value: 4}, + ValidatorType: validators.ECDSAValidatorType, + Validators: validators.NewECDSAValidatorSet(), + } + + registerStakingContractDeploymentHooks(hooks, fork, 10) + + txn := newTestTransition(t) + err := hooks.PreCommitState(newHeaderWithECDSAValidators(10, headerValidators), txn) + assert.NoError(t, err) + assert.True(t, txn.AccountExists(staking.AddrStakingContract)) +} + +func Test_registerStakingContractDeploymentHooks_RejectsEmptyHeaderBootstrapValidators(t *testing.T) { + t.Parallel() + + hooks := &hook.Hooks{} + fork := &IBFTFork{ + Deployment: &common.JSONNumber{Value: 10}, + MinValidatorCount: &common.JSONNumber{Value: 1}, + MaxValidatorCount: &common.JSONNumber{Value: 4}, + ValidatorType: validators.ECDSAValidatorType, + Validators: validators.NewECDSAValidatorSet(), + } + + registerStakingContractDeploymentHooks(hooks, fork, 10) + + err := hooks.PreCommitState(newHeaderWithECDSAValidators(10, validators.NewECDSAValidatorSet()), newTestTransition(t)) + assert.Error(t, err) + assert.Contains(t, err.Error(), "fallback failed") + assert.Contains(t, err.Error(), "decoded validator set is empty") +} + +func newHeaderWithECDSAValidators(number uint64, vals validators.Validators) *types.Header { + extra := &signer.IstanbulExtra{ + Validators: vals, + CommittedSeals: &signer.SerializedSeal{}, + ParentCommittedSeals: &signer.SerializedSeal{}, + } + + return &types.Header{ + Number: number, + ExtraData: extra.MarshalRLPTo(make([]byte, signer.IstanbulExtraVanity)), + } +} diff --git a/consensus/ibft/fork/manager.go b/consensus/ibft/fork/manager.go index 18d61e8..775fd81 100644 --- a/consensus/ibft/fork/manager.go +++ b/consensus/ibft/fork/manager.go @@ -5,6 +5,7 @@ import ( "github.com/hashicorp/go-hclog" "github.com/xgr-network/xgr-node/consensus/ibft/hook" + "github.com/xgr-network/xgr-node/consensus/ibft/pos" "github.com/xgr-network/xgr-node/consensus/ibft/signer" "github.com/xgr-network/xgr-node/secrets" "github.com/xgr-network/xgr-node/state" @@ -66,6 +67,7 @@ type ForkManager struct { forks IBFTForks filePath string epochSize uint64 + uptimeCfg pos.UptimeConfig // submodule lookup keyManagers map[validators.ValidatorType]signer.KeyManager @@ -95,6 +97,7 @@ func NewForkManager( secretsManager: secretManager, filePath: filePath, epochSize: epochSize, + uptimeCfg: pos.ParseUptimeConfig(ibftConfig), forks: forks, keyManagers: make(map[validators.ValidatorType]signer.KeyManager), validatorStores: make(map[store.SourceType]ValidatorStore), @@ -198,6 +201,12 @@ func (m *ForkManager) GetHooks(height uint64) HooksInterface { return hooks } +// IsPosActive returns whether the IBFT mode at the given height is PoS. +func (m *ForkManager) IsPosActive(height uint64) bool { + f := m.forks.getFork(height) + return f != nil && f.Type == PoS +} + func (m *ForkManager) getValidatorStoreByIBFTFork(fork *IBFTFork) ValidatorStore { set, ok := m.validatorStores[ibftTypesToSourceType[fork.Type]] if !ok { @@ -321,6 +330,8 @@ func (m *ForkManager) initializeHooksRegister(ibftType IBFTType) { m.hooksRegisters[PoS] = NewPoSHookRegister( m.forks, m.epochSize, + m.uptimeCfg, + m.GetSigner, ) } } diff --git a/consensus/ibft/fork/storewrapper.go b/consensus/ibft/fork/storewrapper.go index 80fabec..8ad4671 100644 --- a/consensus/ibft/fork/storewrapper.go +++ b/consensus/ibft/fork/storewrapper.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "path/filepath" + "strings" "github.com/hashicorp/go-hclog" "github.com/xgr-network/xgr-node/consensus/ibft/signer" @@ -52,6 +53,10 @@ func (w *SnapshotValidatorStoreWrapper) Close() error { // GetValidators returns validators at the specific height func (w *SnapshotValidatorStoreWrapper) GetValidators(height, _, _ uint64) (validators.Validators, error) { + if height == 0 { + return nil, errors.New("height must be greater than 0") + } + // the biggest height of blocks that have been processed before the given height return w.GetValidatorsByHeight(height - 1) } @@ -117,7 +122,8 @@ func NewSnapshotValidatorStoreWrapper( // in order to add Close and GetValidators type ContractValidatorStoreWrapper struct { *contract.ContractValidatorStore - getSigner func(uint64) (signer.Signer, error) + blockchain store.HeaderGetter + getSigner func(uint64) (signer.Signer, error) } // NewContractValidatorStoreWrapper creates *ContractValidatorStoreWrapper @@ -140,6 +146,7 @@ func NewContractValidatorStoreWrapper( return &ContractValidatorStoreWrapper{ ContractValidatorStore: contractStore, + blockchain: blockchain, getSigner: getSigner, }, nil } @@ -151,44 +158,131 @@ func (w *ContractValidatorStoreWrapper) Close() error { // GetValidators gets and returns validators at the given height func (w *ContractValidatorStoreWrapper) GetValidators( - height, epochSize, forkFrom uint64, + height, + epochSize, + forkFrom uint64, ) (validators.Validators, error) { signer, err := w.getSigner(height) if err != nil { return nil, err } - return w.GetValidatorsByHeight( + if epochSize < 2 { + return nil, errors.New("epochSize must be greater than or equal to 2") + } + + fetchingHeight := calculateContractStoreFetchingHeight( + height, + epochSize, + forkFrom, + ) + + // Before PoS fork, contract store must not be used as validator source. + if height < forkFrom { + headerHeight := uint64(0) + if height > 0 { + headerHeight = height - 1 + } + + headerSigner, signErr := w.getSigner(headerHeight) + if signErr != nil { + return nil, signErr + } + + header, ok := w.blockchain.GetHeaderByNumber(headerHeight) + if !ok { + return nil, errors.New("failed to load pre-fork header validators") + } + + return headerSigner.GetValidators(header) + } + + vals, err := w.GetValidatorsByHeight( signer.Type(), - calculateContractStoreFetchingHeight( - height, - epochSize, - forkFrom, - ), + fetchingHeight, ) + if err == nil && vals != nil && vals.Len() > 0 { + return vals, nil + } + + // Before / at PoS fork boundary we may need to use pre-fork header validator set. + // Reason: the staking contract is deployed at the PoS deployment block, but the block itself + // must still be validated using the PoS fork boundary validator source. + if height <= forkFrom { + if err != nil && !strings.Contains(err.Error(), "empty input") { + return nil, err + } + + headerSigner, signErr := w.getSigner(fetchingHeight) + if signErr != nil { + if err != nil { + return nil, err + } + + return nil, signErr + } + + header, ok := w.blockchain.GetHeaderByNumber(fetchingHeight) + if !ok { + if err != nil { + return nil, err + } + return nil, errors.New("failed to load pre-fork header validators") + } + + fromHeader, hdrErr := headerSigner.GetValidators(header) + if hdrErr != nil { + if err != nil { + return nil, err + } + return nil, hdrErr + } + + return fromHeader, nil + } + + // After PoS fork, staking contract is the source of truth. + if err != nil { + return nil, err + } + + return nil, errors.New("staking contract returned empty validator set") } // calculateContractStoreFetchingHeight calculates the block height at which ContractStore fetches validators // based on height, epoch, and fork beginning height -func calculateContractStoreFetchingHeight(height, epochSize, forkFrom uint64) uint64 { - // calculates the beginning of the epoch the given height is in - beginningEpoch := (height / epochSize) * epochSize - - // calculates the end of the previous epoch - // to determine the height to fetch validators - fetchingHeight := uint64(0) - if beginningEpoch > 0 { - fetchingHeight = beginningEpoch - 1 +func calculateContractStoreFetchingHeight( + height uint64, + epochSize uint64, + forkFrom uint64, +) uint64 { + var fetchingHeight uint64 + + if height == 0 { + fetchingHeight = 0 + } else if height%epochSize == 0 { + // Epoch boundary block itself is finalized with the previous epoch context. + fetchingHeight = height - 1 + } else { + // Non-boundary blocks should read validator data at the epoch boundary block, + // because finalize hooks on that boundary can affect the effective next-epoch set. + fetchingHeight = (height / epochSize) * epochSize } - // use the calculated height if it's bigger than or equal to from - if fetchingHeight >= forkFrom { - return fetchingHeight + // For all heights up to and including the PoS forkFrom block, keep legacy behavior + // (contract fetch height pinned to the last PoA block) so the PoS deployment block can + // still be validated with the PoA validator set. + if height <= forkFrom { + if forkFrom == 0 { + return 0 + } + return forkFrom - 1 } - if forkFrom > 0 { - return forkFrom - 1 + // After the PoS fork, never fetch from a pre-fork height. + if fetchingHeight < forkFrom { + return forkFrom } - return forkFrom + return fetchingHeight } diff --git a/consensus/ibft/fork/storewrapper_test.go b/consensus/ibft/fork/storewrapper_test.go index 91d79dc..d468438 100644 --- a/consensus/ibft/fork/storewrapper_test.go +++ b/consensus/ibft/fork/storewrapper_test.go @@ -305,7 +305,7 @@ func TestSnapshotValidatorStoreWrapperGetValidators(t *testing.T) { } ) - store, err := snapshot.NewSnapshotValidatorStore( + snapshotStore, err := snapshot.NewSnapshotValidatorStore( hclog.NewNullLogger(), &store.MockBlockchain{ HeaderFn: func() *types.Header { @@ -323,12 +323,20 @@ func TestSnapshotValidatorStoreWrapperGetValidators(t *testing.T) { assert.NoError(t, err) wrapper := SnapshotValidatorStoreWrapper{ - SnapshotValidatorStore: store, + SnapshotValidatorStore: snapshotStore, } vals, err := wrapper.GetValidators(11, 0, 0) assert.NoError(t, err) assert.Equal(t, snapshots[0].Set, vals) + + t.Run("should return error for genesis height", func(t *testing.T) { + t.Parallel() + + genesisVals, err := wrapper.GetValidators(0, 0, 0) + assert.Nil(t, genesisVals) + assert.EqualError(t, err, "height must be greater than 0") + }) } func TestSnapshotValidatorStoreWrapperClose(t *testing.T) { @@ -480,6 +488,92 @@ func TestNewContractValidatorStoreWrapperGetValidators(t *testing.T) { assert.Nil(t, res) assert.ErrorContains(t, err, "header not found at 9") }) + + t.Run("should not use contract store before forkFrom", func(t *testing.T) { + t.Parallel() + + var beginTxnCalls int + + expected := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("1")), + ) + + wrapper, err := NewContractValidatorStoreWrapper( + hclog.NewNullLogger(), + &store.MockBlockchain{ + GetHeaderByNumberFn: func(u uint64) (*types.Header, bool) { + if u == 9 { + return &types.Header{Number: 9}, true + } + + return nil, false + }, + }, + &MockExecutor{ + BeginTxnFunc: func(types.Hash, *types.Header, types.Address) (*state.Transition, error) { + beginTxnCalls++ + + return nil, errTest + }, + }, + func(u uint64) (signer.Signer, error) { + return &mockSigner{ + TypeFn: func() validators.ValidatorType { + return validators.ECDSAValidatorType + }, + GetValidatorsFn: func(*types.Header) (validators.Validators, error) { + return expected, nil + }, + }, nil + }, + ) + + assert.NoError(t, err) + + res, err := wrapper.GetValidators(10, 10, 50) + assert.NoError(t, err) + assert.Equal(t, expected, res) + assert.Zero(t, beginTxnCalls) + }) + + t.Run("should use contract store at fork boundary", func(t *testing.T) { + t.Parallel() + + var beginTxnCalls int + + wrapper, err := NewContractValidatorStoreWrapper( + hclog.NewNullLogger(), + &store.MockBlockchain{ + GetHeaderByNumberFn: func(u uint64) (*types.Header, bool) { + if u == 49 { + return &types.Header{Number: 49}, true + } + + return nil, false + }, + }, + &MockExecutor{ + BeginTxnFunc: func(types.Hash, *types.Header, types.Address) (*state.Transition, error) { + beginTxnCalls++ + + return nil, errTest + }, + }, + func(u uint64) (signer.Signer, error) { + return signer.NewSigner( + &signer.ECDSAKeyManager{}, + nil, + ), nil + }, + ) + + assert.NoError(t, err) + + res, err := wrapper.GetValidators(50, 10, 50) + assert.Nil(t, res) + assert.ErrorIs(t, err, errTest) + assert.Equal(t, 1, beginTxnCalls) + }) } func Test_calculateContractStoreFetchingHeight(t *testing.T) { @@ -514,11 +608,11 @@ func Test_calculateContractStoreFetchingHeight(t *testing.T) { expected: 9, }, { - name: "should return 9 if the height is 19 (in the second epoch)", + name: "should return 10 if the height is 19 (in the second epoch)", height: 19, epochSize: 10, forkFrom: 0, - expected: 9, + expected: 10, }, { name: "should return 49 if the height is 10 but forkFrom is 50", @@ -534,6 +628,13 @@ func Test_calculateContractStoreFetchingHeight(t *testing.T) { forkFrom: 50, expected: 59, }, + { + name: "should return 60 if the height is 61 and forkFrom is 50", + height: 61, + epochSize: 10, + forkFrom: 50, + expected: 60, + }, } for _, test := range tests { diff --git a/consensus/ibft/ibft.go b/consensus/ibft/ibft.go index 0f055f7..f582420 100644 --- a/consensus/ibft/ibft.go +++ b/consensus/ibft/ibft.go @@ -3,15 +3,22 @@ package ibft import ( "errors" "fmt" + "math/big" + "sort" + "strconv" "time" + ibftProtoMessages "github.com/0xPolygon/go-ibft/messages/proto" "github.com/armon/go-metrics" "github.com/hashicorp/go-hclog" "github.com/xgr-network/xgr-node/blockchain" + "github.com/xgr-network/xgr-node/chain" "github.com/xgr-network/xgr-node/consensus" "github.com/xgr-network/xgr-node/consensus/ibft/fork" + "github.com/xgr-network/xgr-node/consensus/ibft/pos" "github.com/xgr-network/xgr-node/consensus/ibft/proto" "github.com/xgr-network/xgr-node/consensus/ibft/signer" + xcrypto "github.com/xgr-network/xgr-node/crypto" "github.com/xgr-network/xgr-node/helper/progress" "github.com/xgr-network/xgr-node/network" "github.com/xgr-network/xgr-node/secrets" @@ -41,6 +48,13 @@ var ( ErrWrongDifficulty = errors.New("wrong difficulty") ) +type effectivePowerSnapshot struct { + hash types.Hash + powers map[string]string + totalVotingPower string + quorumThreshold string +} + type txPoolInterface interface { Prepare() Length() uint64 @@ -59,6 +73,7 @@ type forkManagerInterface interface { GetValidatorStore(uint64) (fork.ValidatorStore, error) GetValidators(uint64) (validators.Validators, error) GetHooks(uint64) fork.HooksInterface + IsPosActive(uint64) bool } // backendIBFT represents the IBFT consensus mechanism object @@ -88,6 +103,7 @@ type backendIBFT struct { epochSize uint64 quorumSizeBlockNum uint64 blockTime time.Duration // Minimum block generation time in seconds + uptimeCfg pos.UptimeConfig // Channels closeCh chan struct{} // Channel for closing @@ -95,11 +111,17 @@ type backendIBFT struct { // Factory implements the base consensus Factory method func Factory(params *consensus.Params) (consensus.Consensus, error) { + if err := alignFeePoolSplitWithPoSFork(params.Config.Params, params.Config.Config); err != nil { + return nil, err + } + // defaults for user set fields in genesis var ( epochSize = uint64(DefaultEpochSize) quorumSizeBlockNum = uint64(0) + uptimeCfg = pos.ParseUptimeConfig(params.Config.Config) ) + uptimeCfg.ChainID = params.Config.Params.ChainID if definedEpochSize, ok := params.Config.Config[KeyEpochSize]; ok { // Epoch size is defined, use the passed in one @@ -110,6 +132,12 @@ func Factory(params *consensus.Params) (consensus.Consensus, error) { epochSize = uint64(readSize) } + if err := validateEpochSize(epochSize); err != nil { + return nil, err + } + if err := validateUptimeConfig(uptimeCfg); err != nil { + return nil, err + } if rawBlockNum, ok := params.Config.Config["quorumSizeBlockNum"]; ok { // Block number specified for quorum size switch @@ -159,17 +187,68 @@ func Factory(params *consensus.Params) (consensus.Consensus, error) { epochSize: epochSize, quorumSizeBlockNum: quorumSizeBlockNum, blockTime: time.Duration(params.BlockTime) * time.Second, + uptimeCfg: uptimeCfg, // Channels closeCh: make(chan struct{}), } - // Istanbul requires a different header hash function p.SetHeaderHash() return p, nil } +// alignFeePoolSplitWithPoSFork ensures FeePoolSplit activates exactly when the first PoS IBFT fork starts. +// This removes double-gating at genesis level (PoS-from + feePoolSplit block). +func alignFeePoolSplitWithPoSFork(chainParams *chain.Params, ibftConfig map[string]interface{}) error { + if chainParams == nil { + return nil + } + if ibftConfig == nil { + return nil + } + + ibftForks, err := fork.GetIBFTForks(ibftConfig) + if err != nil { + return err + } + + firstPoSFrom := uint64(0) + foundPoS := false + + for _, ibftFork := range ibftForks { + if ibftFork.Type != fork.PoS { + continue + } + + if !foundPoS || ibftFork.From.Value < firstPoSFrom { + firstPoSFrom = ibftFork.From.Value + foundPoS = true + } + } + + if !foundPoS { + return nil + } + + if chainParams.Forks == nil { + forks := make(chain.Forks) + chainParams.Forks = &forks + } + + if feePoolSplitFork, exists := (*chainParams.Forks)[chain.FeePoolSplit]; exists { + if feePoolSplitFork.Block != firstPoSFrom { + return fmt.Errorf("feePoolSplit fork must match first PoS fork: feePoolSplit=%d firstPoSFrom=%d", feePoolSplitFork.Block, firstPoSFrom) + } + + return nil + } + + chainParams.Forks.SetFork(chain.FeePoolSplit, chain.NewFork(firstPoSFrom)) + + return nil +} + func (i *backendIBFT) Initialize() error { // register the grpc operator if i.Grpc != nil { @@ -208,12 +287,15 @@ func (i *backendIBFT) Initialize() error { // sync runs the syncer in the background to receive blocks from advanced peers func (i *backendIBFT) startSyncing() { callInsertBlockHook := func(fullBlock *types.FullBlock) bool { - if err := i.currentHooks.PostInsertBlock(fullBlock.Block); err != nil { + hooks := i.forkManager.GetHooks(fullBlock.Block.Number()) + + if err := hooks.PostInsertBlock(fullBlock.Block); err != nil { i.logger.Error("failed to call PostInsertBlock", "height", fullBlock.Block.Header.Number, "error", err) } if err := i.updateCurrentModules(fullBlock.Block.Number() + 1); err != nil { i.logger.Error("failed to update sub modules", "height", fullBlock.Block.Number()+1, "err", err) + i.txpool.SetSealing(false) } i.txpool.ResetWithHeaders(fullBlock.Block.Header) @@ -293,6 +375,22 @@ func (i *backendIBFT) startConsensus() { "height", pending, "err", err, ) + + i.txpool.SetSealing(false) + + retryTimer := time.NewTimer(i.blockTime) + + select { + case <-syncerBlockCh: + retryTimer.Stop() + goto nextHeight + case <-retryTimer.C: + goto nextHeight + case <-i.closeCh: + retryTimer.Stop() + + return + } } // Update the No.of validator metric @@ -301,25 +399,31 @@ func (i *backendIBFT) startConsensus() { isValidator = i.isActiveValidator() i.txpool.SetSealing(isValidator) + sequenceCh = make(<-chan struct{}) if isValidator { sequenceCh = i.consensus.runSequence(pending) } - select { - case <-syncerBlockCh: - if isValidator { - i.consensus.stopSequence() - i.logger.Info("canceled sequence", "sequence", pending) - } - case <-sequenceCh: - case <-i.closeCh: - if isValidator { - i.consensus.stopSequence() - } + for { + select { + case <-syncerBlockCh: + if isValidator { + i.consensus.stopSequence() + i.logger.Info("canceled sequence", "sequence", pending) + } + goto nextHeight + case <-sequenceCh: + goto nextHeight + case <-i.closeCh: + if isValidator { + i.consensus.stopSequence() + } - return + return + } } + nextHeight: } } @@ -328,6 +432,18 @@ func (i *backendIBFT) isActiveValidator() bool { return i.currentValidators.Includes(i.currentSigner.Address()) } +// RoundStarts notifies the backend that IBFT is about to start a new round. +func (i *backendIBFT) RoundStarts(view *ibftProtoMessages.View) error { + _ = i + _ = view + return nil +} + +// SequenceCancelled notifies the backend that the active sequence was cancelled. +func (i *backendIBFT) SequenceCancelled(*ibftProtoMessages.View) error { + return nil +} + // updateMetrics will update various metrics based on the given block // currently we capture No.of Txs and block interval metrics using this function func (i *backendIBFT) updateMetrics(block *types.Block) { @@ -374,7 +490,6 @@ func (i *backendIBFT) verifyHeaderImpl( if _, err := headerSigner.GetIBFTExtra(header); err != nil { return err } - // verify the ProposerSeal if err := verifyProposerSeal( header, @@ -391,7 +506,6 @@ func (i *backendIBFT) verifyHeaderImpl( ); err != nil { return err } - // Additional header verification if err := hooks.VerifyHeader(header); err != nil { return err @@ -402,10 +516,18 @@ func (i *backendIBFT) verifyHeaderImpl( // VerifyHeader wrapper for verifying headers func (i *backendIBFT) VerifyHeader(header *types.Header) error { - parent, ok := i.blockchain.GetHeaderByNumber(header.Number - 1) + parent, ok := i.blockchain.GetHeaderByHash(header.ParentHash) if !ok { return fmt.Errorf( - "unable to get parent header for block number %d", + "unable to get parent header %s for block number %d", + header.ParentHash, + header.Number, + ) + } + if parent.Number+1 != header.Number { + return fmt.Errorf( + "invalid parent header number %d for block number %d", + parent.Number, header.Number, ) } @@ -436,7 +558,6 @@ func (i *backendIBFT) VerifyHeader(header *types.Header) error { } hashForCommittedSeal, err := i.calculateProposalHash( - headerSigner, header, extra.RoundNumber, ) @@ -446,15 +567,27 @@ func (i *backendIBFT) VerifyHeader(header *types.Header) error { // verify the Committed Seals // CommittedSeals exists only in the finalized header + committedSealQuorum := i.quorumSize(header.Number)(validators) + if i.forkManager.IsPosActive(header.Number) { + // In PoS weighted mode, signer-level verification is used only for + // structural/cryptographic validation. Weighted voting power check below + // is the sole quorum acceptance rule. + committedSealQuorum = 0 + } + if err := headerSigner.VerifyCommittedSeals( hashForCommittedSeal, extra.CommittedSeals, validators, - i.quorumSize(header.Number)(validators), + committedSealQuorum, ); err != nil { return err } + if err := i.verifyWeightedCommittedPower(header.Number, hashForCommittedSeal, extra.CommittedSeals, validators, header, parent, extra.RoundNumber); err != nil { + return err + } + return nil } @@ -494,9 +627,49 @@ func (i *backendIBFT) GetBlockCreator(header *types.Header) (types.Address, erro // PreCommitState a hook to be called before finalizing state transition on inserting block func (i *backendIBFT) PreCommitState(block *types.Block, txn *state.Transition) error { - hooks := i.forkManager.GetHooks(block.Number()) + if block == nil || block.Header == nil { + return fmt.Errorf("missing block header in PreCommitState") + } + // Deterministic uptime accounting MUST use the parent header (already sealed/final). + // Counting on the current header breaks determinism during buildBlock because proposer seal/hash + // may not be present yet. + parentHeader, ok := i.blockchain.GetHeaderByHash(block.Header.ParentHash) + if !ok { + return fmt.Errorf("parent header %s not found for block %d", block.Header.ParentHash, block.Number()) + } + + parentPosActive := i.forkManager.IsPosActive(parentHeader.Number) + requiresValidators := parentPosActive + var parentValidators validators.Validators + if requiresValidators { + vLoaded, vErr := i.forkManager.GetValidators(parentHeader.Number) + if vErr != nil { + if parentPosActive { + return fmt.Errorf("failed to get validators for uptime at height %d: %w", parentHeader.Number, vErr) + } - return hooks.PreCommitState(block.Header, txn) + return fmt.Errorf("failed to get validators at height %d: %w", parentHeader.Number, vErr) + } + parentValidators = vLoaded + } + + if parentPosActive { + parentSigner, err := i.forkManager.GetSigner(parentHeader.Number) + if err != nil { + return fmt.Errorf("failed to get signer for uptime at height %d: %w", parentHeader.Number, err) + } + if parentSigner == nil { + return fmt.Errorf("missing signer for uptime at height %d", parentHeader.Number) + } + if err := pos.RecordBlockUptime(parentHeader, i.epochSize, parentValidators, parentSigner, i.uptimeCfg, txn); err != nil { + return fmt.Errorf("failed to record parent uptime for block %d: %w", block.Number(), err) + } + } + hooks := i.forkManager.GetHooks(block.Number()) + if err := hooks.PreCommitState(block.Header, txn); err != nil { + return err + } + return nil } // GetEpoch returns the current epoch @@ -534,7 +707,7 @@ func (i *backendIBFT) Close() error { // SetHeaderHash updates hash calculation function for IBFT func (i *backendIBFT) SetHeaderHash() { - types.HeaderHash = func(h *types.Header) types.Hash { + types.SetHeaderHash(func(h *types.Header) types.Hash { signer, err := i.forkManager.GetSigner(h.Number) if err != nil { return types.ZeroHash @@ -546,7 +719,7 @@ func (i *backendIBFT) SetHeaderHash() { } return hash - } + }) } // GetBridgeProvider returns an instance of BridgeDataProvider @@ -615,7 +788,6 @@ func (i *backendIBFT) verifyParentCommittedSeals( } parentHash, err := i.calculateProposalHash( - parentSigner, parentHeader, parentExtra.RoundNumber, ) @@ -625,15 +797,260 @@ func (i *backendIBFT) verifyParentCommittedSeals( // if shouldVerifyParentCommittedSeals is false, skip the verification // when header doesn't have Parent Committed Seals (Backward Compatibility) - return parentSigner.VerifyParentCommittedSeals( + parentCommittedSealQuorum := i.quorumSize(parent.Number)(parentValidators) + if i.forkManager.IsPosActive(parent.Number) { + // In PoS weighted mode, signer-level verification remains a + // structural/cryptographic guard only. Weighted parent committed power + // verification below is the sole quorum acceptance rule. + parentCommittedSealQuorum = 0 + } + + if err := parentSigner.VerifyParentCommittedSeals( parentHash, header, parentValidators, - i.quorumSize(parent.Number)(parentValidators), + parentCommittedSealQuorum, shouldVerifyParentCommittedSeals, + ); err != nil { + return err + } + + if !i.forkManager.IsPosActive(parent.Number) { + return nil + } + + parentCommittedSealsGetter, ok := parentSigner.(interface { + GetParentCommittedSeals(*types.Header) (signer.Seals, error) + }) + if !ok { + return errors.New("parent signer does not support extracting parent committed seals") + } + + parentCommittedSeals, err := parentCommittedSealsGetter.GetParentCommittedSeals(header) + if err != nil { + return err + } + if parentCommittedSeals == nil || parentCommittedSeals.Num() == 0 { + return fmt.Errorf("missing parent committed seals in PoS weighted mode at height %d", parent.Number) + } + + parentParentHeader, ok := i.blockchain.GetHeaderByHash(parentHeader.ParentHash) + if !ok { + return fmt.Errorf("header %s not found", parentHeader.ParentHash) + } + + // ParentCommittedSeals stored in child header are signatures for the parent proposal. + // Verify weighted quorum in the same consensus context: + // state at parent height + evidence transition from grandparent -> parent. + return i.verifyWeightedCommittedPower( + parent.Number, + parentHash, + parentCommittedSeals, + parentValidators, + parentHeader, + parentParentHeader, + parentExtra.RoundNumber, ) } +func validateUptimeConfig(cfg pos.UptimeConfig) error { + if cfg.MicroEpochSize == 0 { + return nil + } + + if cfg.MicroEpochNominalWeight == 0 { + return errors.New("microEpochNominalWeightUnits must be greater than 0 when microEpochSize is enabled") + } + if cfg.MicroEpochInactivityDecayBps == 0 { + return errors.New("microEpochInactivityDecayBps must be greater than 0 when microEpochSize is enabled") + } + if cfg.MicroEpochInactivityDecayBps > 10000 { + return errors.New("microEpochInactivityDecayBps must be less than or equal to 10000") + } + + return nil +} + +func validateEpochSize(epochSize uint64) error { + if epochSize < 2 { + return errors.New("epochSize must be greater than or equal to 2") + } + + return nil +} + +func (i *backendIBFT) verifyWeightedCommittedPower( + height uint64, + hash types.Hash, + committedSeals signer.Seals, + validatorSet validators.Validators, + proposalHeader *types.Header, + parentHeader *types.Header, + roundNumber *uint64, +) error { + if !i.forkManager.IsPosActive(height) { + return nil + } + + votingPowers, err := i.getVotingPowersWithProposal(height, validatorSet, proposalHeader, parentHeader, roundNumber) + if err != nil { + return err + } + + signers, err := committedSealSigners(hash, committedSeals, validatorSet) + if err != nil { + return err + } + + collected := new(big.Int) + total := new(big.Int) + powerByValidator := make(map[string]string, validatorSet.Len()) + for idx := 0; idx < validatorSet.Len(); idx++ { + addr := validatorSet.At(uint64(idx)).Addr() + p, ok := votingPowers[types.AddressToString(addr)] + if !ok { + p = big.NewInt(0) + } + total.Add(total, p) + powerByValidator[addr.String()] = p.String() + if _, ok := signers[addr]; ok { + collected.Add(collected, p) + } + } + + if !hasWeightedQuorum(collected, total) { + signerAddrs := make([]string, 0, len(signers)) + for addr := range signers { + signerAddrs = append(signerAddrs, addr.String()) + } + sort.Strings(signerAddrs) + quorumThreshold := weightedQuorumThreshold(total) + proposalNumber := uint64(0) + proposalHash := types.ZeroHash.String() + proposalTimestamp := uint64(0) + if proposalHeader != nil { + proposalNumber = proposalHeader.Number + proposalHash = proposalHeader.Hash.String() + proposalTimestamp = proposalHeader.Timestamp + } + parentNumber := uint64(0) + parentHash := types.ZeroHash.String() + parentTimestamp := uint64(0) + if parentHeader != nil { + parentNumber = parentHeader.Number + parentHash = parentHeader.Hash.String() + parentTimestamp = parentHeader.Timestamp + } + roundDisplay := "nil" + if roundNumber != nil { + roundDisplay = strconv.FormatUint(*roundNumber, 10) + } + committedSealsCount := 0 + if committedSeals != nil { + committedSealsCount = committedSeals.Num() + } + + return fmt.Errorf( + "not enough weighted committed power: height=%d round=%s committedSeals=%d collected=%s quorum=%s total=%s proposalNumber=%d proposalHash=%s proposalTimestamp=%d parentNumber=%d parentHash=%s parentTimestamp=%d signerAddresses=%v votingPowers=%v: %w", + height, + roundDisplay, + committedSealsCount, + collected.String(), + quorumThreshold.String(), + total.String(), + proposalNumber, + proposalHash, + proposalTimestamp, + parentNumber, + parentHash, + parentTimestamp, + signerAddrs, + powerByValidator, + signer.ErrNotEnoughCommittedSeals, + ) + } + + return nil +} + +func weightedQuorumThreshold(total *big.Int) *big.Int { + if total == nil || total.Sign() <= 0 { + return big.NewInt(0) + } + + // Equivalent to ceil((2*total)/3), integer-safe. + // Keep this aligned with the classic PoA quorum behavior for equal weights. + quorum := new(big.Int).Mul(total, big.NewInt(2)) + quorum.Add(quorum, big.NewInt(2)) + quorum.Div(quorum, big.NewInt(3)) + + return quorum +} + +func hasWeightedQuorum(collected, total *big.Int) bool { + if total == nil || total.Sign() <= 0 { + return false + } + if collected == nil || collected.Sign() <= 0 { + return false + } + + return collected.Cmp(weightedQuorumThreshold(total)) >= 0 +} + +func (i *backendIBFT) getVotingPowersWithProposal( + height uint64, + validatorSet validators.Validators, + proposalHeader *types.Header, + parentHeader *types.Header, + _ *uint64, +) (map[string]*big.Int, error) { + _ = proposalHeader + result, _, err := i.effectiveVotingPowerSnapshot(height, validatorSet, parentHeader) + return result, err +} + +func committedSealSigners( + hash types.Hash, + committedSeals signer.Seals, + validatorSet validators.Validators, +) (map[types.Address]struct{}, error) { + out := map[types.Address]struct{}{} + if committedSeals == nil { + return out, nil + } + + switch seals := committedSeals.(type) { + case *signer.AggregatedSeal: + if seals.Bitmap == nil { + return out, nil + } + for idx := 0; idx < validatorSet.Len(); idx++ { + if seals.Bitmap.Bit(idx) == 0 { + continue + } + out[validatorSet.At(uint64(idx)).Addr()] = struct{}{} + } + return out, nil + case *signer.SerializedSeal: + digest := signer.LegacyCommitDigest(hash.Bytes()) + for _, raw := range *seals { + pub, err := xcrypto.RecoverPubkey(raw, digest) + if err != nil { + return nil, err + } + addr := xcrypto.PubKeyToAddress(pub) + if !validatorSet.Includes(addr) { + return nil, signer.ErrNonValidatorCommittedSeal + } + out[addr] = struct{}{} + } + return out, nil + default: + return nil, signer.ErrInvalidCommittedSealType + } +} + // getModulesFromForkManager is a helper function to get all modules from ForkManager func getModulesFromForkManager(forkManager forkManagerInterface, height uint64) ( signer.Signer, diff --git a/consensus/ibft/ibft_config_validation_test.go b/consensus/ibft/ibft_config_validation_test.go new file mode 100644 index 0000000..c3ce1fc --- /dev/null +++ b/consensus/ibft/ibft_config_validation_test.go @@ -0,0 +1,35 @@ +package ibft + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/consensus/ibft/pos" +) + +func TestValidateUptimeConfig(t *testing.T) { + t.Run("disabled micro epoch is valid", func(t *testing.T) { + require.NoError(t, validateUptimeConfig(pos.UptimeConfig{MicroEpochSize: 0})) + }) + + t.Run("enabled micro epoch requires nominal weight", func(t *testing.T) { + err := validateUptimeConfig(pos.UptimeConfig{MicroEpochSize: 8, MicroEpochInactivityDecayBps: 9000, MicroEpochNominalWeight: 0}) + require.Error(t, err) + }) + + t.Run("enabled micro epoch requires decay bps", func(t *testing.T) { + err := validateUptimeConfig(pos.UptimeConfig{MicroEpochSize: 8, MicroEpochInactivityDecayBps: 0, MicroEpochNominalWeight: 10000}) + require.Error(t, err) + }) + + t.Run("enabled micro epoch rejects decay over 10000", func(t *testing.T) { + err := validateUptimeConfig(pos.UptimeConfig{MicroEpochSize: 8, MicroEpochInactivityDecayBps: 10001, MicroEpochNominalWeight: 10000}) + require.Error(t, err) + }) +} + +func TestValidateEpochSize(t *testing.T) { + require.EqualError(t, validateEpochSize(0), "epochSize must be greater than or equal to 2") + require.EqualError(t, validateEpochSize(1), "epochSize must be greater than or equal to 2") + require.NoError(t, validateEpochSize(2)) +} diff --git a/consensus/ibft/ibft_gate_alignment_test.go b/consensus/ibft/ibft_gate_alignment_test.go new file mode 100644 index 0000000..92ea907 --- /dev/null +++ b/consensus/ibft/ibft_gate_alignment_test.go @@ -0,0 +1,72 @@ +package ibft + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/chain" +) + +func Test_alignFeePoolSplitWithPoSFork(t *testing.T) { + t.Run("sets FeePoolSplit to first PoS from", func(t *testing.T) { + forks := chain.Forks{} + params := &chain.Params{Forks: &forks} + ibftConfig := map[string]interface{}{ + "types": []interface{}{ + map[string]interface{}{"type": "PoA", "from": 0.0, "to": 99.0}, + map[string]interface{}{"type": "PoS", "from": 100.0}, + }, + } + + require.NoError(t, alignFeePoolSplitWithPoSFork(params, ibftConfig)) + require.True(t, params.Forks.IsActive(chain.FeePoolSplit, 100)) + require.False(t, params.Forks.IsActive(chain.FeePoolSplit, 99)) + }) + + t.Run("accepts explicit FeePoolSplit matching first PoS from", func(t *testing.T) { + forks := chain.Forks{chain.FeePoolSplit: chain.NewFork(200)} + params := &chain.Params{Forks: &forks} + ibftConfig := map[string]interface{}{ + "types": []interface{}{ + map[string]interface{}{"type": "PoA", "from": 0.0, "to": 199.0}, + map[string]interface{}{"type": "PoS", "from": 200.0}, + }, + } + + require.NoError(t, alignFeePoolSplitWithPoSFork(params, ibftConfig)) + require.True(t, params.Forks.IsActive(chain.FeePoolSplit, 200)) + require.False(t, params.Forks.IsActive(chain.FeePoolSplit, 199)) + }) + + t.Run("returns error when explicit FeePoolSplit does not match first PoS from", func(t *testing.T) { + forks := chain.Forks{chain.FeePoolSplit: chain.NewFork(500)} + params := &chain.Params{Forks: &forks} + ibftConfig := map[string]interface{}{ + "types": []interface{}{ + map[string]interface{}{"type": "PoA", "from": 0.0, "to": 199.0}, + map[string]interface{}{"type": "PoS", "from": 200.0}, + }, + } + + require.EqualError(t, alignFeePoolSplitWithPoSFork(params, ibftConfig), "feePoolSplit fork must match first PoS fork: feePoolSplit=500 firstPoSFrom=200") + }) + + t.Run("does nothing when PoS is not configured", func(t *testing.T) { + forks := chain.Forks{chain.FeePoolSplit: chain.NewFork(42)} + params := &chain.Params{Forks: &forks} + ibftConfig := map[string]interface{}{"type": "PoA"} + + require.NoError(t, alignFeePoolSplitWithPoSFork(params, ibftConfig)) + require.True(t, params.Forks.IsActive(chain.FeePoolSplit, 42)) + require.False(t, params.Forks.IsActive(chain.FeePoolSplit, 41)) + }) + + t.Run("does nothing for legacy config without types", func(t *testing.T) { + forks := chain.Forks{} + params := &chain.Params{Forks: &forks} + ibftConfig := map[string]interface{}{"type": "PoA"} + + require.NoError(t, alignFeePoolSplitWithPoSFork(params, ibftConfig)) + require.False(t, params.Forks.IsActive(chain.FeePoolSplit, 1)) + }) +} diff --git a/consensus/ibft/ibft_hardening_test.go b/consensus/ibft/ibft_hardening_test.go new file mode 100644 index 0000000..b828120 --- /dev/null +++ b/consensus/ibft/ibft_hardening_test.go @@ -0,0 +1,323 @@ +package ibft + +import ( + "errors" + "sync" + "testing" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/blockchain" + "github.com/xgr-network/xgr-node/chain" + "github.com/xgr-network/xgr-node/consensus/ibft/fork" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + xcrypto "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/helper/progress" + "github.com/xgr-network/xgr-node/state" + itrie "github.com/xgr-network/xgr-node/state/immutable-trie" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +type hardeningTxPool struct { + mu sync.Mutex + sealing []bool + callsCh chan bool +} + +func newHardeningTxPool() *hardeningTxPool { + return &hardeningTxPool{callsCh: make(chan bool, 16)} +} + +func (m *hardeningTxPool) Prepare() {} +func (m *hardeningTxPool) Length() uint64 { return 0 } +func (m *hardeningTxPool) Peek() *types.Transaction { return nil } +func (m *hardeningTxPool) Pop(tx *types.Transaction) {} +func (m *hardeningTxPool) Drop(tx *types.Transaction) {} +func (m *hardeningTxPool) Demote(tx *types.Transaction) {} +func (m *hardeningTxPool) ResetWithHeaders(headers ...*types.Header) {} +func (m *hardeningTxPool) SetSealing(v bool) { + m.mu.Lock() + m.sealing = append(m.sealing, v) + m.mu.Unlock() + + select { + case m.callsCh <- v: + default: + } +} + +func (m *hardeningTxPool) seenTrue() bool { + m.mu.Lock() + defer m.mu.Unlock() + + for _, v := range m.sealing { + if v { + return true + } + } + + return false +} + +type hardeningHooks struct { + mu sync.Mutex + postInsertHeights []uint64 +} + +func (h *hardeningHooks) ShouldWriteTransactions(uint64) bool { return true } +func (h *hardeningHooks) ModifyHeader(*types.Header, types.Address) error { + return nil +} +func (h *hardeningHooks) VerifyHeader(*types.Header) error { return nil } +func (h *hardeningHooks) VerifyBlock(*types.Block) error { return nil } +func (h *hardeningHooks) ProcessHeader(*types.Header) error { + return nil +} +func (h *hardeningHooks) PreCommitState(*types.Header, *state.Transition) error { + return nil +} +func (h *hardeningHooks) PostInsertBlock(block *types.Block) error { + h.mu.Lock() + h.postInsertHeights = append(h.postInsertHeights, block.Number()) + h.mu.Unlock() + + return nil +} + +func (h *hardeningHooks) heights() []uint64 { + h.mu.Lock() + defer h.mu.Unlock() + + cloned := make([]uint64, len(h.postInsertHeights)) + copy(cloned, h.postInsertHeights) + + return cloned +} + +type hardeningForkManager struct { + signer signer.Signer + validators validators.Validators + validatorsByH map[uint64]validators.Validators + getValidators func(uint64) (validators.Validators, error) + hooksByHeight map[uint64]fork.HooksInterface + getSignerError map[uint64]error +} + +func (m *hardeningForkManager) Initialize() error { return nil } +func (m *hardeningForkManager) Close() error { return nil } +func (m *hardeningForkManager) GetSigner(height uint64) (signer.Signer, error) { + if err := m.getSignerError[height]; err != nil { + return nil, err + } + + return m.signer, nil +} +func (m *hardeningForkManager) GetValidatorStore(uint64) (fork.ValidatorStore, error) { + return nil, nil +} +func (m *hardeningForkManager) GetValidators(height uint64) (validators.Validators, error) { + if m.getValidators != nil { + return m.getValidators(height) + } + + if vals, ok := m.validatorsByH[height]; ok { + return vals, nil + } + + return m.validators, nil +} +func (m *hardeningForkManager) GetHooks(height uint64) fork.HooksInterface { + if hooks, ok := m.hooksByHeight[height]; ok { + return hooks + } + + return &hardeningHooks{} +} +func (m *hardeningForkManager) IsPosActive(uint64) bool { return false } + +type hardeningSyncer struct { + syncFn func(func(*types.FullBlock) bool) error +} + +func (s *hardeningSyncer) Start() error { return nil } +func (s *hardeningSyncer) Close() error { return nil } +func (s *hardeningSyncer) GetSyncProgression() *progress.Progression { return nil } +func (s *hardeningSyncer) HasSyncPeer() bool { return false } +func (s *hardeningSyncer) Sync(hook func(*types.FullBlock) bool) error { return s.syncFn(hook) } + +func newHardeningSigner(t *testing.T) signer.Signer { + t.Helper() + + key, err := xcrypto.GenerateECDSAKey() + require.NoError(t, err) + keyManager := signer.NewECDSAKeyManagerFromKey(key) + + return signer.NewSigner(keyManager, keyManager) +} + +func TestStartConsensus_UpdateCurrentModulesError_DisablesSealing(t *testing.T) { + t.Parallel() + + genesis := &types.Header{Number: 0, Difficulty: 1} + genesis.ComputeHash() + h1 := &types.Header{Number: 1, ParentHash: genesis.Hash, Difficulty: 1} + h1.ComputeHash() + bc := blockchain.NewTestBlockchain(t, []*types.Header{genesis, h1}) + + testSigner := newHardeningSigner(t) + staleValidators := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(testSigner.Address())) + + txpool := newHardeningTxPool() + + backend := &backendIBFT{ + logger: hclog.NewNullLogger(), + blockchain: bc, + txpool: txpool, + forkManager: &hardeningForkManager{getSignerError: map[uint64]error{2: errors.New("boom")}}, + currentSigner: testSigner, + currentValidators: staleValidators, + blockTime: 5 * time.Millisecond, + closeCh: make(chan struct{}), + } + + done := make(chan struct{}) + go func() { + backend.startConsensus() + close(done) + }() + + select { + case sealing := <-txpool.callsCh: + require.False(t, sealing) + case <-time.After(time.Second): + t.Fatal("timeout waiting for SetSealing call") + } + + require.False(t, txpool.seenTrue(), "sealing must not be enabled when updateCurrentModules fails") + + close(backend.closeCh) + + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("timeout waiting for startConsensus shutdown") + } +} + +func TestStartSyncing_PostInsertBlock_UsesImportedBlockHooks(t *testing.T) { + t.Parallel() + + staleHooks := &hardeningHooks{} + importedHooks := &hardeningHooks{} + nextHeightHooks := &hardeningHooks{} + + testSigner := newHardeningSigner(t) + valSet := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(testSigner.Address())) + forkManager := &hardeningForkManager{ + signer: testSigner, + validators: valSet, + hooksByHeight: map[uint64]fork.HooksInterface{ + 7: importedHooks, + 8: nextHeightHooks, + }, + } + + txpool := newHardeningTxPool() + block := &types.Block{Header: &types.Header{Number: 7}} + + backend := &backendIBFT{ + logger: hclog.NewNullLogger(), + txpool: txpool, + forkManager: forkManager, + currentHooks: staleHooks, + syncer: &hardeningSyncer{syncFn: func(hook func(*types.FullBlock) bool) error { + hook(&types.FullBlock{Block: block}) + return nil + }}, + } + + backend.startSyncing() + + require.Empty(t, staleHooks.heights(), "stale currentHooks must not be used for PostInsertBlock") + require.Equal(t, []uint64{7}, importedHooks.heights(), "PostInsertBlock must run on hooks resolved for imported height") +} + +func TestStartConsensus_ValidatorToNonValidator_DoesNotReuseClosedSequenceChannel(t *testing.T) { + t.Parallel() + + genesis := &types.Header{Number: 0, Difficulty: 1} + genesis.ComputeHash() + h1 := &types.Header{Number: 1, ParentHash: genesis.Hash, Difficulty: 1} + h1.ComputeHash() + bc := blockchain.NewTestBlockchain(t, []*types.Header{genesis, h1}) + + testSigner := newHardeningSigner(t) + otherSigner := newHardeningSigner(t) + + validatorSetWithSelf := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(testSigner.Address())) + validatorSetWithoutSelf := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(otherSigner.Address())) + + txpool := newHardeningTxPool() + st := itrie.NewState(itrie.NewMemoryStorage()) + executor := state.NewExecutor(&chain.Params{Forks: chain.AllForksEnabled}, st, hclog.NewNullLogger()) + + getValidatorsCalls := 0 + backend := &backendIBFT{ + logger: hclog.NewNullLogger(), + blockchain: bc, + executor: executor, + txpool: txpool, + forkManager: &hardeningForkManager{ + signer: testSigner, + getValidators: func(uint64) (validators.Validators, error) { + if getValidatorsCalls == 0 { + getValidatorsCalls++ + return validatorSetWithSelf, nil + } + + return validatorSetWithoutSelf, nil + }, + }, + blockTime: 5 * time.Millisecond, + closeCh: make(chan struct{}), + } + backend.consensus = newIBFT(hclog.NewNullLogger(), backend, backend) + + done := make(chan struct{}) + go func() { + backend.startConsensus() + close(done) + }() + + select { + case sealing := <-txpool.callsCh: + require.True(t, sealing) + case <-time.After(time.Second): + t.Fatal("timeout waiting for validator SetSealing(true) call") + } + + backend.consensus.stopSequence() + + select { + case sealing := <-txpool.callsCh: + require.False(t, sealing) + case <-time.After(time.Second): + t.Fatal("timeout waiting for non-validator SetSealing(false) call") + } + + select { + case sealing := <-txpool.callsCh: + t.Fatalf("unexpected extra SetSealing(%v) call, non-validator path should wait instead of spinning", sealing) + case <-time.After(100 * time.Millisecond): + } + + close(backend.closeCh) + + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("timeout waiting for startConsensus shutdown") + } +} diff --git a/consensus/ibft/ibft_parent_committed_seals_test.go b/consensus/ibft/ibft_parent_committed_seals_test.go new file mode 100644 index 0000000..aa9de8c --- /dev/null +++ b/consensus/ibft/ibft_parent_committed_seals_test.go @@ -0,0 +1,116 @@ +package ibft + +import ( + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/blockchain" + "github.com/xgr-network/xgr-node/consensus/ibft/fork" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +type parentSealsTestSigner struct { + extra *signer.IstanbulExtra + seals signer.Seals +} + +func (m *parentSealsTestSigner) Type() validators.ValidatorType { return validators.ECDSAValidatorType } +func (m *parentSealsTestSigner) Address() types.Address { return types.ZeroAddress } +func (m *parentSealsTestSigner) InitIBFTExtra(*types.Header, validators.Validators, signer.Seals) { +} +func (m *parentSealsTestSigner) GetIBFTExtra(*types.Header) (*signer.IstanbulExtra, error) { + return m.extra, nil +} +func (m *parentSealsTestSigner) GetValidators(*types.Header) (validators.Validators, error) { + return m.extra.Validators, nil +} +func (m *parentSealsTestSigner) WriteProposerSeal(h *types.Header) (*types.Header, error) { + return h, nil +} +func (m *parentSealsTestSigner) EcrecoverFromHeader(*types.Header) (types.Address, error) { + return types.ZeroAddress, nil +} +func (m *parentSealsTestSigner) CreateCommittedSeal([]byte) ([]byte, error) { return nil, nil } +func (m *parentSealsTestSigner) VerifyCommittedSeal(validators.Validators, types.Address, []byte, []byte) error { + return nil +} +func (m *parentSealsTestSigner) WriteCommittedSeals(*types.Header, uint64, map[types.Address][]byte) (*types.Header, error) { + return nil, nil +} +func (m *parentSealsTestSigner) VerifyCommittedSeals(types.Hash, signer.Seals, validators.Validators, int) error { + return nil +} +func (m *parentSealsTestSigner) VerifyParentCommittedSeals(types.Hash, *types.Header, validators.Validators, int, bool) error { + return nil +} +func (m *parentSealsTestSigner) GetParentCommittedSeals(*types.Header) (signer.Seals, error) { + return m.seals, nil +} +func (m *parentSealsTestSigner) SignIBFTMessage([]byte) ([]byte, error) { return nil, nil } +func (m *parentSealsTestSigner) EcrecoverFromIBFTMessage([]byte, []byte) (types.Address, error) { + return types.ZeroAddress, nil +} +func (m *parentSealsTestSigner) CalculateHeaderHash(*types.Header) (types.Hash, error) { + return types.ZeroHash, nil +} +func (m *parentSealsTestSigner) FilterHeaderForHash(h *types.Header) (*types.Header, error) { + return h, nil +} + +type parentSealsForkManager struct { + signer signer.Signer + vals validators.Validators + pos bool +} + +func (m *parentSealsForkManager) Initialize() error { return nil } +func (m *parentSealsForkManager) Close() error { return nil } +func (m *parentSealsForkManager) GetSigner(uint64) (signer.Signer, error) { return m.signer, nil } +func (m *parentSealsForkManager) GetValidatorStore(uint64) (fork.ValidatorStore, error) { + return nil, nil +} +func (m *parentSealsForkManager) GetValidators(uint64) (validators.Validators, error) { + return m.vals, nil +} +func (m *parentSealsForkManager) GetHooks(uint64) fork.HooksInterface { return nil } +func (m *parentSealsForkManager) IsPosActive(uint64) bool { return m.pos } + +func TestVerifyParentCommittedSeals_PoSRequiresNonEmptySeals(t *testing.T) { + val := validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")) + valSet := validators.NewECDSAValidatorSet(val) + round := uint64(1) + ts := &parentSealsTestSigner{extra: &signer.IstanbulExtra{Validators: valSet, RoundNumber: &round}, seals: nil} + + genesis := &types.Header{Number: 0, Difficulty: 1} + genesis.ComputeHash() + parent := &types.Header{Number: 1, ParentHash: genesis.Hash, Difficulty: 1} + parent.ComputeHash() + child := &types.Header{Number: 2, ParentHash: parent.Hash, Difficulty: 1} + bc := blockchain.NewTestBlockchain(t, []*types.Header{genesis, parent}) + + backend := &backendIBFT{logger: hclog.NewNullLogger(), blockchain: bc, forkManager: &parentSealsForkManager{signer: ts, vals: valSet, pos: true}} + + err := backend.verifyParentCommittedSeals(parent, child, true) + require.EqualError(t, err, "missing parent committed seals in PoS weighted mode at height 1") +} + +func TestVerifyParentCommittedSeals_NonPoSKeepsCompatibility(t *testing.T) { + val := validators.NewECDSAValidator(types.StringToAddress("0x1000000000000000000000000000000000000001")) + valSet := validators.NewECDSAValidatorSet(val) + round := uint64(1) + ts := &parentSealsTestSigner{extra: &signer.IstanbulExtra{Validators: valSet, RoundNumber: &round}, seals: nil} + + genesis := &types.Header{Number: 0, Difficulty: 1} + genesis.ComputeHash() + parent := &types.Header{Number: 1, ParentHash: genesis.Hash, Difficulty: 1} + parent.ComputeHash() + child := &types.Header{Number: 2, ParentHash: parent.Hash, Difficulty: 1} + bc := blockchain.NewTestBlockchain(t, []*types.Header{genesis, parent}) + + backend := &backendIBFT{logger: hclog.NewNullLogger(), blockchain: bc, forkManager: &parentSealsForkManager{signer: ts, vals: valSet, pos: false}} + + require.NoError(t, backend.verifyParentCommittedSeals(parent, child, true)) +} diff --git a/consensus/ibft/messages.go b/consensus/ibft/messages.go index f4cf0a2..c4e0342 100644 --- a/consensus/ibft/messages.go +++ b/consensus/ibft/messages.go @@ -6,7 +6,7 @@ import ( protoIBFT "github.com/0xPolygon/go-ibft/messages/proto" ) -func (i *backendIBFT) signMessage(msg *protoIBFT.Message) *protoIBFT.Message { +func (i *backendIBFT) signMessage(msg *protoIBFT.IbftMessage) *protoIBFT.IbftMessage { raw, err := proto.Marshal(msg) if err != nil { return nil @@ -23,7 +23,7 @@ func (i *backendIBFT) BuildPrePrepareMessage( rawProposal []byte, certificate *protoIBFT.RoundChangeCertificate, view *protoIBFT.View, -) *protoIBFT.Message { +) *protoIBFT.IbftMessage { proposedBlock := &protoIBFT.Proposal{ RawProposal: rawProposal, Round: view.Round, @@ -35,11 +35,11 @@ func (i *backendIBFT) BuildPrePrepareMessage( return nil } - msg := &protoIBFT.Message{ + msg := &protoIBFT.IbftMessage{ View: view, From: i.ID(), Type: protoIBFT.MessageType_PREPREPARE, - Payload: &protoIBFT.Message_PreprepareData{ + Payload: &protoIBFT.IbftMessage_PreprepareData{ PreprepareData: &protoIBFT.PrePrepareMessage{ Proposal: proposedBlock, ProposalHash: proposalHash.Bytes(), @@ -51,12 +51,12 @@ func (i *backendIBFT) BuildPrePrepareMessage( return i.signMessage(msg) } -func (i *backendIBFT) BuildPrepareMessage(proposalHash []byte, view *protoIBFT.View) *protoIBFT.Message { - msg := &protoIBFT.Message{ +func (i *backendIBFT) BuildPrepareMessage(proposalHash []byte, view *protoIBFT.View) *protoIBFT.IbftMessage { + msg := &protoIBFT.IbftMessage{ View: view, From: i.ID(), Type: protoIBFT.MessageType_PREPARE, - Payload: &protoIBFT.Message_PrepareData{ + Payload: &protoIBFT.IbftMessage_PrepareData{ PrepareData: &protoIBFT.PrepareMessage{ ProposalHash: proposalHash, }, @@ -66,7 +66,7 @@ func (i *backendIBFT) BuildPrepareMessage(proposalHash []byte, view *protoIBFT.V return i.signMessage(msg) } -func (i *backendIBFT) BuildCommitMessage(proposalHash []byte, view *protoIBFT.View) *protoIBFT.Message { +func (i *backendIBFT) BuildCommitMessage(proposalHash []byte, view *protoIBFT.View) *protoIBFT.IbftMessage { committedSeal, err := i.currentSigner.CreateCommittedSeal(proposalHash) if err != nil { i.logger.Error("Unable to build commit message, %v", err) @@ -74,11 +74,11 @@ func (i *backendIBFT) BuildCommitMessage(proposalHash []byte, view *protoIBFT.Vi return nil } - msg := &protoIBFT.Message{ + msg := &protoIBFT.IbftMessage{ View: view, From: i.ID(), Type: protoIBFT.MessageType_COMMIT, - Payload: &protoIBFT.Message_CommitData{ + Payload: &protoIBFT.IbftMessage_CommitData{ CommitData: &protoIBFT.CommitMessage{ ProposalHash: proposalHash, CommittedSeal: committedSeal, @@ -93,12 +93,12 @@ func (i *backendIBFT) BuildRoundChangeMessage( proposal *protoIBFT.Proposal, certificate *protoIBFT.PreparedCertificate, view *protoIBFT.View, -) *protoIBFT.Message { - msg := &protoIBFT.Message{ +) *protoIBFT.IbftMessage { + msg := &protoIBFT.IbftMessage{ View: view, From: i.ID(), Type: protoIBFT.MessageType_ROUND_CHANGE, - Payload: &protoIBFT.Message_RoundChangeData{RoundChangeData: &protoIBFT.RoundChangeMessage{ + Payload: &protoIBFT.IbftMessage_RoundChangeData{RoundChangeData: &protoIBFT.RoundChangeMessage{ LastPreparedProposal: proposal, LatestPreparedCertificate: certificate, }}, diff --git a/consensus/ibft/operator_service.go b/consensus/ibft/operator_service.go index f33563b..f489fd4 100644 --- a/consensus/ibft/operator_service.go +++ b/consensus/ibft/operator_service.go @@ -5,9 +5,11 @@ import ( "errors" "fmt" + "github.com/xgr-network/xgr-node/consensus/ibft/pos" "github.com/xgr-network/xgr-node/consensus/ibft/proto" "github.com/xgr-network/xgr-node/consensus/ibft/signer" "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/state" "github.com/xgr-network/xgr-node/types" "github.com/xgr-network/xgr-node/validators" "github.com/xgr-network/xgr-node/validators/store" @@ -66,10 +68,26 @@ func (o *operator) GetSnapshot(ctx context.Context, req *proto.SnapshotReq) (*pr return nil, err } + stateTx, err := o.stateAtHeader(header) + if err != nil { + return nil, err + } + + uptimeActive := o.ibft.forkManager.IsPosActive(height) + resp := &proto.Snapshot{ Number: height, Hash: header.Hash.String(), - Validators: validatorsToProtoValidators(validators), + Validators: validatorsToProtoValidators(validators, stateTx, o.ibft.uptimeCfg, uptimeActive), + } + + if uptimeActive { + for idx := 0; idx < len(resp.Validators); idx++ { + resp.UptimeTotalEffectiveWeight += resp.Validators[idx].UptimeEffectiveWeight + } + for idx := 0; idx < validators.Len(); idx++ { + resp.UptimeActiveEffectiveWeight += pos.UptimeEffectiveWeight(stateTx.Txn(), validators.At(uint64(idx)).Addr()) + } } votes, err := getVotes(validatorsStore, height) @@ -181,17 +199,23 @@ func (o *operator) getLatestSigner() (signer.Signer, error) { } // validatorsToProtoValidators converts validators to response of validators -func validatorsToProtoValidators(validators validators.Validators) []*proto.Snapshot_Validator { +func validatorsToProtoValidators(validators validators.Validators, tx *state.Transition, cfg pos.UptimeConfig, uptimeActive bool) []*proto.Snapshot_Validator { protoValidators := make([]*proto.Snapshot_Validator, validators.Len()) for idx := 0; idx < validators.Len(); idx++ { validator := validators.At(uint64(idx)) - protoValidators[idx] = &proto.Snapshot_Validator{ + entry := &proto.Snapshot_Validator{ Type: string(validator.Type()), Address: validator.Addr().String(), Data: validator.Bytes(), } + if uptimeActive && cfg.Enabled() && tx != nil { + entry.UptimeNominalWeight = pos.UptimeNominalWeight(tx.Txn(), validator.Addr()) + entry.UptimeEffectiveWeight = pos.UptimeEffectiveWeight(tx.Txn(), validator.Addr()) + entry.UptimeInactivity = pos.UptimeInactivity(tx.Txn(), validator.Addr()) + } + protoValidators[idx] = entry } return protoValidators @@ -229,6 +253,14 @@ func candidatesToProtoCandidates(candidates []*store.Candidate) []*proto.Candida return protoCandidates } +func (o *operator) stateAtHeader(header *types.Header) (*state.Transition, error) { + if header == nil { + return nil, ErrHeaderNotFound + } + + return o.ibft.executor.BeginTxn(header.StateRoot, header, types.ZeroAddress) +} + // getVotes gets votes from validator store only if store supports voting func getVotes(validatorStore store.ValidatorStore, height uint64) ([]*store.Vote, error) { votableStore, ok := validatorStore.(Votable) diff --git a/consensus/ibft/pos/epoch_validators.go b/consensus/ibft/pos/epoch_validators.go new file mode 100644 index 0000000..9504b82 --- /dev/null +++ b/consensus/ibft/pos/epoch_validators.go @@ -0,0 +1,83 @@ +package pos + +import ( + "fmt" + + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +const MaxEpochValidatorsSnapshot = uint64(4096) + +const maxEpochValidatorsSnapshot = MaxEpochValidatorsSnapshot + +func addrToHash(addr types.Address) types.Hash { + var h types.Hash + // Left-pad with 12 zero bytes, Solidity-style. + copy(h[12:], addr[:]) + + return h +} + +func addrFromHash(h types.Hash) types.Address { + var a types.Address + copy(a[:], h[12:]) + + return a +} + +func ensureEpochValidatorsSnapshot(txn *state.Transition, epoch uint64, vals validators.Validators) (bool, error) { + if txn == nil || epoch == 0 || vals == nil { + return false, nil + } + if vals.Len() == 0 { + return false, nil + } + + lenKey := keyEpochValidatorsLen(epoch) + if u64FromHash(txn.Txn().GetState(PosSysAddr, lenKey)) != 0 { + return false, nil + } + + l := vals.Len() + if uint64(l) > maxEpochValidatorsSnapshot { + return false, fmt.Errorf( + "epoch validator snapshot exceeds max: have %d max %d", + l, + maxEpochValidatorsSnapshot, + ) + } + + txn.Txn().SetState(PosSysAddr, lenKey, u64ToHash(uint64(l))) + + for i := 0; i < l; i++ { + addr := vals.At(uint64(i)).Addr() + txn.Txn().SetState(PosSysAddr, keyEpochValidator(epoch, uint64(i)), addrToHash(addr)) + } + + return true, nil +} + +func loadEpochValidatorsSnapshot(txn *state.Transition, epoch uint64) []types.Address { + if txn == nil || epoch == 0 { + return nil + } + + l := u64FromHash(txn.Txn().GetState(PosSysAddr, keyEpochValidatorsLen(epoch))) + if l == 0 { + return nil + } + if l > maxEpochValidatorsSnapshot { + // Defensive guard: malformed state must not force huge loops in epoch-finalize. + return nil + } + + out := make([]types.Address, 0, l) + for i := uint64(0); i < l; i++ { + h := txn.Txn().GetState(PosSysAddr, keyEpochValidator(epoch, i)) + out = append(out, addrFromHash(h)) + } + + return out +} diff --git a/consensus/ibft/pos/epoch_validators_test.go b/consensus/ibft/pos/epoch_validators_test.go new file mode 100644 index 0000000..e2ccfa2 --- /dev/null +++ b/consensus/ibft/pos/epoch_validators_test.go @@ -0,0 +1,79 @@ +package pos + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func requireSnapshotCreated(t *testing.T, txn *state.Transition, epoch uint64, vals validators.Validators) { + t.Helper() + + created, err := ensureEpochValidatorsSnapshot(txn, epoch, vals) + require.NoError(t, err) + require.True(t, created) +} + +func makeEpochValidatorSet(t *testing.T, count uint64) validators.Validators { + t.Helper() + + set := validators.NewECDSAValidatorSet() + for i := uint64(0); i < count; i++ { + n := i + 1 + addr := types.Address{} + addr[12] = byte(n >> 56) + addr[13] = byte(n >> 48) + addr[14] = byte(n >> 40) + addr[15] = byte(n >> 32) + addr[16] = byte(n >> 24) + addr[17] = byte(n >> 16) + addr[18] = byte(n >> 8) + addr[19] = byte(n) + require.NoError(t, set.Add(validators.NewECDSAValidator(addr))) + } + + return set +} + +func TestEnsureEpochValidatorsSnapshotRejectsOverMaxWithoutWrites(t *testing.T) { + txn := newPosTestTransition(t) + epoch := uint64(2) + set := makeEpochValidatorSet(t, maxEpochValidatorsSnapshot+1) + + created, err := ensureEpochValidatorsSnapshot(txn, epoch, set) + require.Error(t, err) + require.False(t, created) + require.Contains(t, err.Error(), "epoch validator snapshot exceeds max") + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyEpochValidatorsLen(epoch)))) + for i := uint64(0); i < uint64(set.Len()); i++ { + require.Equal(t, types.ZeroHash, txn.Txn().GetState(PosSysAddr, keyEpochValidator(epoch, i))) + } +} + +func TestEnsureEpochValidatorsSnapshotValidCountAndLoadMax(t *testing.T) { + txn := newPosTestTransition(t) + epoch := uint64(3) + set := makeEpochValidatorSet(t, maxEpochValidatorsSnapshot) + + created, err := ensureEpochValidatorsSnapshot(txn, epoch, set) + require.NoError(t, err) + require.True(t, created) + require.Equal(t, maxEpochValidatorsSnapshot, u64FromHash(txn.Txn().GetState(PosSysAddr, keyEpochValidatorsLen(epoch)))) + + loaded := loadEpochValidatorsSnapshot(txn, epoch) + require.Len(t, loaded, int(maxEpochValidatorsSnapshot)) + for i := uint64(0); i < maxEpochValidatorsSnapshot; i++ { + require.Equal(t, set.At(i).Addr(), loaded[i]) + } +} + +func TestLoadEpochValidatorsSnapshotReturnsNilForMalformedOverMaxLen(t *testing.T) { + txn := newPosTestTransition(t) + epoch := uint64(4) + txn.Txn().SetState(PosSysAddr, keyEpochValidatorsLen(epoch), u64ToHash(maxEpochValidatorsSnapshot+1)) + + require.Nil(t, loadEpochValidatorsSnapshot(txn, epoch)) +} diff --git a/consensus/ibft/pos/events.go b/consensus/ibft/pos/events.go new file mode 100644 index 0000000..bdcc466 --- /dev/null +++ b/consensus/ibft/pos/events.go @@ -0,0 +1,134 @@ +package pos + +import ( + "math/big" + + "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" +) + +var ( + posEventEpochFinalizedTopic = eventTopic("EpochFinalized(uint256,uint256,uint256,uint256,uint256,uint256,uint256)") + posEventStakerRewardedTopic = eventTopic("StakerRewarded(uint256,address,address,uint8,uint256,uint256,uint256,uint256)") + posEventStakerSlashedTopic = eventTopic("StakerSlashed(uint256,address,address,uint8,uint256,uint256,uint256,uint256,uint256,uint256,uint256,address)") + posEventValidatorUptimeFinalizedTopic = eventTopic("ValidatorUptimeFinalized(uint256,address,uint256,uint256,uint256,uint256,uint256,bool,bool)") +) + +type posStakerRewardEvent struct { + Epoch uint64 + Validator types.Address + Staker types.Address + Role uint8 + Amount *big.Int + StakeSnapshot *big.Int + StakeBefore *big.Int + StakeAfter *big.Int +} + +type posValidatorUptimeFinalizedEvent struct { + Epoch uint64 + Validator types.Address + Slots uint64 + Missed uint64 + UptimeBps uint64 + EffectiveWeight *big.Int + StakeSnapshot *big.Int + RewardEligible bool + Slashed bool +} + +type posStakerSlashEvent struct { + Epoch uint64 + Validator types.Address + Staker types.Address + Role uint8 + Amount *big.Int + StakeSnapshot *big.Int + StakeBefore *big.Int + StakeAfter *big.Int + Slots uint64 + Missed uint64 + SlashBps uint64 + Destination types.Address +} + +func eventTopic(signature string) types.Hash { + return types.BytesToHash(crypto.Keccak256([]byte(signature))) +} + +func abiWordBig(v *big.Int) []byte { + var out [32]byte + if v == nil || v.Sign() == 0 { + return out[:] + } + b := v.Bytes() + if len(b) > 32 { + b = b[len(b)-32:] + } + copy(out[32-len(b):], b) + return out[:] +} + +func abiWordU64(v uint64) []byte { return abiWordBig(new(big.Int).SetUint64(v)) } +func abiWordU8(v uint8) []byte { return abiWordBig(new(big.Int).SetUint64(uint64(v))) } + +func abiWordBool(v bool) []byte { + if !v { + return abiWordU64(0) + } + + return abiWordU64(1) +} + +func abiWordAddress(addr types.Address) []byte { + var out [32]byte + copy(out[12:], addr[:]) + return out[:] +} + +func abiHashU64(v uint64) types.Hash { return types.BytesToHash(abiWordU64(v)) } +func abiHashAddress(addr types.Address) types.Hash { return types.BytesToHash(abiWordAddress(addr)) } + +func abiData(words ...[]byte) []byte { + out := make([]byte, 0, len(words)*32) + for _, word := range words { + out = append(out, word...) + } + return out +} + +func appendPosSystemLog(txn *state.Transition, topics []types.Hash, data []byte) { + if txn == nil || txn.GetTxContext().NonPayable { + return + } + txn.EmitLog(PosSysAddr, topics, data) +} + +func appendPosEpochFinalizedLog(txn *state.Transition, epoch, blockNumber uint64, poolBalanceBefore, totalDistributed, poolRemainder, totalSlashed *big.Int, activeValidators uint64) { + appendPosSystemLog(txn, + []types.Hash{posEventEpochFinalizedTopic, abiHashU64(epoch), abiHashU64(blockNumber)}, + abiData(abiWordBig(poolBalanceBefore), abiWordBig(totalDistributed), abiWordBig(poolRemainder), abiWordBig(totalSlashed), abiWordU64(activeValidators)), + ) +} + +func appendPosValidatorUptimeFinalizedLog(txn *state.Transition, ev posValidatorUptimeFinalizedEvent) { + appendPosSystemLog(txn, + []types.Hash{posEventValidatorUptimeFinalizedTopic, abiHashU64(ev.Epoch), abiHashAddress(ev.Validator)}, + abiData(abiWordU64(ev.Slots), abiWordU64(ev.Missed), abiWordU64(ev.UptimeBps), abiWordBig(ev.EffectiveWeight), abiWordBig(ev.StakeSnapshot), abiWordBool(ev.RewardEligible), abiWordBool(ev.Slashed)), + ) +} + +func appendPosStakerRewardedLog(txn *state.Transition, ev posStakerRewardEvent) { + appendPosSystemLog(txn, + []types.Hash{posEventStakerRewardedTopic, abiHashU64(ev.Epoch), abiHashAddress(ev.Validator), abiHashAddress(ev.Staker)}, + abiData(abiWordU8(ev.Role), abiWordBig(ev.Amount), abiWordBig(ev.StakeSnapshot), abiWordBig(ev.StakeBefore), abiWordBig(ev.StakeAfter)), + ) +} + +func appendPosStakerSlashedLog(txn *state.Transition, ev posStakerSlashEvent) { + appendPosSystemLog(txn, + []types.Hash{posEventStakerSlashedTopic, abiHashU64(ev.Epoch), abiHashAddress(ev.Validator), abiHashAddress(ev.Staker)}, + abiData(abiWordU8(ev.Role), abiWordBig(ev.Amount), abiWordBig(ev.StakeSnapshot), abiWordBig(ev.StakeBefore), abiWordBig(ev.StakeAfter), abiWordU64(ev.Slots), abiWordU64(ev.Missed), abiWordU64(ev.SlashBps), abiWordAddress(ev.Destination)), + ) +} diff --git a/consensus/ibft/pos/finalize.go b/consensus/ibft/pos/finalize.go new file mode 100644 index 0000000..95bd5af --- /dev/null +++ b/consensus/ibft/pos/finalize.go @@ -0,0 +1,1038 @@ +package pos + +import ( + "bytes" + "encoding/binary" + "fmt" + "math/big" + "sort" + + "github.com/xgr-network/xgr-node/chain" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + "github.com/xgr-network/xgr-node/contracts/staking" + "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/helper/common" + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" + contractstore "github.com/xgr-network/xgr-node/validators/store/contract" +) + +var ( + posTransferFn = func(txn *state.Transition, from, to types.Address, amount *big.Int) error { + return txn.Transfer(from, to, amount) + } + finalizeEpochRewardSplitFn = splitValidatorRewardByDelegation +) + +// Epoch policy parameters (consensus-critical; fork-gated by FeePoolSplit). +const ( + slashBpsDefault uint64 = 100 // 1% + + // Defense-in-depth only. + // Under current invariants, bootstrap stakes can only participate through emergency mode, + // and emergency mode disables slashing. Normal slashable validators are Tier-1 eligible + // and therefore far above this floor. Keep this guard to avoid slashing bootstrap dust + // if future selection/slashing invariants change. + noSlashStakeFloorWei uint64 = 10 +) + +// FinalizeEpoch runs on the tx-free epoch boundary block (height % epochSize == 0). +// It distributes the pooled fees and applies uptime-based penalties. +// +// Design choice: the epoch boundary block itself is treated as a system/finalize block and is NOT +// included in uptime accounting. Uptime counters are derived from RoundNumber (missed proposer slots), +// i.e. blocks 1..(epochEnd-1) for each epoch. +func FinalizeEpoch(header *types.Header, epochSize uint64, uptimeCfg UptimeConfig, headerSigner signer.Signer, txn *state.Transition) error { + if header == nil || txn == nil { + posDiagLogger.Debug("FinalizeEpoch skipped: nil input", "headerNil", header == nil, "txnNil", txn == nil) + return nil + } + + // Fork-gated: no PoS policy logic before pooling is enabled. + if !txn.ForksInTime().FeePoolSplit { + posDiagLogger.Debug("FinalizeEpoch skipped: FeePoolSplit not active", "block", header.Number) + return nil + } + + // IMPORTANT: keep PosSysAddr alive (otherwise storage is pruned every block). + ensurePosSysAccount(txn) + if epochSize < 2 { + posDiagLogger.Error("FinalizeEpoch failed: epochSize must be >= 2", "block", header.Number, "epochSize", epochSize) + return fmt.Errorf("epochSize must be greater than or equal to 2") + } + + // Only at epoch boundary block (tx-free by hook guard) + if header.Number == 0 || header.Number%epochSize != 0 { + posDiagLogger.Debug("FinalizeEpoch skipped: not epoch boundary", "block", header.Number, "epochSize", epochSize) + return nil + } + + // Finalize the epoch for blocks up to (header.Number - 1) + epoch := epochOf(header.Number-1, epochSize) + if epoch == 0 { + posDiagLogger.Debug("FinalizeEpoch skipped: epoch=0", "block", header.Number, "epochSize", epochSize) + return nil + } + + posDiagLogger.Debug("FinalizeEpoch start", "block", header.Number, "epoch", epoch, "epochSize", epochSize, "hash", header.Hash, "parent", header.ParentHash, "stateRoot", header.StateRoot) + + extra, err := getIBFTExtra(headerSigner, header) + if err != nil { + posDiagLogger.Error("FinalizeEpoch failed decoding extra", "block", header.Number, "epoch", epoch, "err", err) + return fmt.Errorf("decode ibft extra for epoch finalization: %w", err) + } + if extra == nil || extra.Validators == nil || extra.Validators.Len() == 0 { + posDiagLogger.Error("FinalizeEpoch decoded extra but validators are empty", "block", header.Number, "epoch", epoch) + return fmt.Errorf("decode ibft extra for epoch finalization: empty validator set") + } + epochValidators := loadEpochValidatorsSnapshot(txn, epoch) + if len(epochValidators) == 0 { + posDiagLogger.Error("FinalizeEpoch failed: missing epoch validator snapshot", "block", header.Number, "epoch", epoch) + return fmt.Errorf("missing epoch validator snapshot for epoch %d", epoch) + } + posDiagLogger.Debug("FinalizeEpoch using stored epoch validator snapshot", "block", header.Number, "epoch", epoch, "validatorsLen", len(epochValidators)) + uptimeLastProcessed := u64FromHash(txn.Txn().GetState(PosSysAddr, keyUptimeLastProcessedSlot())) + posDiagLogger.Debug("FinalizeEpoch validator source ready", "block", header.Number, "epoch", epoch, "validatorsLen", len(epochValidators), "uptimeLastProcessed", uptimeLastProcessed) + + // Resolve donation address (slash target). + donationAddr := resolveDonationAddress(txn) + slashingEnabled := !contractstore.IsEmergencyModeActive(txn) + posDiagLogger.Debug("FinalizeEpoch donation address", "block", header.Number, "epoch", epoch, "donationAddr", donationAddr) + posDiagLogger.Debug("FinalizeEpoch slashing mode", "block", header.Number, "epoch", epoch, "slashingEnabled", slashingEnabled) + + // Collect per-validator effective weights. + type vinfo struct { + addr types.Address + weight *big.Int + } + + infos := make([]vinfo, 0, len(epochValidators)) + sumWeights := new(big.Int) + slashEvents := make([]posStakerSlashEvent, 0) + uptimeEvents := make([]posValidatorUptimeFinalizedEvent, 0, len(epochValidators)) + snapshotStakers := make([]types.Address, 0, len(epochValidators)) + + for _, addr := range epochValidators { + + // Proposer-slot based availability (provable from RoundNumber). + slotsKey := keyProposerSlots(epoch, addr) + missedKey := keyProposerMissed(epoch, addr) + slots := u64FromHash(txn.Txn().GetState(PosSysAddr, slotsKey)) + missed := u64FromHash(txn.Txn().GetState(PosSysAddr, missedKey)) + + // Diagnostic context: include key material and neighbor-epoch values to isolate + // cross-path epoch/key mismatches without changing consensus behavior. + var ( + slotsPrev uint64 + slotsNext uint64 + missedPrev uint64 + missedNext uint64 + ) + if epoch > 0 { + slotsPrev = u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch-1, addr))) + missedPrev = u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerMissed(epoch-1, addr))) + } + slotsNext = u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch+1, addr))) + missedNext = u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerMissed(epoch+1, addr))) + posDiagLogger.Debug("FinalizeEpoch validator counters", + "epoch", epoch, + "validator", addr, + "slotsKey", slotsKey, + "missedKey", missedKey, + "slots", slots, + "missed", missed, + "slotsPrev", slotsPrev, + "missedPrev", missedPrev, + "slotsNext", slotsNext, + "missedNext", missedNext, + ) + + // If the validator had no proposer slots in this epoch (e.g. joined later), + // do NOT reward and do NOT penalize. + if slots == 0 { + posDiagLogger.Debug("FinalizeEpoch validator slots=0", "epoch", epoch, "validator", addr) + uptimeEvents = append(uptimeEvents, posValidatorUptimeFinalizedEvent{ + Epoch: epoch, + Validator: addr, + EffectiveWeight: new(big.Int), + StakeSnapshot: bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakeSnapshot(epoch, addr))), + }) + infos = append(infos, vinfo{addr: addr, weight: new(big.Int)}) + continue + } + if missed > slots { + missed = slots + } + okSlots := slots - missed + uptimeBps := okSlots * 10_000 / slots + if okSlots == 0 { + setValidatorInactiveInStaking(txn, addr, header.Number) + } + + stakeSnap, err := getOrInitStakeSnapshot(txn, epoch, addr) + if err != nil { + return fmt.Errorf("load stake snapshot for validator %s in epoch %d: %w", addr, epoch, err) + } + + // Thresholds are applied on okSlots/slots. + // >= 90% => full weight + // 80..90 => linear reduced weight: factor = (okSlots/0.9) + // < 80% => 0 weight (reward-ineligible for this epoch) + // < 50% => 0 weight + slash(stake) + eff := new(big.Int) + rewardEligible := okSlots*10 >= slots*8 + slashed := false + if !rewardEligible { + posDiagLogger.Debug("FinalizeEpoch validator below uptime threshold", "epoch", epoch, "validator", addr, "slots", slots, "missed", missed, "okSlots", okSlots) + if okSlots*2 < slots { // <50% + if slashingEnabled { + posDiagLogger.Debug("FinalizeEpoch slashing validator", "epoch", epoch, "validator", addr, "stakeSnap", stakeSnap) + slashEvent, err := slashByEffectiveStakeFromStakingContract(txn, epoch, addr, donationAddr, stakeSnap, slots, missed, slashBpsDefault, header.Number-1, epochSize) + if err != nil { + return fmt.Errorf("slash validator %s in epoch %d: %w", addr, epoch, err) + } + if len(slashEvent) > 0 { + slashed = true + slashEvents = append(slashEvents, slashEvent...) + for _, stakerEvent := range slashEvent { + snapshotStakers = append(snapshotStakers, stakerEvent.Staker) + } + } + } else { + posDiagLogger.Debug("FinalizeEpoch slashing skipped: emergency mode active", "epoch", epoch, "validator", addr) + } + } + uptimeEvents = append(uptimeEvents, posValidatorUptimeFinalizedEvent{ + Epoch: epoch, + Validator: addr, + Slots: slots, + Missed: missed, + UptimeBps: uptimeBps, + EffectiveWeight: eff, + StakeSnapshot: new(big.Int).Set(stakeSnap), + RewardEligible: false, + Slashed: slashed, + }) + infos = append(infos, vinfo{addr: addr, weight: eff}) + continue + } + + // factor = 1 for >=90%, else factor = (okSlots/slots) / 0.9 = okSlots*10/(slots*9) + if okSlots*10 >= slots*9 { + eff = new(big.Int).Set(stakeSnap) + } else { + num := new(big.Int).Mul(stakeSnap, new(big.Int).SetUint64(okSlots*10)) + den := new(big.Int).SetUint64(slots * 9) + eff = num.Div(num, den) + } + + uptimeEvents = append(uptimeEvents, posValidatorUptimeFinalizedEvent{ + Epoch: epoch, + Validator: addr, + Slots: slots, + Missed: missed, + UptimeBps: uptimeBps, + EffectiveWeight: new(big.Int).Set(eff), + StakeSnapshot: new(big.Int).Set(stakeSnap), + RewardEligible: true, + Slashed: false, + }) + infos = append(infos, vinfo{addr: addr, weight: eff}) + sumWeights.Add(sumWeights, eff) + posDiagLogger.Debug("FinalizeEpoch validator weight", "epoch", epoch, "validator", addr, "slots", slots, "missed", missed, "weight", eff) + } + + // Distribute pooled fees proportional to effective weights. + // Note: if there are no pooled fees, we still enforce jail/slash deterministically. + poolBal := new(big.Int).Set(txn.Txn().GetBalance(state.FeePoolAddress)) + posDiagLogger.Debug("FinalizeEpoch pool/weights", "block", header.Number, "epoch", epoch, "poolBal", poolBal, "sumWeights", sumWeights) + if poolBal.Sign() == 0 || sumWeights.Sign() == 0 { + posDiagLogger.Debug("FinalizeEpoch no payout", "block", header.Number, "epoch", epoch, "poolZero", poolBal.Sign() == 0, "weightsZero", sumWeights.Sign() == 0) + } + + type plannedReward struct { + addr types.Address + share *big.Int + parts validatorRewardSplit + } + + plans := make([]plannedReward, 0, len(infos)) + if poolBal.Sign() > 0 && sumWeights.Sign() > 0 { + for _, it := range infos { + if it.weight.Sign() == 0 { + continue + } + + share := new(big.Int).Mul(poolBal, it.weight) + share.Div(share, sumWeights) + if share.Sign() == 0 { + posDiagLogger.Debug("FinalizeEpoch zero share for validator", "epoch", epoch, "validator", it.addr, "weight", it.weight) + continue + } + + rewardParts, err := finalizeEpochRewardSplitFn(txn, header.Number-1, epochSize, it.addr, share) + if err != nil { + return fmt.Errorf("split validator reward for %s in epoch %d: %w", it.addr, epoch, err) + } + if err := validateRewardSplit(epoch, it.addr, share, rewardParts); err != nil { + return err + } + plans = append(plans, plannedReward{ + addr: it.addr, + share: share, + parts: rewardParts, + }) + } + } + + rewardEvents := make([]posStakerRewardEvent, 0, len(plans)) + totalDistributed := new(big.Int) + for _, plan := range plans { + if err := posTransferFn(txn, state.FeePoolAddress, staking.AddrStakingContract, plan.share); err != nil { + posDiagLogger.Error("FinalizeEpoch transfer failed", "epoch", epoch, "validator", plan.addr, "share", plan.share, "err", err) + return fmt.Errorf("transfer epoch reward for validator %s in epoch %d: %w", plan.addr, epoch, err) + } + rewardEvent := creditStakeReward(txn, epoch, plan.addr, plan.parts) + rewardEvents = append(rewardEvents, rewardEvent...) + totalDistributed.Add(totalDistributed, plan.share) + for _, stakerEvent := range rewardEvent { + snapshotStakers = append(snapshotStakers, stakerEvent.Staker) + } + posDiagLogger.Debug("FinalizeEpoch payout", "epoch", epoch, "validator", plan.addr, "share", plan.share) + } + + totalSlashed := new(big.Int) + for _, ev := range slashEvents { + totalSlashed.Add(totalSlashed, ev.Amount) + } + remainder := new(big.Int).Sub(new(big.Int).Set(poolBal), totalDistributed) + activeValidatorsAfter := countActiveEpochValidators(txn, epochValidators) + snapshotStakers = append(snapshotStakers, collectEpochValidatorStakers(txn, epochValidators)...) + cleanupFinalizedEpochState(txn, epoch, epochValidators, snapshotStakers) + + appendPosEpochFinalizedLog(txn, epoch, header.Number, poolBal, totalDistributed, remainder, totalSlashed, activeValidatorsAfter) + for _, ev := range uptimeEvents { + appendPosValidatorUptimeFinalizedLog(txn, ev) + } + for _, ev := range rewardEvents { + appendPosStakerRewardedLog(txn, ev) + } + for _, ev := range slashEvents { + appendPosStakerSlashedLog(txn, ev) + } + + posDiagLogger.Debug("FinalizeEpoch complete", "block", header.Number, "epoch", epoch) + + return nil +} + +func countActiveEpochValidators(txn *state.Transition, validators []types.Address) uint64 { + var active uint64 + for _, validator := range validators { + if readStakerActive(txn, validator) { + active++ + } + } + + return active +} + +func collectEpochValidatorStakers(txn *state.Transition, validators []types.Address) []types.Address { + stakers := make([]types.Address, 0, len(validators)) + for _, validator := range validators { + stakers = append(stakers, validator) + stakers = append(stakers, readValidatorStakers(txn, validator)...) + } + + return stakers +} + +func resolveDonationAddress(txn *state.Transition) types.Address { + // Slashed stake is always burned at 0x...0666. + return chain.DefaultBurnedAddress +} + +func getOrInitStakeSnapshot(txn *state.Transition, epoch uint64, addr types.Address) (*big.Int, error) { + if txn == nil { + return nil, fmt.Errorf("nil transition") + } + + key := keyStakeSnapshot(epoch, addr) + cur := bigFromHash(txn.Txn().GetState(PosSysAddr, key)) + if cur.Sign() != 0 { + return cur, nil + } + + blockNumber := uint64(0) + if txn != nil { + blockNumber = uint64(txn.GetTxContext().Number) + if blockNumber > 0 { + blockNumber-- + } + } + + stakeAmt := contractstore.ReadValidatorEffectiveTotalStakeAt(txn, addr, blockNumber) + txn.Txn().SetState(PosSysAddr, key, bigToHash(stakeAmt)) + return stakeAmt, nil +} + +func getOrInitStakerStakeSnapshot(txn *state.Transition, epoch uint64, addr types.Address) (*big.Int, error) { + if txn == nil { + return nil, fmt.Errorf("nil transition") + } + + key := keyStakerStakeSnapshot(epoch, addr) + cur := bigFromHash(txn.Txn().GetState(PosSysAddr, key)) + if cur.Sign() != 0 { + return cur, nil + } + + blockNumber := uint64(0) + if txn != nil { + blockNumber = uint64(txn.GetTxContext().Number) + if blockNumber > 0 { + blockNumber-- + } + } + + stakeAmt := contractstore.ReadStakerEffectiveStakeAt(txn, addr, blockNumber) + txn.Txn().SetState(PosSysAddr, key, bigToHash(stakeAmt)) + + return stakeAmt, nil +} + +type slashPosition struct { + addr types.Address + amount *big.Int + active bool +} + +type slashAllocation struct { + position slashPosition + slash *big.Int +} + +func slashByEffectiveStakeFromStakingContract(txn *state.Transition, epoch uint64, validator, donationAddr types.Address, stakeSnap *big.Int, slots, missed, slashBps, blockNumber, epochSize uint64) ([]posStakerSlashEvent, error) { + if slashBps == 0 { + posDiagLogger.Debug("slashByEffectiveStakeFromStakingContract skipped: slashBps=0", "epoch", epoch, "validator", validator) + return nil, nil + } + if stakeSnap == nil || stakeSnap.Sign() == 0 { + posDiagLogger.Debug("slashByEffectiveStakeFromStakingContract skipped: empty stake snapshot", "epoch", epoch, "validator", validator) + return nil, nil + } + if stakeSnap.Cmp(new(big.Int).SetUint64(noSlashStakeFloorWei)) <= 0 { + posDiagLogger.Debug("slashByEffectiveStakeFromStakingContract skipped: at no-slash floor", "epoch", epoch, "validator", validator, "stakeSnap", stakeSnap, "floorWei", noSlashStakeFloorWei) + return nil, nil + } + + totalSlash := new(big.Int).Mul(stakeSnap, new(big.Int).SetUint64(slashBps)) + totalSlash.Div(totalSlash, new(big.Int).SetUint64(10_000)) + if totalSlash.Sign() == 0 { + posDiagLogger.Debug("slashByEffectiveStakeFromStakingContract skipped: slash amount zero", "epoch", epoch, "validator", validator) + return nil, nil + } + + effectiveTotalStake := new(big.Int) + positions := make([]slashPosition, 0, 1) + if isStakerEffectiveAt(txn, validator, blockNumber, epochSize) { + selfStake, err := readStakeWeightAtEpoch(txn, epoch, validator) + if err != nil { + return nil, err + } + positions = append(positions, slashPosition{addr: validator, amount: selfStake, active: true}) + effectiveTotalStake.Add(effectiveTotalStake, selfStake) + } + delegations, err := readValidatorEffectiveDelegationsAt(txn, epoch, validator, blockNumber, epochSize) + if err != nil { + return nil, err + } + for _, delegation := range delegations { + positions = append(positions, slashPosition{ + addr: delegation.Delegator, + amount: delegation.Amount, + active: readStakerActive(txn, delegation.Delegator), + }) + effectiveTotalStake.Add(effectiveTotalStake, delegation.Amount) + } + if effectiveTotalStake.Sign() == 0 { + posDiagLogger.Debug("slashByEffectiveStakeFromStakingContract skipped: effective stake zero", "epoch", epoch, "validator", validator) + return nil, nil + } + + if totalSlash.Cmp(effectiveTotalStake) > 0 { + totalSlash.Set(effectiveTotalStake) + } + + contractBal := new(big.Int).Set(txn.Txn().GetBalance(staking.AddrStakingContract)) + if contractBal.Sign() == 0 { + posDiagLogger.Debug("slashByEffectiveStakeFromStakingContract skipped: staking contract balance zero", "epoch", epoch, "validator", validator) + return nil, nil + } + if totalSlash.Cmp(contractBal) > 0 { + totalSlash.Set(contractBal) + } + if totalSlash.Sign() == 0 { + posDiagLogger.Debug("slashByEffectiveStakeFromStakingContract skipped: slash amount zero after caps", "epoch", epoch, "validator", validator) + return nil, nil + } + + remaining := new(big.Int).Set(totalSlash) + allocations := make([]slashAllocation, 0, len(positions)) + for _, position := range positions { + part := new(big.Int).Mul(new(big.Int).Set(totalSlash), position.amount) + part.Div(part, effectiveTotalStake) + if part.Cmp(position.amount) > 0 { + return nil, fmt.Errorf("slash amount exceeds stake for %s: have %s, need %s", position.addr, position.amount, part) + } + allocations = append(allocations, slashAllocation{ + position: position, + slash: part, + }) + remaining.Sub(remaining, part) + } + + sort.Slice(allocations, func(i, j int) bool { + return bytes.Compare(allocations[i].position.addr.Bytes(), allocations[j].position.addr.Bytes()) < 0 + }) + + for remaining.Sign() > 0 { + progress := false + for i := range allocations { + if allocations[i].slash.Cmp(allocations[i].position.amount) >= 0 { + continue + } + allocations[i].slash.Add(allocations[i].slash, big.NewInt(1)) + remaining.Sub(remaining, big.NewInt(1)) + progress = true + if remaining.Sign() == 0 { + break + } + } + if !progress { + return nil, fmt.Errorf("unable to distribute slash remainder %s", remaining) + } + } + + stakerEvents := make([]posStakerSlashEvent, 0, len(allocations)) + for _, allocation := range allocations { + if allocation.slash.Sign() == 0 { + continue + } + curStake := new(big.Int).Set(readStakedAmount(txn, allocation.position.addr)) + if allocation.slash.Cmp(curStake) > 0 { + return nil, fmt.Errorf("slash underflow for %s: have %s, need %s", allocation.position.addr, curStake, allocation.slash) + } + newStake := new(big.Int).Sub(curStake, allocation.slash) + if err := posTransferFn(txn, staking.AddrStakingContract, donationAddr, allocation.slash); err != nil { + return nil, fmt.Errorf("transfer slash amount: %w", err) + } + writeStakedAmount(txn, allocation.position.addr, newStake) + if allocation.position.addr != validator { + if err := reduceValidatorDelegatedAggregate(txn, validator, allocation.slash, allocation.position.active); err != nil { + return nil, err + } + } + role := uint8(2) + if allocation.position.addr == validator { + role = 1 + } + stakerEvents = append(stakerEvents, posStakerSlashEvent{ + Epoch: epoch, Validator: validator, Staker: allocation.position.addr, Role: role, + Amount: new(big.Int).Set(allocation.slash), StakeSnapshot: new(big.Int).Set(allocation.position.amount), + StakeBefore: curStake, StakeAfter: newStake, Slots: slots, Missed: missed, SlashBps: slashBps, Destination: donationAddr, + }) + } + + posDiagLogger.Debug("slashByEffectiveStakeFromStakingContract applied", "epoch", epoch, "validator", validator, "donationAddr", donationAddr, "amount", totalSlash) + + return stakerEvents, nil +} + +type validatorRewardSplit struct { + ValidatorNet *big.Int + Commission *big.Int + DelegatorsNet *big.Int + DelegatorsNetAfterCommission *big.Int + SelfStakeReward *big.Int + DelegatorRemainder *big.Int + DelegatorParts []delegationShare +} + +type delegationShare struct { + Delegator types.Address + Amount *big.Int +} + +func splitValidatorRewardByDelegation(txn *state.Transition, blockNumber, epochSize uint64, validator types.Address, reward *big.Int) (validatorRewardSplit, error) { + out := validatorRewardSplit{ + ValidatorNet: new(big.Int), + Commission: new(big.Int), + DelegatorsNet: new(big.Int), + DelegatorsNetAfterCommission: new(big.Int), + SelfStakeReward: new(big.Int), + DelegatorRemainder: new(big.Int), + } + if reward == nil || reward.Sign() == 0 { + return out, nil + } + + epoch := epochOf(blockNumber, epochSize) + selfStake := new(big.Int) + if isStakerEffectiveAt(txn, validator, blockNumber, epochSize) { + var err error + selfStake, err = readStakeWeightAtEpoch(txn, epoch, validator) + if err != nil { + return out, err + } + } + delegations, err := readValidatorEffectiveDelegationsAt(txn, epoch, validator, blockNumber, epochSize) + if err != nil { + return out, err + } + if len(delegations) == 0 { + out.ValidatorNet = new(big.Int).Set(reward) + out.SelfStakeReward = new(big.Int).Set(reward) + return out, nil + } + + activeDelegatedTotal := new(big.Int) + for _, delegation := range delegations { + activeDelegatedTotal.Add(activeDelegatedTotal, delegation.Amount) + } + if activeDelegatedTotal.Sign() == 0 { + out.ValidatorNet = new(big.Int).Set(reward) + out.SelfStakeReward = new(big.Int).Set(reward) + return out, nil + } + + totalActiveStake := new(big.Int).Add(new(big.Int).Set(selfStake), activeDelegatedTotal) + if totalActiveStake.Sign() == 0 { + return out, nil + } + + selfShare := new(big.Int).Mul(new(big.Int).Set(reward), selfStake) + selfShare.Div(selfShare, totalActiveStake) + delegatedShare := new(big.Int).Sub(new(big.Int).Set(reward), selfShare) + + commissionBps := uint64(readValidatorCommissionBps(txn, validator)) + commission := new(big.Int).Mul(new(big.Int).Set(delegatedShare), new(big.Int).SetUint64(commissionBps)) + commission.Div(commission, new(big.Int).SetUint64(10_000)) + delegatorsNet := new(big.Int).Sub(new(big.Int).Set(delegatedShare), commission) + + allocated := new(big.Int) + out.DelegatorParts = make([]delegationShare, 0, len(delegations)) + for _, delegation := range delegations { + part := new(big.Int).Mul(new(big.Int).Set(delegatorsNet), delegation.Amount) + part.Div(part, activeDelegatedTotal) + if part.Sign() == 0 { + continue + } + allocated.Add(allocated, part) + out.DelegatorParts = append(out.DelegatorParts, delegationShare{ + Delegator: delegation.Delegator, + Amount: part, + }) + } + + delegatorRemainder := new(big.Int).Sub(new(big.Int).Set(delegatorsNet), allocated) + out.Commission.Set(commission) + out.DelegatorsNet.Set(allocated) + out.DelegatorsNetAfterCommission.Set(delegatorsNet) + out.SelfStakeReward.Set(selfShare) + out.DelegatorRemainder.Set(delegatorRemainder) + out.ValidatorNet.Add(selfShare, commission) + out.ValidatorNet.Add(out.ValidatorNet, delegatorRemainder) + + return out, nil +} + +func readValidatorEffectiveDelegationsAt(txn *state.Transition, epoch uint64, validator types.Address, _ uint64, _ uint64) ([]delegationShare, error) { + if txn == nil { + return nil, nil + } + stakers := readValidatorStakers(txn, validator) + out := make([]delegationShare, 0, len(stakers)) + for _, owner := range stakers { + if owner == validator { + continue + } + amount := bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakerStakeSnapshot(epoch, owner))) + if amount.Sign() == 0 { + continue + } + out = append(out, delegationShare{Delegator: owner, Amount: amount}) + } + + return out, nil +} + +func readStakeWeightAtEpoch(txn *state.Transition, epoch uint64, addr types.Address) (*big.Int, error) { + if epoch == 0 { + return nil, fmt.Errorf("invalid epoch for staker snapshot %s", addr) + } + + snap := bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakerStakeSnapshot(epoch, addr))) + if snap.Sign() == 0 { + return nil, fmt.Errorf("missing staker snapshot for epoch %d staker %s", epoch, addr) + } + + return snap, nil +} + +func readValidatorCommissionBps(txn *state.Transition, validator types.Address) uint16 { + base := crypto.Keccak256( + common.PadLeftOrTrim(validator.Bytes(), 32), + common.PadLeftOrTrim(big.NewInt(5).Bytes(), 32), + ) + idx := new(big.Int).Add(new(big.Int).SetBytes(base), big.NewInt(3)) + raw := txn.Txn().GetState(staking.AddrStakingContract, types.BytesToHash(idx.Bytes())) + v := new(big.Int).SetBytes(raw[:]).Uint64() + if v > 10_000 { + return 10_000 + } + + return uint16(v) +} + +func readValidatorStakers(txn *state.Transition, validator types.Address) []types.Address { + base := crypto.Keccak256( + common.PadLeftOrTrim(validator.Bytes(), 32), + common.PadLeftOrTrim(big.NewInt(6).Bytes(), 32), + ) + lengthRaw := txn.Txn().GetState(staking.AddrStakingContract, types.BytesToHash(base)) + length := new(big.Int).SetBytes(lengthRaw[:]).Uint64() + if length == 0 { + return nil + } + dataBase := crypto.Keccak256(common.PadLeftOrTrim(base, 32)) + idx := new(big.Int).SetBytes(dataBase) + out := make([]types.Address, 0, length) + for i := uint64(0); i < length; i++ { + slot := new(big.Int).Add(idx, new(big.Int).SetUint64(i)) + raw := txn.Txn().GetState(staking.AddrStakingContract, types.BytesToHash(slot.Bytes())) + out = append(out, types.BytesToAddress(raw[12:])) + } + + return out +} + +func readStakerJoinedAt(txn *state.Transition, addr types.Address) uint64 { + raw := txn.Txn().GetState(staking.AddrStakingContract, stakerSlotWithOffset(addr, 2)) + return new(big.Int).SetBytes(raw[:]).Uint64() +} + +func readStakerDeactivatedAt(txn *state.Transition, addr types.Address) uint64 { + raw := txn.Txn().GetState(staking.AddrStakingContract, stakerSlotWithOffset(addr, 3)) + return new(big.Int).SetBytes(raw[:]).Uint64() +} + +func readStakerActive(txn *state.Transition, addr types.Address) bool { + raw := txn.Txn().GetState(staking.AddrStakingContract, stakerSlotWithOffset(addr, 0)) + packed := new(big.Int).SetBytes(raw[:]) + if packed.Sign() == 0 { + return false + } + + activeByte := new(big.Int).Rsh(packed, 8) + activeByte.And(activeByte, new(big.Int).SetUint64(0xff)) + + return activeByte.Sign() != 0 +} + +func isStakerEffectiveAt(txn *state.Transition, addr types.Address, blockNumber, epochSize uint64) bool { + joined := readStakerJoinedAt(txn, addr) + if joined == 0 || epochSize == 0 { + return false + } + if blockNumber/epochSize <= joined/epochSize { + return false + } + deactivated := readStakerDeactivatedAt(txn, addr) + if deactivated == 0 { + return true + } + + return blockNumber/epochSize <= deactivated/epochSize +} + +func nonNilBig(v *big.Int) *big.Int { + if v == nil { + return new(big.Int) + } + + return v +} + +func validateRewardSplit(epoch uint64, addr types.Address, totalReward *big.Int, split validatorRewardSplit) error { + totalReward = nonNilBig(totalReward) + split.ValidatorNet = nonNilBig(split.ValidatorNet) + split.Commission = nonNilBig(split.Commission) + split.DelegatorsNetAfterCommission = nonNilBig(split.DelegatorsNetAfterCommission) + split.SelfStakeReward = nonNilBig(split.SelfStakeReward) + split.DelegatorRemainder = nonNilBig(split.DelegatorRemainder) + + delegatorsPaid := new(big.Int) + for _, part := range split.DelegatorParts { + if part.Amount != nil { + delegatorsPaid.Add(delegatorsPaid, part.Amount) + } + } + delegatorsNetAfterCommission := new(big.Int).Set(split.DelegatorsNetAfterCommission) + if delegatorsNetAfterCommission.Sign() == 0 { + delegatorsNetAfterCommission.Add(delegatorsPaid, split.DelegatorRemainder) + } + if new(big.Int).Sub(delegatorsNetAfterCommission, delegatorsPaid).Sign() < 0 { + return fmt.Errorf("negative delegator remainder for validator %s epoch %d", addr, epoch) + } + if split.SelfStakeReward.Sign() < 0 { + return fmt.Errorf("negative self stake reward for validator %s epoch %d", addr, epoch) + } + validatorNetCheck := new(big.Int).Add(new(big.Int).Set(split.SelfStakeReward), split.Commission) + validatorNetCheck.Add(validatorNetCheck, split.DelegatorRemainder) + if validatorNetCheck.Cmp(split.ValidatorNet) != 0 { + return fmt.Errorf("validator reward split mismatch for validator %s epoch %d: self+commission+remainder=%s validatorNet=%s", addr, epoch, validatorNetCheck, split.ValidatorNet) + } + shareCheck := new(big.Int).Add(new(big.Int).Set(split.ValidatorNet), delegatorsPaid) + if shareCheck.Cmp(totalReward) != 0 { + return fmt.Errorf("validator total reward mismatch for validator %s epoch %d: credited+delegators=%s total=%s", addr, epoch, shareCheck, totalReward) + } + + return nil +} + +func creditStakeReward(txn *state.Transition, epoch uint64, addr types.Address, split validatorRewardSplit) []posStakerRewardEvent { + split.ValidatorNet = nonNilBig(split.ValidatorNet) + // Historical reward state is consensus-observable through deterministic PosSysAddr logs, + // not permanent PosSysAddr storage. stakeSnapshot and stakeAfter let explorers prove + // eligibility and reconstruct the post-operation personal stake. + rewardEvents := make([]posStakerRewardEvent, 0, 1+len(split.DelegatorParts)) + + if split.ValidatorNet.Sign() > 0 { + curStake := new(big.Int).Set(readStakedAmount(txn, addr)) + newStake := new(big.Int).Add(new(big.Int).Set(curStake), split.ValidatorNet) + writeStakedAmount(txn, addr, newStake) + stakeSnapshot := bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakerStakeSnapshot(epoch, addr))) + rewardEvents = append(rewardEvents, posStakerRewardEvent{ + Epoch: epoch, Validator: addr, Staker: addr, Role: 1, + Amount: new(big.Int).Set(split.ValidatorNet), StakeSnapshot: stakeSnapshot, + StakeBefore: curStake, StakeAfter: newStake, + }) + } + + for _, part := range split.DelegatorParts { + if part.Amount == nil || part.Amount.Sign() == 0 { + continue + } + + curDelegatorStake := new(big.Int).Set(readStakedAmount(txn, part.Delegator)) + newDelegatorStake := new(big.Int).Add(new(big.Int).Set(curDelegatorStake), part.Amount) + writeStakedAmount(txn, part.Delegator, newDelegatorStake) + // Reward eligibility uses the epoch snapshot, while raw/active pool updates use + // current active status so future voting power reflects current live state. + addValidatorDelegatedAggregate(txn, addr, part.Amount, readStakerActive(txn, part.Delegator)) + stakeSnapshot := bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakerStakeSnapshot(epoch, part.Delegator))) + rewardEvents = append(rewardEvents, posStakerRewardEvent{ + Epoch: epoch, Validator: addr, Staker: part.Delegator, Role: 2, + Amount: new(big.Int).Set(part.Amount), StakeSnapshot: stakeSnapshot, + StakeBefore: curDelegatorStake, StakeAfter: newDelegatorStake, + }) + } + + return rewardEvents +} + +func cleanupFinalizedEpochState(txn *state.Transition, epoch uint64, validators, stakers []types.Address) { + if txn == nil || epoch == 0 { + return + } + // No ringbuffer or marker history is retained: finalized epoch working keys are deleted. + zero := types.Hash{} + for _, validator := range validators { + txn.Txn().SetState(PosSysAddr, keyProposerSlots(epoch, validator), zero) + txn.Txn().SetState(PosSysAddr, keyProposerMissed(epoch, validator), zero) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validator), zero) + txn.Txn().SetState(PosSysAddr, keyStakerStakeSnapshot(epoch, validator), zero) + txn.Txn().SetState(PosSysAddr, keySlashed(epoch, validator), zero) + txn.Txn().SetState(PosSysAddr, keySlashAmount(epoch, validator), zero) + } + uniq := make(map[types.Address]struct{}, len(stakers)+len(validators)) + for _, validator := range validators { + uniq[validator] = struct{}{} + } + for _, staker := range stakers { + uniq[staker] = struct{}{} + } + ordered := make([]types.Address, 0, len(uniq)) + for staker := range uniq { + ordered = append(ordered, staker) + } + sort.Slice(ordered, func(i, j int) bool { return bytes.Compare(ordered[i].Bytes(), ordered[j].Bytes()) < 0 }) + for _, staker := range ordered { + txn.Txn().SetState(PosSysAddr, keyStakerStakeSnapshot(epoch, staker), zero) + } + l := u64FromHash(txn.Txn().GetState(PosSysAddr, keyEpochValidatorsLen(epoch))) + for i := uint64(0); i < l && i < maxEpochValidatorsSnapshot; i++ { + txn.Txn().SetState(PosSysAddr, keyEpochValidator(epoch, i), zero) + } + txn.Txn().SetState(PosSysAddr, keyEpochValidatorsLen(epoch), zero) +} + +func readStakedAmount(txn *state.Transition, addr types.Address) *big.Int { + key := stakingAmountKey(addr) + h := txn.Txn().GetState(staking.AddrStakingContract, key) + return new(big.Int).SetBytes(h[:]) +} + +func writeStakedAmount(txn *state.Transition, addr types.Address, v *big.Int) { + key := stakingAmountKey(addr) + txn.Txn().SetState(staking.AddrStakingContract, key, bigToHash(v)) +} + +func addValidatorDelegatedAggregate(txn *state.Transition, validator types.Address, amount *big.Int, active bool) { + if amount == nil || amount.Sign() == 0 { + return + } + + // StakingV2 storage layout: + // slot 8: _validatorDelegatedStakeRaw + // slot 9: _validatorDelegatedStakeActive + for _, slot := range []int64{8, 9} { + if slot == 9 && !active { + continue + } + base := crypto.Keccak256( + common.PadLeftOrTrim(validator.Bytes(), 32), + common.PadLeftOrTrim(big.NewInt(slot).Bytes(), 32), + ) + key := types.BytesToHash(base) + raw := txn.Txn().GetState(staking.AddrStakingContract, key) + cur := new(big.Int).SetBytes(raw[:]) + cur.Add(cur, amount) + txn.Txn().SetState(staking.AddrStakingContract, key, bigToHash(cur)) + } +} + +func reduceValidatorDelegatedAggregate(txn *state.Transition, validator types.Address, amount *big.Int, active bool) error { + if amount == nil || amount.Sign() == 0 { + return nil + } + + // StakingV2 storage layout: + // slot 8: _validatorDelegatedStakeRaw + // slot 9: _validatorDelegatedStakeActive + for _, slot := range []int64{8, 9} { + if slot == 9 && !active { + continue + } + base := crypto.Keccak256( + common.PadLeftOrTrim(validator.Bytes(), 32), + common.PadLeftOrTrim(big.NewInt(slot).Bytes(), 32), + ) + key := types.BytesToHash(base) + raw := txn.Txn().GetState(staking.AddrStakingContract, key) + cur := new(big.Int).SetBytes(raw[:]) + if amount.Cmp(cur) > 0 { + return fmt.Errorf("delegated aggregate underflow for validator %s slot %d: have %s, need %s", validator, slot, cur, amount) + } + cur.Sub(cur, amount) + txn.Txn().SetState(staking.AddrStakingContract, key, bigToHash(cur)) + } + + return nil +} + +func stakerSlotWithOffset(addr types.Address, offset int64) types.Hash { + base := crypto.Keccak256( + common.PadLeftOrTrim(addr.Bytes(), 32), + common.PadLeftOrTrim(big.NewInt(3).Bytes(), 32), + ) + idx := new(big.Int).SetBytes(base) + idx.Add(idx, big.NewInt(offset)) + + return types.BytesToHash(idx.Bytes()) +} + +func setValidatorInactiveInStaking(txn *state.Transition, addr types.Address, blockNum uint64) { + packedKey := stakingMetaPackedKey(addr) + packedState := txn.Txn().GetState(staking.AddrStakingContract, packedKey) + packedVal := new(big.Int).SetBytes(packedState[:]) + if packedVal.Sign() == 0 { + return + } + if !isValidatorMetaActive(packedVal) { + return + } + + // ValidatorMeta packed slot layout: + // byte0: exists, byte1: active, bytes2..9: index + activeMask := new(big.Int).SetUint64(0xff) + activeMask.Lsh(activeMask, 8) + packedVal.AndNot(packedVal, activeMask) + txn.Txn().SetState(staking.AddrStakingContract, packedKey, bigToHash(packedVal)) + + poolKey := stakingPoolConfigPackedKey(addr) + poolState := txn.Txn().GetState(staking.AddrStakingContract, poolKey) + poolVal := new(big.Int).SetBytes(poolState[:]) + if poolVal.Sign() != 0 { + poolVal.AndNot(poolVal, activeMask) + txn.Txn().SetState(staking.AddrStakingContract, poolKey, bigToHash(poolVal)) + } + + txn.Txn().SetState(staking.AddrStakingContract, stakingDeactivatedAtBlockKey(addr), bigToHash(new(big.Int).SetUint64(blockNum))) +} + +func isValidatorMetaActive(packedVal *big.Int) bool { + if packedVal == nil || packedVal.Sign() == 0 { + return false + } + + activeByte := new(big.Int).Rsh(new(big.Int).Set(packedVal), 8) + activeByte.And(activeByte, new(big.Int).SetUint64(0xff)) + + return activeByte.Sign() != 0 +} + +func stakingMetaPackedKey(addr types.Address) types.Hash { + base := stakingMetaBase(addr) + return types.BytesToHash(base.Bytes()) +} + +func stakingPoolConfigPackedKey(addr types.Address) types.Hash { + base := crypto.Keccak256( + common.PadLeftOrTrim(addr.Bytes(), 32), + common.PadLeftOrTrim(big.NewInt(5).Bytes(), 32), + ) + + return types.BytesToHash(base) +} + +func stakingDeactivatedAtBlockKey(addr types.Address) types.Hash { + base := stakingMetaBase(addr) + base.Add(base, big.NewInt(3)) + return types.BytesToHash(base.Bytes()) +} + +func stakingMetaBase(addr types.Address) *big.Int { + var a [32]byte + copy(a[12:], addr.Bytes()) + + var slot [32]byte + binary.BigEndian.PutUint64(slot[24:], 3) + + return new(big.Int).SetBytes(crypto.Keccak256(a[:], slot[:])) +} + +func stakingAmountKey(addr types.Address) types.Hash { + // StakingV2 storage layout: + // slot 0: VALIDATOR_THRESHOLD + // slot 1: epochSize/minNumValidators/maxNumValidators (packed) + // slot 2: _validators dynamic array + // slot 3: mapping(address => Staker) _staker + // + // Staker.amount is the second storage slot of the struct => base + 1. + base := stakingMetaBase(addr) + base.Add(base, big.NewInt(1)) + + return types.BytesToHash(base.Bytes()) +} diff --git a/consensus/ibft/pos/finalize_epoch_rewards_test.go b/consensus/ibft/pos/finalize_epoch_rewards_test.go new file mode 100644 index 0000000..9b9736e --- /dev/null +++ b/consensus/ibft/pos/finalize_epoch_rewards_test.go @@ -0,0 +1,712 @@ +package pos + +import ( + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/chain" + "github.com/xgr-network/xgr-node/contracts/staking" + "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/helper/common" + stakingHelper "github.com/xgr-network/xgr-node/helper/staking" + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func setStakerForEpoch(t *testing.T, txn *state.Transition, epoch uint64, addr types.Address, joinedAt uint64, amount int64) { + t.Helper() + writeStakerInfo(txn, addr, true, true, joinedAt, 0, types.ZeroAddress) + txn.Txn().SetState(PosSysAddr, keyStakerStakeSnapshot(epoch, addr), bigToHash(big.NewInt(amount))) + writeStakedAmount(txn, addr, big.NewInt(amount)) +} + +func writeStakerInfo(txn *state.Transition, addr types.Address, exists bool, active bool, joinedAt uint64, deactivatedAt uint64, validator types.Address) { + flags := uint64(0) + if exists { + flags |= 1 + } + if active { + flags |= 1 << 8 + } + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(addr, 0), bigToHash(new(big.Int).SetUint64(flags))) + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(addr, 2), bigToHash(new(big.Int).SetUint64(joinedAt))) + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(addr, 3), bigToHash(new(big.Int).SetUint64(deactivatedAt))) + txn.Txn().SetState(staking.AddrStakingContract, stakerSlotWithOffset(addr, 4), types.BytesToHash(common.PadLeftOrTrim(validator.Bytes(), 32))) +} + +func appendValidatorStaker(txn *state.Transition, validator types.Address, staker types.Address) { + base := crypto.Keccak256(common.PadLeftOrTrim(validator.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(6).Bytes(), 32)) + baseHash := types.BytesToHash(base) + curState := txn.Txn().GetState(staking.AddrStakingContract, baseHash) + cur := new(big.Int).SetBytes(curState[:]).Uint64() + txn.Txn().SetState(staking.AddrStakingContract, baseHash, bigToHash(new(big.Int).SetUint64(cur+1))) + dataBase := crypto.Keccak256(common.PadLeftOrTrim(base, 32)) + slot := new(big.Int).Add(new(big.Int).SetBytes(dataBase), new(big.Int).SetUint64(cur)) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(slot.Bytes()), types.BytesToHash(common.PadLeftOrTrim(staker.Bytes(), 32))) +} + +func writeValidatorPoolConfig(txn *state.Transition, validator types.Address, enabled bool, maxDelegated *big.Int, minDelegatorStake *big.Int, commission uint16) { + base := crypto.Keccak256(common.PadLeftOrTrim(validator.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(5).Bytes(), 32)) + var packed uint64 + packed |= 1 + packed |= 1 << 8 + if enabled { + packed |= 1 << 16 + } + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(base), bigToHash(new(big.Int).SetUint64(packed))) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(new(big.Int).Add(new(big.Int).SetBytes(base), big.NewInt(1)).Bytes()), bigToHash(maxDelegated)) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(new(big.Int).Add(new(big.Int).SetBytes(base), big.NewInt(2)).Bytes()), bigToHash(minDelegatorStake)) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(new(big.Int).Add(new(big.Int).SetBytes(base), big.NewInt(3)).Bytes()), bigToHash(new(big.Int).SetUint64(uint64(commission)))) +} + +func newPosTestTransitionWithStakingConfig(t *testing.T, vals validators.Validators, params stakingHelper.PredeployParams) *state.Transition { + t.Helper() + + txn := newPosTestTransition(t) + contractState, err := stakingHelper.PredeployStakingSC(vals, params) + require.NoError(t, err) + require.NoError(t, txn.SetAccountDirectly(staking.AddrStakingContract, contractState)) + + return txn +} + +func setUptimeCounters(txn *state.Transition, epoch uint64, addr types.Address, slots, missed uint64) { + txn.Txn().SetState(PosSysAddr, keyProposerSlots(epoch, addr), u64ToHash(slots)) + txn.Txn().SetState(PosSysAddr, keyProposerMissed(epoch, addr), u64ToHash(missed)) +} + +func TestFinalizeEpoch_RewardSplitError_DoesNotTransferFromFeePool(t *testing.T) { + validator := types.StringToAddress("0x1636") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + setUptimeCounters(txn, epoch, validator, 10, 0) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validator), bigToHash(big.NewInt(100))) + writeStakedAmount(txn, validator, big.NewInt(100)) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(200)) + + origSplit := finalizeEpochRewardSplitFn + finalizeEpochRewardSplitFn = func(_ *state.Transition, _ uint64, _ uint64, _ types.Address, _ *big.Int) (validatorRewardSplit, error) { + return validatorRewardSplit{}, fmt.Errorf("injected split failure") + } + t.Cleanup(func() { finalizeEpochRewardSplitFn = origSplit }) + + preFeePool := new(big.Int).Set(txn.Txn().GetBalance(state.FeePoolAddress)) + preStakingBal := new(big.Int).Set(txn.Txn().GetBalance(staking.AddrStakingContract)) + + err := FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn) + require.Error(t, err) + require.Contains(t, err.Error(), "injected split failure") + + require.Equal(t, preFeePool.String(), txn.Txn().GetBalance(state.FeePoolAddress).String()) + require.Equal(t, preStakingBal.String(), txn.Txn().GetBalance(staking.AddrStakingContract).String()) + require.Equal(t, int64(100), readStakedAmount(txn, validator).Int64()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyRewardTotal(epoch))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, validator))).Sign()) +} + +func TestFinalizeEpoch_RewardSplitError_SecondValidator_DoesNotMutateFirstValidator(t *testing.T) { + v1 := types.StringToAddress("0x1638") + v2 := types.StringToAddress("0x1639") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v1), validators.NewECDSAValidator(v2)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + setUptimeCounters(txn, epoch, v1, 10, 0) + setUptimeCounters(txn, epoch, v2, 10, 0) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v1), bigToHash(big.NewInt(100))) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v2), bigToHash(big.NewInt(100))) + writeStakedAmount(txn, v1, big.NewInt(100)) + writeStakedAmount(txn, v2, big.NewInt(100)) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(200)) + + origSplit := finalizeEpochRewardSplitFn + finalizeEpochRewardSplitFn = func(_ *state.Transition, _ uint64, _ uint64, validator types.Address, reward *big.Int) (validatorRewardSplit, error) { + if validator == v2 { + return validatorRewardSplit{}, fmt.Errorf("injected split failure on second validator") + } + return validatorRewardSplit{ValidatorNet: new(big.Int).Set(reward), Commission: new(big.Int), DelegatorsNet: new(big.Int), DelegatorsNetAfterCommission: new(big.Int), SelfStakeReward: new(big.Int).Set(reward), DelegatorRemainder: new(big.Int)}, nil + } + t.Cleanup(func() { finalizeEpochRewardSplitFn = origSplit }) + + preFeePool := new(big.Int).Set(txn.Txn().GetBalance(state.FeePoolAddress)) + preStakingBal := new(big.Int).Set(txn.Txn().GetBalance(staking.AddrStakingContract)) + + err := FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn) + require.Error(t, err) + require.Contains(t, err.Error(), "injected split failure on second validator") + + require.Equal(t, preFeePool.String(), txn.Txn().GetBalance(state.FeePoolAddress).String()) + require.Equal(t, preStakingBal.String(), txn.Txn().GetBalance(staking.AddrStakingContract).String()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyRewardTotal(epoch))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, v1))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, v2))).Sign()) + require.Equal(t, int64(100), readStakedAmount(txn, v1).Int64()) + require.Equal(t, int64(100), readStakedAmount(txn, v2).Int64()) +} + +func TestFinalizeEpoch_DelegatorWithoutSnapshotDoesNotGetRewardAndDoesNotCrash(t *testing.T) { + v := types.StringToAddress("0x1701") + d := types.StringToAddress("0x1702") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + epoch := uint64(2) + reward := big.NewInt(100) + + writeStakerInfo(txn, v, true, true, 1, 0, types.ZeroAddress) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v), bigToHash(big.NewInt(100))) + setStakerForEpoch(t, txn, epoch, v, 1, 100) + writeStakerInfo(txn, d, true, true, 5, 0, v) // live active, but no epoch snapshot on purpose + writeStakedAmount(txn, d, big.NewInt(50)) + appendValidatorStaker(txn, v, d) + + split, err := splitValidatorRewardByDelegation(txn, 19, 10, v, reward) + require.NoError(t, err) + require.Equal(t, "100", split.ValidatorNet.String()) + require.Zero(t, split.Commission.Sign()) + require.Zero(t, split.DelegatorsNet.Sign()) + require.Empty(t, split.DelegatorParts) +} + +func TestFinalizeEpoch_DelegatorFromPreviousEpochGetsReward(t *testing.T) { + v := types.StringToAddress("0x1703") + d := types.StringToAddress("0x1704") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + epoch := uint64(2) + + writeStakerInfo(txn, v, true, true, 1, 0, types.ZeroAddress) + setStakerForEpoch(t, txn, epoch, v, 1, 100) + setStakerForEpoch(t, txn, epoch, d, 8, 100) + appendValidatorStaker(txn, v, d) + + split, err := splitValidatorRewardByDelegation(txn, 19, 10, v, big.NewInt(100)) + require.NoError(t, err) + require.Equal(t, "50", split.ValidatorNet.String()) + require.Equal(t, "50", split.DelegatorsNet.String()) + require.Len(t, split.DelegatorParts, 1) + require.Equal(t, d, split.DelegatorParts[0].Delegator) + require.Equal(t, "50", split.DelegatorParts[0].Amount.String()) +} + +func TestFinalizeEpoch_ReadValidatorEffectiveDelegationsAt_UsesSnapshotOnly(t *testing.T) { + v := types.StringToAddress("0x1710") + withSnapshot := types.StringToAddress("0x1711") + noSnapshot := types.StringToAddress("0x1712") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + epoch := uint64(2) + + setStakerForEpoch(t, txn, epoch, v, 1, 100) + setStakerForEpoch(t, txn, epoch, withSnapshot, 1, 40) + writeStakerInfo(txn, noSnapshot, true, true, 1, 0, v) + writeStakedAmount(txn, noSnapshot, big.NewInt(60)) + appendValidatorStaker(txn, v, withSnapshot) + appendValidatorStaker(txn, v, noSnapshot) + + delegations, err := readValidatorEffectiveDelegationsAt(txn, epoch, v, 19, 10) + require.NoError(t, err) + require.Len(t, delegations, 1) + require.Equal(t, withSnapshot, delegations[0].Delegator) + require.Equal(t, "40", delegations[0].Amount.String()) +} + +func TestFinalizeEpoch_DelegatorRewardHonorsCommission(t *testing.T) { + v := types.StringToAddress("0x1705") + d := types.StringToAddress("0x1706") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + epoch := uint64(2) + + minDelegatorStake := big.NewInt(7777) + commissionBps := uint16(1000) + require.NotZero(t, minDelegatorStake.Sign()) + require.NotEqual(t, minDelegatorStake.Uint64(), uint64(commissionBps)) + writeValidatorPoolConfig(txn, v, true, big.NewInt(1000), minDelegatorStake, commissionBps) // 10% + setStakerForEpoch(t, txn, epoch, v, 1, 100) + setStakerForEpoch(t, txn, epoch, d, 8, 100) + appendValidatorStaker(txn, v, d) + + split, err := splitValidatorRewardByDelegation(txn, 19, 10, v, big.NewInt(100)) + require.NoError(t, err) + require.Equal(t, "55", split.ValidatorNet.String()) // self 50 + commission 5 + require.Equal(t, "5", split.Commission.String()) + require.Equal(t, "45", split.DelegatorsNet.String()) + require.Len(t, split.DelegatorParts, 1) + require.Equal(t, "45", split.DelegatorParts[0].Amount.String()) +} + +func TestFinalizeEpoch_CommissionUsesCurrentPoolConfigSlot(t *testing.T) { + v := types.StringToAddress("0x1713") + d := types.StringToAddress("0x1714") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + + minDelegatorStake := big.NewInt(7777) + commissionBps := uint16(1000) + require.NotZero(t, minDelegatorStake.Sign()) + require.NotEqual(t, minDelegatorStake.Uint64(), uint64(commissionBps)) + writeValidatorPoolConfig(txn, v, true, big.NewInt(1000), minDelegatorStake, commissionBps) + setStakerForEpoch(t, txn, epoch, v, 1, 100) + setStakerForEpoch(t, txn, epoch, d, 8, 100) + appendValidatorStaker(txn, v, d) + setUptimeCounters(txn, epoch, v, 10, 0) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v), bigToHash(big.NewInt(200))) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(100)) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, v))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorCommissionReward(epoch, v))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorDelegatorsRewardTotal(epoch, v))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyDelegatorReward(epoch, v, d))).Sign()) + require.Equal(t, "155", readStakedAmount(txn, v).String()) + require.Equal(t, "145", readStakedAmount(txn, d).String()) +} + +func writeValidatorDelegatedAggregateForRewardTest(txn *state.Transition, validator types.Address, raw, active *big.Int) { + for slot, amount := range map[int64]*big.Int{8: raw, 9: active} { + base := crypto.Keccak256(common.PadLeftOrTrim(validator.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(slot).Bytes(), 32)) + txn.Txn().SetState(staking.AddrStakingContract, types.BytesToHash(base), bigToHash(amount)) + } +} + +func readValidatorDelegatedAggregateForRewardTest(txn *state.Transition, validator types.Address, slot int64) *big.Int { + base := crypto.Keccak256(common.PadLeftOrTrim(validator.Bytes(), 32), common.PadLeftOrTrim(big.NewInt(slot).Bytes(), 32)) + h := txn.Txn().GetState(staking.AddrStakingContract, types.BytesToHash(base)) + return new(big.Int).SetBytes(h[:]) +} + +func TestFinalizeEpoch_SlashingAndRewards_AccountingOrderAndConservation(t *testing.T) { + validatorA := types.StringToAddress("0x1721") + validatorB := types.StringToAddress("0x1722") + delegatorA := types.StringToAddress("0x1723") + delegatorB := types.StringToAddress("0x1724") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validatorA), validators.NewECDSAValidator(validatorB)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + + writeValidatorPoolConfig(txn, validatorB, true, big.NewInt(10_000), big.NewInt(1), 1_000) + writeStakerInfo(txn, validatorA, true, true, 1, 0, validatorA) + writeStakerInfo(txn, validatorB, true, true, 1, 0, validatorB) + setStakerForEpoch(t, txn, epoch, validatorA, 1, 1_000) + setStakerForEpoch(t, txn, epoch, delegatorA, 1, 500) + setStakerForEpoch(t, txn, epoch, validatorB, 1, 2_000) + setStakerForEpoch(t, txn, epoch, delegatorB, 1, 1_000) + appendValidatorStaker(txn, validatorA, validatorA) + appendValidatorStaker(txn, validatorA, delegatorA) + appendValidatorStaker(txn, validatorB, validatorB) + appendValidatorStaker(txn, validatorB, delegatorB) + writeValidatorDelegatedAggregateForRewardTest(txn, validatorA, big.NewInt(500), big.NewInt(500)) + writeValidatorDelegatedAggregateForRewardTest(txn, validatorB, big.NewInt(1_000), big.NewInt(1_000)) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validatorA), bigToHash(big.NewInt(1_500))) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validatorB), bigToHash(big.NewInt(3_000))) + setUptimeCounters(txn, epoch, validatorA, 1, 1) + setUptimeCounters(txn, epoch, validatorB, 1, 0) + txn.Txn().SetBalance(staking.AddrStakingContract, big.NewInt(4_500)) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(900)) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keySlashed(epoch, validatorA))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keySlashAmount(epoch, validatorA))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, validatorA))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, validatorB))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorCommissionReward(epoch, validatorB))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorDelegatorsRewardTotal(epoch, validatorB))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyDelegatorReward(epoch, validatorB, delegatorB))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyRewardTotal(epoch))).Sign()) + require.Equal(t, "0", txn.Txn().GetBalance(state.FeePoolAddress).String()) + require.Equal(t, "990", readStakedAmount(txn, validatorA).String()) + require.Equal(t, "495", readStakedAmount(txn, delegatorA).String()) + require.Equal(t, "2630", readStakedAmount(txn, validatorB).String()) + require.Equal(t, "1270", readStakedAmount(txn, delegatorB).String()) + require.Equal(t, "495", readValidatorDelegatedAggregateForRewardTest(txn, validatorA, 8).String()) + require.Equal(t, "495", readValidatorDelegatedAggregateForRewardTest(txn, validatorA, 9).String()) + require.Equal(t, "1270", readValidatorDelegatedAggregateForRewardTest(txn, validatorB, 8).String()) + require.Equal(t, "1270", readValidatorDelegatedAggregateForRewardTest(txn, validatorB, 9).String()) + require.Equal(t, "5385", txn.Txn().GetBalance(staking.AddrStakingContract).String()) + require.Equal(t, "0", txn.Txn().GetBalance(state.FeePoolAddress).String()) +} + +func TestFinalizeEpoch_FiveValidatorsEqualStakeEqualUptime_EqualRewardsAndFinalStake(t *testing.T) { + v1 := types.StringToAddress("0x1801") + v2 := types.StringToAddress("0x1802") + v3 := types.StringToAddress("0x1803") + v4 := types.StringToAddress("0x1804") + v5 := types.StringToAddress("0x1805") + vals := []types.Address{v1, v2, v3, v4, v5} + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v1), validators.NewECDSAValidator(v2), validators.NewECDSAValidator(v3), validators.NewECDSAValidator(v4), validators.NewECDSAValidator(v5)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + + stake := int64(2_000_000) + for _, v := range vals { + setUptimeCounters(txn, epoch, v, 10, 0) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v), bigToHash(big.NewInt(stake))) + txn.Txn().SetState(PosSysAddr, keyStakerStakeSnapshot(epoch, v), bigToHash(big.NewInt(stake))) + writeStakedAmount(txn, v, big.NewInt(stake)) + } + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(1_000_000)) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + + for _, v := range vals { + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, v))).Sign()) + require.Equal(t, int64(2_200_000), readStakedAmount(txn, v).Int64()) + } + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyRewardTotal(epoch))).Sign()) + require.Equal(t, int64(0), txn.Txn().GetBalance(state.FeePoolAddress).Int64()) +} + +func TestFinalizeEpoch_FiveValidators_MissedZeroRewardsIndependentOfSlots(t *testing.T) { + v1 := types.StringToAddress("0x1901") + v2 := types.StringToAddress("0x1902") + v3 := types.StringToAddress("0x1903") + v4 := types.StringToAddress("0x1904") + v5 := types.StringToAddress("0x1905") + vals := []types.Address{v1, v2, v3, v4, v5} + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v1), validators.NewECDSAValidator(v2), validators.NewECDSAValidator(v3), validators.NewECDSAValidator(v4), validators.NewECDSAValidator(v5)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + + stake := int64(2_000_000) + slotsByVal := map[types.Address]uint64{v1: 1, v2: 2, v3: 5, v4: 10, v5: 20} + for _, v := range vals { + setUptimeCounters(txn, epoch, v, slotsByVal[v], 0) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v), bigToHash(big.NewInt(stake))) + txn.Txn().SetState(PosSysAddr, keyStakerStakeSnapshot(epoch, v), bigToHash(big.NewInt(stake))) + writeStakedAmount(txn, v, big.NewInt(stake)) + } + header := makeFinalizeHeader(20, 10, set) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(1_000_000)) + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + + firstReward := bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, v1))) + firstFinalStake := readStakedAmount(txn, v1) + for _, v := range vals { + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerMissed(epoch, v)))) + require.Equal(t, firstReward.String(), bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, v))).String()) + require.Equal(t, firstFinalStake.String(), readStakedAmount(txn, v).String()) + } +} + +func TestFinalizeEpoch_MissedSlotsCauseExplainedRewardDifference(t *testing.T) { + v1 := types.StringToAddress("0x1a01") + v2 := types.StringToAddress("0x1a02") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v1), validators.NewECDSAValidator(v2)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + for _, v := range []types.Address{v1, v2} { + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v), bigToHash(big.NewInt(2_000_000))) + txn.Txn().SetState(PosSysAddr, keyStakerStakeSnapshot(epoch, v), bigToHash(big.NewInt(2_000_000))) + writeStakedAmount(txn, v, big.NewInt(2_000_000)) + } + setUptimeCounters(txn, epoch, v1, 10, 0) + setUptimeCounters(txn, epoch, v2, 10, 2) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(1_000_000)) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + require.True(t, readStakedAmount(txn, v1).Cmp(readStakedAmount(txn, v2)) > 0) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch, v1)))) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch, v2)))) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerMissed(epoch, v1)))) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerMissed(epoch, v2)))) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakeSnapshot(epoch, v1))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakeSnapshot(epoch, v2))).Sign()) + require.Equal(t, int64(1), txn.Txn().GetBalance(state.FeePoolAddress).Int64()) +} + +func TestFinalizeEpoch_EmitsDeterministicPosSystemLogsAndClearsHistoryState(t *testing.T) { + validatorA := types.StringToAddress("0x2721") + validatorB := types.StringToAddress("0x2722") + delegatorA := types.StringToAddress("0x2723") + delegatorB := types.StringToAddress("0x2724") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validatorA), validators.NewECDSAValidator(validatorB)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + + writeValidatorPoolConfig(txn, validatorB, true, big.NewInt(10_000), big.NewInt(1), 1_000) + writeStakerInfo(txn, validatorA, true, true, 1, 0, validatorA) + writeStakerInfo(txn, validatorB, true, true, 1, 0, validatorB) + setStakerForEpoch(t, txn, epoch, validatorA, 1, 1_000) + setStakerForEpoch(t, txn, epoch, delegatorA, 1, 500) + setStakerForEpoch(t, txn, epoch, validatorB, 1, 2_000) + setStakerForEpoch(t, txn, epoch, delegatorB, 1, 1_000) + appendValidatorStaker(txn, validatorA, validatorA) + appendValidatorStaker(txn, validatorA, delegatorA) + appendValidatorStaker(txn, validatorB, validatorB) + appendValidatorStaker(txn, validatorB, delegatorB) + writeValidatorDelegatedAggregateForRewardTest(txn, validatorA, big.NewInt(500), big.NewInt(500)) + writeValidatorDelegatedAggregateForRewardTest(txn, validatorB, big.NewInt(1_000), big.NewInt(1_000)) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validatorA), bigToHash(big.NewInt(1_500))) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validatorB), bigToHash(big.NewInt(3_000))) + setUptimeCounters(txn, epoch, validatorA, 1, 1) + setUptimeCounters(txn, epoch, validatorB, 1, 0) + txn.Txn().SetBalance(staking.AddrStakingContract, big.NewInt(4_500)) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(900)) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + + logs := txn.Txn().Logs() + require.Len(t, logs, 7) + for _, log := range logs { + require.Equal(t, PosSysAddr, log.Address) + } + require.Equal(t, posEventEpochFinalizedTopic, logs[0].Topics[0]) + require.Equal(t, posEventValidatorUptimeFinalizedTopic, logs[1].Topics[0]) + require.Equal(t, posEventValidatorUptimeFinalizedTopic, logs[2].Topics[0]) + require.Equal(t, posEventStakerRewardedTopic, logs[3].Topics[0]) + require.Equal(t, posEventStakerRewardedTopic, logs[4].Topics[0]) + require.Equal(t, posEventStakerSlashedTopic, logs[5].Topics[0]) + require.Equal(t, posEventStakerSlashedTopic, logs[6].Topics[0]) + + word := func(data []byte, idx int) *big.Int { return new(big.Int).SetBytes(data[idx*32 : (idx+1)*32]) } + require.Equal(t, "900", word(logs[0].Data, 0).String()) // poolBalanceBefore + require.Equal(t, "900", word(logs[0].Data, 1).String()) // totalDistributed + require.Equal(t, "0", word(logs[0].Data, 2).String()) // poolRemainder + require.Equal(t, "15", word(logs[0].Data, 3).String()) // totalSlashed + require.Equal(t, "1", word(logs[0].Data, 4).String()) // activeValidators + + require.Equal(t, abiHashAddress(validatorA), logs[1].Topics[2]) + require.Equal(t, "1", word(logs[1].Data, 0).String()) // slots + require.Equal(t, "1", word(logs[1].Data, 1).String()) // missed + require.Equal(t, "0", word(logs[1].Data, 2).String()) // uptimeBps + require.Equal(t, "0", word(logs[1].Data, 3).String()) // effectiveWeight + require.Equal(t, "1500", word(logs[1].Data, 4).String()) // stakeSnapshot + require.Equal(t, "0", word(logs[1].Data, 5).String()) // rewardEligible + require.Equal(t, "1", word(logs[1].Data, 6).String()) // slashed + + require.Equal(t, abiHashAddress(validatorB), logs[2].Topics[2]) + require.Equal(t, "1", word(logs[2].Data, 0).String()) // slots + require.Equal(t, "0", word(logs[2].Data, 1).String()) // missed + require.Equal(t, "10000", word(logs[2].Data, 2).String()) // uptimeBps + require.Equal(t, "3000", word(logs[2].Data, 3).String()) // effectiveWeight + require.Equal(t, "3000", word(logs[2].Data, 4).String()) // stakeSnapshot + require.Equal(t, "1", word(logs[2].Data, 5).String()) // rewardEligible + require.Equal(t, "0", word(logs[2].Data, 6).String()) // slashed + + require.Equal(t, abiHashAddress(validatorB), logs[3].Topics[2]) + require.Equal(t, abiHashAddress(validatorB), logs[3].Topics[3]) + require.Equal(t, "1", word(logs[3].Data, 0).String()) // validator role + require.Equal(t, "630", word(logs[3].Data, 1).String()) // amount + require.Equal(t, "2000", word(logs[3].Data, 2).String()) // stakeSnapshot + require.Equal(t, "2000", word(logs[3].Data, 3).String()) // stakeBefore + require.Equal(t, "2630", word(logs[3].Data, 4).String()) // stakeAfter + + require.Equal(t, abiHashAddress(validatorB), logs[4].Topics[2]) + require.Equal(t, abiHashAddress(delegatorB), logs[4].Topics[3]) + require.Equal(t, "2", word(logs[4].Data, 0).String()) // delegator role + require.Equal(t, "270", word(logs[4].Data, 1).String()) // amount + require.Equal(t, "1000", word(logs[4].Data, 2).String()) // stakeSnapshot + require.Equal(t, "1000", word(logs[4].Data, 3).String()) // stakeBefore + require.Equal(t, "1270", word(logs[4].Data, 4).String()) // stakeAfter + + require.Equal(t, abiHashAddress(validatorA), logs[5].Topics[2]) + require.Equal(t, abiHashAddress(validatorA), logs[5].Topics[3]) + require.Equal(t, "1", word(logs[5].Data, 0).String()) // validator role + require.Equal(t, "10", word(logs[5].Data, 1).String()) // slash amount + require.Equal(t, "1000", word(logs[5].Data, 2).String()) // stakeSnapshot + require.Equal(t, "1000", word(logs[5].Data, 3).String()) // stakeBefore + require.Equal(t, "990", word(logs[5].Data, 4).String()) // stakeAfter + require.Equal(t, "1", word(logs[5].Data, 5).String()) // slots + require.Equal(t, "1", word(logs[5].Data, 6).String()) // missed + require.Equal(t, "100", word(logs[5].Data, 7).String()) // slashBps + require.Equal(t, abiWordAddress(chain.DefaultBurnedAddress), logs[5].Data[8*32:9*32]) + + require.Equal(t, abiHashAddress(validatorA), logs[6].Topics[2]) + require.Equal(t, abiHashAddress(delegatorA), logs[6].Topics[3]) + require.Equal(t, "2", word(logs[6].Data, 0).String()) // delegator role + require.Equal(t, "5", word(logs[6].Data, 1).String()) // slash amount + require.Equal(t, "500", word(logs[6].Data, 2).String()) // stakeSnapshot + require.Equal(t, "500", word(logs[6].Data, 3).String()) // stakeBefore + require.Equal(t, "495", word(logs[6].Data, 4).String()) // stakeAfter + require.Equal(t, "1", word(logs[6].Data, 5).String()) // slots + require.Equal(t, "1", word(logs[6].Data, 6).String()) // missed + require.Equal(t, "100", word(logs[6].Data, 7).String()) // slashBps + require.Equal(t, abiWordAddress(chain.DefaultBurnedAddress), logs[6].Data[8*32:9*32]) + + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyRewardTotal(epoch))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyValidatorReward(epoch, validatorB))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyDelegatorReward(epoch, validatorB, delegatorB))).Sign()) + require.Zero(t, u64FromHash(txn.Txn().GetState(PosSysAddr, keyEpochValidatorsLen(epoch)))) + require.Zero(t, u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch, validatorB)))) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakeSnapshot(epoch, validatorA))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakerStakeSnapshot(epoch, delegatorB))).Sign()) +} + +func TestFinalizeEpoch_ValidatorUptimeFinalized_EmitsAllValidatorsWithoutRewardsOrSlash(t *testing.T) { + v1 := types.StringToAddress("0x2a01") + v2 := types.StringToAddress("0x2a02") + v3 := types.StringToAddress("0x2a03") + v4 := types.StringToAddress("0x2a04") + set := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(v1), + validators.NewECDSAValidator(v2), + validators.NewECDSAValidator(v3), + validators.NewECDSAValidator(v4), + ) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + + for _, v := range []types.Address{v1, v2, v3, v4} { + writeStakerInfo(txn, v, true, true, 1, 0, v) + writeStakedAmount(txn, v, big.NewInt(900)) + } + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v1), bigToHash(big.NewInt(900))) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v2), bigToHash(big.NewInt(900))) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v3), bigToHash(big.NewInt(900))) + setUptimeCounters(txn, epoch, v1, 10, 0) + setUptimeCounters(txn, epoch, v2, 10, 2) + setUptimeCounters(txn, epoch, v3, 10, 3) + setUptimeCounters(txn, epoch, v4, 0, 0) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(0)) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + + logs := txn.Txn().Logs() + require.Len(t, logs, 5) + require.Equal(t, posEventEpochFinalizedTopic, logs[0].Topics[0]) + for i, v := range []types.Address{v1, v2, v3, v4} { + log := logs[i+1] + require.Equal(t, PosSysAddr, log.Address) + require.Equal(t, posEventValidatorUptimeFinalizedTopic, log.Topics[0]) + require.Equal(t, abiHashU64(epoch), log.Topics[1]) + require.Equal(t, abiHashAddress(v), log.Topics[2]) + } + + word := func(data []byte, idx int) *big.Int { return new(big.Int).SetBytes(data[idx*32 : (idx+1)*32]) } + assertUptime := func(logIdx int, slots, missed, uptimeBps, effectiveWeight, stakeSnapshot uint64, rewardEligible, slashed bool) { + t.Helper() + log := logs[logIdx] + require.Equal(t, new(big.Int).SetUint64(slots).String(), word(log.Data, 0).String()) + require.Equal(t, new(big.Int).SetUint64(missed).String(), word(log.Data, 1).String()) + require.Equal(t, new(big.Int).SetUint64(uptimeBps).String(), word(log.Data, 2).String()) + require.Equal(t, new(big.Int).SetUint64(effectiveWeight).String(), word(log.Data, 3).String()) + require.Equal(t, new(big.Int).SetUint64(stakeSnapshot).String(), word(log.Data, 4).String()) + if rewardEligible { + require.Equal(t, "1", word(log.Data, 5).String()) + } else { + require.Equal(t, "0", word(log.Data, 5).String()) + } + if slashed { + require.Equal(t, "1", word(log.Data, 6).String()) + } else { + require.Equal(t, "0", word(log.Data, 6).String()) + } + } + assertUptime(1, 10, 0, 10_000, 900, 900, true, false) + assertUptime(2, 10, 2, 8_000, 800, 900, true, false) + assertUptime(3, 10, 3, 7_000, 0, 900, false, false) + assertUptime(4, 0, 0, 0, 0, 0, false, false) + + for _, v := range []types.Address{v1, v2, v3, v4} { + require.Zero(t, u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch, v)))) + require.Zero(t, u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerMissed(epoch, v)))) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakeSnapshot(epoch, v))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keySlashed(epoch, v))).Sign()) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keySlashAmount(epoch, v))).Sign()) + } + require.Zero(t, u64FromHash(txn.Txn().GetState(PosSysAddr, keyEpochValidatorsLen(epoch)))) +} + +func TestFinalizeEpoch_NonPayableDoesNotEmitPosSystemLogs(t *testing.T) { + validator := types.StringToAddress("0x2821") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + setStakerForEpoch(t, txn, epoch, validator, 1, 1_000) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validator), bigToHash(big.NewInt(1_000))) + setUptimeCounters(txn, epoch, validator, 1, 0) + txn.SetNonPayable(true) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + require.Empty(t, txn.Txn().Logs()) +} + +func TestFinalizeEpoch_ClearsZeroRewardDelegatorSnapshot(t *testing.T) { + validator := types.StringToAddress("0x2921") + delegator := types.StringToAddress("0x2922") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + requireSnapshotCreated(t, txn, epoch, set) + + setStakerForEpoch(t, txn, epoch, validator, 1, 100) + setStakerForEpoch(t, txn, epoch, delegator, 1, 50) + appendValidatorStaker(txn, validator, delegator) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validator), bigToHash(big.NewInt(150))) + setUptimeCounters(txn, epoch, validator, 1, 0) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(0)) + + require.NotZero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakerStakeSnapshot(epoch, delegator))).Sign()) + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + require.Zero(t, bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakerStakeSnapshot(epoch, delegator))).Sign()) +} + +func TestFinalizeEpochLogsPersistInSystemReceipt(t *testing.T) { + validator := types.StringToAddress("0x17f1") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + txn := newPosTestTransitionWithStakingConfig(t, set, stakingHelper.PredeployParams{MinValidatorCount: 0, MaxValidatorCount: 10, EpochSize: 10}) + + header := makeFinalizeHeader(20, 10, set) + epoch := uint64(2) + receiptTx := EpochFinalizationSystemTx(header.Number) + + requireSnapshotCreated(t, txn, epoch, set) + setUptimeCounters(txn, epoch, validator, 10, 0) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validator), bigToHash(big.NewInt(100))) + txn.Txn().SetState(PosSysAddr, keyStakerStakeSnapshot(epoch, validator), bigToHash(big.NewInt(100))) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(10)) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + require.NoError(t, txn.WriteSystemReceipt(receiptTx)) + require.Empty(t, txn.Txn().Logs(), "system logs must be drained into the receipt") + + receipts := txn.Receipts() + require.Len(t, receipts, 1) + require.Equal(t, receiptTx.Hash, receipts[0].TxHash) + require.Equal(t, types.StateTx, receipts[0].TransactionType) + require.NotEmpty(t, receipts[0].Logs) + require.Equal(t, posEventEpochFinalizedTopic, receipts[0].Logs[0].Topics[0]) + + var foundUptime bool + for _, log := range receipts[0].Logs { + require.Equal(t, PosSysAddr, log.Address) + require.Equal(t, receiptTx.Hash, log.TxHash) + require.Equal(t, uint64(0), log.BlockNumber) + if log.Topics[0] == posEventValidatorUptimeFinalizedTopic { + foundUptime = true + } + } + require.True(t, foundUptime, "ValidatorUptimeFinalized log must be persisted in receipt logs") + require.NotEqual(t, types.Bloom{}, receipts[0].LogsBloom) + for _, log := range receipts[0].Logs { + require.True(t, receipts[0].LogsBloom.IsLogInBloom(log), "receipt bloom must include PosSysAddr finalization logs") + } +} diff --git a/consensus/ibft/pos/finalize_epoch_test.go b/consensus/ibft/pos/finalize_epoch_test.go new file mode 100644 index 0000000..0f4eb18 --- /dev/null +++ b/consensus/ibft/pos/finalize_epoch_test.go @@ -0,0 +1,203 @@ +package pos + +import ( + "errors" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + "github.com/xgr-network/xgr-node/contracts/staking" + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" + contractstore "github.com/xgr-network/xgr-node/validators/store/contract" +) + +func makeFinalizeHeader(number uint64, epochSize uint64, set validators.Validators) *types.Header { + extra := &signer.IstanbulExtra{ + Validators: set, + CommittedSeals: &signer.SerializedSeal{}, + ParentCommittedSeals: &signer.SerializedSeal{}, + } + + return &types.Header{ + Number: number, + Timestamp: number * epochSize, + ExtraData: extra.MarshalRLPTo(make([]byte, signer.IstanbulExtraVanity)), + } +} + +func TestFinalizeEpoch_ZeroSlotsDoesNotDeactivateValidator(t *testing.T) { + t.Parallel() + + validator := types.StringToAddress("0xabc1") + txn := newPosTestTransitionWithStaking(t, validator) + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + header := makeFinalizeHeader(20, 10, set) + epoch := epochOf(header.Number-1, 10) + requireSnapshotCreated(t, txn, epoch, set) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + + packed := txn.Txn().GetState(staking.AddrStakingContract, stakingMetaPackedKey(validator)) + require.True(t, isValidatorMetaActive(bigFromHash(packed))) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingDeactivatedAtBlockKey(validator)))) +} + +func TestFinalizeEpoch_DeactivatedAtBlockIsIdempotent(t *testing.T) { + t.Parallel() + + validator := types.StringToAddress("0xabc2") + txn := newPosTestTransitionWithStaking(t, validator) + + setValidatorInactiveInStaking(txn, validator, 100) + require.Equal(t, uint64(100), u64FromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingDeactivatedAtBlockKey(validator)))) + + setValidatorInactiveInStaking(txn, validator, 200) + require.Equal(t, uint64(100), u64FromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingDeactivatedAtBlockKey(validator)))) +} + +func TestFinalizeEpoch_ReturnsErrorOnDecodeFailure(t *testing.T) { + validator := types.StringToAddress("0xabc3") + txn := newPosTestTransitionWithStaking(t, validator) + header := &types.Header{ + Number: 20, + Timestamp: 200, + ExtraData: []byte{0x1, 0x2, 0x3}, + } + + err := FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn) + require.Error(t, err) +} + +func TestFinalizeEpoch_ReturnsErrorOnEmptyValidators(t *testing.T) { + validator := types.StringToAddress("0xabc4") + txn := newPosTestTransitionWithStaking(t, validator) + header := makeFinalizeHeader(20, 10, validators.NewECDSAValidatorSet()) + + err := FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn) + require.Error(t, err) +} + +func TestFinalizeEpoch_ReturnsErrorOnMissingEpochValidatorSnapshot(t *testing.T) { + validator := types.StringToAddress("0xabca") + txn := newPosTestTransitionWithStaking(t, validator) + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + header := makeFinalizeHeader(20, 10, set) + txn.Txn().SetState(PosSysAddr, keyUptimeLastProcessedSlot(), u64ToHash(header.Number-1)) + + err := FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn) + require.Error(t, err) + require.Contains(t, err.Error(), "missing epoch validator snapshot for epoch 2") +} + +func TestFinalizeEpoch_MissingSnapshotForPrePoSEpochAtCutoverBoundaryErrors(t *testing.T) { + t.Parallel() + + validator := types.StringToAddress("0xabcb") + txn := newPosTestTransitionWithStaking(t, validator) + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + header := makeFinalizeHeader(50, 50, set) + + require.Error(t, FinalizeEpoch(header, 50, UptimeConfig{}, testHeaderSigner(), txn)) +} + +func TestFinalizeEpoch_DoesNotSkipMissingSnapshotOutsideMigrationCase(t *testing.T) { + t.Parallel() + + validator := types.StringToAddress("0xabcc") + txn := newPosTestTransitionWithStaking(t, validator) + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + header := makeFinalizeHeader(150, 50, set) + + err := FinalizeEpoch(header, 50, UptimeConfig{}, testHeaderSigner(), txn) + require.Error(t, err) + require.Contains(t, err.Error(), "missing epoch validator snapshot for epoch 3") +} + +func TestFinalizeEpoch_ReturnsErrorOnRewardTransferFailure(t *testing.T) { + validator := types.StringToAddress("0xabc5") + txn := newPosTestTransitionWithStaking(t, validator) + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + header := makeFinalizeHeader(20, 10, set) + epoch := epochOf(header.Number-1, 10) + requireSnapshotCreated(t, txn, epoch, set) + originalStake := new(big.Int).Set(readStakedAmount(txn, validator)) + + txn.Txn().SetState(PosSysAddr, keyProposerSlots(epoch, validator), u64ToHash(1)) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, validator), bigToHash(big.NewInt(100))) + txn.Txn().SetBalance(state.FeePoolAddress, big.NewInt(100)) + + origTransfer := posTransferFn + posTransferFn = func(txn *state.Transition, from, to types.Address, amount *big.Int) error { + if from == state.FeePoolAddress && to == staking.AddrStakingContract { + return errors.New("injected transfer failure") + } + return origTransfer(txn, from, to, amount) + } + defer func() { + posTransferFn = origTransfer + }() + + err := FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn) + require.Error(t, err) + require.Equal(t, 0, readStakedAmount(txn, validator).Cmp(originalStake)) +} + +func TestFinalizeEpoch_ZeroUptimeMarksValidatorInactiveAndFetcherExcludesFromEligiblePath(t *testing.T) { + v1 := types.StringToAddress("0xabc6") + v2 := types.StringToAddress("0xabc7") + set := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(v1), + validators.NewECDSAValidator(v2), + ) + txn := newPosTestTransitionWithStakingValidatorsAtBlock(t, set, 1, 10, 21) + header := makeFinalizeHeader(20, 10, set) + epoch := epochOf(header.Number-1, 10) + requireSnapshotCreated(t, txn, epoch, set) + + // Ensure both pass strict threshold before uptime filtering in finalize (values in wei). + wei := big.NewInt(1_000_000_000_000_000_000) + highStakeWei := new(big.Int).Mul(big.NewInt(3_000_000), wei) // 3,000,000 XGR + writeStakedAmount(txn, v1, highStakeWei) + writeStakedAmount(txn, v2, highStakeWei) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v1), bigToHash(highStakeWei)) + txn.Txn().SetState(PosSysAddr, keyStakeSnapshot(epoch, v2), bigToHash(highStakeWei)) + txn.Txn().SetState(PosSysAddr, keyStakerStakeSnapshot(epoch, v1), bigToHash(highStakeWei)) + txn.Txn().SetState(PosSysAddr, keyStakerStakeSnapshot(epoch, v2), bigToHash(highStakeWei)) + + // v1 has zero uptime for this epoch; v2 is healthy. + txn.Txn().SetState(PosSysAddr, keyProposerSlots(epoch, v1), u64ToHash(1)) + txn.Txn().SetState(PosSysAddr, keyProposerMissed(epoch, v1), u64ToHash(1)) + txn.Txn().SetState(PosSysAddr, keyProposerSlots(epoch, v2), u64ToHash(1)) + txn.Txn().SetState(PosSysAddr, keyProposerMissed(epoch, v2), u64ToHash(0)) + + require.NoError(t, FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn)) + logs := txn.Txn().Logs() + require.NotEmpty(t, logs) + require.Equal(t, posEventEpochFinalizedTopic, logs[0].Topics[0]) + require.Equal(t, uint64(1), new(big.Int).SetBytes(logs[0].Data[4*32:5*32]).Uint64(), "EpochFinalized.activeValidators must reflect post-finalization active validators") + + packed := bigFromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingMetaPackedKey(v1))) + require.False(t, isValidatorMetaActive(packed)) + require.Equal(t, uint64(header.Number), u64FromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingDeactivatedAtBlockKey(v1)))) + require.Greater(t, readStakedAmount(txn, v1).Sign(), 0, "validator must remain in registry/state with stake") + require.False(t, contractstore.IsEmergencyModeActive(txn), "fetcher must use normal strict eligible path in this test") + + fetched, err := contractstore.FetchECDSAValidators(txn, types.ZeroAddress) + require.NoError(t, err) + require.Equal(t, 1, fetched.Len()) + require.Equal(t, v2, fetched.At(0).Addr(), "inactive validator must not be in the regular eligible set") +} + +func TestFinalizeEpoch_FailsDeterministicallyWhenEpochProducedNoUsableSnapshot(t *testing.T) { + validator := types.StringToAddress("0xabd0") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + txn := newPosTestTransitionWithStakingValidators(t, set, 1, 10) + + // Finalize epoch 2 at boundary block 20 without any successful RecordBlockUptime freeze in epoch 2. + header := makeFinalizeHeader(20, 10, set) + err := FinalizeEpoch(header, 10, UptimeConfig{}, testHeaderSigner(), txn) + require.EqualError(t, err, "missing epoch validator snapshot for epoch 2") +} diff --git a/consensus/ibft/pos/finalize_test.go b/consensus/ibft/pos/finalize_test.go new file mode 100644 index 0000000..665e9dc --- /dev/null +++ b/consensus/ibft/pos/finalize_test.go @@ -0,0 +1,274 @@ +package pos + +import ( + "math/big" + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/chain" + "github.com/xgr-network/xgr-node/contracts/staking" + stakingHelper "github.com/xgr-network/xgr-node/helper/staking" + "github.com/xgr-network/xgr-node/state" + itrie "github.com/xgr-network/xgr-node/state/immutable-trie" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func TestSlashByEffectiveStake_DistributesRemainderBySortedAddress(t *testing.T) { + t.Parallel() + + validator := types.StringToAddress("0x00000000000000000000000000000000000000f3") + delegatorHigh := types.StringToAddress("0x00000000000000000000000000000000000000f2") + delegatorLow := types.StringToAddress("0x00000000000000000000000000000000000000f1") + donation := types.StringToAddress("0x2006") + txn := newPosTestTransitionWithStaking(t, validator) + epoch := uint64(2) + + setStakerForEpoch(t, txn, epoch, validator, 1, 101) + setStakerForEpoch(t, txn, epoch, delegatorHigh, 1, 101) + setStakerForEpoch(t, txn, epoch, delegatorLow, 1, 101) + appendValidatorStaker(txn, validator, delegatorHigh) + appendValidatorStaker(txn, validator, delegatorLow) + addValidatorDelegatedAggregate(txn, validator, big.NewInt(101), true) + addValidatorDelegatedAggregate(txn, validator, big.NewInt(101), true) + txn.Txn().SetBalance(staking.AddrStakingContract, big.NewInt(303)) + + before := map[types.Address]*big.Int{ + validator: new(big.Int).Set(readStakedAmount(txn, validator)), + delegatorHigh: new(big.Int).Set(readStakedAmount(txn, delegatorHigh)), + delegatorLow: new(big.Int).Set(readStakedAmount(txn, delegatorLow)), + } + + slashEvent, err := slashByEffectiveStakeFromStakingContract(txn, epoch, validator, donation, big.NewInt(500), 10, 10, 100, 20, 10) + require.NoError(t, err) + require.NotNil(t, slashEvent) + + allocated := map[types.Address]*big.Int{} + totalAllocated := big.NewInt(0) + for addr, stakeBefore := range before { + allocation := new(big.Int).Sub(stakeBefore, readStakedAmount(txn, addr)) + allocated[addr] = allocation + totalAllocated.Add(totalAllocated, allocation) + require.LessOrEqual(t, allocation.Cmp(stakeBefore), 0) + } + + require.Equal(t, int64(5), totalAllocated.Int64()) + require.Equal(t, int64(5), txn.Txn().GetBalance(donation).Int64()) + require.Equal(t, int64(2), allocated[delegatorLow].Int64(), "lowest address receives first remainder wei") + require.Equal(t, int64(2), allocated[delegatorHigh].Int64(), "second-lowest address receives second remainder wei") + require.Equal(t, int64(1), allocated[validator].Int64(), "highest address does not receive remainder before lower addresses") +} + +func TestSetValidatorInactiveInStaking_ClearsPoolConfigActiveAndIsIdempotent(t *testing.T) { + t.Parallel() + + validator := types.StringToAddress("0x1006") + txn := newPosTestTransitionWithStaking(t, validator) + poolKey := stakingPoolConfigPackedKey(validator) + poolPacked := new(big.Int).SetUint64(0x0101 | 1<<16) + txn.Txn().SetState(staking.AddrStakingContract, poolKey, bigToHash(poolPacked)) + poolBase := new(big.Int).SetBytes(poolKey.Bytes()) + maxKey := types.BytesToHash(new(big.Int).Add(new(big.Int).Set(poolBase), big.NewInt(2)).Bytes()) + minKey := types.BytesToHash(new(big.Int).Add(new(big.Int).Set(poolBase), big.NewInt(3)).Bytes()) + commissionKey := types.BytesToHash(new(big.Int).Add(new(big.Int).Set(poolBase), big.NewInt(4)).Bytes()) + txn.Txn().SetState(staking.AddrStakingContract, maxKey, bigToHash(big.NewInt(123))) + txn.Txn().SetState(staking.AddrStakingContract, minKey, bigToHash(big.NewInt(456))) + txn.Txn().SetState(staking.AddrStakingContract, commissionKey, bigToHash(big.NewInt(789))) + maxBefore := txn.Txn().GetState(staking.AddrStakingContract, maxKey) + minBefore := txn.Txn().GetState(staking.AddrStakingContract, minKey) + commissionBefore := txn.Txn().GetState(staking.AddrStakingContract, commissionKey) + + require.True(t, poolConfigActiveForTest(txn, validator)) + setValidatorInactiveInStaking(txn, validator, 100) + + packed := new(big.Int).SetBytes(txn.Txn().GetState(staking.AddrStakingContract, stakingMetaPackedKey(validator)).Bytes()) + require.False(t, isValidatorMetaActive(packed)) + require.False(t, poolConfigActiveForTest(txn, validator)) + require.True(t, poolConfigDelegationEnabledForTest(txn, validator)) + require.Equal(t, maxBefore, txn.Txn().GetState(staking.AddrStakingContract, maxKey)) + require.Equal(t, minBefore, txn.Txn().GetState(staking.AddrStakingContract, minKey)) + require.Equal(t, commissionBefore, txn.Txn().GetState(staking.AddrStakingContract, commissionKey)) + require.Equal(t, uint64(100), u64FromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingDeactivatedAtBlockKey(validator)))) + + setValidatorInactiveInStaking(txn, validator, 200) + require.Equal(t, uint64(100), u64FromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingDeactivatedAtBlockKey(validator)))) +} + +func TestPosSysAddrNormalUserCallCannotMutateInternalStorage(t *testing.T) { + t.Parallel() + + user := types.StringToAddress("0x3001") + st := itrie.NewState(itrie.NewMemoryStorage()) + ex := state.NewExecutor(&chain.Params{Forks: chain.AllForksEnabled, BurnContract: map[uint64]types.Address{0: types.ZeroAddress}}, st, hclog.NewNullLogger()) + root, err := ex.WriteGenesis(map[types.Address]*chain.GenesisAccount{user: {Balance: big.NewInt(1_000_000)}}, types.ZeroHash) + require.NoError(t, err) + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { return root } + } + txn, err := ex.BeginTxn(root, &types.Header{GasLimit: 100_000}, types.ZeroAddress) + require.NoError(t, err) + key := keyUptimeLastProcessedSlot() + before := u64ToHash(777) + txn.Txn().SetState(PosSysAddr, key, before) + unknownKey := types.StringToHash("0x1234") + + tx := &types.Transaction{ + From: user, + To: &PosSysAddr, + Value: big.NewInt(1), + Gas: 50_000, + GasPrice: big.NewInt(0), + Input: []byte{0x55, 0x00, 0x55}, + } + _, err = txn.Apply(tx) + require.NoError(t, err) + + require.Equal(t, before, txn.Txn().GetState(PosSysAddr, key)) + require.Equal(t, types.ZeroHash, txn.Txn().GetState(PosSysAddr, unknownKey)) + require.Equal(t, int64(1), txn.Txn().GetBalance(PosSysAddr).Int64()) +} + +func poolConfigActiveForTest(txn *state.Transition, validator types.Address) bool { + packed := new(big.Int).SetBytes(txn.Txn().GetState(staking.AddrStakingContract, stakingPoolConfigPackedKey(validator)).Bytes()) + active := new(big.Int).Rsh(packed, 8) + active.And(active, new(big.Int).SetUint64(0xff)) + return active.Sign() != 0 +} + +func poolConfigDelegationEnabledForTest(txn *state.Transition, validator types.Address) bool { + packed := new(big.Int).SetBytes(txn.Txn().GetState(staking.AddrStakingContract, stakingPoolConfigPackedKey(validator)).Bytes()) + enabled := new(big.Int).Rsh(packed, 16) + enabled.And(enabled, new(big.Int).SetUint64(0xff)) + return enabled.Sign() != 0 +} + +func TestSetValidatorInactiveInStaking_DoesNotRewriteDeactivatedAtBlockForAlreadyInactive(t *testing.T) { + t.Parallel() + + validator := types.StringToAddress("0x1004") + txn := newPosTestTransitionWithStaking(t, validator) + + setValidatorInactiveInStaking(txn, validator, 100) + firstPackedState := txn.Txn().GetState(staking.AddrStakingContract, stakingMetaPackedKey(validator)) + firstPacked := new(big.Int).SetBytes(firstPackedState[:]) + require.False(t, isValidatorMetaActive(firstPacked)) + require.Equal(t, uint64(100), u64FromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingDeactivatedAtBlockKey(validator)))) + + setValidatorInactiveInStaking(txn, validator, 200) + secondPackedState := txn.Txn().GetState(staking.AddrStakingContract, stakingMetaPackedKey(validator)) + secondPacked := new(big.Int).SetBytes(secondPackedState[:]) + require.False(t, isValidatorMetaActive(secondPacked)) + require.Equal(t, uint64(100), u64FromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingDeactivatedAtBlockKey(validator)))) +} + +func newPosTestTransition(t *testing.T) *state.Transition { + t.Helper() + + st := itrie.NewState(itrie.NewMemoryStorage()) + + forks := chain.Forks{} + for name, fork := range *chain.AllForksEnabled { + forks[name] = fork + } + forks[chain.FeePoolSplit] = chain.NewFork(0) + + ex := state.NewExecutor(&chain.Params{ + Forks: &forks, + BurnContract: map[uint64]types.Address{ + 0: types.ZeroAddress, + }, + }, st, hclog.NewNullLogger()) + + rootHash, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { + return rootHash + } + } + + transition, err := ex.BeginTxn(rootHash, &types.Header{}, types.ZeroAddress) + require.NoError(t, err) + + return transition +} + +func newPosTestTransitionWithStaking(t *testing.T, validator types.Address) *state.Transition { + t.Helper() + + txn := newPosTestTransition(t) + vals := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(validator)) + + contractState, err := stakingHelper.PredeployStakingSC(vals, stakingHelper.PredeployParams{ + MinValidatorCount: 1, + MaxValidatorCount: 10, + EpochSize: 10, + }) + require.NoError(t, err) + require.NoError(t, txn.SetAccountDirectly(staking.AddrStakingContract, contractState)) + + return txn +} + +func newPosTestTransitionWithStakingValidators(t *testing.T, vals validators.Validators, minValidators, maxValidators uint64) *state.Transition { + t.Helper() + + txn := newPosTestTransition(t) + + contractState, err := stakingHelper.PredeployStakingSC(vals, stakingHelper.PredeployParams{ + MinValidatorCount: minValidators, + MaxValidatorCount: maxValidators, + EpochSize: 10, + }) + require.NoError(t, err) + require.NoError(t, txn.SetAccountDirectly(staking.AddrStakingContract, contractState)) + + return txn +} + +func newPosTestTransitionWithStakingValidatorsAtBlock( + t *testing.T, + vals validators.Validators, + minValidators, maxValidators, blockNumber uint64, +) *state.Transition { + t.Helper() + + st := itrie.NewState(itrie.NewMemoryStorage()) + + forks := chain.Forks{} + for name, fork := range *chain.AllForksEnabled { + forks[name] = fork + } + forks[chain.FeePoolSplit] = chain.NewFork(0) + + ex := state.NewExecutor(&chain.Params{ + Forks: &forks, + BurnContract: map[uint64]types.Address{ + 0: types.ZeroAddress, + }, + }, st, hclog.NewNullLogger()) + + rootHash, err := ex.WriteGenesis(nil, types.Hash{}) + require.NoError(t, err) + + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { + return rootHash + } + } + + txn, err := ex.BeginTxn(rootHash, &types.Header{Number: blockNumber}, types.ZeroAddress) + require.NoError(t, err) + + contractState, err := stakingHelper.PredeployStakingSC(vals, stakingHelper.PredeployParams{ + MinValidatorCount: minValidators, + MaxValidatorCount: maxValidators, + EpochSize: 10, + }) + require.NoError(t, err) + require.NoError(t, txn.SetAccountDirectly(staking.AddrStakingContract, contractState)) + + return txn +} diff --git a/consensus/ibft/pos/keys.go b/consensus/ibft/pos/keys.go new file mode 100644 index 0000000..b33e7c6 --- /dev/null +++ b/consensus/ibft/pos/keys.go @@ -0,0 +1,222 @@ +package pos + +import ( + "encoding/binary" + "math/big" + + "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/types" +) + +// PosSysAddr is a PoS system state address (consensus-owned storage namespace). +// It remains fixed so fork-gating stays deterministic. +var PosSysAddr = types.StringToAddress("0x0000000000000000000000000000000000009999") + +const ( + keyPrefixPropSlots = "xgr.pos.prop.slots" + keyPrefixPropMiss = "xgr.pos.prop.miss" + keyPrefixLastProp = "xgr.pos.last.proposer" + keyPrefixEpochValsLen = "xgr.pos.epoch.validators.len" + keyPrefixEpochVal = "xgr.pos.epoch.validators.val" + keyPrefixStakeSnap = "xgr.pos.stake.snapshot" + keyPrefixStakerStakeSnap = "xgr.pos.staker.stake.snapshot" + keyPrefixSlashed = "xgr.pos.slashed" + keyPrefixSlashAmt = "xgr.pos.slash.amount" + keyPrefixRewardTot = "xgr.pos.reward.total" + keyPrefixRewardVal = "xgr.pos.reward.validator" + keyPrefixRewardValComm = "xgr.pos.reward.validator.commission" + keyPrefixRewardDelTot = "xgr.pos.reward.delegators.total" + keyPrefixRewardDelegator = "xgr.pos.reward.delegator" + keyPrefixMicroEpApplied = "xgr.uptime.microepoch.last.applied" + keyPrefixMicroEpSeen = "xgr.uptime.microepoch.seen" + keyPrefixMicroEpDuty = "xgr.uptime.microepoch.duty" + keyPrefixUptimeEffW = "xgr.uptime.effective.weight" + keyPrefixUptimeNomW = "xgr.uptime.nominal.weight" + keyPrefixUptimeInact = "xgr.uptime.inactivity.count" + keyPrefixUptimeLastSlot = "xgr.uptime.last.processed.slot" +) + +func epochOf(number, epochSize uint64) uint64 { + // mirror polygon-edge logic: + // if number % epochSize == 0 => epoch = number/epochSize + // else => epoch = number/epochSize + 1 + if number == 0 || epochSize == 0 { + return 0 + } + + if number%epochSize == 0 { + return number / epochSize + } + + return number/epochSize + 1 +} + +func u64ToHash(v uint64) types.Hash { + var b [32]byte + binary.BigEndian.PutUint64(b[24:], v) + + return types.BytesToHash(b[:]) +} + +func u64FromHash(h types.Hash) uint64 { + return binary.BigEndian.Uint64(h[24:]) +} + +func keyProposerSlots(epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixPropSlots), eb[:], addr.Bytes())) +} + +func keyProposerMissed(epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixPropMiss), eb[:], addr.Bytes())) +} + +func keyLastProposer() types.Hash { + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixLastProp))) +} + +func keyEpochValidatorsLen(epoch uint64) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixEpochValsLen), eb[:])) +} + +func keyEpochValidator(epoch, idx uint64) types.Hash { + var eb [8]byte + var ib [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + binary.BigEndian.PutUint64(ib[:], idx) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixEpochVal), eb[:], ib[:])) +} + +func keyStakeSnapshot(epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixStakeSnap), eb[:], addr.Bytes())) +} + +func keyStakerStakeSnapshot(epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixStakerStakeSnap), eb[:], addr.Bytes())) +} + +func keySlashed(epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixSlashed), eb[:], addr.Bytes())) +} + +func keySlashAmount(epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixSlashAmt), eb[:], addr.Bytes())) +} + +// Legacy reward-history keys. Do not write these keys. Kept only for tests/audit +// checks that finalized PosSysAddr reward history remains absent. +func keyRewardTotal(epoch uint64) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixRewardTot), eb[:])) +} + +func keyValidatorReward(epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixRewardVal), eb[:], addr.Bytes())) +} + +func keyValidatorCommissionReward(epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixRewardValComm), eb[:], addr.Bytes())) +} + +func keyValidatorDelegatorsRewardTotal(epoch uint64, addr types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixRewardDelTot), eb[:], addr.Bytes())) +} + +func keyDelegatorReward(epoch uint64, validator, delegator types.Address) types.Hash { + var eb [8]byte + binary.BigEndian.PutUint64(eb[:], epoch) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixRewardDelegator), eb[:], validator.Bytes(), delegator.Bytes())) +} + +func keyMicroEpochApplied() types.Hash { + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixMicroEpApplied))) +} + +func keyMicroEpochSeen(me uint64, addr types.Address) types.Hash { + var mb [8]byte + binary.BigEndian.PutUint64(mb[:], me) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixMicroEpSeen), mb[:], addr.Bytes())) +} + +func keyMicroEpochDuty(me uint64, addr types.Address) types.Hash { + var mb [8]byte + binary.BigEndian.PutUint64(mb[:], me) + + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixMicroEpDuty), mb[:], addr.Bytes())) +} + +func keyUptimeNominalWeight(addr types.Address) types.Hash { + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixUptimeNomW), addr.Bytes())) +} + +func keyUptimeEffectiveWeight(addr types.Address) types.Hash { + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixUptimeEffW), addr.Bytes())) +} + +func keyUptimeInactivity(addr types.Address) types.Hash { + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixUptimeInact), addr.Bytes())) +} + +func keyUptimeLastProcessedSlot() types.Hash { + return types.BytesToHash(crypto.Keccak256([]byte(keyPrefixUptimeLastSlot))) +} + +func bigToHash(v *big.Int) types.Hash { + if v == nil { + return types.Hash{} + } + + b := v.Bytes() + if len(b) == 0 { + return types.Hash{} + } + + var out [32]byte + if len(b) > 32 { + copy(out[:], b[len(b)-32:]) + } else { + copy(out[32-len(b):], b) + } + + return types.BytesToHash(out[:]) +} + +func bigFromHash(h types.Hash) *big.Int { + if h == (types.Hash{}) { + return new(big.Int) + } + + return new(big.Int).SetBytes(h[:]) +} diff --git a/consensus/ibft/pos/signer_test_helpers_test.go b/consensus/ibft/pos/signer_test_helpers_test.go new file mode 100644 index 0000000..2d34095 --- /dev/null +++ b/consensus/ibft/pos/signer_test_helpers_test.go @@ -0,0 +1,27 @@ +package pos + +import ( + "sync" + + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + xcrypto "github.com/xgr-network/xgr-node/crypto" +) + +var ( + testSignerOnce sync.Once + testSignerInst signer.Signer +) + +func testHeaderSigner() signer.Signer { + testSignerOnce.Do(func() { + key, err := xcrypto.GenerateECDSAKey() + if err != nil { + panic(err) + } + + keyManager := signer.NewECDSAKeyManagerFromKey(key) + testSignerInst = signer.NewSigner(keyManager, keyManager) + }) + + return testSignerInst +} diff --git a/consensus/ibft/pos/sys_account.go b/consensus/ibft/pos/sys_account.go new file mode 100644 index 0000000..6ba7d63 --- /dev/null +++ b/consensus/ibft/pos/sys_account.go @@ -0,0 +1,20 @@ +package pos + +import ( + "github.com/xgr-network/xgr-node/state" +) + +// ensurePosSysAccount makes the PoS system storage account non-empty so it won't be pruned +// by CleanDeleteObjects(deleteEmptyObjects=true). StateObject.Empty() ignores storage, so a +// pure "storage-only" account would be deleted every block otherwise. +// +// Consensus-critical: deterministic and idempotent. +func ensurePosSysAccount(txn *state.Transition) { + if txn == nil { + return + } + + if txn.Txn().GetNonce(PosSysAddr) == 0 { + txn.Txn().SetNonce(PosSysAddr, 1) + } +} diff --git a/consensus/ibft/pos/system_tx.go b/consensus/ibft/pos/system_tx.go new file mode 100644 index 0000000..70adb3b --- /dev/null +++ b/consensus/ibft/pos/system_tx.go @@ -0,0 +1,33 @@ +package pos + +import ( + "math/big" + + "github.com/xgr-network/xgr-node/contracts" + "github.com/xgr-network/xgr-node/types" +) + +// EpochFinalizationSystemTx returns the deterministic native +// epoch-finalization system transaction for boundary blocks. It is +// consensus-generated, not mempool/user-provided, and represents native epoch +// finalization for receipt/log/indexing purposes. +func EpochFinalizationSystemTx(blockNumber uint64) *types.Transaction { + tx := &types.Transaction{ + Nonce: blockNumber, + GasPrice: big.NewInt(0), + Gas: types.StateTransactionGasLimit, + To: &PosSysAddr, + Value: big.NewInt(0), + Input: []byte(types.EpochFinalizationSystemTxInput), + From: contracts.SystemCaller, + Type: types.StateTx, + } + + return tx.ComputeHash(blockNumber) +} + +// IsEpochFinalizationSystemTx reports whether tx is the deterministic native +// PoS epoch-finalization system transaction for blockNumber. +func IsEpochFinalizationSystemTx(tx *types.Transaction, blockNumber uint64) bool { + return types.IsEpochFinalizationSystemTxForBlock(tx, blockNumber, contracts.SystemCaller) +} diff --git a/consensus/ibft/pos/test_txn_helper_test.go b/consensus/ibft/pos/test_txn_helper_test.go new file mode 100644 index 0000000..86f8e43 --- /dev/null +++ b/consensus/ibft/pos/test_txn_helper_test.go @@ -0,0 +1,10 @@ +package pos + +import "github.com/xgr-network/xgr-node/types" + +type memUptimeTxn struct { + m map[types.Hash]types.Hash +} + +func (m *memUptimeTxn) SetState(_ types.Address, k types.Hash, v types.Hash) { m.m[k] = v } +func (m *memUptimeTxn) GetState(_ types.Address, k types.Hash) types.Hash { return m.m[k] } diff --git a/consensus/ibft/pos/uptime.go b/consensus/ibft/pos/uptime.go new file mode 100644 index 0000000..bbdc130 --- /dev/null +++ b/consensus/ibft/pos/uptime.go @@ -0,0 +1,289 @@ +package pos + +import ( + "fmt" + + "github.com/hashicorp/go-hclog" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +var posDiagLogger = hclog.L().Named("ibft-pos-diag") + +var ensureEpochValidatorsSnapshotFn = ensureEpochValidatorsSnapshot +var getOrInitStakeSnapshotFn = getOrInitStakeSnapshot +var getOrInitStakerStakeSnapshotFn = getOrInitStakerStakeSnapshot + +const maxFinalRoundForUptime = uint64(1000) + +// RecordBlockUptime accounts proposer availability deterministically from the finalized header. +// +// Rationale: +// Counting commit signatures is unsafe if the header only contains quorum seals (subset), +// which can lead to systematic false slashing. RoundNumber is consensus-verified and lets +// us penalize only provable missed proposer slots. +// +// Accounting model (per epoch, per validator): +// - proposerSlots += 1 for each assigned proposer slot (for each round attempt) +// - proposerMissed += 1 for each failed round (r < finalRound) +// +// Notes: +// - Genesis (block 0) is ignored. +// - Epoch boundary blocks (height % epochSize == 0) are skipped to keep alignment with FinalizeEpoch +// (which finalizes epochOf(height-1)). +func RecordBlockUptime( + header *types.Header, + epochSize uint64, + epochValidators validators.Validators, + headerSigner signer.Signer, + uptimeCfg UptimeConfig, + txn *state.Transition, +) error { + if header == nil || txn == nil { + return nil + } + + // Fork-gated: no PoS accounting before pooling overlay is active. + if !txn.ForksInTime().FeePoolSplit { + return nil + } + + // IMPORTANT: keep PosSysAddr alive (otherwise storage is pruned every block). + ensurePosSysAccount(txn) + if epochSize < 2 { + posDiagLogger.Error("RecordBlockUptime failed: epochSize must be >= 2", "block", header.Number, "epochSize", epochSize) + return fmt.Errorf("epochSize must be greater than or equal to 2") + } + + if header.Number == 0 { + return nil + } + + // Skip the tx-free epoch boundary block. This block exists as a deterministic finalize/system block. + if header.Number%epochSize == 0 { + return nil + } + + epoch := epochOf(header.Number, epochSize) + if epoch == 0 { + return nil + } + + // A single monotonic slot guard replaces unbounded per-block processed keys. + // Reprocessing the same or an older block is idempotently ignored. + if u64FromHash(txn.Txn().GetState(PosSysAddr, keyUptimeLastProcessedSlot())) >= header.Number { + return nil + } + + extra, err := getIBFTExtra(headerSigner, header) + if err != nil { + // Don't brick block import. + posDiagLogger.Error("RecordBlockUptime cannot decode IBFT extra", "block", header.Number, "epoch", epoch, "err", err) + return fmt.Errorf("decode IBFT extra for block %d epoch %d: %w", header.Number, epoch, err) + } + + valSet := epochValidators + if valSet == nil || valSet.Len() == 0 { + valSet = extra.Validators + } + if valSet == nil || valSet.Len() == 0 { + posDiagLogger.Error("RecordBlockUptime skipped: validator set is empty", "block", header.Number, "epoch", epoch) + return fmt.Errorf("validator set empty for block %d epoch %d", header.Number, epoch) + } + // Freeze the exact validator membership of this epoch on the first processed + // non-boundary block. FinalizeEpoch and monitoring for epoch X must use the + // epoch-X membership, even if set-active=false was toggled during X and only + // becomes effective at X+1. + lenKey := keyEpochValidatorsLen(epoch) + if u64FromHash(txn.Txn().GetState(PosSysAddr, lenKey)) == 0 { + ok, err := ensureEpochValidatorsSnapshotFn(txn, epoch, valSet) + if err != nil { + return fmt.Errorf("freeze epoch validator snapshot for epoch %d: %w", epoch, err) + } + if !ok { + return fmt.Errorf("freeze epoch validator snapshot for epoch %d", epoch) + } + if u64FromHash(txn.Txn().GetState(PosSysAddr, lenKey)) == 0 { + return fmt.Errorf("freeze epoch validator snapshot for epoch %d: missing after write", epoch) + } + } + + // Freeze per-epoch stake snapshots as early as possible (first processed block in epoch) + // for all validators in the consensus set, so last-minute stake changes do not distort + // epoch reward/slash weights at finalize. + for i := 0; i < valSet.Len(); i++ { + addr := valSet.At(uint64(i)).Addr() + if _, err := getOrInitStakeSnapshotFn(txn, epoch, addr); err != nil { + return fmt.Errorf("freeze stake snapshot for validator %s in epoch %d: %w", addr, epoch, err) + } + if _, err := getOrInitStakerStakeSnapshotFn(txn, epoch, addr); err != nil { + return fmt.Errorf("freeze staker snapshot for validator %s in epoch %d: %w", addr, epoch, err) + } + for _, staker := range readValidatorStakers(txn, addr) { + if !isStakerEffectiveAt(txn, staker, header.Number-1, epochSize) { + continue + } + if _, err := getOrInitStakerStakeSnapshotFn(txn, epoch, staker); err != nil { + return fmt.Errorf("freeze staker snapshot for staker %s (validator %s) in epoch %d: %w", staker, addr, epoch, err) + } + } + } + + // Round begins with zero; nil means 0 for backward compatibility. + finalRound := uint64(0) + if extra.RoundNumber != nil { + finalRound = *extra.RoundNumber + } + if finalRound > maxFinalRoundForUptime { + posDiagLogger.Error( + "RecordBlockUptime finalRound too large, clamping", + "block", header.Number, + "epoch", epoch, + "finalRound", finalRound, + "maxFinalRoundForUptime", maxFinalRoundForUptime, + ) + finalRound = maxFinalRoundForUptime + } + actual, err := resolveProposer(headerSigner, header, valSet) + if err != nil { + posDiagLogger.Error("RecordBlockUptime cannot resolve proposer", "block", header.Number, "epoch", epoch, "err", err) + return fmt.Errorf("resolve proposer for block %d epoch %d: %w", header.Number, epoch, err) + } + + // Use chain-persistent proposer context (previous block proposer), + // instead of deriving it from the current block's actual proposer. + // Deriving from actual makes expected==actual tautological and can skew rewards. + lastProp := getLastProposer(txn) + + if uptimeCfg.MicroEpochSize > 0 { + for i := 0; i < valSet.Len(); i++ { + addr := valSet.At(uint64(i)).Addr() + if UptimeNominalWeight(txn.Txn(), addr) == 0 { + txn.Txn().SetState(PosSysAddr, keyUptimeNominalWeight(addr), u64ToHash(uptimeCfg.MicroEpochNominalWeight)) + } + if UptimeEffectiveWeight(txn.Txn(), addr) == 0 { + txn.Txn().SetState(PosSysAddr, keyUptimeEffectiveWeight(addr), u64ToHash(uptimeCfg.MicroEpochNominalWeight)) + } + } + + currentME := microEpochOfBlock(header.Number, uptimeCfg.MicroEpochSize) + if actual != types.ZeroAddress { + txn.Txn().SetState(PosSysAddr, keyMicroEpochSeen(currentME, actual), u64ToHash(1)) + } + + for r := uint64(0); r <= finalRound; r++ { + p := calcProposer(valSet, r, lastProp) + if p == types.ZeroAddress { + continue + } + txn.Txn().SetState(PosSysAddr, keyMicroEpochDuty(currentME, p), u64ToHash(1)) + } + + if err := applyMicroEpochUptime(txn, valSet, header.Number, actual, uptimeCfg); err != nil { + return err + } + } + + // Attribute missed proposer slots for each failed round. + for r := uint64(0); r < finalRound; r++ { + p := calcProposer(valSet, r, lastProp) + if p == types.ZeroAddress { + continue + } + incU64State(txn, keyProposerSlots(epoch, p), 1) + incU64State(txn, keyProposerMissed(epoch, p), 1) + } + // Attribute the successful finalized round to the actual finalized proposer. + + incU64State(txn, keyProposerSlots(epoch, actual), 1) + + // Persist the finalized block proposer for the next height proposer selection context. + setLastProposer(txn, actual) + + // Mark this slot as accounted without creating per-block history. + txn.Txn().SetState(PosSysAddr, keyUptimeLastProcessedSlot(), u64ToHash(header.Number)) + + return nil +} + +func calcProposer(set validators.Validators, round uint64, lastProposer types.Address) types.Address { + if set == nil || set.Len() == 0 { + return types.ZeroAddress + } + + var seed uint64 + if lastProposer == types.ZeroAddress { + seed = round + } else { + offset := int64(0) + if index := set.Index(lastProposer); index != -1 { + offset = int64(index) + } + seed = uint64(offset) + round + 1 + } + + pick := seed % uint64(set.Len()) + return set.At(pick).Addr() +} + +func getLastProposer(txn *state.Transition) types.Address { + if txn == nil { + return types.ZeroAddress + } + + raw := txn.Txn().GetState(PosSysAddr, keyLastProposer()) + var addr types.Address + copy(addr[:], raw[12:]) + + return addr +} + +func setLastProposer(txn *state.Transition, addr types.Address) { + if txn == nil || addr == types.ZeroAddress { + return + } + + var raw types.Hash + copy(raw[12:], addr[:]) + txn.Txn().SetState(PosSysAddr, keyLastProposer(), raw) +} + +func incU64State(txn *state.Transition, key types.Hash, delta uint64) { + cur := txn.Txn().GetState(PosSysAddr, key) + v := u64FromHash(cur) + v += delta + txn.Txn().SetState(PosSysAddr, key, u64ToHash(v)) +} + +func resolveProposer(headerSigner signer.Signer, header *types.Header, set validators.Validators) (types.Address, error) { + if headerSigner == nil || header == nil { + return types.ZeroAddress, fmt.Errorf("nil") + } + + proposer, err := headerSigner.EcrecoverFromHeader(header) + if err == nil { + if set != nil && set.Index(proposer) >= 0 { + return proposer, nil + } + + return types.ZeroAddress, fmt.Errorf("recovered proposer not in validator set: %s", proposer.String()) + } + + return types.ZeroAddress, fmt.Errorf("proposer seal recovery failed: %w", err) +} + +func getIBFTExtra(headerSigner signer.Signer, header *types.Header) (*signer.IstanbulExtra, error) { + if headerSigner == nil || header == nil { + return nil, fmt.Errorf("nil") + } + + extra, err := headerSigner.GetIBFTExtra(header) + if err != nil { + posDiagLogger.Error("getIBFTExtra decode failed via signer", "block", header.Number, "err", err) + return nil, err + } + + return extra, nil +} diff --git a/consensus/ibft/pos/uptime_bls_test.go b/consensus/ibft/pos/uptime_bls_test.go new file mode 100644 index 0000000..9b50154 --- /dev/null +++ b/consensus/ibft/pos/uptime_bls_test.go @@ -0,0 +1,67 @@ +package pos + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func TestCollectSignedAddresses_BLSAggregatedSeal(t *testing.T) { + v1 := validators.NewBLSValidator(types.StringToAddress("0x11"), validators.BLSValidatorPublicKey("pk1")) + v2 := validators.NewBLSValidator(types.StringToAddress("0x22"), validators.BLSValidatorPublicKey("pk2")) + v3 := validators.NewBLSValidator(types.StringToAddress("0x33"), validators.BLSValidatorPublicKey("pk3")) + + bitmap := new(big.Int) + bitmap.SetBit(bitmap, 1, 1) + bitmap.SetBit(bitmap, 2, 1) + + extra := &signer.IstanbulExtra{ + Validators: validators.NewBLSValidatorSet(v1, v2, v3), + ParentCommittedSeals: &signer.AggregatedSeal{ + Bitmap: bitmap, + Signature: []byte("aggregate-signature"), + }, + } + + seen := collectSignedAddresses(extra, types.StringToHash("0xabc"), nil) + require.Len(t, seen, 2) + _, ok2 := seen[v2.Addr()] + require.True(t, ok2) + _, ok3 := seen[v3.Addr()] + require.True(t, ok3) +} + +func TestShouldSkipUptimeAccounting_BLSBitmapWithoutValidators(t *testing.T) { + bitmap := new(big.Int) + bitmap.SetBit(bitmap, 0, 1) + + extra := &signer.IstanbulExtra{ + Validators: validators.NewBLSValidatorSet(), + ParentCommittedSeals: &signer.AggregatedSeal{ + Bitmap: bitmap, + Signature: []byte("aggregate-signature"), + }, + } + + require.True(t, shouldSkipUptimeAccounting(extra, nil, map[types.Address]struct{}{})) +} + +func TestShouldSkipUptimeAccounting_BLSBitmapWithCollectedSigners(t *testing.T) { + v1 := validators.NewBLSValidator(types.StringToAddress("0x11"), validators.BLSValidatorPublicKey("pk1")) + bitmap := new(big.Int) + bitmap.SetBit(bitmap, 0, 1) + + extra := &signer.IstanbulExtra{ + Validators: validators.NewBLSValidatorSet(v1), + ParentCommittedSeals: &signer.AggregatedSeal{ + Bitmap: bitmap, + Signature: []byte("aggregate-signature"), + }, + } + + require.False(t, shouldSkipUptimeAccounting(extra, nil, map[types.Address]struct{}{v1.Addr(): {}})) +} diff --git a/consensus/ibft/pos/uptime_config.go b/consensus/ibft/pos/uptime_config.go new file mode 100644 index 0000000..45f6af9 --- /dev/null +++ b/consensus/ibft/pos/uptime_config.go @@ -0,0 +1,68 @@ +package pos + +import "encoding/json" + +const ( + keyMicroEpochSize = "microEpochSize" + keyMicroEpochInactivityDecayBps = "microEpochInactivityDecayBps" + keyMicroEpochNominalWeightUnits = "microEpochNominalWeightUnits" +) + +// UptimeConfig holds the micro-epoch uptime/liveness configuration used by PoS. +type UptimeConfig struct { + MicroEpochSize uint64 + MicroEpochInactivityDecayBps uint64 + MicroEpochNominalWeight uint64 + ChainID int64 +} + +// DefaultUptimeConfig returns default micro-epoch uptime/liveness parameters. +func DefaultUptimeConfig() UptimeConfig { + return UptimeConfig{ + MicroEpochSize: 0, + MicroEpochInactivityDecayBps: 9000, + MicroEpochNominalWeight: 10000, + } +} + +// ParseUptimeConfig parses uptime/micro-epoch configuration from IBFT config. +func ParseUptimeConfig(ibftConfig map[string]interface{}) UptimeConfig { + cfg := DefaultUptimeConfig() + if ibftConfig == nil { + return cfg + } + + readOK := func(key string) (uint64, bool) { + raw, ok := ibftConfig[key] + if !ok || raw == nil { + return 0, false + } + n, ok := raw.(float64) + if !ok || n < 0 { + return 0, false + } + return uint64(n), true + } + + if v, ok := readOK(keyMicroEpochSize); ok { + cfg.MicroEpochSize = v + } + + if v, ok := readOK(keyMicroEpochInactivityDecayBps); ok { + cfg.MicroEpochInactivityDecayBps = v + } + + if v, ok := readOK(keyMicroEpochNominalWeightUnits); ok { + cfg.MicroEpochNominalWeight = v + } + + return cfg +} + +func (c UptimeConfig) Enabled() bool { + return c.MicroEpochSize > 0 && c.MicroEpochNominalWeight > 0 +} + +func MarshalUptimeConfigJSON(cfg UptimeConfig) ([]byte, error) { + return json.Marshal(cfg) +} diff --git a/consensus/ibft/pos/uptime_config_test.go b/consensus/ibft/pos/uptime_config_test.go new file mode 100644 index 0000000..86d61a1 --- /dev/null +++ b/consensus/ibft/pos/uptime_config_test.go @@ -0,0 +1,39 @@ +package pos + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseUptimeConfig_NewKeys(t *testing.T) { + cfg := ParseUptimeConfig(map[string]interface{}{ + keyMicroEpochSize: float64(8), + keyMicroEpochInactivityDecayBps: float64(9000), + keyMicroEpochNominalWeightUnits: float64(10000), + }) + + require.Equal(t, uint64(8), cfg.MicroEpochSize) + require.Equal(t, uint64(9000), cfg.MicroEpochInactivityDecayBps) + require.Equal(t, uint64(10000), cfg.MicroEpochNominalWeight) +} + +func TestParseUptimeConfig_LegacyBeaconKeysIgnored(t *testing.T) { + cfg := ParseUptimeConfig(map[string]interface{}{ + "beaconSlotsPerEpoch": float64(8), + "beaconInactivityDecayBps": float64(9000), + "beaconNominalWeightUnits": float64(10000), + }) + + require.Equal(t, uint64(0), cfg.MicroEpochSize) + require.Equal(t, uint64(9000), cfg.MicroEpochInactivityDecayBps) + require.Equal(t, uint64(10000), cfg.MicroEpochNominalWeight) +} + +func TestDefaultUptimeConfig(t *testing.T) { + cfg := DefaultUptimeConfig() + + require.Equal(t, uint64(0), cfg.MicroEpochSize) + require.Equal(t, uint64(9000), cfg.MicroEpochInactivityDecayBps) + require.Equal(t, uint64(10000), cfg.MicroEpochNominalWeight) +} diff --git a/consensus/ibft/pos/uptime_extra_decode_test.go b/consensus/ibft/pos/uptime_extra_decode_test.go new file mode 100644 index 0000000..2a0a8f7 --- /dev/null +++ b/consensus/ibft/pos/uptime_extra_decode_test.go @@ -0,0 +1,58 @@ +package pos + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + xcrypto "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func TestGetIBFTExtra_UsesProvidedSignerForECDSAExtra(t *testing.T) { + v := types.StringToAddress("0x9991") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + header := makeUptimeHeader(42, set) + + extra, err := getIBFTExtra(testHeaderSigner(), header) + require.NoError(t, err) + require.NotNil(t, extra) + require.NotNil(t, extra.Validators) + require.Equal(t, 1, extra.Validators.Len()) + require.Equal(t, v, extra.Validators.At(0).Addr()) +} + +func TestGetIBFTExtra_ReturnsErrorOnMalformedExtraPayload(t *testing.T) { + header := &types.Header{ + Number: 43, + ExtraData: []byte{0x01, 0x02, 0x03, 0x04}, + } + + extra, err := getIBFTExtra(testHeaderSigner(), header) + require.Error(t, err) + require.Nil(t, extra) +} + +func TestGetIBFTExtra_UsesProvidedSignerForBLSExtra(t *testing.T) { + v := validators.NewBLSValidator(types.StringToAddress("0x9992"), validators.BLSValidatorPublicKey("pk1")) + set := validators.NewBLSValidatorSet(v) + header := makeUptimeHeader(44, set) + + ecdsaExtra, ecdsaErr := getIBFTExtra(testHeaderSigner(), header) + require.Error(t, ecdsaErr) + require.Nil(t, ecdsaExtra) + + ecdsaKey, err := xcrypto.GenerateECDSAKey() + require.NoError(t, err) + blsKey, err := xcrypto.GenerateBLSKey() + require.NoError(t, err) + blsKeyManager := signer.NewBLSKeyManagerFromKeys(ecdsaKey, blsKey) + blsSigner := signer.NewSigner(blsKeyManager, blsKeyManager) + + blsExtra, err := getIBFTExtra(blsSigner, header) + require.NoError(t, err) + require.NotNil(t, blsExtra) + require.Equal(t, 1, blsExtra.Validators.Len()) + require.Equal(t, v.Addr(), blsExtra.Validators.At(0).Addr()) +} diff --git a/consensus/ibft/pos/uptime_microepoch.go b/consensus/ibft/pos/uptime_microepoch.go new file mode 100644 index 0000000..c729379 --- /dev/null +++ b/consensus/ibft/pos/uptime_microepoch.go @@ -0,0 +1,66 @@ +package pos + +import ( + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func microEpochOfBlock(blockNumber, microEpochSize uint64) uint64 { + if blockNumber == 0 || microEpochSize == 0 { + return 0 + } + + return (blockNumber - 1) / microEpochSize +} + +func applyMicroEpochUptime(txn *state.Transition, valSet validators.Validators, currentBlock uint64, _ types.Address, cfg UptimeConfig) error { + if txn == nil || valSet == nil || cfg.MicroEpochSize == 0 { + return nil + } + + currentME := microEpochOfBlock(currentBlock, cfg.MicroEpochSize) + lastApplied := u64FromHash(txn.Txn().GetState(PosSysAddr, keyMicroEpochApplied())) + + for me := lastApplied; me < currentME; me++ { + for i := 0; i < valSet.Len(); i++ { + addr := valSet.At(uint64(i)).Addr() + dutyKey := keyMicroEpochDuty(me, addr) + seenKey := keyMicroEpochSeen(me, addr) + duty := u64FromHash(txn.Txn().GetState(PosSysAddr, dutyKey)) == 1 + seen := u64FromHash(txn.Txn().GetState(PosSysAddr, seenKey)) == 1 + + nominal := UptimeNominalWeight(txn.Txn(), addr) + if nominal == 0 { + nominal = cfg.MicroEpochNominalWeight + } + eff := UptimeEffectiveWeight(txn.Txn(), addr) + if eff == 0 { + eff = nominal + } + + switch { + case seen: + txn.Txn().SetState(PosSysAddr, keyUptimeEffectiveWeight(addr), u64ToHash(nominal)) + txn.Txn().SetState(PosSysAddr, keyUptimeInactivity(addr), u64ToHash(0)) + eff = nominal + case duty: + decayed := eff * cfg.MicroEpochInactivityDecayBps / 10000 + if decayed < 1 { + decayed = 1 + } + txn.Txn().SetState(PosSysAddr, keyUptimeEffectiveWeight(addr), u64ToHash(decayed)) + incU64State(txn, keyUptimeInactivity(addr), 1) + eff = decayed + } + + // Micro-epoch duty/seen flags are working state only; clear them once applied + // so old flags cannot affect future uptime calculations. + txn.Txn().SetState(PosSysAddr, dutyKey, types.Hash{}) + txn.Txn().SetState(PosSysAddr, seenKey, types.Hash{}) + } + txn.Txn().SetState(PosSysAddr, keyMicroEpochApplied(), u64ToHash(me+1)) + } + + return nil +} diff --git a/consensus/ibft/pos/uptime_microepoch_test.go b/consensus/ibft/pos/uptime_microepoch_test.go new file mode 100644 index 0000000..06b5d4e --- /dev/null +++ b/consensus/ibft/pos/uptime_microepoch_test.go @@ -0,0 +1,178 @@ +package pos + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func TestMicroEpochOfBlock(t *testing.T) { + require.Equal(t, uint64(0), microEpochOfBlock(0, 8)) + require.Equal(t, uint64(0), microEpochOfBlock(1, 8)) + require.Equal(t, uint64(0), microEpochOfBlock(8, 8)) + require.Equal(t, uint64(1), microEpochOfBlock(9, 8)) + require.Equal(t, uint64(1), microEpochOfBlock(16, 8)) + require.Equal(t, uint64(2), microEpochOfBlock(17, 8)) +} + +func TestApplyMicroEpochUptime_DutySeenSemantics(t *testing.T) { + txn := newPosTestTransition(t) + v0 := types.StringToAddress("0x1001") + v1 := types.StringToAddress("0x1002") + v2 := types.StringToAddress("0x1003") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v0), validators.NewECDSAValidator(v1), validators.NewECDSAValidator(v2)) + cfg := UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10000, MicroEpochInactivityDecayBps: 9000} + for _, a := range []types.Address{v0, v1, v2} { + txn.Txn().SetState(PosSysAddr, keyUptimeNominalWeight(a), u64ToHash(10000)) + txn.Txn().SetState(PosSysAddr, keyUptimeEffectiveWeight(a), u64ToHash(10000)) + } + txn.Txn().SetState(PosSysAddr, keyMicroEpochDuty(0, v0), u64ToHash(1)) + txn.Txn().SetState(PosSysAddr, keyMicroEpochDuty(0, v1), u64ToHash(1)) + txn.Txn().SetState(PosSysAddr, keyMicroEpochSeen(0, v1), u64ToHash(1)) + + require.NoError(t, applyMicroEpochUptime(txn, set, 3, types.ZeroAddress, cfg)) + require.Equal(t, uint64(9000), UptimeEffectiveWeight(txn.Txn(), v0)) + require.Equal(t, uint64(10000), UptimeEffectiveWeight(txn.Txn(), v1)) + require.Equal(t, uint64(10000), UptimeEffectiveWeight(txn.Txn(), v2)) + require.Zero(t, u64FromHash(txn.Txn().GetState(PosSysAddr, keyMicroEpochDuty(0, v0)))) + require.Zero(t, u64FromHash(txn.Txn().GetState(PosSysAddr, keyMicroEpochDuty(0, v1)))) + require.Zero(t, u64FromHash(txn.Txn().GetState(PosSysAddr, keyMicroEpochSeen(0, v1)))) +} + +func TestApplyMicroEpochUptime_RestoreUsesNominalNotEffective(t *testing.T) { + txn := newPosTestTransition(t) + v := types.StringToAddress("0x2001") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + cfg := UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10000, MicroEpochInactivityDecayBps: 9000} + txn.Txn().SetState(PosSysAddr, keyUptimeNominalWeight(v), u64ToHash(10000)) + txn.Txn().SetState(PosSysAddr, keyUptimeEffectiveWeight(v), u64ToHash(5000)) + txn.Txn().SetState(PosSysAddr, keyMicroEpochSeen(0, v), u64ToHash(1)) + require.NoError(t, applyMicroEpochUptime(txn, set, 3, types.ZeroAddress, cfg)) + require.Equal(t, uint64(10000), UptimeEffectiveWeight(txn.Txn(), v)) +} + +func TestApplyMicroEpochUptime_DoesNotRestoreFromCurrentBlockActual(t *testing.T) { + txn := newPosTestTransition(t) + v := types.StringToAddress("0x3001") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + cfg := UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10000, MicroEpochInactivityDecayBps: 9000} + txn.Txn().SetState(PosSysAddr, keyUptimeNominalWeight(v), u64ToHash(10000)) + txn.Txn().SetState(PosSysAddr, keyUptimeEffectiveWeight(v), u64ToHash(10000)) + txn.Txn().SetState(PosSysAddr, keyMicroEpochDuty(0, v), u64ToHash(1)) + require.NoError(t, applyMicroEpochUptime(txn, set, 3, v, cfg)) + require.Equal(t, uint64(9000), UptimeEffectiveWeight(txn.Txn(), v)) + require.Equal(t, uint64(1), u64FromHash(txn.Txn().GetState(PosSysAddr, keyUptimeInactivity(v)))) +} + +func TestApplyMicroEpochUptime_NoOpWhenMicroEpochSizeZero(t *testing.T) { + txn := newPosTestTransition(t) + v := types.StringToAddress("0x4001") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + cfg := UptimeConfig{MicroEpochSize: 0, MicroEpochNominalWeight: 10000, MicroEpochInactivityDecayBps: 9000} + txn.Txn().SetState(PosSysAddr, keyUptimeEffectiveWeight(v), u64ToHash(7777)) + require.NoError(t, applyMicroEpochUptime(txn, set, 3, v, cfg)) + require.Equal(t, uint64(7777), UptimeEffectiveWeight(txn.Txn(), v)) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyMicroEpochApplied()))) +} + +func TestRecordBlockUptime_MicroEpochMarksDutyAndSeen(t *testing.T) { + v := types.StringToAddress("0x5001") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + r := uint64(0) + extra := &signer.IstanbulExtra{Validators: set, RoundNumber: &r, CommittedSeals: &signer.SerializedSeal{}, ParentCommittedSeals: &signer.SerializedSeal{}} + h := &types.Header{Number: 11, ExtraData: extra.MarshalRLPTo(make([]byte, signer.IstanbulExtraVanity))} + s := &fixedSigner{proposer: v, extra: extra} + txn := newPosTestTransitionWithStaking(t, v) + cfg := UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10000, MicroEpochInactivityDecayBps: 9000} + require.NoError(t, RecordBlockUptime(h, 10, set, s, cfg, txn)) + me := microEpochOfBlock(h.Number, cfg.MicroEpochSize) + require.Equal(t, uint64(1), u64FromHash(txn.Txn().GetState(PosSysAddr, keyMicroEpochSeen(me, v)))) + require.Equal(t, uint64(1), u64FromHash(txn.Txn().GetState(PosSysAddr, keyMicroEpochDuty(me, v)))) + require.Equal(t, uint64(10000), UptimeNominalWeight(txn.Txn(), v)) + require.Equal(t, uint64(10000), UptimeEffectiveWeight(txn.Txn(), v)) +} + +func TestRecordBlockUptime_MicroEpochMissedDutyDecaysAfterBoundary(t *testing.T) { + txn := newPosTestTransition(t) + v := types.StringToAddress("0x6001") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + cfg := UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10000, MicroEpochInactivityDecayBps: 9000} + txn.Txn().SetState(PosSysAddr, keyUptimeNominalWeight(v), u64ToHash(10000)) + txn.Txn().SetState(PosSysAddr, keyUptimeEffectiveWeight(v), u64ToHash(10000)) + txn.Txn().SetState(PosSysAddr, keyMicroEpochDuty(0, v), u64ToHash(1)) + require.NoError(t, applyMicroEpochUptime(txn, set, 3, types.ZeroAddress, cfg)) + require.Equal(t, uint64(9000), UptimeEffectiveWeight(txn.Txn(), v)) +} + +func TestRecordBlockUptime_MicroEpochMismatchStillMarksDutyAndDecays(t *testing.T) { + a := types.StringToAddress("0x7001") + b := types.StringToAddress("0x7002") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(a), validators.NewECDSAValidator(b)) + txn := newPosTestTransitionWithStakingValidators(t, set, 1, uint64(set.Len())) + cfg := UptimeConfig{MicroEpochSize: 2, MicroEpochNominalWeight: 10000, MicroEpochInactivityDecayBps: 9000} + + for _, addr := range []types.Address{a, b} { + txn.Txn().SetState(PosSysAddr, keyUptimeNominalWeight(addr), u64ToHash(10000)) + txn.Txn().SetState(PosSysAddr, keyUptimeEffectiveWeight(addr), u64ToHash(10000)) + } + + // lastProposer=a => expected proposer for round 0 is b. We set actual proposer to a (mismatch). + setLastProposer(txn, a) + r := uint64(0) + extra := &signer.IstanbulExtra{Validators: set, RoundNumber: &r, CommittedSeals: &signer.SerializedSeal{}, ParentCommittedSeals: &signer.SerializedSeal{}} + h1 := &types.Header{Number: 11, ExtraData: extra.MarshalRLPTo(make([]byte, signer.IstanbulExtraVanity))} + s := &fixedSigner{proposer: a, extra: extra} + require.NoError(t, RecordBlockUptime(h1, 10, set, s, cfg, txn)) + + me0 := microEpochOfBlock(11, cfg.MicroEpochSize) + require.Equal(t, uint64(1), u64FromHash(txn.Txn().GetState(PosSysAddr, keyMicroEpochSeen(me0, a)))) + require.Equal(t, uint64(1), u64FromHash(txn.Txn().GetState(PosSysAddr, keyMicroEpochDuty(me0, b)))) + require.Equal(t, uint64(10000), UptimeEffectiveWeight(txn.Txn(), b)) + + h2 := &types.Header{Number: 13, ExtraData: extra.MarshalRLPTo(make([]byte, signer.IstanbulExtraVanity))} + require.NoError(t, RecordBlockUptime(h2, 10, set, s, cfg, txn)) + + require.Equal(t, uint64(9000), UptimeEffectiveWeight(txn.Txn(), b)) + require.Equal(t, uint64(1), u64FromHash(txn.Txn().GetState(PosSysAddr, keyUptimeInactivity(b)))) +} + +type fixedSigner struct { + proposer types.Address + extra *signer.IstanbulExtra +} + +func (m *fixedSigner) Type() validators.ValidatorType { return validators.ECDSAValidatorType } +func (m *fixedSigner) Address() types.Address { return m.proposer } +func (m *fixedSigner) InitIBFTExtra(*types.Header, validators.Validators, signer.Seals) {} +func (m *fixedSigner) GetIBFTExtra(*types.Header) (*signer.IstanbulExtra, error) { return m.extra, nil } +func (m *fixedSigner) GetValidators(*types.Header) (validators.Validators, error) { + return m.extra.Validators, nil +} +func (m *fixedSigner) WriteProposerSeal(h *types.Header) (*types.Header, error) { return h, nil } +func (m *fixedSigner) EcrecoverFromHeader(*types.Header) (types.Address, error) { + return m.proposer, nil +} +func (m *fixedSigner) CreateCommittedSeal([]byte) ([]byte, error) { return nil, nil } +func (m *fixedSigner) VerifyCommittedSeal(validators.Validators, types.Address, []byte, []byte) error { + return nil +} +func (m *fixedSigner) WriteCommittedSeals(*types.Header, uint64, map[types.Address][]byte) (*types.Header, error) { + return nil, nil +} +func (m *fixedSigner) VerifyCommittedSeals(types.Hash, signer.Seals, validators.Validators, int) error { + return nil +} +func (m *fixedSigner) VerifyParentCommittedSeals(types.Hash, *types.Header, validators.Validators, int, bool) error { + return nil +} +func (m *fixedSigner) SignIBFTMessage([]byte) ([]byte, error) { return nil, nil } +func (m *fixedSigner) EcrecoverFromIBFTMessage([]byte, []byte) (types.Address, error) { + return m.proposer, nil +} +func (m *fixedSigner) CalculateHeaderHash(*types.Header) (types.Hash, error) { + return types.ZeroHash, nil +} +func (m *fixedSigner) FilterHeaderForHash(h *types.Header) (*types.Header, error) { return h, nil } diff --git a/consensus/ibft/pos/uptime_record_test.go b/consensus/ibft/pos/uptime_record_test.go new file mode 100644 index 0000000..32f0dfa --- /dev/null +++ b/consensus/ibft/pos/uptime_record_test.go @@ -0,0 +1,293 @@ +package pos + +import ( + "errors" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + "github.com/xgr-network/xgr-node/contracts/staking" + "github.com/xgr-network/xgr-node/state" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func makeUptimeHeader(number uint64, set validators.Validators) *types.Header { + extra := &signer.IstanbulExtra{ + Validators: set, + CommittedSeals: &signer.SerializedSeal{}, + ParentCommittedSeals: &signer.SerializedSeal{}, + } + + return &types.Header{ + Number: number, + ExtraData: extra.MarshalRLPTo(make([]byte, signer.IstanbulExtraVanity)), + } +} + +func makeUptimeHeaderWithFixedSigner(number uint64, set validators.Validators, proposer types.Address) (*types.Header, *fixedSigner) { + r := uint64(0) + extra := &signer.IstanbulExtra{ + Validators: set, + RoundNumber: &r, + CommittedSeals: &signer.SerializedSeal{}, + ParentCommittedSeals: &signer.SerializedSeal{}, + } + + return &types.Header{ + Number: number, + ExtraData: extra.MarshalRLPTo(make([]byte, signer.IstanbulExtraVanity)), + }, &fixedSigner{proposer: proposer, extra: extra} +} + +func TestRecordBlockUptime_ReturnsErrorWhenEpochValidatorSnapshotFreezeFails(t *testing.T) { + v := types.StringToAddress("0x1111") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + header := makeUptimeHeader(11, set) + txn := newPosTestTransitionWithStaking(t, v) + + origEnsure := ensureEpochValidatorsSnapshotFn + ensureEpochValidatorsSnapshotFn = func(_ *state.Transition, _ uint64, _ validators.Validators) (bool, error) { + return false, nil + } + defer func() { ensureEpochValidatorsSnapshotFn = origEnsure }() + + err := RecordBlockUptime(header, 10, set, testHeaderSigner(), UptimeConfig{}, txn) + require.Error(t, err) + require.Contains(t, err.Error(), "freeze epoch validator snapshot") +} + +func TestRecordBlockUptime_PropagatesEpochValidatorSnapshotFreezeError(t *testing.T) { + v := types.StringToAddress("0x1112") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + header := makeUptimeHeader(11, set) + txn := newPosTestTransitionWithStaking(t, v) + + origEnsure := ensureEpochValidatorsSnapshotFn + ensureEpochValidatorsSnapshotFn = func(_ *state.Transition, _ uint64, _ validators.Validators) (bool, error) { + return false, errors.New("snapshot cap exceeded") + } + defer func() { ensureEpochValidatorsSnapshotFn = origEnsure }() + + err := RecordBlockUptime(header, 10, set, testHeaderSigner(), UptimeConfig{}, txn) + require.Error(t, err) + require.Contains(t, err.Error(), "freeze epoch validator snapshot") + require.Contains(t, err.Error(), "snapshot cap exceeded") +} + +func TestRecordBlockUptime_IdempotentWhenEpochValidatorSnapshotExists(t *testing.T) { + v := types.StringToAddress("0x2222") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + header, fs := makeUptimeHeaderWithFixedSigner(11, set, v) + txn := newPosTestTransitionWithStaking(t, v) + epoch := epochOf(header.Number, 10) + requireSnapshotCreated(t, txn, epoch, set) + + origEnsure := ensureEpochValidatorsSnapshotFn + ensureEpochValidatorsSnapshotFn = func(_ *state.Transition, _ uint64, _ validators.Validators) (bool, error) { + t.Fatalf("ensureEpochValidatorsSnapshot should not be called when snapshot already exists") + return false, nil + } + defer func() { ensureEpochValidatorsSnapshotFn = origEnsure }() + + err := RecordBlockUptime(header, 10, set, fs, UptimeConfig{}, txn) + require.NoError(t, err) + require.Equal(t, header.Number, u64FromHash(txn.Txn().GetState(PosSysAddr, keyUptimeLastProcessedSlot()))) + slots := u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch, v))) + require.NoError(t, RecordBlockUptime(header, 10, set, fs, UptimeConfig{}, txn)) + require.Equal(t, slots, u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch, v)))) +} + +func TestRecordBlockUptime_ReturnsErrorWhenStakeSnapshotFreezeFails(t *testing.T) { + v := types.StringToAddress("0x3333") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + header := makeUptimeHeader(11, set) + txn := newPosTestTransitionWithStaking(t, v) + + origStake := getOrInitStakeSnapshotFn + getOrInitStakeSnapshotFn = func(_ *state.Transition, _ uint64, _ types.Address) (*big.Int, error) { + return nil, errors.New("injected stake snapshot failure") + } + defer func() { getOrInitStakeSnapshotFn = origStake }() + + err := RecordBlockUptime(header, 10, set, testHeaderSigner(), UptimeConfig{}, txn) + require.Error(t, err) + require.Contains(t, err.Error(), "freeze stake snapshot") +} + +func TestRecordBlockUptime_UsesHeaderValidatorSetWhenEpochValidatorsUnavailable(t *testing.T) { + v := types.StringToAddress("0x4444") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + header, fs := makeUptimeHeaderWithFixedSigner(11, set, v) + txn := newPosTestTransitionWithStaking(t, v) + + origEnsure := ensureEpochValidatorsSnapshotFn + var capturedLen int + ensureEpochValidatorsSnapshotFn = func(st *state.Transition, epoch uint64, got validators.Validators) (bool, error) { + if got != nil { + capturedLen = got.Len() + } + + return origEnsure(st, epoch, got) + } + defer func() { ensureEpochValidatorsSnapshotFn = origEnsure }() + + err := RecordBlockUptime(header, 10, nil, fs, UptimeConfig{}, txn) + require.NoError(t, err) + require.Equal(t, set.Len(), capturedLen) +} + +func TestRecordBlockUptime_DoesNotRefreezeEpochSnapshotWhenReprocessingWithMissingEpochValidators(t *testing.T) { + v := types.StringToAddress("0x5555") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + header, fs := makeUptimeHeaderWithFixedSigner(11, set, v) + txn := newPosTestTransitionWithStaking(t, v) + + origEnsure := ensureEpochValidatorsSnapshotFn + calls := 0 + ensureEpochValidatorsSnapshotFn = func(st *state.Transition, epoch uint64, got validators.Validators) (bool, error) { + calls++ + return origEnsure(st, epoch, got) + } + defer func() { ensureEpochValidatorsSnapshotFn = origEnsure }() + + require.NoError(t, RecordBlockUptime(header, 10, nil, fs, UptimeConfig{}, txn)) + require.NoError(t, RecordBlockUptime(header, 10, nil, fs, UptimeConfig{}, txn)) + require.Equal(t, 1, calls) +} + +func TestRecordBlockUptime_FreezesEpochSnapshotsOnceAndKeepsFinalizeBasisStable(t *testing.T) { + v1 := types.StringToAddress("0x7771") + v2 := types.StringToAddress("0x7772") + set := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(v1), + validators.NewECDSAValidator(v2), + ) + headerA, fsA := makeUptimeHeaderWithFixedSigner(11, set, v1) // first non-boundary block of epoch 2 + headerB, fsB := makeUptimeHeaderWithFixedSigner(12, set, v1) + txn := newPosTestTransitionWithStakingValidators(t, set, 1, 10) + epoch := epochOf(headerA.Number, 10) + + writeStakedAmount(txn, v1, big.NewInt(100)) + writeStakedAmount(txn, v2, big.NewInt(200)) + + origEnsure := ensureEpochValidatorsSnapshotFn + origStake := getOrInitStakeSnapshotFn + origStakerStake := getOrInitStakerStakeSnapshotFn + calls := 0 + currentStake := map[types.Address]*big.Int{ + v1: big.NewInt(100), + v2: big.NewInt(200), + } + ensureEpochValidatorsSnapshotFn = func(st *state.Transition, ep uint64, got validators.Validators) (bool, error) { + calls++ + return origEnsure(st, ep, got) + } + getOrInitStakeSnapshotFn = func(st *state.Transition, ep uint64, addr types.Address) (*big.Int, error) { + key := keyStakeSnapshot(ep, addr) + cur := bigFromHash(st.Txn().GetState(PosSysAddr, key)) + if cur.Sign() != 0 { + return cur, nil + } + v, ok := currentStake[addr] + if !ok { + v = big.NewInt(0) + } + st.Txn().SetState(PosSysAddr, key, bigToHash(v)) + return new(big.Int).Set(v), nil + } + getOrInitStakerStakeSnapshotFn = func(st *state.Transition, ep uint64, addr types.Address) (*big.Int, error) { + key := keyStakerStakeSnapshot(ep, addr) + cur := bigFromHash(st.Txn().GetState(PosSysAddr, key)) + if cur.Sign() != 0 { + return cur, nil + } + v, ok := currentStake[addr] + if !ok { + v = big.NewInt(0) + } + st.Txn().SetState(PosSysAddr, key, bigToHash(v)) + return new(big.Int).Set(v), nil + } + defer func() { + ensureEpochValidatorsSnapshotFn = origEnsure + getOrInitStakeSnapshotFn = origStake + getOrInitStakerStakeSnapshotFn = origStakerStake + }() + + require.NoError(t, RecordBlockUptime(headerA, 10, set, fsA, UptimeConfig{}, txn)) + require.Equal(t, 1, calls) + + // Mutate staking state after the initial epoch freeze. + writeStakedAmount(txn, v1, big.NewInt(999)) + currentStake[v1] = big.NewInt(999) + setValidatorInactiveInStaking(txn, v2, 12) + require.False(t, isValidatorMetaActive(bigFromHash(txn.Txn().GetState(staking.AddrStakingContract, stakingMetaPackedKey(v2))))) + + require.NoError(t, RecordBlockUptime(headerB, 10, set, fsB, UptimeConfig{}, txn)) + require.Equal(t, 1, calls, "epoch snapshot must only be frozen once per epoch") + + // Snapshot-backed finalize basis must stay frozen to the first processed block state. + require.Equal(t, int64(100), bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakeSnapshot(epoch, v1))).Int64()) + require.Equal(t, int64(200), bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakeSnapshot(epoch, v2))).Int64()) + require.ElementsMatch(t, []types.Address{v1, v2}, loadEpochValidatorsSnapshot(txn, epoch)) +} + +func TestRecordBlockUptime_ProposerUnavailableDoesNotOverwriteSnapshotOrDoubleCount(t *testing.T) { + v := types.StringToAddress("0x8881") + set := validators.NewECDSAValidatorSet(validators.NewECDSAValidator(v)) + header := makeUptimeHeader(11, set) // empty proposer seal => proposer unavailable path + txn := newPosTestTransitionWithStakingValidators(t, set, 1, 10) + epoch := epochOf(header.Number, 10) + + writeStakedAmount(txn, v, big.NewInt(55)) + + origEnsure := ensureEpochValidatorsSnapshotFn + origStake := getOrInitStakeSnapshotFn + origStakerStake := getOrInitStakerStakeSnapshotFn + calls := 0 + currentStake := big.NewInt(55) + ensureEpochValidatorsSnapshotFn = func(st *state.Transition, ep uint64, got validators.Validators) (bool, error) { + calls++ + return origEnsure(st, ep, got) + } + getOrInitStakeSnapshotFn = func(st *state.Transition, ep uint64, addr types.Address) (*big.Int, error) { + key := keyStakeSnapshot(ep, addr) + cur := bigFromHash(st.Txn().GetState(PosSysAddr, key)) + if cur.Sign() != 0 { + return cur, nil + } + st.Txn().SetState(PosSysAddr, key, bigToHash(currentStake)) + return new(big.Int).Set(currentStake), nil + } + getOrInitStakerStakeSnapshotFn = func(st *state.Transition, ep uint64, addr types.Address) (*big.Int, error) { + key := keyStakerStakeSnapshot(ep, addr) + cur := bigFromHash(st.Txn().GetState(PosSysAddr, key)) + if cur.Sign() != 0 { + return cur, nil + } + st.Txn().SetState(PosSysAddr, key, bigToHash(currentStake)) + return new(big.Int).Set(currentStake), nil + } + defer func() { + ensureEpochValidatorsSnapshotFn = origEnsure + getOrInitStakeSnapshotFn = origStake + getOrInitStakerStakeSnapshotFn = origStakerStake + }() + + require.Error(t, RecordBlockUptime(header, 10, set, testHeaderSigner(), UptimeConfig{}, txn)) + require.Equal(t, 1, calls) + require.Equal(t, int64(55), bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakeSnapshot(epoch, v))).Int64()) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch, v)))) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerMissed(epoch, v)))) + + // Reprocessing same block with changed live stake must not rewrite frozen snapshot or double count. + writeStakedAmount(txn, v, big.NewInt(777)) + currentStake = big.NewInt(777) + require.Error(t, RecordBlockUptime(header, 10, set, testHeaderSigner(), UptimeConfig{}, txn)) + require.Equal(t, 1, calls) + require.Equal(t, int64(55), bigFromHash(txn.Txn().GetState(PosSysAddr, keyStakeSnapshot(epoch, v))).Int64()) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerSlots(epoch, v)))) + require.Equal(t, uint64(0), u64FromHash(txn.Txn().GetState(PosSysAddr, keyProposerMissed(epoch, v)))) +} diff --git a/consensus/ibft/pos/uptime_state_account.go b/consensus/ibft/pos/uptime_state_account.go new file mode 100644 index 0000000..5b95353 --- /dev/null +++ b/consensus/ibft/pos/uptime_state_account.go @@ -0,0 +1,25 @@ +package pos + +import "github.com/xgr-network/xgr-node/types" + +// TxnWrapper captures SetState/GetState used by uptime helpers. +type TxnWrapper interface { + SetState(types.Address, types.Hash, types.Hash) + GetState(types.Address, types.Hash) types.Hash +} + +type nonceTxnWrapper interface { + GetNonce(types.Address) uint64 + SetNonce(types.Address, uint64) +} + +func ensureUptimeStateAccount(tx TxnWrapper) { + nonceTx, ok := tx.(nonceTxnWrapper) + if !ok { + return + } + + if nonceTx.GetNonce(PosSysAddr) == 0 { + nonceTx.SetNonce(PosSysAddr, 1) + } +} diff --git a/consensus/ibft/pos/uptime_test.go b/consensus/ibft/pos/uptime_test.go new file mode 100644 index 0000000..3c7917d --- /dev/null +++ b/consensus/ibft/pos/uptime_test.go @@ -0,0 +1,44 @@ +package pos + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func TestDeriveLastProposer(t *testing.T) { + v1 := validators.NewECDSAValidator(types.StringToAddress("0x1001")) + v2 := validators.NewECDSAValidator(types.StringToAddress("0x1002")) + v3 := validators.NewECDSAValidator(types.StringToAddress("0x1003")) + v4 := validators.NewECDSAValidator(types.StringToAddress("0x1004")) + set := validators.NewECDSAValidatorSet(v1, v2, v3, v4) + + t.Run("round zero derives previous validator", func(t *testing.T) { + last := deriveLastProposer(set, 0, v3.Addr()) + require.Equal(t, v2.Addr(), last) + }) + + t.Run("non-zero round derives parent proposer", func(t *testing.T) { + last := deriveLastProposer(set, 2, v1.Addr()) + require.Equal(t, v2.Addr(), last) + + expected := calcProposer(set, 2, last) + require.Equal(t, v1.Addr(), expected) + }) + + t.Run("wrap-around works", func(t *testing.T) { + last := deriveLastProposer(set, 1, v1.Addr()) + require.Equal(t, v3.Addr(), last) + + expected := calcProposer(set, 1, last) + require.Equal(t, v1.Addr(), expected) + }) + + t.Run("unknown proposer returns zero", func(t *testing.T) { + last := deriveLastProposer(set, 0, types.StringToAddress("0x9999")) + require.Equal(t, types.ZeroAddress, last) + }) +} + diff --git a/consensus/ibft/pos/uptime_test_helpers_test.go b/consensus/ibft/pos/uptime_test_helpers_test.go new file mode 100644 index 0000000..48349bf --- /dev/null +++ b/consensus/ibft/pos/uptime_test_helpers_test.go @@ -0,0 +1,82 @@ +package pos + +import ( + "github.com/hashicorp/go-hclog" + "github.com/xgr-network/xgr-node/consensus/ibft/signer" + xcrypto "github.com/xgr-network/xgr-node/crypto" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +// deriveLastProposer is test helper that reconstructs previous proposer context. +func deriveLastProposer(set validators.Validators, round uint64, proposer types.Address) types.Address { + if set == nil || set.Len() == 0 { + return types.ZeroAddress + } + + idx := set.Index(proposer) + if idx == -1 { + return types.ZeroAddress + } + + l := uint64(set.Len()) + lastIdx := (uint64(idx) + l - ((round+1)%l)) % l + + return set.At(lastIdx).Addr() +} + +// collectSignedAddresses is a test helper for seal/bitmap signer extraction. +func collectSignedAddresses(extra *signer.IstanbulExtra, blockHash types.Hash, _ hclog.Logger) map[types.Address]struct{} { + seen := make(map[types.Address]struct{}) + if extra == nil || extra.Validators == nil || extra.ParentCommittedSeals == nil { + return seen + } + + switch seals := extra.ParentCommittedSeals.(type) { + case *signer.AggregatedSeal: + if seals.Bitmap == nil { + return seen + } + + for i := 0; i < extra.Validators.Len(); i++ { + if seals.Bitmap.Bit(i) == 0 { + continue + } + + seen[extra.Validators.At(uint64(i)).Addr()] = struct{}{} + } + case *signer.SerializedSeal: + digest := signer.LegacyCommitDigest(blockHash.Bytes()) + + for _, sig := range *seals { + pub, err := xcrypto.RecoverPubkey(sig, digest) + if err != nil { + continue + } + + seen[xcrypto.PubKeyToAddress(pub)] = struct{}{} + } + } + + return seen +} + +// shouldSkipUptimeAccounting mirrors old guard behavior used by dedicated tests. +func shouldSkipUptimeAccounting(extra *signer.IstanbulExtra, _ hclog.Logger, signed map[types.Address]struct{}) bool { + if extra == nil || extra.ParentCommittedSeals == nil { + return true + } + + switch seals := extra.ParentCommittedSeals.(type) { + case *signer.AggregatedSeal: + if seals.Bitmap != nil && seals.Bitmap.Sign() > 0 { + if extra.Validators == nil || extra.Validators.Len() == 0 { + return true + } + + return len(signed) == 0 + } + } + + return false +} diff --git a/consensus/ibft/pos/uptime_weight.go b/consensus/ibft/pos/uptime_weight.go new file mode 100644 index 0000000..9dc3649 --- /dev/null +++ b/consensus/ibft/pos/uptime_weight.go @@ -0,0 +1,44 @@ +package pos + +import ( + "math/big" + + "github.com/xgr-network/xgr-node/types" +) + +func EffectiveWeight(tx TxnWrapper, addr types.Address, cfg UptimeConfig) uint64 { + eff := u64FromHash(tx.GetState(PosSysAddr, keyUptimeEffectiveWeight(addr))) + if eff > 0 { + return eff + } + + nom := u64FromHash(tx.GetState(PosSysAddr, keyUptimeNominalWeight(addr))) + if nom > 0 { + return nom + } + + return cfg.MicroEpochNominalWeight +} + +func WeightedStake(stake *big.Int, effective, nominal uint64) *big.Int { + if stake == nil || stake.Sign() == 0 || nominal == 0 { + return new(big.Int) + } + if effective >= nominal { + return new(big.Int).Set(stake) + } + num := new(big.Int).Mul(stake, new(big.Int).SetUint64(effective)) + return num.Div(num, new(big.Int).SetUint64(nominal)) +} + +func UptimeNominalWeight(tx TxnWrapper, addr types.Address) uint64 { + return u64FromHash(tx.GetState(PosSysAddr, keyUptimeNominalWeight(addr))) +} + +func UptimeEffectiveWeight(tx TxnWrapper, addr types.Address) uint64 { + return u64FromHash(tx.GetState(PosSysAddr, keyUptimeEffectiveWeight(addr))) +} + +func UptimeInactivity(tx TxnWrapper, addr types.Address) uint64 { + return u64FromHash(tx.GetState(PosSysAddr, keyUptimeInactivity(addr))) +} diff --git a/consensus/ibft/proto/ibft_operator.pb.go b/consensus/ibft/proto/ibft_operator.pb.go index 2863467..ff7cb5d 100644 --- a/consensus/ibft/proto/ibft_operator.pb.go +++ b/consensus/ibft/proto/ibft_operator.pb.go @@ -128,10 +128,14 @@ type Snapshot struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Validators []*Snapshot_Validator `protobuf:"bytes,1,rep,name=validators,proto3" json:"validators,omitempty"` - Number uint64 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"` - Hash string `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty"` - Votes []*Snapshot_Vote `protobuf:"bytes,4,rep,name=votes,proto3" json:"votes,omitempty"` + Validators []*Snapshot_Validator `protobuf:"bytes,1,rep,name=validators,proto3" json:"validators,omitempty"` + Number uint64 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"` + Hash string `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty"` + Votes []*Snapshot_Vote `protobuf:"bytes,4,rep,name=votes,proto3" json:"votes,omitempty"` + MicroEpoch uint64 `protobuf:"varint,5,opt,name=micro_epoch,json=microEpoch,proto3" json:"micro_epoch,omitempty"` + UptimeEpoch uint64 `protobuf:"varint,6,opt,name=uptime_epoch,json=uptimeEpoch,proto3" json:"uptime_epoch,omitempty"` + UptimeTotalEffectiveWeight uint64 `protobuf:"varint,7,opt,name=uptime_total_effective_weight,json=uptimeTotalEffectiveWeight,proto3" json:"uptime_total_effective_weight,omitempty"` + UptimeActiveEffectiveWeight uint64 `protobuf:"varint,8,opt,name=uptime_active_effective_weight,json=uptimeActiveEffectiveWeight,proto3" json:"uptime_active_effective_weight,omitempty"` } func (x *Snapshot) Reset() { @@ -194,6 +198,34 @@ func (x *Snapshot) GetVotes() []*Snapshot_Vote { return nil } +func (x *Snapshot) GetMicroEpoch() uint64 { + if x != nil { + return x.MicroEpoch + } + return 0 +} + +func (x *Snapshot) GetUptimeEpoch() uint64 { + if x != nil { + return x.UptimeEpoch + } + return 0 +} + +func (x *Snapshot) GetUptimeTotalEffectiveWeight() uint64 { + if x != nil { + return x.UptimeTotalEffectiveWeight + } + return 0 +} + +func (x *Snapshot) GetUptimeActiveEffectiveWeight() uint64 { + if x != nil { + return x.UptimeActiveEffectiveWeight + } + return 0 +} + type ProposeReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -364,9 +396,12 @@ type Snapshot_Validator struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + UptimeNominalWeight uint64 `protobuf:"varint,4,opt,name=uptime_nominal_weight,json=uptimeNominalWeight,proto3" json:"uptime_nominal_weight,omitempty"` + UptimeEffectiveWeight uint64 `protobuf:"varint,5,opt,name=uptime_effective_weight,json=uptimeEffectiveWeight,proto3" json:"uptime_effective_weight,omitempty"` + UptimeInactivity uint64 `protobuf:"varint,6,opt,name=uptime_inactivity,json=uptimeInactivity,proto3" json:"uptime_inactivity,omitempty"` } func (x *Snapshot_Validator) Reset() { @@ -422,6 +457,27 @@ func (x *Snapshot_Validator) GetData() []byte { return nil } +func (x *Snapshot_Validator) GetUptimeNominalWeight() uint64 { + if x != nil { + return x.UptimeNominalWeight + } + return 0 +} + +func (x *Snapshot_Validator) GetUptimeEffectiveWeight() uint64 { + if x != nil { + return x.UptimeEffectiveWeight + } + return 0 +} + +func (x *Snapshot_Validator) GetUptimeInactivity() uint64 { + if x != nil { + return x.UptimeInactivity + } + return 0 +} + type Snapshot_Vote struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache diff --git a/consensus/ibft/proto/ibft_operator.proto b/consensus/ibft/proto/ibft_operator.proto index 8c75026..90710e6 100644 --- a/consensus/ibft/proto/ibft_operator.proto +++ b/consensus/ibft/proto/ibft_operator.proto @@ -31,10 +31,18 @@ message Snapshot { repeated Vote votes = 4; + uint64 micro_epoch = 5; + uint64 uptime_epoch = 6; + uint64 uptime_total_effective_weight = 7; + uint64 uptime_active_effective_weight = 8; + message Validator { string type = 1; string address = 2; bytes data = 3; + uint64 uptime_nominal_weight = 4; + uint64 uptime_effective_weight = 5; + uint64 uptime_inactivity = 6; } message Vote { diff --git a/consensus/ibft/quorum_semantics_test.go b/consensus/ibft/quorum_semantics_test.go new file mode 100644 index 0000000..8dcb9fc --- /dev/null +++ b/consensus/ibft/quorum_semantics_test.go @@ -0,0 +1,96 @@ +package ibft + +import ( + "math/big" + "strconv" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHasWeightedQuorum_ThresholdMatrix(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + total uint64 + collected uint64 + ok bool + }{ + {name: "total=0,signed=0", total: 0, collected: 0, ok: false}, + {name: "total=5,signed=0", total: 5, collected: 0, ok: false}, + {name: "total=0,signed=1", total: 0, collected: 1, ok: false}, + {name: "total=5,signed=3", total: 5, collected: 3, ok: false}, + {name: "total=5,signed=4", total: 5, collected: 4, ok: true}, + {name: "total=6,signed=4", total: 6, collected: 4, ok: true}, + {name: "total=6,signed=3", total: 6, collected: 3, ok: false}, + {name: "total=8,signed=5", total: 8, collected: 5, ok: false}, + {name: "total=8,signed=6", total: 8, collected: 6, ok: true}, + {name: "total=9,signed=6", total: 9, collected: 6, ok: true}, + {name: "total=9,signed=5", total: 9, collected: 5, ok: false}, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + require.Equal( + t, + tc.ok, + hasWeightedQuorum( + new(big.Int).SetUint64(tc.collected), + new(big.Int).SetUint64(tc.total), + ), + ) + }) + } +} + +func TestEqualWeights_PoAAndWeightedSemantics(t *testing.T) { + t.Parallel() + + cases := []struct { + totalPower uint64 + weightedQuorum uint64 + classicPoAQuorum int + }{ + // Weighted PoS quorum intentionally uses ceil(2/3 * totalVotingPower). + // For N=3 equal unit weights this differs from classic IBFT + // OptimalQuorumSize, which requires the full set when F=0. + {totalPower: 1, weightedQuorum: 1, classicPoAQuorum: 1}, + {totalPower: 2, weightedQuorum: 2, classicPoAQuorum: 2}, + {totalPower: 3, weightedQuorum: 2, classicPoAQuorum: 3}, + {totalPower: 5, weightedQuorum: 4, classicPoAQuorum: 4}, + {totalPower: 6, weightedQuorum: 4, classicPoAQuorum: 4}, + {totalPower: 8, weightedQuorum: 6, classicPoAQuorum: 6}, + {totalPower: 9, weightedQuorum: 6, classicPoAQuorum: 6}, + } + + for _, tc := range cases { + tc := tc + t.Run("N="+strconv.FormatUint(tc.totalPower, 10), func(t *testing.T) { + t.Parallel() + + pool := newTesterAccountPool(t, int(tc.totalPower)) + require.Equal(t, tc.classicPoAQuorum, OptimalQuorumSize(pool.ValidatorSet())) + require.Equal( + t, + new(big.Int).SetUint64(tc.weightedQuorum), + weightedQuorumThreshold(new(big.Int).SetUint64(tc.totalPower)), + ) + }) + } +} + +func TestSnapshotVotingPowers_UsesUnifiedWeightedThreshold(t *testing.T) { + t.Parallel() + + pool := newTesterAccountPool(t, 6) + backend := &backendIBFT{} + + _, snapshot, err := backend.snapshotVotingPowers(1, 0, pool.ValidatorSet(), nil, false) + require.NoError(t, err) + require.Equal(t, "6", snapshot.totalVotingPower) + require.Equal(t, "4", snapshot.quorumThreshold) +} diff --git a/consensus/ibft/signer/extra.go b/consensus/ibft/signer/extra.go index 82fd189..8ac4e56 100644 --- a/consensus/ibft/signer/extra.go +++ b/consensus/ibft/signer/extra.go @@ -283,6 +283,10 @@ func packProposerSealIntoExtra( newArrayValue.Set(oldValues[4]) } + for idx := 5; idx < len(oldValues); idx++ { + newArrayValue.Set(oldValues[idx]) + } + return nil }, ) @@ -325,6 +329,10 @@ func packCommittedSealsAndRoundNumberIntoExtra( )) } + for idx := 5; idx < len(oldValues); idx++ { + newArrayValue.Set(oldValues[idx]) + } + return nil }, ) diff --git a/consensus/ibft/signer/extra_core_test.go b/consensus/ibft/signer/extra_core_test.go new file mode 100644 index 0000000..3b43cff --- /dev/null +++ b/consensus/ibft/signer/extra_core_test.go @@ -0,0 +1,35 @@ +package signer + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xgr-network/xgr-node/types" + "github.com/xgr-network/xgr-node/validators" +) + +func TestIstanbulExtra_CoreRLPFieldsOnly(t *testing.T) { + t.Parallel() + + round := uint64(7) + set := validators.NewECDSAValidatorSet( + validators.NewECDSAValidator(types.StringToAddress("0x1001")), + ) + extra := &IstanbulExtra{ + Validators: set, + ProposerSeal: []byte{0x1, 0x2}, + CommittedSeals: &SerializedSeal{[]byte{0xaa}}, + ParentCommittedSeals: &SerializedSeal{[]byte{0xbb}}, + RoundNumber: &round, + } + + enc := extra.MarshalRLPTo(nil) + dec := &IstanbulExtra{Validators: validators.NewECDSAValidatorSet(), CommittedSeals: &SerializedSeal{}, ParentCommittedSeals: &SerializedSeal{}} + require.NoError(t, dec.UnmarshalRLP(enc)) + require.Equal(t, 1, dec.Validators.Len()) + require.Equal(t, []byte{0x1, 0x2}, dec.ProposerSeal) + require.Equal(t, 1, dec.CommittedSeals.Num()) + require.Equal(t, 1, dec.ParentCommittedSeals.Num()) + require.NotNil(t, dec.RoundNumber) + require.Equal(t, round, *dec.RoundNumber) +} diff --git a/consensus/ibft/signer/extra_test.go b/consensus/ibft/signer/extra_test.go deleted file mode 100644 index 9c5a753..0000000 --- a/consensus/ibft/signer/extra_test.go +++ /dev/null @@ -1,491 +0,0 @@ -package signer - -import ( - "encoding/json" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/xgr-network/xgr-node/types" - "github.com/xgr-network/xgr-node/validators" -) - -func JSONMarshalHelper(t *testing.T, extra *IstanbulExtra) string { - t.Helper() - - res, err := json.Marshal(extra) - - assert.NoError(t, err) - - return string(res) -} - -func TestIstanbulExtraMarshalAndUnmarshal(t *testing.T) { - tests := []struct { - name string - extra *IstanbulExtra - }{ - { - name: "ECDSAExtra", - extra: &IstanbulExtra{ - Validators: validators.NewECDSAValidatorSet( - validators.NewECDSAValidator( - testAddr1, - ), - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &SerializedSeal{ - []byte{0x1}, - []byte{0x2}, - }, - ParentCommittedSeals: &SerializedSeal{ - []byte{0x3}, - []byte{0x4}, - }, - }, - }, - { - name: "ECDSAExtra without ParentCommittedSeals", - extra: &IstanbulExtra{ - Validators: validators.NewECDSAValidatorSet( - ecdsaValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &SerializedSeal{ - []byte{0x1}, - []byte{0x2}, - }, - }, - }, - { - name: "BLSExtra", - extra: &IstanbulExtra{ - Validators: validators.NewBLSValidatorSet( - blsValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x8}), - Signature: []byte{0x1}, - }, - ParentCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x9}), - Signature: []byte{0x2}, - }, - }, - }, - { - name: "BLSExtra without ParentCommittedSeals", - extra: &IstanbulExtra{ - Validators: validators.NewBLSValidatorSet( - blsValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x8}), - Signature: []byte{0x1}, - }, - ParentCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x9}), - Signature: []byte{0x2}, - }, - }, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - // create original data - originalExtraJSON := JSONMarshalHelper(t, test.extra) - - bytesData := test.extra.MarshalRLPTo(nil) - err := test.extra.UnmarshalRLP(bytesData) - assert.NoError(t, err) - - // make sure all data is recovered - assert.Equal( - t, - originalExtraJSON, - JSONMarshalHelper(t, test.extra), - ) - }) - } -} - -func Test_packProposerSealIntoExtra(t *testing.T) { - newProposerSeal := []byte("new proposer seal") - - tests := []struct { - name string - extra *IstanbulExtra - }{ - { - name: "ECDSAExtra", - extra: &IstanbulExtra{ - Validators: validators.NewECDSAValidatorSet( - ecdsaValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &SerializedSeal{ - []byte{0x1}, - []byte{0x2}, - }, - ParentCommittedSeals: &SerializedSeal{ - []byte{0x3}, - []byte{0x4}, - }, - }, - }, - { - name: "ECDSAExtra without ParentCommittedSeals", - extra: &IstanbulExtra{ - Validators: validators.NewECDSAValidatorSet( - ecdsaValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &SerializedSeal{ - []byte{0x1}, - []byte{0x2}, - }, - }, - }, - { - name: "BLSExtra", - extra: &IstanbulExtra{ - Validators: validators.NewBLSValidatorSet( - blsValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x8}), - Signature: []byte{0x1}, - }, - ParentCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x9}), - Signature: []byte{0x2}, - }, - }, - }, - { - name: "BLSExtra without ParentCommittedSeals", - extra: &IstanbulExtra{ - Validators: validators.NewBLSValidatorSet( - blsValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x8}), - Signature: []byte{0x1}, - }, - ParentCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x9}), - Signature: []byte{0x2}, - }, - }, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - originalProposerSeal := test.extra.ProposerSeal - - // create expected data - test.extra.ProposerSeal = newProposerSeal - expectedJSON := JSONMarshalHelper(t, test.extra) - test.extra.ProposerSeal = originalProposerSeal - - newExtraBytes := packProposerSealIntoExtra( - // prepend IstanbulExtraHeader to parse - append( - make([]byte, IstanbulExtraVanity), - test.extra.MarshalRLPTo(nil)..., - ), - newProposerSeal, - ) - - assert.NoError( - t, - test.extra.UnmarshalRLP(newExtraBytes[IstanbulExtraVanity:]), - ) - - // check json of decoded data matches with the original data - jsonData := JSONMarshalHelper(t, test.extra) - - assert.Equal( - t, - expectedJSON, - jsonData, - ) - }) - } -} - -func Test_packCommittedSealsAndRoundNumberIntoExtra(t *testing.T) { - tests := []struct { - name string - extra *IstanbulExtra - newCommittedSeals Seals - roundNumber *uint64 - }{ - { - name: "ECDSAExtra", - extra: &IstanbulExtra{ - Validators: validators.NewECDSAValidatorSet( - ecdsaValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &SerializedSeal{ - []byte{0x1}, - []byte{0x2}, - }, - ParentCommittedSeals: &SerializedSeal{ - []byte{0x3}, - []byte{0x4}, - }, - }, - newCommittedSeals: &SerializedSeal{ - []byte{0x3}, - []byte{0x4}, - }, - roundNumber: nil, - }, - { - name: "ECDSAExtra without ParentCommittedSeals", - extra: &IstanbulExtra{ - Validators: validators.NewECDSAValidatorSet( - ecdsaValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &SerializedSeal{ - []byte{0x1}, - []byte{0x2}, - }, - }, - newCommittedSeals: &SerializedSeal{ - []byte{0x3}, - []byte{0x4}, - }, - roundNumber: nil, - }, - { - name: "BLSExtra", - extra: &IstanbulExtra{ - Validators: validators.NewBLSValidatorSet( - blsValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x8}), - Signature: []byte{0x1}, - }, - ParentCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x9}), - Signature: []byte{0x2}, - }, - }, - newCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0xa}), - Signature: []byte{0x2}, - }, - roundNumber: nil, - }, - { - name: "BLSExtra without ParentCommittedSeals", - extra: &IstanbulExtra{ - Validators: validators.NewBLSValidatorSet( - blsValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x8}), - Signature: []byte{0x1}, - }, - ParentCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x9}), - Signature: []byte{0x2}, - }, - }, - newCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0xa}), - Signature: []byte{0x2}, - }, - roundNumber: nil, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - originalCommittedSeals := test.extra.CommittedSeals - - // create expected data - test.extra.CommittedSeals = test.newCommittedSeals - expectedJSON := JSONMarshalHelper(t, test.extra) - test.extra.CommittedSeals = originalCommittedSeals - - // update committed seals - newExtraBytes := packCommittedSealsAndRoundNumberIntoExtra( - // prepend IstanbulExtraHeader - append( - make([]byte, IstanbulExtraVanity), - test.extra.MarshalRLPTo(nil)..., - ), - test.newCommittedSeals, - test.roundNumber, - ) - - // decode RLP data - assert.NoError( - t, - test.extra.UnmarshalRLP(newExtraBytes[IstanbulExtraVanity:]), - ) - - // check json of decoded data matches with the original data - jsonData := JSONMarshalHelper(t, test.extra) - - assert.Equal( - t, - expectedJSON, - jsonData, - ) - }) - } -} - -func Test_unmarshalRLPForParentCS(t *testing.T) { - tests := []struct { - name string - extra *IstanbulExtra - targetExtra *IstanbulExtra - }{ - { - name: "ECDSAExtra", - extra: &IstanbulExtra{ - Validators: validators.NewECDSAValidatorSet( - ecdsaValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &SerializedSeal{ - []byte{0x1}, - []byte{0x2}, - }, - ParentCommittedSeals: &SerializedSeal{ - []byte{0x3}, - []byte{0x4}, - }, - }, - targetExtra: &IstanbulExtra{ - ParentCommittedSeals: &SerializedSeal{}, - }, - }, - { - name: "BLSExtra", - extra: &IstanbulExtra{ - Validators: validators.NewBLSValidatorSet( - blsValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x8}), - Signature: []byte{0x1}, - }, - ParentCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x9}), - Signature: []byte{0x2}, - }, - }, - targetExtra: &IstanbulExtra{ - ParentCommittedSeals: &AggregatedSeal{}, - }, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - bytesData := test.extra.MarshalRLPTo(nil) - - assert.NoError(t, test.targetExtra.unmarshalRLPForParentCS(bytesData)) - - // make sure all data is recovered - assert.Equal( - t, - test.extra.ParentCommittedSeals, - test.targetExtra.ParentCommittedSeals, - ) - }) - } -} - -func Test_putIbftExtra(t *testing.T) { - tests := []struct { - name string - header *types.Header - extra *IstanbulExtra - }{ - { - name: "ECDSAExtra", - header: &types.Header{ - ExtraData: []byte{}, - }, - extra: &IstanbulExtra{ - Validators: validators.NewECDSAValidatorSet( - ecdsaValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &SerializedSeal{ - []byte{0x1}, - []byte{0x2}, - }, - ParentCommittedSeals: &SerializedSeal{ - []byte{0x3}, - []byte{0x4}, - }, - }, - }, - { - name: "BLSExtra", - header: &types.Header{ - ExtraData: make([]byte, IstanbulExtraVanity+10), - }, - extra: &IstanbulExtra{ - Validators: validators.NewBLSValidatorSet( - blsValidator1, - ), - ProposerSeal: testProposerSeal, - CommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x8}), - Signature: []byte{0x1}, - }, - ParentCommittedSeals: &AggregatedSeal{ - Bitmap: new(big.Int).SetBytes([]byte{0x9}), - Signature: []byte{0x2}, - }, - }, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - putIbftExtra(test.header, test.extra) - - expectedExtraHeader := make([]byte, IstanbulExtraVanity) - expectedExtraBody := test.extra.MarshalRLPTo(nil) - expectedExtra := append(expectedExtraHeader, expectedExtraBody...) //nolint:makezero - - assert.Equal( - t, - expectedExtra, - test.header.ExtraData, - ) - }) - } -} diff --git a/consensus/ibft/signer/helper.go b/consensus/ibft/signer/helper.go index fc722cd..6cced02 100644 --- a/consensus/ibft/signer/helper.go +++ b/consensus/ibft/signer/helper.go @@ -3,6 +3,7 @@ package signer import ( "crypto/ecdsa" "fmt" + "sync" "testing" "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" @@ -15,6 +16,8 @@ import ( "github.com/xgr-network/xgr-node/validators" ) +var testHeaderHashOverrideMu sync.Mutex + const ( // legacyCommitCode is the value that is contained in // legacy committed seals, so it needs to be preserved in order @@ -126,17 +129,20 @@ func verifyIBFTExtraSize(header *types.Header) error { func UseIstanbulHeaderHashInTest(t *testing.T, signer Signer) { t.Helper() - originalHashCalc := types.HeaderHash - types.HeaderHash = func(h *types.Header) types.Hash { + testHeaderHashOverrideMu.Lock() + + originalHashCalc := types.GetHeaderHash() + types.SetHeaderHash(func(h *types.Header) types.Hash { hash, err := signer.CalculateHeaderHash(h) if err != nil { return types.ZeroHash } return hash - } + }) t.Cleanup(func() { - types.HeaderHash = originalHashCalc + types.SetHeaderHash(originalHashCalc) + testHeaderHashOverrideMu.Unlock() }) } diff --git a/consensus/ibft/signer/legacy_commit.go b/consensus/ibft/signer/legacy_commit.go new file mode 100644 index 0000000..2a233ad --- /dev/null +++ b/consensus/ibft/signer/legacy_commit.go @@ -0,0 +1,10 @@ +package signer + +import "github.com/xgr-network/xgr-node/crypto" + +// LegacyCommitDigest returns the *exact* digest used by the legacy IBFT committed-seal scheme. +// Consensus-critical: must stay bit-identical to CreateCommittedSeal / VerifyParentCommittedSeals. +func LegacyCommitDigest(hash []byte) []byte { + // Same as in signer.go: crypto.Keccak256(wrapCommitHash(hash)) + return crypto.Keccak256(wrapCommitHash(hash)) +} diff --git a/consensus/ibft/signer/signer.go b/consensus/ibft/signer/signer.go index 1dcd829..7b9f3f2 100644 --- a/consensus/ibft/signer/signer.go +++ b/consensus/ibft/signer/signer.go @@ -379,6 +379,5 @@ func (s *SignerImpl) FilterHeaderForHash(header *types.Header) (*types.Header, e // This will effectively remove the Seal and CommittedSeals from the IBFT Extra of header, // while keeping proposer vanity, validator set, and ParentCommittedSeals s.initIbftExtra(clone, extra.Validators, parentCommittedSeals) - return clone, nil } diff --git a/consensus/ibft/transport.go b/consensus/ibft/transport.go index 37b4faa..44a2f5f 100644 --- a/consensus/ibft/transport.go +++ b/consensus/ibft/transport.go @@ -4,31 +4,37 @@ import ( "github.com/0xPolygon/go-ibft/messages/proto" "github.com/libp2p/go-libp2p/core/peer" "github.com/xgr-network/xgr-node/network" - "github.com/xgr-network/xgr-node/types" ) type transport interface { - Multicast(msg *proto.Message) error + Multicast(msg *proto.IbftMessage) error } type gossipTransport struct { topic *network.Topic } -func (g *gossipTransport) Multicast(msg *proto.Message) error { +func (g *gossipTransport) Multicast(msg *proto.IbftMessage) error { return g.topic.Publish(msg) } -func (i *backendIBFT) Multicast(msg *proto.Message) { +func (i *backendIBFT) Multicast(msg *proto.IbftMessage) { if err := i.transport.Multicast(msg); err != nil { i.logger.Error("fail to gossip", "err", err) } + + // Ensure locally-originated messages are visible to the consensus core immediately. + // Relying solely on pubsub loopback can cause higher-round RCC starvation in some + // network setups where self-published messages aren't delivered back to the sender. + if i.consensus != nil && i.isActiveValidator() && msg != nil { + i.consensus.AddMessage(msg) + } } // setupTransport sets up the gossip transport protocol func (i *backendIBFT) setupTransport() error { // Define a new topic - topic, err := i.network.NewTopic(ibftProto, &proto.Message{}) + topic, err := i.network.NewTopic(ibftProto, &proto.IbftMessage{}) if err != nil { return err } @@ -40,7 +46,7 @@ func (i *backendIBFT) setupTransport() error { return } - msg, ok := obj.(*proto.Message) + msg, ok := obj.(*proto.IbftMessage) if !ok { i.logger.Error("invalid type assertion for message request") @@ -48,14 +54,6 @@ func (i *backendIBFT) setupTransport() error { } i.consensus.AddMessage(msg) - - i.logger.Debug( - "validator message received", - "type", msg.Type.String(), - "height", msg.GetView().Height, - "round", msg.GetView().Round, - "addr", types.BytesToAddress(msg.From).String(), - ) }, ); err != nil { return err diff --git a/consensus/ibft/verifier.go b/consensus/ibft/verifier.go index 44b01d0..0658382 100644 --- a/consensus/ibft/verifier.go +++ b/consensus/ibft/verifier.go @@ -7,7 +7,6 @@ import ( "github.com/0xPolygon/go-ibft/messages" protoIBFT "github.com/0xPolygon/go-ibft/messages/proto" - "github.com/xgr-network/xgr-node/consensus/ibft/signer" "github.com/xgr-network/xgr-node/crypto" "github.com/xgr-network/xgr-node/types" ) @@ -24,13 +23,12 @@ func (i *backendIBFT) calculateProposalHashFromBlockBytes( return types.ZeroHash, err } - signer, err := i.forkManager.GetSigner(block.Number()) + _, err := i.forkManager.GetSigner(block.Number()) if err != nil { return types.ZeroHash, err } return i.calculateProposalHash( - signer, block.Header, round, ) @@ -39,7 +37,6 @@ func (i *backendIBFT) calculateProposalHashFromBlockBytes( // calculateProposalHash is new hash calculation for proposal in go-ibft, // which includes round number block is finalized at func (i *backendIBFT) calculateProposalHash( - signer signer.Signer, header *types.Header, round *uint64, ) (types.Hash, error) { @@ -107,11 +104,10 @@ func (i *backendIBFT) IsValidProposal(rawProposal []byte) bool { return false } - return true } -func (i *backendIBFT) IsValidValidator(msg *protoIBFT.Message) bool { +func (i *backendIBFT) IsValidValidator(msg *protoIBFT.IbftMessage) bool { msgNoSig, err := msg.PayloadNoSig() if err != nil { return false @@ -173,16 +169,17 @@ func (i *backendIBFT) IsProposer(id []byte, height, round uint64) bool { return false } - nextProposer := CalcProposer( - i.currentValidators, - round, - previousProposer, - ) + nextProposer := CalcProposer(i.currentValidators, round, previousProposer) return types.BytesToAddress(id) == nextProposer.Addr() } func (i *backendIBFT) IsValidProposalHash(proposal *protoIBFT.Proposal, hash []byte) bool { + block := &types.Block{} + if err := block.UnmarshalRLP(proposal.RawProposal); err != nil { + return false + } + proposalHash, err := i.calculateProposalHashFromBlockBytes(proposal.RawProposal, &proposal.Round) if err != nil { return false diff --git a/consensus/ibft/weighted_quorum_state_test.go b/consensus/ibft/weighted_quorum_state_test.go new file mode 100644 index 0000000..64f41d7 --- /dev/null +++ b/consensus/ibft/weighted_quorum_state_test.go @@ -0,0 +1,19 @@ +package ibft + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestWeightedQuorum_TwoOfFourEqualWeightsIsNotEnough(t *testing.T) { + t.Parallel() + + total := big.NewInt(40000) + quorum := weightedQuorumThreshold(total) + require.Equal(t, "26667", quorum.String()) + + collected := big.NewInt(20000) // 2 of 4 validators with equal 10000 weight each + require.False(t, hasWeightedQuorum(collected, total)) +} diff --git a/consensus/polybft/README.md b/consensus/polybft/README.md deleted file mode 100644 index 6d4effc..0000000 --- a/consensus/polybft/README.md +++ /dev/null @@ -1,158 +0,0 @@ - -# Polybft consensus protocol - -Polybft is a consensus protocol, which runs [go-ibft](https://github.com/0xPolygon/go-ibft) consensus engine. - -It has a native support for running bridge, which enables running cross-chain transactions with Ethereum-compatible blockchains. - -## Setup local testing environment - -1. Build binary - - ```bash - $ go build -o xgrchain . - ``` - -2. Init secrets - this command is used to generate account secrets (ECDSA, BLS as well as P2P networking node id). `--data-dir` denotes folder prefix names and `--num` how many accounts need to be created. **This command is for testing purposes only.** - - ```bash - $ xgrchain polybft-secrets --data-dir test-chain- --num 4 - ``` - -3. Create chain configuration - this command creates chain configuration, which is needed to run a blockchain. - It contains initial validator set as well and there are two ways to specify it: - - - all the validators information are present in local storage of single host and therefore directory if provided using `--validators-path` flag and validators folder prefix names using `--validators-prefix` flag - - for reward distribution to work, user must define a reward wallet address with its balance. Wallet address is used to distribute reward tokens from that address to validators that signed blocks in that epoch. - - ```bash - $ xgrchain genesis --block-gas-limit 10000000 --epoch-size 10 \ - --proxy-contracts-admin 0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed \ - [--validators-path ./] [--validators-prefix test-chain-] \ - [--consensus polybft] \ - [--reward-wallet address:amount] - ``` - - - validators information are scafollded on multiple hosts and therefore necessary information are supplied using `--validators` flag. Validator information needs to be supplied in the strictly following format: - `:::`. - **Note:** when specifying validators via validators flag, entire multi address must be specified. - - ```bash - $ xgrchain genesis --block-gas-limit 10000000 --epoch-size 10 \ - --proxy-contracts-admin 0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed \ - --validators /ip4/127.0.0.1/tcp/30301/p2p/16Uiu2HAmV5hqAp77untfJRorxqKmyUxgaVn8YHFjBJm9gKMms3mr:0xDcBe0024206ec42b0Ef4214Ac7B71aeae1A11af0:1cf134e02c6b2afb2ceda50bf2c9a01da367ac48f7783ee6c55444e1cab418ec0f52837b90a4d8cf944814073fc6f2bd96f35366a3846a8393e3cb0b19197cde23e2b40c6401fa27ff7d0c36779d9d097d1393cab6fc1d332f92fb3df850b78703b2989d567d1344e219f0667a1863f52f7663092276770cf513f9704b5351c4:11b18bde524f4b02258a8d196b687f8d8e9490d536718666dc7babca14eccb631c238fb79aa2b44a5a4dceccad2dd797f537008dda185d952226a814c1acf7c2 - [--validators /ip4/127.0.0.1/tcp/30302/p2p/16Uiu2HAmGmidRQY5BGJPGVRF8p1pYFdfzuf1StHzXGLDizuxJxex:0x2da750eD4AE1D5A7F7c996Faec592F3d44060e90:088d92c25b5f278750534e8a902da604a1aa39b524b4511f5f47c3a386374ca3031b667beb424faef068a01cee3428a1bc8c1c8bab826f30a1ee03fbe90cb5f01abcf4abd7af3bbe83eaed6f82179b9cbdc417aad65d919b802d91c2e1aaefec27ba747158bc18a0556e39bfc9175c099dd77517a85731894bbea3d191a622bc:08dc3006352fdc01b331907fd3a68d4d68ed40329032598c1c0faa260421d66720965ace3ba29c6d6608ec1facdbf4624bca72df36c34afd4bdd753c4dfe049c] - ``` - -4. Start rootchain server - rootchain server is a Geth instance running in dev mode, which simulates Ethereum network. **This command is for testing purposes only.** - - ```bash - $ xgrchain rootchain server - ``` - -5. Deploy StakeManager - if not already deployed to rootchain. Command has a test flag used only in testing purposes which would deploy a mock ERC20 token which would be used for staking. If not used for testing, stake-token flag should be provided: - - ```bash - $ xgrchain polybft stake-manager-deploy \ - --deployer-key \ - --proxy-contracts-admin 0xaddressOfProxyContractsAdmin \ - [--genesis ./genesis.json] \ - [--json-rpc http://127.0.0.1:8545] \ - [--stake-token 0xaddressOfStakeToken] \ - [--test] - ``` - -6. Deploy and initialize rootchain contracts - this command deploys rootchain smart contracts and initializes them. It also updates genesis configuration with rootchain contract addresses and rootchain default sender address. - - ```bash - $ xgrchain rootchain deploy \ - --deployer-key \ - --stake-manager \ - --stake-token 0xaddressOfStakeToken \ - --proxy-contracts-admin 0xaddressOfProxyContractsAdmin \ - [--genesis ./genesis.json] \ - [--json-rpc http://127.0.0.1:8545] \ - [--test] - ``` - -7. Fund validators on rootchain - in order for validators to be able to send transactions to Ethereum, they need to be funded in order to be able to cover gas cost. **This command is for testing purposes only.** - - ```bash - $ xgrchain rootchain fund \ - --addresses 0x1234567890123456789012345678901234567890 \ - --amounts 200000000000000000000 - ``` - -8. Whitelist validators on rootchain - in order for validators to be able to be registered on the SupernetManager contract on rootchain. Note that only deployer of SupernetManager contract (the one who run the deploy command) can whitelist validators on rootchain. He can use either its hex encoded private key, or data-dir flag if he has secerets initialized: - - ```bash - $ xgrchain polybft whitelist-validators --private-key \ - --addresses --supernet-manager - ``` - -9. Register validators on rootchain - each validator registers itself on SupernetManager. **This command is for testing purposes only.** - - ```bash - $ xgrchain polybft register-validator --data-dir ./test-chain-1 \ - --supernet-manager - ``` - -10. Initial staking on rootchain - each validator needs to do initial staking on rootchain (StakeManager) contract. **This command is for testing purposes only.** - - ```bash - $ xgrchain polybft stake --data-dir ./test-chain-1 --supernet-id \ - --amount \ - --stake-manager --stake-token - ``` - -11. Do mint and premine for relayer node. **These commands should only be executed if non-mintable erc20 token is used** - - ```bash - $ xgrchain bridge mint-erc20 \ - --erc20-token \ - --private-key \ - --addresses \ - --amounts - ``` - - ```bash - $ xgrchain rootchain premine \ - --erc20-token \ - --root-erc20-predicate \ - --supernet-manager \ - --private-key \ - --amount - ``` - -12. Finalize genesis validator set on rootchain (SupernetManager) contract. This is done after all validators from genesis do initial staking on rootchain, and it's a final step that is required before starting the child chain. This needs to be done by the deployer of SupernetManager contract (the user that run the deploy command). He can use either its hex encoded private key, or data-dir flag if he has secerets initialized. If enable-staking flag is provided, validators will be able to continue staking on rootchain. If not, genesis validators will not be able update its stake or unstake, nor will newly registered validators after genesis will be able to stake tokens on the rootchain. Enabling of staking can be done through this command, or later after the child chain starts. - - ```bash - $ xgrchain polybft supernet --private-key \ - --genesis \ - --supernet-manager \ - --finalize-genesis --enable-staking - ``` - -13. Run (child chain) cluster, consisting of 4 Edge clients in this particular example - - ```bash - $ xgrchain server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :5001 --libp2p :30301 --jsonrpc :9545 \ - --seal --log-level DEBUG - - $ xgrchain server --data-dir ./test-chain-2 --chain genesis.json --grpc-address :5002 --libp2p :30302 --jsonrpc :10002 \ - --seal --log-level DEBUG - - $ xgrchain server --data-dir ./test-chain-3 --chain genesis.json --grpc-address :5003 --libp2p :30303 --jsonrpc :10003 \ - --seal --log-level DEBUG - - $ xgrchain server --data-dir ./test-chain-4 --chain genesis.json --grpc-address :5004 --libp2p :30304 --jsonrpc :10004 \ - --seal --log-level DEBUG - ``` - - It is possible to run child chain nodes in "relayer" mode. It allows automatic execution of deposit events on behalf of users. - In order to start node in relayer mode, it is necessary to supply `--relayer` flag: - - ```bash - $ xgrchain server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :5001 --libp2p :30301 --jsonrpc :9545 \ - --seal --log-level DEBUG --relayer - ``` diff --git a/consensus/polybft/bitmap/bitmap.go b/consensus/polybft/bitmap/bitmap.go deleted file mode 100644 index 5e56fea..0000000 --- a/consensus/polybft/bitmap/bitmap.go +++ /dev/null @@ -1,37 +0,0 @@ -package bitmap - -// Bitmap Index 0 is LSB from the first bitmap byte -type Bitmap []byte - -func (b *Bitmap) Set(idx uint64) { - index := idx / 8 - *b = extendByteSlice(*b, int(index)+1) - - bit := uint8(1 << (idx % 8)) - (*b)[idx/8] |= bit -} - -func (b *Bitmap) Len() uint64 { - return uint64(len(*b) * 8) -} - -func (b *Bitmap) IsSet(idx uint64) bool { - if b.Len() <= idx { - return false - } - - bit := uint8(1 << (idx % 8)) - - return (*b)[idx/8]&bit == bit -} - -func extendByteSlice(b []byte, needLen int) []byte { - // for example if we need to store idx 277 we need 35 bytes - // But if have slice which length is 5 bytes we need to add additional 30 bytes - // append function is smart enough to use capacity of slice if needed otherwise it will create new slice - if n := needLen - len(b); n > 0 { - b = append(b, make([]byte, n)...) - } - - return b -} diff --git a/consensus/polybft/bitmap/bitmap_test.go b/consensus/polybft/bitmap/bitmap_test.go deleted file mode 100644 index 2d01027..0000000 --- a/consensus/polybft/bitmap/bitmap_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package bitmap - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestBitmap_Set(t *testing.T) { - t.Parallel() - - data := []int{8, 15, 0, 7, 31, 60, 112, 7, 16, 241, 189, 60, 0, 19, 14, 25} - unique := map[int]struct{}{} - - b := Bitmap{} - - for _, v := range data { - unique[v] = struct{}{} - - b.Set(uint64(v)) - } - - // check if only values from data are populated - for _, v := range data { - require.True(t, b.IsSet(uint64(v))) - } - - cntSet := 0 - - for i := uint64(0); i < b.Len(); i++ { - if _, exists := unique[int(i)]; exists { - cntSet++ - } - } - - require.Equal(t, len(unique), cntSet) -} - -func TestBitmap_Extend(t *testing.T) { - t.Parallel() - - data := []struct { - index uint64 - length uint64 - }{ - {0, 1}, - {8, 2}, - {15, 2}, - {30, 4}, - {17, 4}, - {120, 16}, - {39, 16}, - {277, 35}, - {8, 35}, - } - b := Bitmap{} - - for _, dt := range data { - b.Set(dt.index) - require.True(t, b.Len() == dt.length*8, "assertion failed when setting index %d", dt.index) - require.Len(t, b, int(dt.length)) - } -} diff --git a/consensus/polybft/block_builder.go b/consensus/polybft/block_builder.go deleted file mode 100644 index 2ebff72..0000000 --- a/consensus/polybft/block_builder.go +++ /dev/null @@ -1,221 +0,0 @@ -package polybft - -import ( - "fmt" - "time" - - hcf "github.com/hashicorp/go-hclog" - "github.com/xgr-network/xgr-node/consensus" - "github.com/xgr-network/xgr-node/state" - "github.com/xgr-network/xgr-node/txpool" - "github.com/xgr-network/xgr-node/types" -) - -//nolint:godox -// TODO: Add opentracing (to be fixed in EVM-540) - -// BlockBuilderParams are fields for the block that cannot be changed -type BlockBuilderParams struct { - // Parent block - Parent *types.Header - - // Executor - Executor *state.Executor - - // Coinbase that is signing the block - Coinbase types.Address - - // GasLimit is the gas limit for the block - GasLimit uint64 - - // duration for one block - BlockTime time.Duration - - // Logger - Logger hcf.Logger - - // txPoolInterface implementation - TxPool txPoolInterface - - // BaseFee is the base fee - BaseFee uint64 -} - -func NewBlockBuilder(params *BlockBuilderParams) *BlockBuilder { - return &BlockBuilder{ - params: params, - } -} - -var _ blockBuilder = &BlockBuilder{} - -type BlockBuilder struct { - // input params for the block - params *BlockBuilderParams - - // header is the header for the block being created - header *types.Header - - // transactions are the data included in the block - txns []*types.Transaction - - // block is a reference to the already built block - block *types.Block - - // state is in memory state transition - state *state.Transition -} - -// Init initializes block builder before adding transactions and actual block building -func (b *BlockBuilder) Reset() error { - // set the timestamp - parentTime := time.Unix(int64(b.params.Parent.Timestamp), 0) - headerTime := parentTime.Add(b.params.BlockTime) - - if headerTime.Before(time.Now().UTC()) { - headerTime = time.Now().UTC() - } - - b.header = &types.Header{ - ParentHash: b.params.Parent.Hash, - Number: b.params.Parent.Number + 1, - Miner: b.params.Coinbase[:], - Difficulty: 1, - StateRoot: types.EmptyRootHash, // this avoids needing state for now - TxRoot: types.EmptyRootHash, - ReceiptsRoot: types.EmptyRootHash, // this avoids needing state for now - Sha3Uncles: types.EmptyUncleHash, - GasLimit: b.params.GasLimit, - BaseFee: b.params.BaseFee, - Timestamp: uint64(headerTime.Unix()), - } - - transition, err := b.params.Executor.BeginTxn(b.params.Parent.StateRoot, b.header, b.params.Coinbase) - if err != nil { - return err - } - - b.state = transition - b.block = nil - b.txns = []*types.Transaction{} - - return nil -} - -// Block returns the built block if nil, it is not built yet -func (b *BlockBuilder) Block() *types.Block { - return b.block -} - -// Build creates the state and the final block -func (b *BlockBuilder) Build(handler func(h *types.Header)) (*types.FullBlock, error) { - if handler != nil { - handler(b.header) - } - - _, stateRoot, err := b.state.Commit() - if err != nil { - return nil, fmt.Errorf("failed to commit the state changes: %w", err) - } - - b.header.StateRoot = stateRoot - b.header.GasUsed = b.state.TotalGas() - b.header.LogsBloom = types.CreateBloom(b.Receipts()) - - // build the block - b.block = consensus.BuildBlock(consensus.BuildBlockParams{ - Header: b.header, - Txns: b.txns, - Receipts: b.state.Receipts(), - }) - - b.block.Header.ComputeHash() - - return &types.FullBlock{ - Block: b.block, - Receipts: b.state.Receipts(), - }, nil -} - -// WriteTx applies given transaction to the state. If transaction apply fails, it reverts the saved snapshot. -func (b *BlockBuilder) WriteTx(tx *types.Transaction) error { - if tx.Gas > b.params.GasLimit { - b.params.Logger.Info("Transaction gas limit exceedes block gas limit", "hash", tx.Hash, - "tx gas limit", tx.Gas, "block gas limt", b.params.GasLimit) - - return txpool.ErrBlockLimitExceeded - } - - if err := b.state.Write(tx); err != nil { - return err - } - - b.txns = append(b.txns, tx) - - return nil -} - -// Fill fills the block with transactions from the txpool -func (b *BlockBuilder) Fill() { - blockTimer := time.NewTimer(b.params.BlockTime) - - b.params.TxPool.Prepare() -write: - for { - select { - case <-blockTimer.C: - return - default: - tx := b.params.TxPool.Peek() - - // execute transactions one by one - finished, err := b.writeTxPoolTransaction(tx) - if err != nil { - b.params.Logger.Debug("Fill transaction error", "hash", tx.Hash, "err", err) - } - - if finished { - break write - } - } - } - - // wait for the timer to expire - <-blockTimer.C -} - -// Receipts returns the collection of transaction receipts for given block -func (b *BlockBuilder) Receipts() []*types.Receipt { - return b.state.Receipts() -} - -func (b *BlockBuilder) writeTxPoolTransaction(tx *types.Transaction) (bool, error) { - if tx == nil { - return true, nil - } - - if err := b.WriteTx(tx); err != nil { - if _, ok := err.(*state.GasLimitReachedTransitionApplicationError); ok { //nolint:errorlint - // stop processing - return true, err - } else if appErr, ok := err.(*state.TransitionApplicationError); ok && appErr.IsRecoverable { //nolint:errorlint - b.params.TxPool.Demote(tx) - - return false, err - } else { - b.params.TxPool.Drop(tx) - - return false, err - } - } - - // remove tx from the pool and add it to the list of all block transactions - b.params.TxPool.Pop(tx) - - return false, nil -} - -// GetState returns Transition reference -func (b *BlockBuilder) GetState() *state.Transition { - return b.state -} diff --git a/consensus/polybft/block_builder_test.go b/consensus/polybft/block_builder_test.go deleted file mode 100644 index 7f62abd..0000000 --- a/consensus/polybft/block_builder_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package polybft - -import ( - "math/big" - "testing" - "time" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/crypto" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/state" - itrie "github.com/xgr-network/xgr-node/state/immutable-trie" - "github.com/xgr-network/xgr-node/types" -) - -func TestBlockBuilder_BuildBlockTxOneFailedTxAndOneTakesTooMuchGas(t *testing.T) { - t.Parallel() - - const ( - amount = 1_000 - gasPrice = 1_000 - gasLimit = 21000 - blockGasLimit = 21000 * 3 - chainID = 100 - ) - - accounts := [6]*wallet.Account{} - - for i := range accounts { - accounts[i] = generateTestAccount(t) - } - - forks := &chain.Forks{} - logger := hclog.NewNullLogger() - signer := crypto.NewSigner(forks.At(0), chainID) - - mchain := &chain.Chain{ - Params: &chain.Params{ - ChainID: chainID, - Forks: forks, - }, - } - - mstate := itrie.NewState(itrie.NewMemoryStorage()) - executor := state.NewExecutor(mchain.Params, mstate, logger) - - executor.GetHash = func(header *types.Header) func(i uint64) types.Hash { - return func(i uint64) (res types.Hash) { - return types.BytesToHash(common.EncodeUint64ToBytes(i)) - } - } - - balanceMap := map[types.Address]*chain.GenesisAccount{} - - for i, acc := range accounts { - // the third tx will fail because of insufficient balance - if i != 2 { - balanceMap[types.Address(acc.Ecdsa.Address())] = &chain.GenesisAccount{ - Balance: ethgo.Ether(1), - } - } - } - - hash, err := executor.WriteGenesis(balanceMap, types.ZeroHash) - - require.NoError(t, err) - require.NotEqual(t, types.ZeroHash, hash) - - // Gas Limit is important to be high for tx pool - parentHeader := &types.Header{StateRoot: hash, GasLimit: 1_000_000_000_000_000} - - txPool := &txPoolMock{} - txPool.On("Prepare").Once() - - for i, acc := range accounts { - receiver := types.Address(acc.Ecdsa.Address()) - privateKey, err := acc.GetEcdsaPrivateKey() - - require.NoError(t, err) - - tx := &types.Transaction{ - Value: big.NewInt(amount), - GasPrice: big.NewInt(gasPrice), - Gas: gasLimit, - Nonce: 0, - To: &receiver, - } - - // fifth tx will cause filling to stop - if i == 4 { - tx.Gas = blockGasLimit - 1 - } - - tx, err = signer.SignTx(tx, privateKey) - require.NoError(t, err) - - // all tx until the fifth will be retrieved from the pool - if i <= 4 { - txPool.On("Peek").Return(tx).Once() - } - - // first two and fourth will be added to the block, third will be demoted - if i == 2 { - txPool.On("Demote", tx) - } else if i < 4 { - txPool.On("Pop", tx) - } - } - - bb := NewBlockBuilder(&BlockBuilderParams{ - BlockTime: time.Millisecond * 100, - Parent: parentHeader, - Coinbase: types.ZeroAddress, - Executor: executor, - GasLimit: blockGasLimit, - TxPool: txPool, - Logger: logger, - }) - - require.NoError(t, bb.Reset()) - - bb.Fill() - - fb, err := bb.Build(func(h *types.Header) { - // fake the logs for bloom - rs := bb.Receipts() - - if len(rs) == 3 { - rs[0].Logs = []*types.Log{ - {Address: types.StringToAddress("ff7783")}, - } - rs[1].Logs = []*types.Log{ - {Address: types.StringToAddress("03bbcc")}, - {Address: types.StringToAddress("112233")}, - } - } - }) - require.NoError(t, err) - - txPool.AssertExpectations(t) - require.Len(t, bb.txns, 3, "Should have 3 transactions but has %d", len(bb.txns)) - require.Len(t, bb.Receipts(), 3) - - // assert logs bloom - for _, r := range bb.Receipts() { - for _, l := range r.Logs { - assert.True(t, fb.Block.Header.LogsBloom.IsLogInBloom(l)) - } - } - - assert.False(t, fb.Block.Header.LogsBloom.IsLogInBloom( - &types.Log{Address: types.StringToAddress("999911117777")})) - assert.False(t, fb.Block.Header.LogsBloom.IsLogInBloom( - &types.Log{Address: types.StringToAddress("111177779999")})) -} diff --git a/consensus/polybft/blockchain_wrapper.go b/consensus/polybft/blockchain_wrapper.go deleted file mode 100644 index a5055b8..0000000 --- a/consensus/polybft/blockchain_wrapper.go +++ /dev/null @@ -1,227 +0,0 @@ -package polybft - -import ( - "errors" - "fmt" - "math/big" - "time" - - "github.com/hashicorp/go-hclog" - - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/contract" - "github.com/xgr-network/xgr-node/blockchain" - "github.com/xgr-network/xgr-node/consensus" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/state" - "github.com/xgr-network/xgr-node/types" -) - -const ( - consensusSource = "consensus" -) - -var ( - errSendTxnUnsupported = errors.New("system state does not support send transactions") -) - -// blockchain is an interface that wraps the methods called on blockchain -type blockchainBackend interface { - // CurrentHeader returns the header of blockchain block head - CurrentHeader() *types.Header - - // CommitBlock commits a block to the chain. - CommitBlock(block *types.FullBlock) error - - // NewBlockBuilder is a factory method that returns a block builder on top of 'parent'. - NewBlockBuilder(parent *types.Header, coinbase types.Address, - txPool txPoolInterface, blockTime time.Duration, logger hclog.Logger) (blockBuilder, error) - - // ProcessBlock builds a final block from given 'block' on top of 'parent'. - ProcessBlock(parent *types.Header, block *types.Block) (*types.FullBlock, error) - - // GetStateProviderForBlock returns a reference to make queries to the state at 'block'. - GetStateProviderForBlock(block *types.Header) (contract.Provider, error) - - // GetStateProvider returns a reference to make queries to the provided state. - GetStateProvider(transition *state.Transition) contract.Provider - - // GetHeaderByNumber returns a reference to block header for the given block number. - GetHeaderByNumber(number uint64) (*types.Header, bool) - - // GetHeaderByHash returns a reference to block header for the given block hash - GetHeaderByHash(hash types.Hash) (*types.Header, bool) - - // GetSystemState creates a new instance of SystemState interface - GetSystemState(provider contract.Provider) SystemState - - // SubscribeEvents subscribes to blockchain events - SubscribeEvents() blockchain.Subscription - - // UnubscribeEvents unsubscribes from blockchain events - UnubscribeEvents(subscription blockchain.Subscription) - - // GetChainID returns chain id of the current blockchain - GetChainID() uint64 - - // GetReceiptsByHash retrieves receipts by hash - GetReceiptsByHash(hash types.Hash) ([]*types.Receipt, error) -} - -var _ blockchainBackend = &blockchainWrapper{} - -type blockchainWrapper struct { - executor *state.Executor - blockchain *blockchain.Blockchain -} - -// CurrentHeader returns the header of blockchain block head -func (p *blockchainWrapper) CurrentHeader() *types.Header { - return p.blockchain.Header() -} - -// CommitBlock commits a block to the chain -func (p *blockchainWrapper) CommitBlock(block *types.FullBlock) error { - return p.blockchain.WriteFullBlock(block, consensusSource) -} - -// ProcessBlock builds a final block from given 'block' on top of 'parent' -func (p *blockchainWrapper) ProcessBlock(parent *types.Header, block *types.Block) (*types.FullBlock, error) { - header := block.Header.Copy() - start := time.Now().UTC() - - transition, err := p.executor.BeginTxn(parent.StateRoot, header, types.BytesToAddress(header.Miner)) - if err != nil { - return nil, err - } - - // apply transactions from block - for _, tx := range block.Transactions { - if err = transition.Write(tx); err != nil { - return nil, fmt.Errorf("process block tx error, tx = %v, err = %w", tx.Hash, err) - } - } - - _, root, err := transition.Commit() - if err != nil { - return nil, fmt.Errorf("failed to commit the state changes: %w", err) - } - - updateBlockExecutionMetric(start) - - if root != block.Header.StateRoot { - return nil, fmt.Errorf("incorrect state root: (%s, %s)", root, block.Header.StateRoot) - } - - // build the block - builtBlock := consensus.BuildBlock(consensus.BuildBlockParams{ - Header: header, - Txns: block.Transactions, - Receipts: transition.Receipts(), - }) - - if builtBlock.Header.TxRoot != block.Header.TxRoot { - return nil, fmt.Errorf("incorrect tx root (expected: %s, actual: %s)", - builtBlock.Header.TxRoot, block.Header.TxRoot) - } - - return &types.FullBlock{ - Block: builtBlock, - Receipts: transition.Receipts(), - }, nil -} - -// GetStateProviderForBlock is an implementation of blockchainBackend interface -func (p *blockchainWrapper) GetStateProviderForBlock(header *types.Header) (contract.Provider, error) { - transition, err := p.executor.BeginTxn(header.StateRoot, header, types.ZeroAddress) - if err != nil { - return nil, err - } - - return NewStateProvider(transition), nil -} - -// GetStateProvider returns a reference to make queries to the provided state -func (p *blockchainWrapper) GetStateProvider(transition *state.Transition) contract.Provider { - return NewStateProvider(transition) -} - -// GetHeaderByNumber is an implementation of blockchainBackend interface -func (p *blockchainWrapper) GetHeaderByNumber(number uint64) (*types.Header, bool) { - return p.blockchain.GetHeaderByNumber(number) -} - -// GetHeaderByHash is an implementation of blockchainBackend interface -func (p *blockchainWrapper) GetHeaderByHash(hash types.Hash) (*types.Header, bool) { - return p.blockchain.GetHeaderByHash(hash) -} - -// NewBlockBuilder is an implementation of blockchainBackend interface -func (p *blockchainWrapper) NewBlockBuilder( - parent *types.Header, coinbase types.Address, - txPool txPoolInterface, blockTime time.Duration, logger hclog.Logger) (blockBuilder, error) { - gasLimit, err := p.blockchain.CalculateGasLimit(parent.Number + 1) - if err != nil { - return nil, err - } - - return NewBlockBuilder(&BlockBuilderParams{ - BlockTime: blockTime, - Parent: parent, - Coinbase: coinbase, - Executor: p.executor, - GasLimit: gasLimit, - BaseFee: p.blockchain.CalculateBaseFee(parent), - TxPool: txPool, - Logger: logger, - }), nil -} - -// GetSystemState is an implementation of blockchainBackend interface -func (p *blockchainWrapper) GetSystemState(provider contract.Provider) SystemState { - return NewSystemState(contracts.ValidatorSetContract, contracts.StateReceiverContract, provider) -} - -func (p *blockchainWrapper) SubscribeEvents() blockchain.Subscription { - return p.blockchain.SubscribeEvents() -} - -func (p *blockchainWrapper) UnubscribeEvents(subscription blockchain.Subscription) { - p.blockchain.UnsubscribeEvents(subscription) -} - -func (p *blockchainWrapper) GetChainID() uint64 { - return uint64(p.blockchain.Config().ChainID) -} - -func (p *blockchainWrapper) GetReceiptsByHash(hash types.Hash) ([]*types.Receipt, error) { - return p.blockchain.GetReceiptsByHash(hash) -} - -var _ contract.Provider = &stateProvider{} - -type stateProvider struct { - transition *state.Transition -} - -// NewStateProvider initializes EVM against given state and chain config and returns stateProvider instance -// which is an abstraction for smart contract calls -func NewStateProvider(transition *state.Transition) contract.Provider { - return &stateProvider{transition: transition} -} - -// Call implements the contract.Provider interface to make contract calls directly to the state -func (s *stateProvider) Call(addr ethgo.Address, input []byte, opts *contract.CallOpts) ([]byte, error) { - result := s.transition.Call2(contracts.SystemCaller, types.Address(addr), input, big.NewInt(0), 10000000) - if result.Failed() { - return nil, result.Err - } - - return result.ReturnValue, nil -} - -// Txn is part of the contract.Provider interface to make Ethereum transactions. We disable this function -// since the system state does not make any transaction -func (s *stateProvider) Txn(ethgo.Address, ethgo.Key, []byte) (contract.Txn, error) { - return nil, errSendTxnUnsupported -} diff --git a/consensus/polybft/checkpoint_manager.go b/consensus/polybft/checkpoint_manager.go deleted file mode 100644 index 4d174db..0000000 --- a/consensus/polybft/checkpoint_manager.go +++ /dev/null @@ -1,492 +0,0 @@ -package polybft - -import ( - "bytes" - "fmt" - "math/big" - "strconv" - - hclog "github.com/hashicorp/go-hclog" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/helper/hex" - "github.com/xgr-network/xgr-node/merkle-tree" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" -) - -var ( - // currentCheckpointBlockNumMethod is an ABI method object representation for - // currentCheckpointBlockNumber getter function on CheckpointManager contract - currentCheckpointBlockNumMethod = contractsapi.CheckpointManager.Abi.Methods["currentCheckpointBlockNumber"] - // frequency at which checkpoints are sent to the rootchain (in blocks count) - defaultCheckpointsOffset = uint64(900) -) - -type CheckpointManager interface { - EventSubscriber - PostBlock(req *PostBlockRequest) error - BuildEventRoot(epoch uint64) (types.Hash, error) - GenerateExitProof(exitID uint64) (types.Proof, error) -} - -var _ CheckpointManager = (*dummyCheckpointManager)(nil) - -type dummyCheckpointManager struct{} - -func (d *dummyCheckpointManager) PostBlock(req *PostBlockRequest) error { return nil } -func (d *dummyCheckpointManager) BuildEventRoot(epoch uint64) (types.Hash, error) { - return types.ZeroHash, nil -} -func (d *dummyCheckpointManager) GenerateExitProof(exitID uint64) (types.Proof, error) { - return types.Proof{}, nil -} - -// EventSubscriber implementation -func (d *dummyCheckpointManager) GetLogFilters() map[types.Address][]types.Hash { - return make(map[types.Address][]types.Hash) -} -func (d *dummyCheckpointManager) ProcessLog(header *types.Header, - log *ethgo.Log, dbTx *bolt.Tx) error { - return nil -} - -var _ CheckpointManager = (*checkpointManager)(nil) - -// checkpointManager encapsulates logic for checkpoint data submission -type checkpointManager struct { - // key is the identity of the node submitting a checkpoint - key ethgo.Key - // blockchain is abstraction for blockchain - blockchain blockchainBackend - // consensusBackend is abstraction for polybft consensus specific functions - consensusBackend polybftBackend - // rootChainRelayer abstracts rootchain interaction logic (Call and SendTransaction invocations to the rootchain) - rootChainRelayer txrelayer.TxRelayer - // checkpointsOffset represents offset between checkpoint blocks (applicable only for non-epoch ending blocks) - checkpointsOffset uint64 - // checkpointManagerAddr is address of CheckpointManager smart contract - checkpointManagerAddr types.Address - // lastSentBlock represents the last block on which a checkpoint transaction was sent - lastSentBlock uint64 - // logger instance - logger hclog.Logger - // state boltDb instance - state *State -} - -// newCheckpointManager creates a new instance of checkpointManager -func newCheckpointManager(key ethgo.Key, checkpointOffset uint64, - checkpointManagerSC types.Address, txRelayer txrelayer.TxRelayer, - blockchain blockchainBackend, backend polybftBackend, logger hclog.Logger, - state *State) *checkpointManager { - return &checkpointManager{ - key: key, - blockchain: blockchain, - consensusBackend: backend, - rootChainRelayer: txRelayer, - checkpointsOffset: checkpointOffset, - checkpointManagerAddr: checkpointManagerSC, - logger: logger, - state: state, - } -} - -// getCurrentCheckpointBlock queries CheckpointManager smart contract and retrieves the current checkpoint block number -func getCurrentCheckpointBlock(relayer txrelayer.TxRelayer, checkpointManagerAddr types.Address) (uint64, error) { - checkpointBlockNumInput, err := currentCheckpointBlockNumMethod.Encode([]interface{}{}) - if err != nil { - return 0, fmt.Errorf("failed to encode currentCheckpointBlockNumber function parameters: %w", err) - } - - currentCheckpointBlockRaw, err := relayer.Call(ethgo.ZeroAddress, ethgo.Address(checkpointManagerAddr), - checkpointBlockNumInput) - if err != nil { - return 0, fmt.Errorf("failed to invoke currentCheckpointBlockNumber function on the rootchain: %w", err) - } - - currentCheckpointBlock, err := strconv.ParseUint(currentCheckpointBlockRaw, 0, 64) - if err != nil { - return 0, fmt.Errorf("failed to convert current checkpoint block number '%s' to number: %w", - currentCheckpointBlockRaw, err) - } - - return currentCheckpointBlock, nil -} - -// submitCheckpoint sends a transaction with checkpoint data to the rootchain -func (c *checkpointManager) submitCheckpoint(latestHeader *types.Header, isEndOfEpoch bool) error { - lastCheckpointBlockNumber, err := getCurrentCheckpointBlock(c.rootChainRelayer, c.checkpointManagerAddr) - if err != nil { - return err - } - - if lastCheckpointBlockNumber > latestHeader.Number { - // node is out of sync (haven't reached the tip of the chain), so even though it is a proposer, - // it would checkpoint block that is already checkpointed and transaction would fail anyway - return nil - } - - c.logger.Debug("submitCheckpoint invoked...", - "latest checkpoint block", lastCheckpointBlockNumber, - "checkpoint block", latestHeader.Number) - - var ( - initialBlockNumber = lastCheckpointBlockNumber + 1 - parentExtra *Extra - parentHeader *types.Header - currentExtra *Extra - found bool - ) - - if initialBlockNumber < latestHeader.Number { - parentHeader, found = c.blockchain.GetHeaderByNumber(initialBlockNumber) - if !found { - return fmt.Errorf("block %d was not found", initialBlockNumber) - } - - parentExtra, err = GetIbftExtra(parentHeader.ExtraData) - if err != nil { - return err - } - } - - // detect any pending (previously failed) checkpoints and send them - for blockNumber := initialBlockNumber + 1; blockNumber <= latestHeader.Number; blockNumber++ { - currentHeader, found := c.blockchain.GetHeaderByNumber(blockNumber) - if !found { - return fmt.Errorf("block %d was not found", blockNumber) - } - - currentExtra, err = GetIbftExtra(currentHeader.ExtraData) - if err != nil { - return err - } - - parentEpochNumber := parentExtra.Checkpoint.EpochNumber - currentEpochNumber := currentExtra.Checkpoint.EpochNumber - // send pending checkpoints only for epoch ending blocks - if blockNumber == 1 || parentEpochNumber == currentEpochNumber { - parentHeader = currentHeader - parentExtra = currentExtra - - continue - } - - if err = c.encodeAndSendCheckpoint(parentHeader, parentExtra, true); err != nil { - return err - } - - parentHeader = currentHeader - parentExtra = currentExtra - } - - // latestHeader extra could be set in the for loop above - // (in case there were pending checkpoint blocks) - if currentExtra == nil { - // we need to send checkpoint for the latest block - currentExtra, err = GetIbftExtra(latestHeader.ExtraData) - if err != nil { - return err - } - } - - return c.encodeAndSendCheckpoint(latestHeader, currentExtra, isEndOfEpoch) -} - -// encodeAndSendCheckpoint encodes checkpoint data for the given block and -// sends a transaction to the CheckpointManager rootchain contract -func (c *checkpointManager) encodeAndSendCheckpoint(header *types.Header, extra *Extra, isEndOfEpoch bool) error { - c.logger.Debug("send checkpoint txn...", "block number", header.Number) - - checkpointManager := ethgo.Address(c.checkpointManagerAddr) - - nextEpochValidators := validator.AccountSet{} - - if isEndOfEpoch { - var err error - nextEpochValidators, err = c.consensusBackend.GetValidators(header.Number, nil) - - if err != nil { - return err - } - } - - input, err := c.abiEncodeCheckpointBlock(header.Number, header.Hash, extra, nextEpochValidators) - if err != nil { - return fmt.Errorf("failed to encode checkpoint data to ABI for block %d: %w", header.Number, err) - } - - txn := ðgo.Transaction{ - To: &checkpointManager, - Input: input, - Type: ethgo.TransactionDynamicFee, - } - - receipt, err := c.rootChainRelayer.SendTransaction(txn, c.key) - if err != nil { - return err - } - - if receipt.Status == uint64(types.ReceiptFailed) { - return fmt.Errorf("checkpoint submission transaction failed for block %d", header.Number) - } - - c.logger.Debug("send checkpoint txn success", "block number", header.Number, "gasUsed", receipt.GasUsed) - - return nil -} - -// abiEncodeCheckpointBlock encodes checkpoint data into ABI format for a given header -func (c *checkpointManager) abiEncodeCheckpointBlock(blockNumber uint64, blockHash types.Hash, extra *Extra, - nextValidators validator.AccountSet) ([]byte, error) { - aggs, err := bls.UnmarshalSignature(extra.Committed.AggregatedSignature) - if err != nil { - return nil, err - } - - encodedAggSigs, err := aggs.ToBigInt() - if err != nil { - return nil, err - } - - submit := &contractsapi.SubmitCheckpointManagerFn{ - CheckpointMetadata: &contractsapi.CheckpointMetadata{ - BlockHash: blockHash, - BlockRound: new(big.Int).SetUint64(extra.Checkpoint.BlockRound), - CurrentValidatorSetHash: extra.Checkpoint.CurrentValidatorsHash, - }, - Checkpoint: &contractsapi.Checkpoint{ - Epoch: new(big.Int).SetUint64(extra.Checkpoint.EpochNumber), - BlockNumber: new(big.Int).SetUint64(blockNumber), - EventRoot: extra.Checkpoint.EventRoot, - }, - Signature: encodedAggSigs, - Bitmap: extra.Committed.Bitmap, - NewValidatorSet: nextValidators.ToAPIBinding(), - } - - return submit.EncodeAbi() -} - -// isCheckpointBlock returns true for blocks in the middle of the epoch -// which are offset by predefined count of blocks -// or if given block is an epoch ending block -func (c *checkpointManager) isCheckpointBlock(blockNumber uint64, isEpochEndingBlock bool) bool { - return isEpochEndingBlock || blockNumber == c.lastSentBlock+c.checkpointsOffset -} - -// PostBlock is called on every insert of finalized block (either from consensus or syncer) -// It sends a checkpoint if given block is checkpoint block and block proposer is given validator -func (c *checkpointManager) PostBlock(req *PostBlockRequest) error { - if c.isCheckpointBlock(req.FullBlock.Block.Header.Number, req.IsEpochEndingBlock) && - bytes.Equal(c.key.Address().Bytes(), req.FullBlock.Block.Header.Miner) { - go func(header *types.Header, epochNumber uint64) { - if err := c.submitCheckpoint(header, req.IsEpochEndingBlock); err != nil { - c.logger.Warn("failed to submit checkpoint", - "checkpoint block", header.Number, - "epoch number", epochNumber, - "error", err) - } - }(req.FullBlock.Block.Header, req.Epoch) - - c.lastSentBlock = req.FullBlock.Block.Number() - } - - return nil -} - -// BuildEventRoot returns an exit event root hash for exit tree of given epoch -func (c *checkpointManager) BuildEventRoot(epoch uint64) (types.Hash, error) { - exitEvents, err := c.state.CheckpointStore.getExitEventsByEpoch(epoch) - if err != nil { - return types.ZeroHash, err - } - - if len(exitEvents) == 0 { - return types.ZeroHash, nil - } - - tree, err := createExitTree(exitEvents) - if err != nil { - return types.ZeroHash, err - } - - return tree.Hash(), nil -} - -// GenerateExitProof generates proof of exit event -func (c *checkpointManager) GenerateExitProof(exitID uint64) (types.Proof, error) { - c.logger.Debug("Generating proof for exit", "exitID", exitID) - - exitEvent, err := c.state.CheckpointStore.getExitEvent(exitID) - if err != nil { - return types.Proof{}, err - } - - getCheckpointBlockFn := &contractsapi.GetCheckpointBlockCheckpointManagerFn{ - BlockNumber: new(big.Int).SetUint64(exitEvent.BlockNumber), - } - - input, err := getCheckpointBlockFn.EncodeAbi() - if err != nil { - return types.Proof{}, fmt.Errorf("failed to encode get checkpoint block input: %w", err) - } - - getCheckpointBlockResp, err := c.rootChainRelayer.Call( - ethgo.ZeroAddress, - ethgo.Address(c.checkpointManagerAddr), - input) - if err != nil { - return types.Proof{}, fmt.Errorf("failed to retrieve checkpoint block for exit ID %d: %w", exitID, err) - } - - getCheckpointBlockRespRaw, err := hex.DecodeHex(getCheckpointBlockResp) - if err != nil { - return types.Proof{}, fmt.Errorf("failed to decode hex response for exit ID %d: %w", exitID, err) - } - - getCheckpointBlockGeneric, err := contractsapi.GetCheckpointBlockABIResponse.Decode(getCheckpointBlockRespRaw) - if err != nil { - return types.Proof{}, fmt.Errorf("failed to decode checkpoint block response for exit ID %d: %w", exitID, err) - } - - checkpointBlockMap, ok := getCheckpointBlockGeneric.(map[string]interface{}) - if !ok { - return types.Proof{}, fmt.Errorf("failed to convert for checkpoint block response exit ID %d", exitID) - } - - isFoundGeneric, ok := checkpointBlockMap["isFound"] - if !ok { - return types.Proof{}, fmt.Errorf("invalid response for exit ID %d", exitID) - } - - isCheckpointFound, ok := isFoundGeneric.(bool) - if !ok || !isCheckpointFound { - return types.Proof{}, fmt.Errorf("checkpoint block not found for exit ID %d", exitID) - } - - checkpointBlockGeneric, ok := checkpointBlockMap["checkpointBlock"] - if !ok { - return types.Proof{}, fmt.Errorf("checkpoint block not found for exit ID %d", exitID) - } - - checkpointBlock, ok := checkpointBlockGeneric.(*big.Int) - if !ok { - return types.Proof{}, fmt.Errorf("checkpoint block not found for exit ID %d", exitID) - } - - exitEventEncoded, err := exitEvent.L2StateSyncedEvent.Encode() - if err != nil { - return types.Proof{}, err - } - - exitEvents, err := c.state.CheckpointStore.getExitEventsForProof(exitEvent.EpochNumber, checkpointBlock.Uint64()) - if err != nil { - return types.Proof{}, err - } - - tree, err := createExitTree(exitEvents) - if err != nil { - return types.Proof{}, err - } - - leafIndex, err := tree.LeafIndex(exitEventEncoded) - if err != nil { - return types.Proof{}, err - } - - proof, err := tree.GenerateProof(exitEventEncoded) - if err != nil { - return types.Proof{}, err - } - - c.logger.Debug("Generated proof for exit", "exitID", exitID, "leafIndex", leafIndex, "proofLen", len(proof)) - - exitEventHex := hex.EncodeToString(exitEventEncoded) - - return types.Proof{ - Data: proof, - Metadata: map[string]interface{}{ - "LeafIndex": leafIndex, - "ExitEvent": exitEventHex, - "CheckpointBlock": checkpointBlock, - }, - }, nil -} - -// EventSubscriber implementation - -// GetLogFilters returns a map of log filters for getting desired events, -// where the key is the address of contract that emits desired events, -// and the value is a slice of signatures of events we want to get. -// This function is the implementation of EventSubscriber interface -func (c *checkpointManager) GetLogFilters() map[types.Address][]types.Hash { - var l2StateSyncedEvent contractsapi.L2StateSyncedEvent - - return map[types.Address][]types.Hash{ - contracts.L2StateSenderContract: {types.Hash(l2StateSyncedEvent.Sig())}, - } -} - -// ProcessLog is the implementation of EventSubscriber interface, -// used to handle a log defined in GetLogFilters, provided by event provider -func (c *checkpointManager) ProcessLog(header *types.Header, log *ethgo.Log, dbTx *bolt.Tx) error { - exitEvent, doesMatch, err := parseExitEvent(header, log) - if err != nil { - return err - } - - if !doesMatch { - return nil - } - - return c.state.CheckpointStore.insertExitEvent(exitEvent, dbTx) -} - -// createExitTree creates an exit event merkle tree from provided exit events -func createExitTree(exitEvents []*ExitEvent) (*merkle.MerkleTree, error) { - numOfEvents := len(exitEvents) - data := make([][]byte, numOfEvents) - - for i := 0; i < numOfEvents; i++ { - b, err := exitEvents[i].L2StateSyncedEvent.Encode() - if err != nil { - return nil, err - } - - data[i] = b - } - - return merkle.NewMerkleTree(data) -} - -// parseExitEvent parses exit event from provided log -func parseExitEvent(h *types.Header, l *ethgo.Log) (*ExitEvent, bool, error) { - extra, err := GetIbftExtra(h.ExtraData) - if err != nil { - return nil, false, - fmt.Errorf("could not get header extra on exit event parsing. Error: %w", err) - } - - epoch := extra.Checkpoint.EpochNumber - block := h.Number - - if extra.Validators != nil { - // exit events that happened in epoch ending blocks, - // should be added to the tree of the next epoch - epoch++ - block++ - } - - event, err := decodeExitEvent(l, epoch, block) - if err != nil { - return nil, false, err - } - - return event, true, nil -} diff --git a/consensus/polybft/checkpoint_manager_test.go b/consensus/polybft/checkpoint_manager_test.go deleted file mode 100644 index 12f8dda..0000000 --- a/consensus/polybft/checkpoint_manager_test.go +++ /dev/null @@ -1,524 +0,0 @@ -package polybft - -import ( - "encoding/hex" - "errors" - "math/big" - "strconv" - "testing" - - "github.com/umbracle/ethgo/abi" - "github.com/umbracle/ethgo/jsonrpc" - - hclog "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/merkle-tree" - - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -func TestCheckpointManager_SubmitCheckpoint(t *testing.T) { - t.Parallel() - - const ( - blocksCount = 10 - epochSize = 2 - ) - - t.Run("submit checkpoint happy path", func(t *testing.T) { - t.Parallel() - - var aliases = []string{"A", "B", "C", "D", "E"} - - validators := validator.NewTestValidatorsWithAliases(t, aliases) - validatorsMetadata := validators.GetPublicIdentities() - - txRelayerMock := newDummyTxRelayer(t) - txRelayerMock.On("Call", mock.Anything, mock.Anything, mock.Anything). - Return("2", error(nil)). - Once() - txRelayerMock.On("SendTransaction", mock.Anything, mock.Anything). - Return(ðgo.Receipt{Status: uint64(types.ReceiptSuccess)}, error(nil)). - Times(4) // send transactions for checkpoint blocks: 4, 6, 8 (pending checkpoint blocks) and 10 (latest checkpoint block) - - backendMock := new(polybftBackendMock) - backendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validatorsMetadata) - - var ( - headersMap = &testHeadersMap{} - epochNumber = uint64(1) - dummyMsg = []byte("checkpoint") - idx = uint64(0) - header *types.Header - bitmap bitmap.Bitmap - signatures bls.Signatures - ) - - validators.IterAcct(aliases, func(t *validator.TestValidator) { - bitmap.Set(idx) - signatures = append(signatures, t.MustSign(dummyMsg, signer.DomainCheckpointManager)) - idx++ - }) - - signature, err := signatures.Aggregate().Marshal() - require.NoError(t, err) - - for i := uint64(1); i <= blocksCount; i++ { - if i%epochSize == 1 { - // epoch-beginning block - checkpoint := &CheckpointData{ - BlockRound: 0, - EpochNumber: epochNumber, - EventRoot: types.BytesToHash(generateRandomBytes(t)), - } - extra := createTestExtraObject(validatorsMetadata, validatorsMetadata, 3, 3, 3) - extra.Checkpoint = checkpoint - extra.Committed = &Signature{Bitmap: bitmap, AggregatedSignature: signature} - header = &types.Header{ - ExtraData: extra.MarshalRLPTo(nil), - } - epochNumber++ - } else { - header = header.Copy() - } - - header.Number = i - header.ComputeHash() - headersMap.addHeader(header) - } - - // mock blockchain - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) - - validatorAcc := validators.GetValidator("A") - c := &checkpointManager{ - key: wallet.NewEcdsaSigner(validatorAcc.Key()), - rootChainRelayer: txRelayerMock, - consensusBackend: backendMock, - blockchain: blockchainMock, - logger: hclog.NewNullLogger(), - } - - err = c.submitCheckpoint(headersMap.getHeader(blocksCount), false) - require.NoError(t, err) - txRelayerMock.AssertExpectations(t) - - // make sure that expected blocks are checkpointed (epoch-ending ones) - for _, checkpointBlock := range txRelayerMock.checkpointBlocks { - header := headersMap.getHeader(checkpointBlock) - require.NotNil(t, header) - require.True(t, isEndOfPeriod(header.Number, epochSize)) - } - }) - - t.Run("checkpoint not submitted when node is syncing", func(t *testing.T) { - t.Parallel() - - var aliases = []string{"A"} - - validators := validator.NewTestValidatorsWithAliases(t, aliases) - - txRelayerMock := newDummyTxRelayer(t) - txRelayerMock.On("Call", mock.Anything, mock.Anything, mock.Anything). - Return("20", error(nil)). - Once() - - c := &checkpointManager{ - key: wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), - rootChainRelayer: txRelayerMock, - logger: hclog.NewNullLogger(), - } - - err := c.submitCheckpoint(&types.Header{Number: 19 /*lower than what Call returns*/}, false) - require.NoError(t, err) - // since transaction will not be sent to CheckpointManager, we only expect that Call - // will called on tx relayer, and nothing else in submitCheckpoint will be executed - txRelayerMock.AssertExpectations(t) - }) -} - -func TestCheckpointManager_abiEncodeCheckpointBlock(t *testing.T) { - t.Parallel() - - const epochSize = uint64(10) - - currentValidators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D"}) - nextValidators := validator.NewTestValidatorsWithAliases(t, []string{"E", "F", "G", "H"}) - header := &types.Header{Number: 50} - checkpoint := &CheckpointData{ - BlockRound: 1, - EpochNumber: getEpochNumber(t, header.Number, epochSize), - EventRoot: types.BytesToHash(generateRandomBytes(t)), - } - - proposalHash := generateRandomBytes(t) - - bmp := bitmap.Bitmap{} - i := uint64(0) - - var signatures bls.Signatures - - currentValidators.IterAcct(nil, func(v *validator.TestValidator) { - signatures = append(signatures, v.MustSign(proposalHash, signer.DomainCheckpointManager)) - bmp.Set(i) - i++ - }) - - aggSignature, err := signatures.Aggregate().Marshal() - require.NoError(t, err) - - extra := &Extra{Checkpoint: checkpoint} - extra.Committed = &Signature{ - AggregatedSignature: aggSignature, - Bitmap: bmp, - } - header.ExtraData = extra.MarshalRLPTo(nil) - header.ComputeHash() - - backendMock := new(polybftBackendMock) - backendMock.On("GetValidators", mock.Anything, mock.Anything).Return(currentValidators.GetPublicIdentities()) - - c := &checkpointManager{ - blockchain: &blockchainMock{}, - consensusBackend: backendMock, - logger: hclog.NewNullLogger(), - } - checkpointDataEncoded, err := c.abiEncodeCheckpointBlock(header.Number, header.Hash, extra, nextValidators.GetPublicIdentities()) - require.NoError(t, err) - - submit := &contractsapi.SubmitCheckpointManagerFn{} - require.NoError(t, submit.DecodeAbi(checkpointDataEncoded)) - - require.Equal(t, new(big.Int).SetUint64(checkpoint.EpochNumber), submit.Checkpoint.Epoch) - require.Equal(t, new(big.Int).SetUint64(header.Number), submit.Checkpoint.BlockNumber) - require.Equal(t, checkpoint.EventRoot, submit.Checkpoint.EventRoot) - require.Equal(t, new(big.Int).SetUint64(checkpoint.BlockRound), submit.CheckpointMetadata.BlockRound) -} - -func TestCheckpointManager_getCurrentCheckpointID(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - checkpointID string - returnError error - errSubstring string - }{ - { - name: "Happy path", - checkpointID: "16", - returnError: error(nil), - errSubstring: "", - }, - { - name: "Rootchain call returns an error", - checkpointID: "", - returnError: errors.New("internal error"), - errSubstring: "failed to invoke currentCheckpointBlockNumber function on the rootchain", - }, - { - name: "Failed to parse return value from rootchain", - checkpointID: "Hello World!", - returnError: error(nil), - errSubstring: "failed to convert current checkpoint block number", - }, - } - - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - - txRelayerMock := newDummyTxRelayer(t) - txRelayerMock.On("Call", mock.Anything, mock.Anything, mock.Anything). - Return(c.checkpointID, c.returnError). - Once() - acc, err := wallet.GenerateAccount() - require.NoError(t, err) - - checkpointMgr := &checkpointManager{ - rootChainRelayer: txRelayerMock, - key: acc.Ecdsa, - logger: hclog.NewNullLogger(), - } - actualCheckpointID, err := getCurrentCheckpointBlock(checkpointMgr.rootChainRelayer, - checkpointMgr.checkpointManagerAddr) - if c.errSubstring == "" { - expectedCheckpointID, err := strconv.ParseUint(c.checkpointID, 0, 64) - require.NoError(t, err) - require.Equal(t, expectedCheckpointID, actualCheckpointID) - } else { - require.ErrorContains(t, err, c.errSubstring) - } - - txRelayerMock.AssertExpectations(t) - }) - } -} - -func TestCheckpointManager_IsCheckpointBlock(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - blockNumber uint64 - checkpointsOffset uint64 - isEpochEndingBlock bool - isCheckpointBlock bool - }{ - { - name: "Not checkpoint block", - blockNumber: 3, - checkpointsOffset: 6, - isEpochEndingBlock: false, - isCheckpointBlock: false, - }, - { - name: "Checkpoint block", - blockNumber: 6, - checkpointsOffset: 6, - isEpochEndingBlock: false, - isCheckpointBlock: true, - }, - { - name: "Epoch ending block - Fixed epoch size met", - blockNumber: 10, - checkpointsOffset: 5, - isEpochEndingBlock: true, - isCheckpointBlock: true, - }, - { - name: "Epoch ending block - Epoch ended before fix size was met", - blockNumber: 9, - checkpointsOffset: 5, - isEpochEndingBlock: true, - isCheckpointBlock: true, - }, - } - - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - - checkpointMgr := newCheckpointManager(wallet.NewEcdsaSigner(createTestKey(t)), c.checkpointsOffset, types.ZeroAddress, nil, nil, nil, hclog.NewNullLogger(), nil) - require.Equal(t, c.isCheckpointBlock, checkpointMgr.isCheckpointBlock(c.blockNumber, c.isEpochEndingBlock)) - }) - } -} - -func TestCheckpointManager_BuildEventRoot(t *testing.T) { - t.Parallel() - - const ( - numOfBlocks = 10 - numOfEventsPerBlock = 2 - ) - - state := newTestState(t) - checkpointManager := &checkpointManager{state: state} - - encodedEvents := insertTestExitEvents(t, state, 1, numOfBlocks, numOfEventsPerBlock) - - t.Run("Get exit event root hash", func(t *testing.T) { - t.Parallel() - - tree, err := createExitTree(encodedEvents) - require.NoError(t, err) - - hash, err := checkpointManager.BuildEventRoot(1) - require.NoError(t, err) - require.Equal(t, tree.Hash(), hash) - }) - - t.Run("Get exit event root hash - no events", func(t *testing.T) { - t.Parallel() - - hash, err := checkpointManager.BuildEventRoot(2) - require.NoError(t, err) - require.Equal(t, types.Hash{}, hash) - }) -} - -func TestCheckpointManager_GenerateExitProof(t *testing.T) { - t.Parallel() - - const ( - numOfBlocks = 10 - numOfEventsPerBlock = 2 - correctBlockToGetExit = 1 - futureBlockToGetExit = 2 - ) - - state := newTestState(t) - - // setup mocks for valid case - foundCheckpointReturn, err := contractsapi.GetCheckpointBlockABIResponse.Encode(map[string]interface{}{ - "isFound": true, - "checkpointBlock": 1, - }) - require.NoError(t, err) - - getCheckpointBlockFn := &contractsapi.GetCheckpointBlockCheckpointManagerFn{ - BlockNumber: new(big.Int).SetUint64(correctBlockToGetExit), - } - - input, err := getCheckpointBlockFn.EncodeAbi() - require.NoError(t, err) - - dummyTxRelayer := newDummyTxRelayer(t) - dummyTxRelayer.On("Call", ethgo.ZeroAddress, ethgo.ZeroAddress, input). - Return(hex.EncodeToString(foundCheckpointReturn), error(nil)) - - // create checkpoint manager and insert exit events - checkpointMgr := newCheckpointManager(wallet.NewEcdsaSigner( - createTestKey(t)), - 0, - types.ZeroAddress, - dummyTxRelayer, - nil, - nil, - hclog.NewNullLogger(), - state) - - exitEvents := insertTestExitEvents(t, state, 1, numOfBlocks, numOfEventsPerBlock) - encodedEvents := encodeExitEvents(t, exitEvents) - checkpointEvents := encodedEvents[:numOfEventsPerBlock] - - // manually create merkle tree for a desired checkpoint to verify the generated proof - tree, err := merkle.NewMerkleTree(checkpointEvents) - require.NoError(t, err) - - proof, err := checkpointMgr.GenerateExitProof(correctBlockToGetExit) - require.NoError(t, err) - require.NotNil(t, proof) - - t.Run("Generate and validate exit proof", func(t *testing.T) { - t.Parallel() - // verify generated proof on desired tree - require.NoError(t, merkle.VerifyProof(correctBlockToGetExit, encodedEvents[1], proof.Data, tree.Hash())) - }) - - t.Run("Generate and validate exit proof - invalid proof", func(t *testing.T) { - t.Parallel() - - // copy and make proof invalid - invalidProof := make([]types.Hash, len(proof.Data)) - copy(invalidProof, proof.Data) - invalidProof[0][0]++ - - // verify generated proof on desired tree - require.ErrorContains(t, merkle.VerifyProof(correctBlockToGetExit, - encodedEvents[1], invalidProof, tree.Hash()), "not a member of merkle tree") - }) - - t.Run("Generate exit proof - no event", func(t *testing.T) { - t.Parallel() - - _, err := checkpointMgr.GenerateExitProof(21) - require.ErrorContains(t, err, "could not find any exit event that has an id") - }) - - t.Run("Generate exit proof - future lookup where checkpoint not yet submitted", func(t *testing.T) { - t.Parallel() - - // setup mocks for invalid case - notFoundCheckpointReturn, err := contractsapi.GetCheckpointBlockABIResponse.Encode(map[string]interface{}{ - "isFound": false, - "checkpointBlock": 0, - }) - require.NoError(t, err) - - getCheckpointBlockFn.BlockNumber = new(big.Int).SetUint64(futureBlockToGetExit) - inputTwo, err := getCheckpointBlockFn.EncodeAbi() - require.NoError(t, err) - - dummyTxRelayer.On("Call", ethgo.ZeroAddress, ethgo.ZeroAddress, inputTwo). - Return(hex.EncodeToString(notFoundCheckpointReturn), error(nil)) - - _, err = checkpointMgr.GenerateExitProof(futureBlockToGetExit) - require.ErrorContains(t, err, "checkpoint block not found for exit ID") - }) -} - -var _ txrelayer.TxRelayer = (*dummyTxRelayer)(nil) - -type dummyTxRelayer struct { - mock.Mock - - test *testing.T - checkpointBlocks []uint64 -} - -func newDummyTxRelayer(t *testing.T) *dummyTxRelayer { - t.Helper() - - return &dummyTxRelayer{test: t} -} - -func (d *dummyTxRelayer) Call(from ethgo.Address, to ethgo.Address, input []byte) (string, error) { - args := d.Called(from, to, input) - - return args.String(0), args.Error(1) -} - -func (d *dummyTxRelayer) SendTransaction(transaction *ethgo.Transaction, key ethgo.Key) (*ethgo.Receipt, error) { - blockNumber := getBlockNumberCheckpointSubmitInput(d.test, transaction.Input) - d.checkpointBlocks = append(d.checkpointBlocks, blockNumber) - args := d.Called(transaction, key) - - return args.Get(0).(*ethgo.Receipt), args.Error(1) //nolint:forcetypeassert -} - -// SendTransactionLocal sends non-signed transaction (this is only for testing purposes) -func (d *dummyTxRelayer) SendTransactionLocal(txn *ethgo.Transaction) (*ethgo.Receipt, error) { - args := d.Called(txn) - - return args.Get(0).(*ethgo.Receipt), args.Error(1) //nolint:forcetypeassert -} - -func (d *dummyTxRelayer) Client() *jsonrpc.Client { - return nil -} - -func getBlockNumberCheckpointSubmitInput(t *testing.T, input []byte) uint64 { - t.Helper() - - submit := &contractsapi.SubmitCheckpointManagerFn{} - require.NoError(t, submit.DecodeAbi(input)) - - return submit.Checkpoint.BlockNumber.Uint64() -} - -func createTestLogForExitEvent(t *testing.T, exitEventID uint64) *types.Log { - t.Helper() - - var exitEvent contractsapi.L2StateSyncedEvent - - topics := make([]types.Hash, 4) - topics[0] = types.Hash(exitEvent.Sig()) - topics[1] = types.BytesToHash(common.EncodeUint64ToBytes(exitEventID)) - topics[2] = types.BytesToHash(types.StringToAddress("0x1111").Bytes()) - topics[3] = types.BytesToHash(types.StringToAddress("0x2222").Bytes()) - someType := abi.MustNewType("tuple(string firstName, string lastName)") - encodedData, err := someType.Encode(map[string]string{"firstName": "John", "lastName": "Doe"}) - require.NoError(t, err) - - return &types.Log{ - Address: contracts.L2StateSenderContract, - Topics: topics, - Data: encodedData, - } -} diff --git a/consensus/polybft/consensus_metrics.go b/consensus/polybft/consensus_metrics.go deleted file mode 100644 index d3b4426..0000000 --- a/consensus/polybft/consensus_metrics.go +++ /dev/null @@ -1,58 +0,0 @@ -package polybft - -import ( - "time" - - "github.com/armon/go-metrics" - "github.com/xgr-network/xgr-node/types" -) - -const ( - // consensusMetricsPrefix is a consensus-related metrics prefix - consensusMetricsPrefix = "consensus" -) - -// updateBlockMetrics updates various metrics based on the given block -// (such as block interval, number of transactions and block rounds metrics) -func updateBlockMetrics(currentBlock *types.Block, parentHeader *types.Header) error { - if currentBlock.Number() > 1 { - parentTime := time.Unix(int64(parentHeader.Timestamp), 0) - headerTime := time.Unix(int64(currentBlock.Header.Timestamp), 0) - // update the block interval metric - metrics.SetGauge([]string{consensusMetricsPrefix, "block_interval"}, float32(headerTime.Sub(parentTime).Seconds())) - } - - // update the number of transactions in the block metric - metrics.SetGauge([]string{consensusMetricsPrefix, "num_txs"}, float32(len(currentBlock.Transactions))) - - extra, err := GetIbftExtra(currentBlock.Header.ExtraData) - if err != nil { - return err - } - - // number of rounds needed to seal a block - metrics.SetGauge([]string{consensusMetricsPrefix, "rounds"}, float32(extra.Checkpoint.BlockRound)) - metrics.SetGauge([]string{consensusMetricsPrefix, "chain_head"}, float32(currentBlock.Number())) - metrics.IncrCounter([]string{consensusMetricsPrefix, "block_counter"}, float32(1)) - metrics.SetGauge([]string{consensusMetricsPrefix, "block_space_used"}, float32(currentBlock.Header.GasUsed)) - - // Update the base fee metric - metrics.SetGauge([]string{consensusMetricsPrefix, "base_fee"}, float32(currentBlock.Header.BaseFee)) - - return nil -} - -// updateEpochMetrics updates epoch-related metrics -// (e.g. epoch number, validator set length) -func updateEpochMetrics(epoch epochMetadata) { - // update epoch number metrics - metrics.SetGauge([]string{consensusMetricsPrefix, "epoch_number"}, float32(epoch.Number)) - // update number of validators metrics - metrics.SetGauge([]string{consensusMetricsPrefix, "validators"}, float32(epoch.Validators.Len())) -} - -// updateBlockExecutionMetric updates the block execution metric -func updateBlockExecutionMetric(start time.Time) { - metrics.SetGauge([]string{consensusMetricsPrefix, "block_execution_time"}, - float32(time.Now().UTC().Sub(start).Seconds())) -} diff --git a/consensus/polybft/consensus_runtime.go b/consensus/polybft/consensus_runtime.go deleted file mode 100644 index 03c5ce5..0000000 --- a/consensus/polybft/consensus_runtime.go +++ /dev/null @@ -1,1091 +0,0 @@ -package polybft - -import ( - "bytes" - "errors" - "fmt" - "math/big" - "sort" - "sync" - "sync/atomic" - "time" - - "github.com/xgr-network/xgr-node/consensus" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" - - "github.com/0xPolygon/go-ibft/messages" - "github.com/0xPolygon/go-ibft/messages/proto" - hcf "github.com/hashicorp/go-hclog" -) - -const ( - maxCommitmentSize = 10 - stateFileName = "consensusState.db" - commitEpochLookbackSize = 2 // number of blocks to calculate commit epoch info from the previous epoch -) - -var ( - // errNotAValidator represents "node is not a validator" error message - errNotAValidator = errors.New("node is not a validator") - // errQuorumNotReached represents "quorum not reached for commitment message" error message - errQuorumNotReached = errors.New("quorum not reached for commitment message") -) - -// txPoolInterface is an abstraction of transaction pool -type txPoolInterface interface { - Prepare() - Length() uint64 - Peek() *types.Transaction - Pop(*types.Transaction) - Drop(*types.Transaction) - Demote(*types.Transaction) - SetSealing(bool) - ResetWithHeaders(...*types.Header) -} - -// epochMetadata is the static info for epoch currently being processed -type epochMetadata struct { - // Number is the number of the epoch - Number uint64 - - FirstBlockInEpoch uint64 - - // Validators is the set of validators for the epoch - Validators validator.AccountSet -} - -type guardedDataDTO struct { - // last built block header at the time of collecting data - lastBuiltBlock *types.Header - - // epoch metadata at the time of collecting data - epoch *epochMetadata - - // proposerSnapshot at the time of collecting data - proposerSnapshot *ProposerSnapshot -} - -// runtimeConfig is a struct that holds configuration data for given consensus runtime -type runtimeConfig struct { - PolyBFTConfig *PolyBFTConfig - DataDir string - Key *wallet.Key - State *State - blockchain blockchainBackend - polybftBackend polybftBackend - txPool txPoolInterface - bridgeTopic topic - numBlockConfirmations uint64 - consensusConfig *consensus.Config -} - -// consensusRuntime is a struct that provides consensus runtime features like epoch, state and event management -type consensusRuntime struct { - // config represents wrapper around required parameters which are received from the outside - config *runtimeConfig - - // state is reference to the struct which encapsulates bridge events persistence logic - state *State - - // fsm instance which is created for each `runSequence` - fsm *fsm - - // lock is a lock to access 'epoch' and `lastBuiltBlock` - lock sync.RWMutex - - // epoch is the metadata for the current epoch - epoch *epochMetadata - - // lastBuiltBlock is the header of the last processed block - lastBuiltBlock *types.Header - - // activeValidatorFlag indicates whether the given node is amongst currently active validator set - activeValidatorFlag atomic.Bool - - // checkpointManager represents abstraction for checkpoint submission - checkpointManager CheckpointManager - - // proposerCalculator is the object which manipulates with ProposerSnapshot - proposerCalculator *ProposerCalculator - - // manager for state sync bridge transactions - stateSyncManager StateSyncManager - - // manager for handling validator stake change and updating validator set - stakeManager StakeManager - - eventProvider *EventProvider - - // stateSyncRelayer is relayer for commitment events - stateSyncRelayer StateSyncRelayer - - // logger instance - logger hcf.Logger -} - -// newConsensusRuntime creates and starts a new consensus runtime instance with event tracking -func newConsensusRuntime(log hcf.Logger, config *runtimeConfig) (*consensusRuntime, error) { - dbTx, err := config.State.beginDBTransaction(true) - if err != nil { - return nil, fmt.Errorf("could not begin dbTx to init consensus runtime: %w", err) - } - - defer dbTx.Rollback() //nolint:errcheck - - proposerCalculator, err := NewProposerCalculator(config, log.Named("proposer_calculator"), dbTx) - if err != nil { - return nil, fmt.Errorf("failed to create consensus runtime, error while creating proposer calculator %w", err) - } - - runtime := &consensusRuntime{ - state: config.State, - config: config, - lastBuiltBlock: config.blockchain.CurrentHeader(), - proposerCalculator: proposerCalculator, - logger: log.Named("consensus_runtime"), - eventProvider: NewEventProvider(config.blockchain), - } - - if err := runtime.initStateSyncManager(log); err != nil { - return nil, err - } - - if err := runtime.initCheckpointManager(log); err != nil { - return nil, err - } - - if err := runtime.initStakeManager(log, dbTx); err != nil { - return nil, err - } - - if err := runtime.initStateSyncRelayer(log); err != nil { - return nil, err - } - - // we need to call restart epoch on runtime to initialize epoch state - runtime.epoch, err = runtime.restartEpoch(runtime.lastBuiltBlock, dbTx) - if err != nil { - return nil, fmt.Errorf("consensus runtime creation - restart epoch failed: %w", err) - } - - if err := dbTx.Commit(); err != nil { - return nil, fmt.Errorf("could not commit db tx to init consensus runtime: %w", err) - } - - return runtime, nil -} - -// close is used to tear down allocated resources -func (c *consensusRuntime) close() { - c.stateSyncRelayer.Close() - c.stateSyncManager.Close() -} - -// initStateSyncManager initializes state sync manager -// if bridge is not enabled, then a dummy state sync manager will be used -func (c *consensusRuntime) initStateSyncManager(logger hcf.Logger) error { - if c.IsBridgeEnabled() { - stateSenderAddr := c.config.PolyBFTConfig.Bridge.StateSenderAddr - stateSyncManager := newStateSyncManager( - logger.Named("state-sync-manager"), - c.config.State, - &stateSyncConfig{ - key: c.config.Key, - stateSenderAddr: stateSenderAddr, - stateSenderStartBlock: c.config.PolyBFTConfig.Bridge.EventTrackerStartBlocks[stateSenderAddr], - jsonrpcAddr: c.config.PolyBFTConfig.Bridge.JSONRPCEndpoint, - dataDir: c.config.DataDir, - topic: c.config.bridgeTopic, - maxCommitmentSize: maxCommitmentSize, - numBlockConfirmations: c.config.numBlockConfirmations, - blockTrackerPollInterval: c.config.PolyBFTConfig.BlockTrackerPollInterval.Duration, - }, - c, - ) - - c.stateSyncManager = stateSyncManager - } else { - c.stateSyncManager = &dummyStateSyncManager{} - } - - return c.stateSyncManager.Init() -} - -// initCheckpointManager initializes checkpoint manager -// if bridge is not enabled, then a dummy checkpoint manager will be used -func (c *consensusRuntime) initCheckpointManager(logger hcf.Logger) error { - if c.IsBridgeEnabled() { - // enable checkpoint manager - txRelayer, err := txrelayer.NewTxRelayer( - txrelayer.WithIPAddress(c.config.PolyBFTConfig.Bridge.JSONRPCEndpoint), - txrelayer.WithWriter(logger.StandardWriter(&hcf.StandardLoggerOptions{}))) - if err != nil { - return err - } - - c.checkpointManager = newCheckpointManager( - wallet.NewEcdsaSigner(c.config.Key), - defaultCheckpointsOffset, - c.config.PolyBFTConfig.Bridge.CheckpointManagerAddr, - txRelayer, - c.config.blockchain, - c.config.polybftBackend, - logger.Named("checkpoint_manager"), - c.state) - } else { - c.checkpointManager = &dummyCheckpointManager{} - } - - c.eventProvider.Subscribe(c.checkpointManager) - - return nil -} - -// initStateSyncRelayer initializes state sync relayer -// if not enabled, then a dummy state sync relayer will be used -func (c *consensusRuntime) initStateSyncRelayer(logger hcf.Logger) error { - if c.config.consensusConfig.IsRelayer { - txRelayer, err := getStateSyncTxRelayer(c.config.consensusConfig.RPCEndpoint, logger) - if err != nil { - return err - } - - c.stateSyncRelayer = NewStateSyncRelayer( - txRelayer, - contracts.StateReceiverContract, - c.state.StateSyncStore, - c, - c.config.blockchain, - wallet.NewEcdsaSigner(c.config.Key), - nil, - logger.Named("state_sync_relayer")) - } else { - c.stateSyncRelayer = &dummyStateSyncRelayer{} - } - - c.eventProvider.Subscribe(c.stateSyncRelayer) - - return c.stateSyncRelayer.Init() -} - -// initStakeManager initializes stake manager -func (c *consensusRuntime) initStakeManager(logger hcf.Logger, dbTx *bolt.Tx) error { - rootRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(c.config.PolyBFTConfig.Bridge.JSONRPCEndpoint)) - if err != nil { - return err - } - - c.stakeManager, err = newStakeManager( - logger.Named("stake-manager"), - c.state, - rootRelayer, - wallet.NewEcdsaSigner(c.config.Key), - contracts.ValidatorSetContract, - c.config.PolyBFTConfig.Bridge.CustomSupernetManagerAddr, - c.config.blockchain, - c.config.polybftBackend, - int(c.config.PolyBFTConfig.MaxValidatorSetSize), - dbTx, - ) - - c.eventProvider.Subscribe(c.stakeManager) - - return err -} - -// getGuardedData returns last build block, proposer snapshot and current epochMetadata in a thread-safe manner. -func (c *consensusRuntime) getGuardedData() (guardedDataDTO, error) { - c.lock.RLock() - defer c.lock.RUnlock() - - lastBuiltBlock := c.lastBuiltBlock.Copy() - epoch := new(epochMetadata) - *epoch = *c.epoch // shallow copy, don't need to make validators copy because AccountSet is immutable - proposerSnapshot, ok := c.proposerCalculator.GetSnapshot() - - if !ok { - return guardedDataDTO{}, errors.New("cannot collect shared data, snapshot is empty") - } - - return guardedDataDTO{ - epoch: epoch, - lastBuiltBlock: lastBuiltBlock, - proposerSnapshot: proposerSnapshot, - }, nil -} - -func (c *consensusRuntime) IsBridgeEnabled() bool { - return c.config.PolyBFTConfig.IsBridgeEnabled() -} - -// OnBlockInserted is called whenever fsm or syncer inserts new block -func (c *consensusRuntime) OnBlockInserted(fullBlock *types.FullBlock) { - startTime := time.Now().UTC() - - c.lock.Lock() - defer c.lock.Unlock() - - if c.lastBuiltBlock != nil && c.lastBuiltBlock.Number >= fullBlock.Block.Number() { - c.logger.Debug("on block inserted already handled", - "current", c.lastBuiltBlock.Number, "block", fullBlock.Block.Number()) - - return - } - - if err := updateBlockMetrics(fullBlock.Block, c.lastBuiltBlock); err != nil { - c.logger.Error("failed to update block metrics", "error", err) - } - - // after the block has been written we reset the txpool so that the old transactions are removed - c.config.txPool.ResetWithHeaders(fullBlock.Block.Header) - - var ( - epoch = c.epoch - err error - // calculation of epoch and sprint end does not consider slashing currently - - isEndOfEpoch = c.isFixedSizeOfEpochMet(fullBlock.Block.Header.Number, epoch) - ) - - // begin DB transaction - dbTx, err := c.state.beginDBTransaction(true) - if err != nil { - c.logger.Error("failed to begin db transaction on block finalization", - "block", fullBlock.Block.Number(), "err", err) - - return - } - - defer dbTx.Rollback() //nolint:errcheck - - lastProcessedEventsBlock, err := c.state.getLastProcessedEventsBlock(dbTx) - if err != nil { - c.logger.Error("failed to get last processed events block on block finalization", - "block", fullBlock.Block.Number(), "err", err) - - return - } - - if err := c.eventProvider.GetEventsFromBlocks(lastProcessedEventsBlock, fullBlock, dbTx); err != nil { - c.logger.Error("failed to process events on block finalization", "block", fullBlock.Block.Number(), "err", err) - - return - } - - postBlock := &PostBlockRequest{ - FullBlock: fullBlock, - Epoch: epoch.Number, - IsEpochEndingBlock: isEndOfEpoch, - DBTx: dbTx, - } - - if err := c.stateSyncManager.PostBlock(postBlock); err != nil { - c.logger.Error("failed to post block state sync", "err", err) - - return - } - - // update proposer priorities - if err := c.proposerCalculator.PostBlock(postBlock); err != nil { - c.logger.Error("Could not update proposer calculator", "err", err) - - return - } - - // handle transfer events that happened in block - if err := c.stakeManager.PostBlock(postBlock); err != nil { - c.logger.Error("failed to post block in stake manager", "err", err) - - return - } - - // handle state sync relayer events that happened in block - if err := c.stateSyncRelayer.PostBlock(postBlock); err != nil { - c.logger.Error("post block callback failed in state sync relayer", "err", err) - } - - if isEndOfEpoch { - if epoch, err = c.restartEpoch(fullBlock.Block.Header, dbTx); err != nil { - c.logger.Error("failed to restart epoch after block inserted", "error", err) - - return - } - } - - if err := c.state.insertLastProcessedEventsBlock(fullBlock.Block.Number(), dbTx); err != nil { - c.logger.Error("failed to update the last processed events block in db", "error", err) - - return - } - - // commit DB transaction - if err := dbTx.Commit(); err != nil { - c.logger.Error("failed to commit transaction on PostBlock", - "block", fullBlock.Block.Number(), "error", err) - - return - } - - // finally update runtime state (lastBuiltBlock, epoch, proposerSnapshot) - c.epoch = epoch - c.lastBuiltBlock = fullBlock.Block.Header - - // we will do PostBlock on checkpoint manager at the end, because it only - // sends a checkpoint in a separate routine. It doesn't do any db operations - if err := c.checkpointManager.PostBlock(postBlock); err != nil { - c.logger.Error("failed to post block in checkpoint manager", "err", err) - } - - endTime := time.Now().UTC() - - c.logger.Debug("OnBlockInserted finished", "elapsedTime", endTime.Sub(startTime), - "epoch", epoch.Number, "block", fullBlock.Block.Number()) -} - -// FSM creates a new instance of fsm -func (c *consensusRuntime) FSM() error { - sharedData, err := c.getGuardedData() - if err != nil { - return fmt.Errorf("cannot create fsm: %w", err) - } - - parent, epoch, proposerSnapshot := sharedData.lastBuiltBlock, sharedData.epoch, sharedData.proposerSnapshot - - if !epoch.Validators.ContainsNodeID(c.config.Key.String()) { - return errNotAValidator - } - - blockBuilder, err := c.config.blockchain.NewBlockBuilder( - parent, - types.Address(c.config.Key.Address()), - c.config.txPool, - c.config.PolyBFTConfig.BlockTime.Duration, - c.logger, - ) - - if err != nil { - return fmt.Errorf("cannot create block builder for fsm: %w", err) - } - - pendingBlockNumber := parent.Number + 1 - // calculation of epoch and sprint end does not consider slashing currently - isEndOfSprint := c.isFixedSizeOfSprintMet(pendingBlockNumber, epoch) - isEndOfEpoch := c.isFixedSizeOfEpochMet(pendingBlockNumber, epoch) - - valSet := validator.NewValidatorSet(epoch.Validators, c.logger) - - exitRootHash, err := c.checkpointManager.BuildEventRoot(epoch.Number) - if err != nil { - return fmt.Errorf("could not build exit root hash for fsm: %w", err) - } - - ff := &fsm{ - config: c.config.PolyBFTConfig, - parent: parent, - backend: c.config.blockchain, - polybftBackend: c.config.polybftBackend, - exitEventRootHash: exitRootHash, - epochNumber: epoch.Number, - blockBuilder: blockBuilder, - validators: valSet, - isEndOfEpoch: isEndOfEpoch, - isEndOfSprint: isEndOfSprint, - proposerSnapshot: proposerSnapshot, - logger: c.logger.Named("fsm"), - } - - if isEndOfSprint { - commitment, err := c.stateSyncManager.Commitment(pendingBlockNumber) - if err != nil { - return err - } - - ff.proposerCommitmentToRegister = commitment - } - - if isEndOfEpoch { - ff.commitEpochInput, ff.distributeRewardsInput, err = c.calculateCommitEpochInput(parent, epoch) - if err != nil { - return fmt.Errorf("cannot calculate commit epoch info: %w", err) - } - - ff.newValidatorsDelta, err = c.stakeManager.UpdateValidatorSet(epoch.Number, epoch.Validators.Copy()) - if err != nil { - return fmt.Errorf("cannot update validator set on epoch ending: %w", err) - } - } - - c.logger.Info( - "[FSM built]", - "epoch", epoch.Number, - "endOfEpoch", isEndOfEpoch, - "endOfSprint", isEndOfSprint, - ) - - c.lock.Lock() - c.fsm = ff - c.lock.Unlock() - - return nil -} - -// restartEpoch resets the previously run epoch and moves to the next one -// returns *epochMetadata different from nil if the lastEpoch is not the current one and everything was successful -func (c *consensusRuntime) restartEpoch(header *types.Header, dbTx *bolt.Tx) (*epochMetadata, error) { - lastEpoch := c.epoch - - systemState, err := c.getSystemState(header) - if err != nil { - return nil, fmt.Errorf("get system state: %w", err) - } - - epochNumber, err := systemState.GetEpoch() - if err != nil { - return nil, fmt.Errorf("get epoch: %w", err) - } - - if lastEpoch != nil { - // Epoch might be already in memory, if its the same number do nothing -> just return provided last one - // Otherwise, reset the epoch metadata and restart the async services - if lastEpoch.Number == epochNumber { - return lastEpoch, nil - } - } - - validatorSet, err := c.config.polybftBackend.GetValidatorsWithTx(header.Number, nil, dbTx) - if err != nil { - return nil, fmt.Errorf("restart epoch - cannot get validators: %w", err) - } - - updateEpochMetrics(epochMetadata{ - Number: epochNumber, - Validators: validatorSet, - }) - - firstBlockInEpoch, err := c.getFirstBlockOfEpoch(epochNumber, header) - if err != nil { - return nil, err - } - - if err := c.state.EpochStore.cleanEpochsFromDB(dbTx); err != nil { - c.logger.Error("Could not clean previous epochs from db.", "error", err) - } - - if err := c.state.EpochStore.insertEpoch(epochNumber, dbTx); err != nil { - return nil, fmt.Errorf("an error occurred while inserting new epoch in db. Reason: %w", err) - } - - c.logger.Info( - "restartEpoch", - "block number", header.Number, - "epoch", epochNumber, - "validators", validatorSet.Len(), - "firstBlockInEpoch", firstBlockInEpoch, - ) - - reqObj := &PostEpochRequest{ - SystemState: systemState, - NewEpochID: epochNumber, - FirstBlockOfEpoch: firstBlockInEpoch, - ValidatorSet: validator.NewValidatorSet(validatorSet, c.logger), - DBTx: dbTx, - } - - if err := c.stateSyncManager.PostEpoch(reqObj); err != nil { - return nil, err - } - - return &epochMetadata{ - Number: epochNumber, - Validators: validatorSet, - FirstBlockInEpoch: firstBlockInEpoch, - }, nil -} - -// calculateCommitEpochInput calculates commit epoch input data for blocks starting from the last built block -// in the current epoch, and ending at the last block of previous epoch -func (c *consensusRuntime) calculateCommitEpochInput( - currentBlock *types.Header, - epoch *epochMetadata, -) (*contractsapi.CommitEpochValidatorSetFn, - *contractsapi.DistributeRewardForRewardPoolFn, error) { - uptimeCounter := map[types.Address]int64{} - blockHeader := currentBlock - epochID := epoch.Number - totalBlocks := int64(0) - - getSealersForBlock := func(blockExtra *Extra, validators validator.AccountSet) error { - signers, err := validators.GetFilteredValidators(blockExtra.Parent.Bitmap) - if err != nil { - return err - } - - totalBlocks++ - - for _, a := range signers.GetAddresses() { - uptimeCounter[a]++ - } - - return nil - } - - blockExtra, err := GetIbftExtra(currentBlock.ExtraData) - if err != nil { - return nil, nil, err - } - - // calculate uptime for current epoch - for blockHeader.Number > epoch.FirstBlockInEpoch { - if err := getSealersForBlock(blockExtra, epoch.Validators); err != nil { - return nil, nil, err - } - - blockHeader, blockExtra, err = getBlockData(blockHeader.Number-1, c.config.blockchain) - if err != nil { - return nil, nil, err - } - } - - // calculate uptime for blocks from previous epoch that were not processed in previous uptime - // since we can not calculate uptime for the last block in epoch (because of parent signatures) - if blockHeader.Number > commitEpochLookbackSize { - for i := 0; i < commitEpochLookbackSize; i++ { - validators, err := c.config.polybftBackend.GetValidators(blockHeader.Number-2, nil) - if err != nil { - return nil, nil, err - } - - if err := getSealersForBlock(blockExtra, validators); err != nil { - return nil, nil, err - } - - blockHeader, blockExtra, err = getBlockData(blockHeader.Number-1, c.config.blockchain) - if err != nil { - return nil, nil, err - } - } - } - - // include the data in the uptime counter in a deterministic way - addrSet := []types.Address{} - - for addr := range uptimeCounter { - addrSet = append(addrSet, addr) - } - - uptime := make([]*contractsapi.Uptime, len(addrSet)) - - sort.Slice(addrSet, func(i, j int) bool { - return bytes.Compare(addrSet[i][:], addrSet[j][:]) > 0 - }) - - for i, addr := range addrSet { - uptime[i] = &contractsapi.Uptime{ - Validator: addr, - SignedBlocks: new(big.Int).SetInt64(uptimeCounter[addr]), - } - } - - commitEpoch := &contractsapi.CommitEpochValidatorSetFn{ - ID: new(big.Int).SetUint64(epochID), - Epoch: &contractsapi.Epoch{ - StartBlock: new(big.Int).SetUint64(epoch.FirstBlockInEpoch), - EndBlock: new(big.Int).SetUint64(currentBlock.Number + 1), - EpochRoot: types.Hash{}, - }, - } - - distributeRewards := &contractsapi.DistributeRewardForRewardPoolFn{ - EpochID: new(big.Int).SetUint64(epochID), - Uptime: uptime, - } - - return commitEpoch, distributeRewards, nil -} - -// GenerateExitProof generates proof of exit and is a bridge endpoint store function -func (c *consensusRuntime) GenerateExitProof(exitID uint64) (types.Proof, error) { - return c.checkpointManager.GenerateExitProof(exitID) -} - -// GetStateSyncProof returns the proof for the state sync -func (c *consensusRuntime) GetStateSyncProof(stateSyncID uint64) (types.Proof, error) { - return c.stateSyncManager.GetStateSyncProof(stateSyncID) -} - -// setIsActiveValidator updates the activeValidatorFlag field -func (c *consensusRuntime) setIsActiveValidator(isActiveValidator bool) { - c.activeValidatorFlag.Store(isActiveValidator) -} - -// isActiveValidator indicates if node is in validator set or not -func (c *consensusRuntime) IsActiveValidator() bool { - return c.activeValidatorFlag.Load() -} - -// isFixedSizeOfEpochMet checks if epoch reached its end that was configured by its default size -// this is only true if no slashing occurred in the given epoch -func (c *consensusRuntime) isFixedSizeOfEpochMet(blockNumber uint64, epoch *epochMetadata) bool { - return epoch.FirstBlockInEpoch+c.config.PolyBFTConfig.EpochSize-1 == blockNumber -} - -// isFixedSizeOfSprintMet checks if an end of an sprint is reached with the current block -func (c *consensusRuntime) isFixedSizeOfSprintMet(blockNumber uint64, epoch *epochMetadata) bool { - return (blockNumber-epoch.FirstBlockInEpoch+1)%c.config.PolyBFTConfig.SprintSize == 0 -} - -// getSystemState builds SystemState instance for the most current block header -func (c *consensusRuntime) getSystemState(header *types.Header) (SystemState, error) { - provider, err := c.config.blockchain.GetStateProviderForBlock(header) - if err != nil { - return nil, err - } - - return c.config.blockchain.GetSystemState(provider), nil -} - -func (c *consensusRuntime) IsValidProposal(rawProposal []byte) bool { - if err := c.fsm.Validate(rawProposal); err != nil { - c.logger.Error("failed to validate proposal", "error", err) - - return false - } - - return true -} - -func (c *consensusRuntime) IsValidValidator(msg *proto.Message) bool { - c.lock.RLock() - defer c.lock.RUnlock() - - if c.fsm == nil { - c.logger.Warn("unable to validate IBFT message sender, because FSM is not initialized") - - return false - } - - if err := c.fsm.ValidateSender(msg); err != nil { - c.logger.Error("invalid IBFT message received", "error", err) - - return false - } - - return true -} - -func (c *consensusRuntime) IsProposer(id []byte, height, round uint64) bool { - c.lock.RLock() - defer c.lock.RUnlock() - - nextProposer, err := c.fsm.proposerSnapshot.CalcProposer(round, height) - if err != nil { - c.logger.Error("cannot calculate proposer", "error", err) - - return false - } - - c.logger.Info("Proposer calculated", "height", height, "round", round, "address", nextProposer) - - return bytes.Equal(id, nextProposer[:]) -} - -func (c *consensusRuntime) IsValidProposalHash(proposal *proto.Proposal, hash []byte) bool { - if len(proposal.RawProposal) == 0 { - c.logger.Error("proposal hash is not valid because proposal is empty") - - return false - } - - block := types.Block{} - if err := block.UnmarshalRLP(proposal.RawProposal); err != nil { - c.logger.Error("unable to unmarshal proposal", "error", err) - - return false - } - - extra, err := GetIbftExtra(block.Header.ExtraData) - if err != nil { - c.logger.Error("failed to retrieve extra", "block number", block.Number(), "error", err) - - return false - } - - proposalHash, err := extra.Checkpoint.Hash(c.config.blockchain.GetChainID(), block.Number(), block.Hash()) - if err != nil { - c.logger.Error("failed to calculate proposal hash", "block number", block.Number(), "error", err) - - return false - } - - return bytes.Equal(proposalHash.Bytes(), hash) -} - -func (c *consensusRuntime) IsValidCommittedSeal(proposalHash []byte, committedSeal *messages.CommittedSeal) bool { - err := c.fsm.ValidateCommit(committedSeal.Signer, committedSeal.Signature, proposalHash) - if err != nil { - c.logger.Info("Invalid committed seal", "error", err) - - return false - } - - return true -} - -func (c *consensusRuntime) BuildProposal(view *proto.View) []byte { - sharedData, err := c.getGuardedData() - if err != nil { - c.logger.Error("unable to build proposal", "error", err) - - return nil - } - - if sharedData.lastBuiltBlock.Number+1 != view.Height { - c.logger.Error("unable to build proposal, due to lack of parent block", - "parent height", sharedData.lastBuiltBlock.Number, "current height", view.Height) - - return nil - } - - proposal, err := c.fsm.BuildProposal(view.Round) - if err != nil { - c.logger.Error("unable to build proposal", "blockNumber", view, "error", err) - - return nil - } - - return proposal -} - -// InsertProposal inserts a proposal with the specified committed seals -func (c *consensusRuntime) InsertProposal(proposal *proto.Proposal, committedSeals []*messages.CommittedSeal) { - fsm := c.fsm - - fullBlock, err := fsm.Insert(proposal.RawProposal, committedSeals) - if err != nil { - c.logger.Error("cannot insert proposal", "error", err) - - return - } - - c.OnBlockInserted(fullBlock) -} - -// ID return ID (address actually) of the current node -func (c *consensusRuntime) ID() []byte { - return c.config.Key.Address().Bytes() -} - -// GetVotingPowers returns map of validators addresses and their voting powers for the specified height. -func (c *consensusRuntime) GetVotingPowers(height uint64) (map[string]*big.Int, error) { - c.lock.RLock() - defer c.lock.RUnlock() - - if c.fsm == nil { - return nil, errors.New("getting voting power failed - backend is not initialized") - } else if c.fsm.Height() != height { - return nil, fmt.Errorf("getting voting power failed - backend is not initialized for height %d, fsm height %d", - height, c.fsm.Height()) - } - - return c.fsm.validators.GetVotingPowers(), nil -} - -// BuildPrePrepareMessage builds a PREPREPARE message based on the passed in proposal -func (c *consensusRuntime) BuildPrePrepareMessage( - rawProposal []byte, - certificate *proto.RoundChangeCertificate, - view *proto.View, -) *proto.Message { - if len(rawProposal) == 0 { - c.logger.Error("can not build pre-prepare message, since proposal is empty") - - return nil - } - - block := types.Block{} - if err := block.UnmarshalRLP(rawProposal); err != nil { - c.logger.Error(fmt.Sprintf("cannot unmarshal RLP: %s", err)) - - return nil - } - - extra, err := GetIbftExtra(block.Header.ExtraData) - if err != nil { - c.logger.Error("failed to retrieve extra for block %d: %w", block.Number(), err) - - return nil - } - - proposalHash, err := extra.Checkpoint.Hash(c.config.blockchain.GetChainID(), block.Number(), block.Hash()) - if err != nil { - c.logger.Error("failed to calculate proposal hash", "block number", block.Number(), "error", err) - - return nil - } - - proposal := &proto.Proposal{ - RawProposal: rawProposal, - Round: view.Round, - } - - msg := proto.Message{ - View: view, - From: c.ID(), - Type: proto.MessageType_PREPREPARE, - Payload: &proto.Message_PreprepareData{ - PreprepareData: &proto.PrePrepareMessage{ - Proposal: proposal, - ProposalHash: proposalHash.Bytes(), - Certificate: certificate, - }, - }, - } - - message, err := c.config.Key.SignIBFTMessage(&msg) - if err != nil { - c.logger.Error("Cannot sign message", "error", err) - - return nil - } - - return message -} - -// BuildPrepareMessage builds a PREPARE message based on the passed in proposal -func (c *consensusRuntime) BuildPrepareMessage(proposalHash []byte, view *proto.View) *proto.Message { - msg := proto.Message{ - View: view, - From: c.ID(), - Type: proto.MessageType_PREPARE, - Payload: &proto.Message_PrepareData{ - PrepareData: &proto.PrepareMessage{ - ProposalHash: proposalHash, - }, - }, - } - - message, err := c.config.Key.SignIBFTMessage(&msg) - if err != nil { - c.logger.Error("Cannot sign message.", "error", err) - - return nil - } - - return message -} - -// BuildCommitMessage builds a COMMIT message based on the passed in proposal -func (c *consensusRuntime) BuildCommitMessage(proposalHash []byte, view *proto.View) *proto.Message { - committedSeal, err := c.config.Key.SignWithDomain(proposalHash, signer.DomainCheckpointManager) - if err != nil { - c.logger.Error("Cannot create committed seal message.", "error", err) - - return nil - } - - msg := proto.Message{ - View: view, - From: c.ID(), - Type: proto.MessageType_COMMIT, - Payload: &proto.Message_CommitData{ - CommitData: &proto.CommitMessage{ - ProposalHash: proposalHash, - CommittedSeal: committedSeal, - }, - }, - } - - message, err := c.config.Key.SignIBFTMessage(&msg) - if err != nil { - c.logger.Error("Cannot sign message", "Error", err) - - return nil - } - - return message -} - -// BuildRoundChangeMessage builds a ROUND_CHANGE message based on the passed in proposal -func (c *consensusRuntime) BuildRoundChangeMessage( - proposal *proto.Proposal, - certificate *proto.PreparedCertificate, - view *proto.View, -) *proto.Message { - msg := proto.Message{ - View: view, - From: c.ID(), - Type: proto.MessageType_ROUND_CHANGE, - Payload: &proto.Message_RoundChangeData{ - RoundChangeData: &proto.RoundChangeMessage{ - LastPreparedProposal: proposal, - LatestPreparedCertificate: certificate, - }}, - } - - signedMsg, err := c.config.Key.SignIBFTMessage(&msg) - if err != nil { - c.logger.Error("Cannot sign message", "Error", err) - - return nil - } - - return signedMsg -} - -// getFirstBlockOfEpoch returns the first block of epoch in which provided header resides -func (c *consensusRuntime) getFirstBlockOfEpoch(epochNumber uint64, latestHeader *types.Header) (uint64, error) { - if latestHeader.Number == 0 { - // if we are starting the chain, we know that the first block is block 1 - return 1, nil - } - - blockHeader := latestHeader - - blockExtra, err := GetIbftExtra(latestHeader.ExtraData) - if err != nil { - return 0, err - } - - if epochNumber != blockExtra.Checkpoint.EpochNumber { - // its a regular epoch ending. No out of sync happened - return latestHeader.Number + 1, nil - } - - // node was out of sync, so we need to figure out what was the first block of the given epoch - epoch := blockExtra.Checkpoint.EpochNumber - - var firstBlockInEpoch uint64 - - for blockExtra.Checkpoint.EpochNumber == epoch { - firstBlockInEpoch = blockHeader.Number - blockHeader, blockExtra, err = getBlockData(blockHeader.Number-1, c.config.blockchain) - - if err != nil { - return 0, err - } - } - - return firstBlockInEpoch, nil -} - -// getSealersForBlock checks who sealed a given block and updates the counter -func getSealersForBlock(sealersCounter map[types.Address]uint64, - blockExtra *Extra, validators validator.AccountSet) error { - signers, err := validators.GetFilteredValidators(blockExtra.Parent.Bitmap) - if err != nil { - return err - } - - for _, a := range signers.GetAddresses() { - sealersCounter[a]++ - } - - return nil -} diff --git a/consensus/polybft/consensus_runtime_test.go b/consensus/polybft/consensus_runtime_test.go deleted file mode 100644 index 35dcfae..0000000 --- a/consensus/polybft/consensus_runtime_test.go +++ /dev/null @@ -1,1093 +0,0 @@ -package polybft - -import ( - "fmt" - "math/big" - "math/rand" - "os" - "testing" - "time" - - "github.com/0xPolygon/go-ibft/messages/proto" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/consensus" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/types" -) - -func TestConsensusRuntime_isFixedSizeOfEpochMet_NotReachedEnd(t *testing.T) { - t.Parallel() - - // because of slashing, we can assume some epochs started at random numbers - var cases = []struct { - epochSize, firstBlockInEpoch, parentBlockNumber uint64 - }{ - {4, 1, 2}, - {5, 1, 3}, - {6, 0, 6}, - {7, 0, 4}, - {8, 0, 5}, - {9, 4, 9}, - {10, 7, 10}, - {10, 1, 1}, - } - - runtime := &consensusRuntime{ - config: &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{}, - }, - lastBuiltBlock: &types.Header{}, - epoch: &epochMetadata{}, - } - - for _, c := range cases { - runtime.config.PolyBFTConfig.EpochSize = c.epochSize - runtime.epoch.FirstBlockInEpoch = c.firstBlockInEpoch - assert.False( - t, - runtime.isFixedSizeOfEpochMet(c.parentBlockNumber+1, runtime.epoch), - fmt.Sprintf( - "Not expected end of epoch for epoch size=%v and parent block number=%v", - c.epochSize, - c.parentBlockNumber), - ) - } -} - -func TestConsensusRuntime_isFixedSizeOfEpochMet_ReachedEnd(t *testing.T) { - t.Parallel() - - // because of slashing, we can assume some epochs started at random numbers - var cases = []struct { - epochSize, firstBlockInEpoch, blockNumber uint64 - }{ - {4, 1, 4}, - {5, 1, 5}, - {6, 0, 5}, - {7, 0, 6}, - {8, 0, 7}, - {9, 4, 12}, - {10, 7, 16}, - {10, 1, 10}, - } - - runtime := &consensusRuntime{ - config: &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{}, - }, - epoch: &epochMetadata{}, - } - - for _, c := range cases { - runtime.config.PolyBFTConfig.EpochSize = c.epochSize - runtime.epoch.FirstBlockInEpoch = c.firstBlockInEpoch - assert.True( - t, - runtime.isFixedSizeOfEpochMet(c.blockNumber, runtime.epoch), - fmt.Sprintf( - "Not expected end of epoch for epoch size=%v and parent block number=%v", - c.epochSize, - c.blockNumber), - ) - } -} - -func TestConsensusRuntime_isFixedSizeOfSprintMet_NotReachedEnd(t *testing.T) { - t.Parallel() - - // because of slashing, we can assume some epochs started at random numbers - var cases = []struct { - sprintSize, firstBlockInEpoch, blockNumber uint64 - }{ - {4, 1, 2}, - {5, 1, 3}, - {6, 0, 6}, - {7, 0, 4}, - {8, 0, 5}, - {9, 4, 9}, - {10, 7, 10}, - {10, 1, 1}, - } - - runtime := &consensusRuntime{ - config: &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{}, - }, - epoch: &epochMetadata{}, - } - - for _, c := range cases { - runtime.config.PolyBFTConfig.SprintSize = c.sprintSize - runtime.epoch.FirstBlockInEpoch = c.firstBlockInEpoch - assert.False(t, - runtime.isFixedSizeOfSprintMet(c.blockNumber, runtime.epoch), - fmt.Sprintf( - "Not expected end of sprint for sprint size=%v and parent block number=%v", - c.sprintSize, - c.blockNumber), - ) - } -} - -func TestConsensusRuntime_isFixedSizeOfSprintMet_ReachedEnd(t *testing.T) { - t.Parallel() - - // because of slashing, we can assume some epochs started at random numbers - var cases = []struct { - sprintSize, firstBlockInEpoch, blockNumber uint64 - }{ - {4, 1, 4}, - {5, 1, 5}, - {6, 0, 5}, - {7, 0, 6}, - {8, 0, 7}, - {9, 4, 12}, - {10, 7, 16}, - {10, 1, 10}, - {5, 1, 10}, - {3, 3, 5}, - } - - runtime := &consensusRuntime{ - config: &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{}, - }, - epoch: &epochMetadata{}, - } - - for _, c := range cases { - runtime.config.PolyBFTConfig.SprintSize = c.sprintSize - runtime.epoch.FirstBlockInEpoch = c.firstBlockInEpoch - assert.True(t, - runtime.isFixedSizeOfSprintMet(c.blockNumber, runtime.epoch), - fmt.Sprintf( - "Not expected end of sprint for sprint size=%v and parent block number=%v", - c.sprintSize, - c.blockNumber), - ) - } -} - -func TestConsensusRuntime_OnBlockInserted_EndOfEpoch(t *testing.T) { - t.Parallel() - - const ( - epochSize = uint64(10) - validatorsCount = 7 - ) - - currentEpochNumber := getEpochNumber(t, epochSize, epochSize) - validatorSet := validator.NewTestValidators(t, validatorsCount).GetPublicIdentities() - header, headerMap := createTestBlocks(t, epochSize, epochSize, validatorSet) - builtBlock := consensus.BuildBlock(consensus.BuildBlockParams{ - Header: header, - }) - - newEpochNumber := currentEpochNumber + 1 - systemStateMock := new(systemStateMock) - systemStateMock.On("GetEpoch").Return(newEpochNumber).Once() - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetStateProviderForBlock", mock.Anything).Return(new(stateProviderMock)).Once() - blockchainMock.On("GetSystemState", mock.Anything, mock.Anything).Return(systemStateMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headerMap.getHeader) - - polybftBackendMock := new(polybftBackendMock) - polybftBackendMock.On("GetValidatorsWithTx", mock.Anything, mock.Anything, mock.Anything).Return(validatorSet).Times(3) - - txPool := new(txPoolMock) - txPool.On("ResetWithHeaders", mock.Anything).Once() - - snapshot := NewProposerSnapshot(epochSize-1, validatorSet) - config := &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{ - EpochSize: epochSize, - }, - blockchain: blockchainMock, - polybftBackend: polybftBackendMock, - txPool: txPool, - State: newTestState(t), - } - require.NoError(t, config.State.insertLastProcessedEventsBlock(builtBlock.Number()-1, nil)) - - runtime := &consensusRuntime{ - proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), - logger: hclog.NewNullLogger(), - state: config.State, - config: config, - epoch: &epochMetadata{ - Number: currentEpochNumber, - FirstBlockInEpoch: header.Number - epochSize + 1, - }, - lastBuiltBlock: &types.Header{Number: header.Number - 1}, - stateSyncManager: &dummyStateSyncManager{}, - checkpointManager: &dummyCheckpointManager{}, - stakeManager: &dummyStakeManager{}, - eventProvider: NewEventProvider(blockchainMock), - stateSyncRelayer: &dummyStateSyncRelayer{}, - } - runtime.OnBlockInserted(&types.FullBlock{Block: builtBlock}) - - require.True(t, runtime.state.EpochStore.isEpochInserted(currentEpochNumber+1)) - require.Equal(t, newEpochNumber, runtime.epoch.Number) - - blockchainMock.AssertExpectations(t) - systemStateMock.AssertExpectations(t) -} - -func TestConsensusRuntime_OnBlockInserted_MiddleOfEpoch(t *testing.T) { - t.Parallel() - - const ( - epoch = 2 - epochSize = uint64(10) - firstBlockInEpoch = epochSize + 1 - blockNumber = epochSize + 2 - ) - - header := &types.Header{Number: blockNumber} - builtBlock := consensus.BuildBlock(consensus.BuildBlockParams{ - Header: header, - }) - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(builtBlock.Header, true).Once() - - polybftBackendMock := new(polybftBackendMock) - polybftBackendMock.On("GetValidatorsWithTx", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - - txPool := new(txPoolMock) - txPool.On("ResetWithHeaders", mock.Anything).Once() - - snapshot := NewProposerSnapshot(blockNumber, []*validator.ValidatorMetadata{}) - config := &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{EpochSize: epochSize}, - blockchain: blockchainMock, - txPool: txPool, - } - - runtime := &consensusRuntime{ - lastBuiltBlock: header, - config: &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{EpochSize: epochSize}, - blockchain: blockchainMock, - txPool: txPool, - }, - epoch: &epochMetadata{ - Number: epoch, - FirstBlockInEpoch: firstBlockInEpoch, - }, - logger: hclog.NewNullLogger(), - proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), - } - runtime.OnBlockInserted(&types.FullBlock{Block: builtBlock}) - - require.Equal(t, header.Number, runtime.lastBuiltBlock.Number) -} - -func TestConsensusRuntime_FSM_NotInValidatorSet(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D"}) - - snapshot := NewProposerSnapshot(1, nil) - config := &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{ - EpochSize: 1, - }, - Key: createTestKey(t), - } - runtime := &consensusRuntime{ - proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), - config: config, - epoch: &epochMetadata{ - Number: 1, - Validators: validators.GetPublicIdentities(), - }, - lastBuiltBlock: &types.Header{}, - } - runtime.setIsActiveValidator(true) - - err := runtime.FSM() - assert.ErrorIs(t, err, errNotAValidator) -} - -func TestConsensusRuntime_FSM_NotEndOfEpoch_NotEndOfSprint(t *testing.T) { - t.Parallel() - - extra := &Extra{ - Checkpoint: &CheckpointData{}, - } - lastBlock := &types.Header{ - Number: 1, - ExtraData: extra.MarshalRLPTo(nil), - } - - validators := validator.NewTestValidators(t, 3) - blockchainMock := new(blockchainMock) - blockchainMock.On("NewBlockBuilder", mock.Anything).Return(&BlockBuilder{}, nil).Once() - - snapshot := NewProposerSnapshot(1, nil) - config := &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{ - EpochSize: 10, - SprintSize: 5, - }, - Key: wallet.NewKey(validators.GetPrivateIdentities()[0]), - blockchain: blockchainMock, - } - runtime := &consensusRuntime{ - proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), - logger: hclog.NewNullLogger(), - config: config, - epoch: &epochMetadata{ - Number: 1, - Validators: validators.GetPublicIdentities(), - FirstBlockInEpoch: 1, - }, - lastBuiltBlock: lastBlock, - state: newTestState(t), - stateSyncManager: &dummyStateSyncManager{}, - checkpointManager: &dummyCheckpointManager{}, - } - runtime.setIsActiveValidator(true) - - err := runtime.FSM() - require.NoError(t, err) - - assert.True(t, runtime.IsActiveValidator()) - assert.False(t, runtime.fsm.isEndOfEpoch) - assert.False(t, runtime.fsm.isEndOfSprint) - assert.Equal(t, lastBlock.Number, runtime.fsm.parent.Number) - - address := types.Address(runtime.config.Key.Address()) - assert.True(t, runtime.fsm.ValidatorSet().Includes(address)) - - assert.NotNil(t, runtime.fsm.blockBuilder) - assert.NotNil(t, runtime.fsm.backend) - - blockchainMock.AssertExpectations(t) -} - -func TestConsensusRuntime_FSM_EndOfEpoch_BuildCommitEpoch(t *testing.T) { - t.Parallel() - - const ( - epoch = 0 - epochSize = uint64(10) - sprintSize = uint64(3) - firstBlockInEpoch = uint64(1) - fromIndex = uint64(0) - toIndex = uint64(9) - ) - - validatorAccounts := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - validators := validatorAccounts.GetPublicIdentities() - - lastBuiltBlock, headerMap := createTestBlocks(t, 9, epochSize, validators) - - blockchainMock := new(blockchainMock) - blockchainMock.On("NewBlockBuilder", mock.Anything).Return(&BlockBuilder{}, nil).Once() - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headerMap.getHeader) - - state := newTestState(t) - require.NoError(t, state.EpochStore.insertEpoch(epoch, nil)) - - metadata := &epochMetadata{ - Validators: validators, - Number: epoch, - FirstBlockInEpoch: firstBlockInEpoch, - } - - config := &runtimeConfig{ - PolyBFTConfig: &PolyBFTConfig{ - EpochSize: epochSize, - SprintSize: sprintSize, - }, - Key: validatorAccounts.GetValidator("A").Key(), - blockchain: blockchainMock, - } - - snapshot := NewProposerSnapshot(1, nil) - runtime := &consensusRuntime{ - proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), - logger: hclog.NewNullLogger(), - state: state, - epoch: metadata, - config: config, - lastBuiltBlock: lastBuiltBlock, - stateSyncManager: &dummyStateSyncManager{}, - checkpointManager: &dummyCheckpointManager{}, - stakeManager: &dummyStakeManager{}, - } - - err := runtime.FSM() - fsm := runtime.fsm - - assert.NoError(t, err) - assert.True(t, fsm.isEndOfEpoch) - assert.NotNil(t, fsm.commitEpochInput) - assert.NotEmpty(t, fsm.commitEpochInput) - - blockchainMock.AssertExpectations(t) -} - -func Test_NewConsensusRuntime(t *testing.T) { - t.Parallel() - - _, err := os.Create("/tmp/consensusState.db") - require.NoError(t, err) - - polyBftConfig := &PolyBFTConfig{ - Bridge: &BridgeConfig{ - StateSenderAddr: types.Address{0x13}, - CheckpointManagerAddr: types.Address{0x10}, - JSONRPCEndpoint: "testEndpoint", - }, - EpochSize: 10, - SprintSize: 10, - BlockTime: common.Duration{Duration: 2 * time.Second}, - } - - validators := validator.NewTestValidators(t, 3).GetPublicIdentities() - - systemStateMock := new(systemStateMock) - systemStateMock.On("GetEpoch").Return(uint64(1)).Once() - systemStateMock.On("GetNextCommittedIndex").Return(uint64(1)).Once() - - blockchainMock := &blockchainMock{} - blockchainMock.On("CurrentHeader").Return(&types.Header{Number: 1, ExtraData: createTestExtraForAccounts(t, 1, validators, nil)}) - blockchainMock.On("GetStateProviderForBlock", mock.Anything).Return(new(stateProviderMock)).Once() - blockchainMock.On("GetSystemState", mock.Anything, mock.Anything).Return(systemStateMock).Once() - blockchainMock.On("GetHeaderByNumber", uint64(0)).Return(&types.Header{Number: 0, ExtraData: createTestExtraForAccounts(t, 0, validators, nil)}) - blockchainMock.On("GetHeaderByNumber", uint64(1)).Return(&types.Header{Number: 1, ExtraData: createTestExtraForAccounts(t, 1, validators, nil)}) - - polybftBackendMock := new(polybftBackendMock) - polybftBackendMock.On("GetValidatorsWithTx", mock.Anything, mock.Anything, mock.Anything).Return(validators).Times(3) - - tmpDir := t.TempDir() - config := &runtimeConfig{ - polybftBackend: polybftBackendMock, - State: newTestState(t), - PolyBFTConfig: polyBftConfig, - DataDir: tmpDir, - Key: createTestKey(t), - blockchain: blockchainMock, - bridgeTopic: &mockTopic{}, - consensusConfig: &consensus.Config{}, - } - - require.NoError(t, config.State.StakeStore.insertFullValidatorSet(validatorSetState{ - BlockNumber: 1, - }, nil)) - - runtime, err := newConsensusRuntime(hclog.NewNullLogger(), config) - require.NoError(t, err) - - assert.False(t, runtime.IsActiveValidator()) - assert.Equal(t, runtime.config.DataDir, tmpDir) - assert.Equal(t, uint64(10), runtime.config.PolyBFTConfig.SprintSize) - assert.Equal(t, uint64(10), runtime.config.PolyBFTConfig.EpochSize) - assert.Equal(t, "0x0000000000000000000000000000000000000101", contracts.ValidatorSetContract.String()) - assert.Equal(t, "0x1300000000000000000000000000000000000000", runtime.config.PolyBFTConfig.Bridge.StateSenderAddr.String()) - assert.Equal(t, "0x1000000000000000000000000000000000000000", runtime.config.PolyBFTConfig.Bridge.CheckpointManagerAddr.String()) - assert.True(t, runtime.IsBridgeEnabled()) - systemStateMock.AssertExpectations(t) - blockchainMock.AssertExpectations(t) - polybftBackendMock.AssertExpectations(t) -} - -func TestConsensusRuntime_restartEpoch_SameEpochNumberAsTheLastOne(t *testing.T) { - t.Parallel() - - const originalBlockNumber = uint64(5) - - newCurrentHeader := &types.Header{Number: originalBlockNumber + 1} - validatorSet := validator.NewTestValidators(t, 3).GetPublicIdentities() - - systemStateMock := new(systemStateMock) - systemStateMock.On("GetEpoch").Return(uint64(1), nil).Once() - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetStateProviderForBlock", mock.Anything).Return(new(stateProviderMock)).Once() - blockchainMock.On("GetSystemState", mock.Anything, mock.Anything).Return(systemStateMock).Once() - - snapshot := NewProposerSnapshot(1, nil) - config := &runtimeConfig{ - blockchain: blockchainMock, - } - runtime := &consensusRuntime{ - proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), - config: config, - epoch: &epochMetadata{ - Number: 1, - Validators: validatorSet, - FirstBlockInEpoch: 1, - }, - lastBuiltBlock: &types.Header{ - Number: originalBlockNumber, - }, - } - runtime.setIsActiveValidator(true) - - epoch, err := runtime.restartEpoch(newCurrentHeader, nil) - - require.NoError(t, err) - - for _, a := range validatorSet.GetAddresses() { - assert.True(t, epoch.Validators.ContainsAddress(a)) - } - - systemStateMock.AssertExpectations(t) - blockchainMock.AssertExpectations(t) -} - -func TestConsensusRuntime_calculateCommitEpochInput_SecondEpoch(t *testing.T) { - t.Parallel() - - const ( - epoch = 2 - epochSize = 10 - epochStartBlock = 11 - epochEndBlock = 20 - sprintSize = 5 - ) - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E"}) - polybftConfig := &PolyBFTConfig{ - EpochSize: epochSize, - SprintSize: sprintSize, - } - - lastBuiltBlock, headerMap := createTestBlocks(t, 19, epochSize, validators.GetPublicIdentities()) - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headerMap.getHeader) - - polybftBackendMock := new(polybftBackendMock) - polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validators.GetPublicIdentities()).Twice() - - config := &runtimeConfig{ - PolyBFTConfig: polybftConfig, - blockchain: blockchainMock, - polybftBackend: polybftBackendMock, - Key: validators.GetValidator("A").Key(), - } - - consensusRuntime := &consensusRuntime{ - config: config, - epoch: &epochMetadata{ - Number: epoch, - Validators: validators.GetPublicIdentities(), - FirstBlockInEpoch: epochStartBlock, - }, - lastBuiltBlock: lastBuiltBlock, - } - - commitEpochInput, distributeRewardsInput, err := consensusRuntime.calculateCommitEpochInput(lastBuiltBlock, - consensusRuntime.epoch) - assert.NoError(t, err) - assert.NotEmpty(t, commitEpochInput) - assert.Equal(t, uint64(epoch), commitEpochInput.ID.Uint64()) - assert.Equal(t, uint64(epochStartBlock), commitEpochInput.Epoch.StartBlock.Uint64()) - assert.Equal(t, uint64(epochEndBlock), commitEpochInput.Epoch.EndBlock.Uint64()) - assert.Equal(t, uint64(epoch), distributeRewardsInput.EpochID.Uint64()) - - blockchainMock.AssertExpectations(t) - polybftBackendMock.AssertExpectations(t) -} - -func TestConsensusRuntime_IsValidValidator_BasicCases(t *testing.T) { - t.Parallel() - - setupFn := func(t *testing.T) (*consensusRuntime, *validator.TestValidators) { - t.Helper() - - validatorAccounts := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - epoch := &epochMetadata{ - Validators: validatorAccounts.GetPublicIdentities("A", "B", "C", "D"), - } - runtime := &consensusRuntime{ - epoch: epoch, - logger: hclog.NewNullLogger(), - fsm: &fsm{validators: validator.NewValidatorSet(epoch.Validators, hclog.NewNullLogger())}, - } - - return runtime, validatorAccounts - } - - cases := []struct { - name string - signerAlias string - senderAlias string - isValidSender bool - }{ - { - name: "Valid sender", - signerAlias: "A", - senderAlias: "A", - isValidSender: true, - }, - { - name: "Sender not amongst current validators", - signerAlias: "F", - senderAlias: "F", - isValidSender: false, - }, - { - name: "Sender and signer accounts mismatch", - signerAlias: "A", - senderAlias: "B", - isValidSender: false, - }, - } - - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - - runtime, validatorAccounts := setupFn(t) - signer := validatorAccounts.GetValidator(c.signerAlias) - sender := validatorAccounts.GetValidator(c.senderAlias) - msg, err := signer.Key().SignIBFTMessage(&proto.Message{From: sender.Address().Bytes()}) - - require.NoError(t, err) - require.Equal(t, c.isValidSender, runtime.IsValidValidator(msg)) - }) - } -} - -func TestConsensusRuntime_IsValidValidator_TamperSignature(t *testing.T) { - t.Parallel() - - validatorAccounts := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - epoch := &epochMetadata{ - Validators: validatorAccounts.GetPublicIdentities("A", "B", "C", "D"), - } - runtime := &consensusRuntime{ - epoch: epoch, - logger: hclog.NewNullLogger(), - fsm: &fsm{validators: validator.NewValidatorSet(epoch.Validators, hclog.NewNullLogger())}, - } - - // provide invalid signature - sender := validatorAccounts.GetValidator("A") - msg := &proto.Message{ - From: sender.Address().Bytes(), - Signature: []byte{1, 2, 3, 4, 5}, - } - require.False(t, runtime.IsValidValidator(msg)) -} - -func TestConsensusRuntime_TamperMessageContent(t *testing.T) { - t.Parallel() - - validatorAccounts := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - epoch := &epochMetadata{ - Validators: validatorAccounts.GetPublicIdentities("A", "B", "C", "D"), - } - runtime := &consensusRuntime{ - epoch: epoch, - logger: hclog.NewNullLogger(), - fsm: &fsm{validators: validator.NewValidatorSet(epoch.Validators, hclog.NewNullLogger())}, - } - sender := validatorAccounts.GetValidator("A") - proposalHash := []byte{2, 4, 6, 8, 10} - proposalSignature, err := sender.Key().SignWithDomain(proposalHash, signer.DomainCheckpointManager) - require.NoError(t, err) - - msg := &proto.Message{ - View: &proto.View{}, - From: sender.Address().Bytes(), - Type: proto.MessageType_COMMIT, - Payload: &proto.Message_CommitData{ - CommitData: &proto.CommitMessage{ - ProposalHash: proposalHash, - CommittedSeal: proposalSignature, - }, - }, - } - // sign the message itself - msg, err = sender.Key().SignIBFTMessage(msg) - assert.NoError(t, err) - // signature verification works - assert.True(t, runtime.IsValidValidator(msg)) - - // modify message without signing it again - msg.Payload = &proto.Message_CommitData{ - CommitData: &proto.CommitMessage{ - ProposalHash: []byte{1, 3, 5, 7, 9}, // modification - CommittedSeal: proposalSignature, - }, - } - // signature isn't valid, because message was tampered - assert.False(t, runtime.IsValidValidator(msg)) -} - -func TestConsensusRuntime_IsValidProposalHash(t *testing.T) { - t.Parallel() - - extra := &Extra{ - Checkpoint: &CheckpointData{ - EpochNumber: 1, - BlockRound: 1, - }, - } - block := &types.Block{ - Header: &types.Header{ - Number: 10, - ExtraData: extra.MarshalRLPTo(nil), - }, - } - block.Header.ComputeHash() - - proposalHash, err := extra.Checkpoint.Hash(0, block.Number(), block.Hash()) - require.NoError(t, err) - - runtime := &consensusRuntime{ - logger: hclog.NewNullLogger(), - config: &runtimeConfig{blockchain: new(blockchainMock)}, - } - - require.True(t, runtime.IsValidProposalHash(&proto.Proposal{RawProposal: block.MarshalRLP()}, proposalHash.Bytes())) -} - -func TestConsensusRuntime_IsValidProposalHash_InvalidProposalHash(t *testing.T) { - t.Parallel() - - extra := &Extra{ - Checkpoint: &CheckpointData{ - EpochNumber: 1, - BlockRound: 1, - }, - } - - block := &types.Block{ - Header: &types.Header{ - Number: 10, - ExtraData: extra.MarshalRLPTo(nil), - }, - } - - proposalHash, err := extra.Checkpoint.Hash(0, block.Number(), block.Hash()) - require.NoError(t, err) - - extra.Checkpoint.BlockRound = 2 // change it so it is not the same as in proposal hash - block.Header.ExtraData = extra.MarshalRLPTo(nil) - block.Header.ComputeHash() - - runtime := &consensusRuntime{ - logger: hclog.NewNullLogger(), - config: &runtimeConfig{blockchain: new(blockchainMock)}, - } - - require.False(t, runtime.IsValidProposalHash(&proto.Proposal{RawProposal: block.MarshalRLP()}, proposalHash.Bytes())) -} - -func TestConsensusRuntime_IsValidProposalHash_InvalidExtra(t *testing.T) { - t.Parallel() - - extra := &Extra{ - Checkpoint: &CheckpointData{ - EpochNumber: 1, - BlockRound: 1, - }, - } - - block := &types.Block{ - Header: &types.Header{ - Number: 10, - ExtraData: []byte{1, 2, 3}, // invalid extra in block - }, - } - block.Header.ComputeHash() - - proposalHash, err := extra.Checkpoint.Hash(0, block.Number(), block.Hash()) - require.NoError(t, err) - - runtime := &consensusRuntime{ - logger: hclog.NewNullLogger(), - config: &runtimeConfig{blockchain: new(blockchainMock)}, - } - - require.False(t, runtime.IsValidProposalHash(&proto.Proposal{RawProposal: block.MarshalRLP()}, proposalHash.Bytes())) -} - -func TestConsensusRuntime_BuildProposal_InvalidParent(t *testing.T) { - config := &runtimeConfig{} - snapshot := NewProposerSnapshot(1, nil) - runtime := &consensusRuntime{ - logger: hclog.NewNullLogger(), - lastBuiltBlock: &types.Header{Number: 2}, - epoch: &epochMetadata{Number: 1}, - config: config, - proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), - } - - require.Nil(t, runtime.BuildProposal(&proto.View{Round: 5})) -} - -func TestConsensusRuntime_ID(t *testing.T) { - t.Parallel() - - key1, key2 := createTestKey(t), createTestKey(t) - runtime := &consensusRuntime{ - config: &runtimeConfig{Key: key1}, - } - - require.Equal(t, runtime.ID(), key1.Address().Bytes()) - require.NotEqual(t, runtime.ID(), key2.Address().Bytes()) -} - -func TestConsensusRuntime_GetVotingPowers(t *testing.T) { - t.Parallel() - - const height = 100 - - validatorAccounts := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C"}, []uint64{1, 3, 2}) - runtime := &consensusRuntime{} - - _, err := runtime.GetVotingPowers(height) - require.Error(t, err) - - runtime.fsm = &fsm{ - validators: validatorAccounts.ToValidatorSet(), - parent: &types.Header{Number: height}, - } - - _, err = runtime.GetVotingPowers(height) - require.Error(t, err) - - runtime.fsm.parent.Number = height - 1 - - val, err := runtime.GetVotingPowers(height) - require.NoError(t, err) - - addresses := validatorAccounts.GetPublicIdentities([]string{"A", "B", "C"}...).GetAddresses() - - assert.Equal(t, uint64(1), val[types.AddressToString(addresses[0])].Uint64()) - assert.Equal(t, uint64(3), val[types.AddressToString(addresses[1])].Uint64()) - assert.Equal(t, uint64(2), val[types.AddressToString(addresses[2])].Uint64()) -} - -func TestConsensusRuntime_BuildRoundChangeMessage(t *testing.T) { - t.Parallel() - - key := createTestKey(t) - view, rawProposal, certificate := &proto.View{}, []byte{1}, &proto.PreparedCertificate{} - - runtime := &consensusRuntime{ - config: &runtimeConfig{ - Key: key, - }, - } - - proposal := &proto.Proposal{ - RawProposal: rawProposal, - Round: view.Round, - } - - expected := proto.Message{ - View: view, - From: key.Address().Bytes(), - Type: proto.MessageType_ROUND_CHANGE, - Payload: &proto.Message_RoundChangeData{RoundChangeData: &proto.RoundChangeMessage{ - LatestPreparedCertificate: certificate, - LastPreparedProposal: proposal, - }}, - } - - signedMsg, err := key.SignIBFTMessage(&expected) - require.NoError(t, err) - - assert.Equal(t, signedMsg, runtime.BuildRoundChangeMessage(proposal, certificate, view)) -} - -func TestConsensusRuntime_BuildCommitMessage(t *testing.T) { - t.Parallel() - - key := createTestKey(t) - view, proposalHash := &proto.View{}, []byte{1, 2, 4} - - runtime := &consensusRuntime{ - config: &runtimeConfig{ - Key: key, - }, - } - - committedSeal, err := key.SignWithDomain(proposalHash, signer.DomainCheckpointManager) - require.NoError(t, err) - - expected := proto.Message{ - View: view, - From: key.Address().Bytes(), - Type: proto.MessageType_COMMIT, - Payload: &proto.Message_CommitData{ - CommitData: &proto.CommitMessage{ - ProposalHash: proposalHash, - CommittedSeal: committedSeal, - }, - }, - } - - signedMsg, err := key.SignIBFTMessage(&expected) - require.NoError(t, err) - - assert.Equal(t, signedMsg, runtime.BuildCommitMessage(proposalHash, view)) -} - -func TestConsensusRuntime_BuildPrePrepareMessage_EmptyProposal(t *testing.T) { - t.Parallel() - - runtime := &consensusRuntime{logger: hclog.NewNullLogger()} - - assert.Nil(t, runtime.BuildPrePrepareMessage(nil, &proto.RoundChangeCertificate{}, &proto.View{Height: 1, Round: 0})) -} - -func TestConsensusRuntime_IsValidProposalHash_EmptyProposal(t *testing.T) { - t.Parallel() - - runtime := &consensusRuntime{logger: hclog.NewNullLogger()} - - assert.False(t, runtime.IsValidProposalHash(&proto.Proposal{}, []byte("hash"))) -} - -func TestConsensusRuntime_BuildPrepareMessage(t *testing.T) { - t.Parallel() - - key := createTestKey(t) - view, proposalHash := &proto.View{}, []byte{1, 2, 4} - - runtime := &consensusRuntime{ - config: &runtimeConfig{ - Key: key, - }, - } - - expected := proto.Message{ - View: view, - From: key.Address().Bytes(), - Type: proto.MessageType_PREPARE, - Payload: &proto.Message_PrepareData{ - PrepareData: &proto.PrepareMessage{ - ProposalHash: proposalHash, - }, - }, - } - - signedMsg, err := key.SignIBFTMessage(&expected) - require.NoError(t, err) - - assert.Equal(t, signedMsg, runtime.BuildPrepareMessage(proposalHash, view)) -} - -func createTestBlocks(t *testing.T, numberOfBlocks, defaultEpochSize uint64, - validatorSet validator.AccountSet) (*types.Header, *testHeadersMap) { - t.Helper() - - headerMap := &testHeadersMap{} - bitmaps := createTestBitmaps(t, validatorSet, numberOfBlocks) - - extra := &Extra{ - Checkpoint: &CheckpointData{EpochNumber: 0}, - } - - genesisBlock := &types.Header{ - Number: 0, - ExtraData: extra.MarshalRLPTo(nil), - } - parentHash := types.BytesToHash(big.NewInt(0).Bytes()) - - headerMap.addHeader(genesisBlock) - - var hash types.Hash - - var blockHeader *types.Header - - for i := uint64(1); i <= numberOfBlocks; i++ { - big := big.NewInt(int64(i)) - hash = types.BytesToHash(big.Bytes()) - - header := &types.Header{ - Number: i, - ParentHash: parentHash, - ExtraData: createTestExtraForAccounts(t, getEpochNumber(t, i, defaultEpochSize), validatorSet, bitmaps[i]), - GasLimit: types.StateTransactionGasLimit, - } - - headerMap.addHeader(header) - - parentHash = hash - blockHeader = header - } - - return blockHeader, headerMap -} - -func createTestBitmaps(t *testing.T, validators validator.AccountSet, numberOfBlocks uint64) map[uint64]bitmap.Bitmap { - t.Helper() - - bitmaps := make(map[uint64]bitmap.Bitmap, numberOfBlocks) - - rand.Seed(time.Now().UTC().Unix()) - - for i := numberOfBlocks; i > 1; i-- { - bitmap := bitmap.Bitmap{} - j := 0 - - for j != 3 { - validator := validators[rand.Intn(validators.Len())] - index := uint64(validators.Index(validator.Address)) - - if !bitmap.IsSet(index) { - bitmap.Set(index) - j++ - } - } - - bitmaps[i] = bitmap - } - - return bitmaps -} - -func createTestExtraForAccounts(t *testing.T, epoch uint64, validators validator.AccountSet, b bitmap.Bitmap) []byte { - t.Helper() - - dummySignature := [64]byte{} - extraData := Extra{ - Validators: &validator.ValidatorSetDelta{ - Added: validators, - Removed: bitmap.Bitmap{}, - }, - Parent: &Signature{Bitmap: b, AggregatedSignature: dummySignature[:]}, - Committed: &Signature{Bitmap: b, AggregatedSignature: dummySignature[:]}, - Checkpoint: &CheckpointData{EpochNumber: epoch}, - } - - return extraData.MarshalRLPTo(nil) -} - -func encodeExitEvents(t *testing.T, exitEvents []*ExitEvent) [][]byte { - t.Helper() - - encodedEvents := make([][]byte, len(exitEvents)) - - for i, e := range exitEvents { - encodedEvent, err := e.L2StateSyncedEvent.Encode() - require.NoError(t, err) - - encodedEvents[i] = encodedEvent - } - - return encodedEvents -} diff --git a/consensus/polybft/contracts_initializer.go b/consensus/polybft/contracts_initializer.go deleted file mode 100644 index 4513103..0000000 --- a/consensus/polybft/contracts_initializer.go +++ /dev/null @@ -1,278 +0,0 @@ -package polybft - -import ( - "fmt" - "math/big" - - "github.com/umbracle/ethgo/abi" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/state" - "github.com/xgr-network/xgr-node/types" -) - -const ( - contractCallGasLimit = 100_000_000 -) - -// initValidatorSet initializes ValidatorSet SC -func initValidatorSet(polyBFTConfig PolyBFTConfig, transition *state.Transition) error { - initialValidators := make([]*contractsapi.ValidatorInit, len(polyBFTConfig.InitialValidatorSet)) - for i, validator := range polyBFTConfig.InitialValidatorSet { - initialValidators[i] = &contractsapi.ValidatorInit{ - Addr: validator.Address, - Stake: validator.Stake, - } - } - - initFn := &contractsapi.InitializeValidatorSetFn{ - NewStateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewRootChainManager: polyBFTConfig.Bridge.CustomSupernetManagerAddr, - NewEpochSize: new(big.Int).SetUint64(polyBFTConfig.EpochSize), - InitialValidators: initialValidators, - } - - input, err := initFn.EncodeAbi() - if err != nil { - return fmt.Errorf("ValidatorSet.initialize params encoding failed: %w", err) - } - - return callContract(contracts.SystemCaller, - contracts.ValidatorSetContract, input, "ValidatorSet.initialize", transition) -} - -// initRewardPool initializes RewardPool SC -func initRewardPool(polybftConfig PolyBFTConfig, transition *state.Transition) error { - initFn := &contractsapi.InitializeRewardPoolFn{ - NewRewardToken: polybftConfig.RewardConfig.TokenAddress, - NewRewardWallet: polybftConfig.RewardConfig.WalletAddress, - NewValidatorSet: contracts.ValidatorSetContract, - NewBaseReward: new(big.Int).SetUint64(polybftConfig.EpochReward), - } - - input, err := initFn.EncodeAbi() - if err != nil { - return fmt.Errorf("RewardPool.initialize params encoding failed: %w", err) - } - - return callContract(contracts.SystemCaller, - contracts.RewardPoolContract, input, "RewardPool.initialize", transition) -} - -// getInitERC20PredicateInput builds initialization input parameters for child chain ERC20Predicate SC -func getInitERC20PredicateInput(config *BridgeConfig, childChainMintable bool) ([]byte, error) { - var params contractsapi.StateTransactionInput - if childChainMintable { - params = &contractsapi.InitializeRootMintableERC20PredicateFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewChildERC20Predicate: config.ChildMintableERC20PredicateAddr, - NewChildTokenTemplate: config.ChildERC20Addr, - } - } else { - params = &contractsapi.InitializeChildERC20PredicateFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewRootERC20Predicate: config.RootERC20PredicateAddr, - NewChildTokenTemplate: contracts.ChildERC20Contract, - NewNativeTokenRootAddress: config.RootNativeERC20Addr, - } - } - - return params.EncodeAbi() -} - -// getInitERC20PredicateACLInput builds initialization input parameters for child chain ERC20PredicateAccessList SC -func getInitERC20PredicateACLInput(config *BridgeConfig, owner types.Address, - useAllowList, useBlockList, childChainMintable bool) ([]byte, error) { - var params contractsapi.StateTransactionInput - if childChainMintable { - params = &contractsapi.InitializeRootMintableERC20PredicateACLFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewChildERC20Predicate: config.ChildMintableERC20PredicateAddr, - NewChildTokenTemplate: config.ChildERC20Addr, - NewUseAllowList: useAllowList, - NewUseBlockList: useBlockList, - NewOwner: owner, - } - } else { - params = &contractsapi.InitializeChildERC20PredicateACLFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewRootERC20Predicate: config.RootERC20PredicateAddr, - NewChildTokenTemplate: contracts.ChildERC20Contract, - NewNativeTokenRootAddress: config.RootNativeERC20Addr, - NewUseAllowList: useAllowList, - NewUseBlockList: useBlockList, - NewOwner: owner, - } - } - - return params.EncodeAbi() -} - -// getInitERC721PredicateInput builds initialization input parameters for child chain ERC721Predicate SC -func getInitERC721PredicateInput(config *BridgeConfig, childOriginatedTokens bool) ([]byte, error) { - var params contractsapi.StateTransactionInput - if childOriginatedTokens { - params = &contractsapi.InitializeRootMintableERC721PredicateFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewChildERC721Predicate: config.ChildMintableERC721PredicateAddr, - NewChildTokenTemplate: config.ChildERC721Addr, - } - } else { - params = &contractsapi.InitializeChildERC721PredicateFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewRootERC721Predicate: config.RootERC721PredicateAddr, - NewChildTokenTemplate: contracts.ChildERC721Contract, - } - } - - return params.EncodeAbi() -} - -// getInitERC721PredicateACLInput builds initialization input parameters -// for child chain ERC721PredicateAccessList SC -func getInitERC721PredicateACLInput(config *BridgeConfig, owner types.Address, - useAllowList, useBlockList, childChainMintable bool) ([]byte, error) { - var params contractsapi.StateTransactionInput - if childChainMintable { - params = &contractsapi.InitializeRootMintableERC721PredicateACLFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewChildERC721Predicate: config.ChildMintableERC721PredicateAddr, - NewChildTokenTemplate: config.ChildERC721Addr, - NewUseAllowList: useAllowList, - NewUseBlockList: useBlockList, - NewOwner: owner, - } - } else { - params = &contractsapi.InitializeChildERC721PredicateACLFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewRootERC721Predicate: config.RootERC721PredicateAddr, - NewChildTokenTemplate: contracts.ChildERC721Contract, - NewUseAllowList: useAllowList, - NewUseBlockList: useBlockList, - NewOwner: owner, - } - } - - return params.EncodeAbi() -} - -// getInitERC1155PredicateInput builds initialization input parameters for child chain ERC1155Predicate SC -func getInitERC1155PredicateInput(config *BridgeConfig, childChainMintable bool) ([]byte, error) { - var params contractsapi.StateTransactionInput - if childChainMintable { - params = &contractsapi.InitializeRootMintableERC1155PredicateFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewChildERC1155Predicate: config.ChildMintableERC1155PredicateAddr, - NewChildTokenTemplate: config.ChildERC1155Addr, - } - } else { - params = &contractsapi.InitializeChildERC1155PredicateFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewRootERC1155Predicate: config.RootERC1155PredicateAddr, - NewChildTokenTemplate: contracts.ChildERC1155Contract, - } - } - - return params.EncodeAbi() -} - -// getInitERC1155PredicateACLInput builds initialization input parameters -// for child chain ERC1155PredicateAccessList SC -func getInitERC1155PredicateACLInput(config *BridgeConfig, owner types.Address, - useAllowList, useBlockList, childChainMintable bool) ([]byte, error) { - var params contractsapi.StateTransactionInput - if childChainMintable { - params = &contractsapi.InitializeRootMintableERC1155PredicateACLFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewChildERC1155Predicate: config.ChildMintableERC1155PredicateAddr, - NewChildTokenTemplate: config.ChildERC1155Addr, - NewUseAllowList: useAllowList, - NewUseBlockList: useBlockList, - NewOwner: owner, - } - } else { - params = &contractsapi.InitializeChildERC1155PredicateACLFn{ - NewL2StateSender: contracts.L2StateSenderContract, - NewStateReceiver: contracts.StateReceiverContract, - NewRootERC1155Predicate: config.RootERC1155PredicateAddr, - NewChildTokenTemplate: contracts.ChildERC1155Contract, - NewUseAllowList: useAllowList, - NewUseBlockList: useBlockList, - NewOwner: owner, - } - } - - return params.EncodeAbi() -} - -// mintRewardTokensToWallet mints configured amount of reward tokens to reward wallet address -func mintRewardTokensToWallet(polyBFTConfig PolyBFTConfig, transition *state.Transition) error { - if isNativeRewardToken(polyBFTConfig) { - // if reward token is a native erc20 token, we don't need to mint an amount of tokens - // for given wallet address to it since this is done in premine - return nil - } - - mintFn := contractsapi.MintRootERC20Fn{ - To: polyBFTConfig.RewardConfig.WalletAddress, - Amount: polyBFTConfig.RewardConfig.WalletAmount, - } - - input, err := mintFn.EncodeAbi() - if err != nil { - return fmt.Errorf("RewardToken.mint params encoding failed: %w", err) - } - - return callContract(contracts.SystemCaller, polyBFTConfig.RewardConfig.TokenAddress, input, - "RewardToken.mint", transition) -} - -// approveRewardPoolAsSpender approves reward pool contract as reward token spender -// since reward pool distributes rewards. -func approveRewardPoolAsSpender(polyBFTConfig PolyBFTConfig, transition *state.Transition) error { - approveFn := &contractsapi.ApproveRootERC20Fn{ - Spender: contracts.RewardPoolContract, - Amount: polyBFTConfig.RewardConfig.WalletAmount, - } - - input, err := approveFn.EncodeAbi() - if err != nil { - return fmt.Errorf("RewardToken.approve params encoding failed: %w", err) - } - - return callContract(polyBFTConfig.RewardConfig.WalletAddress, - polyBFTConfig.RewardConfig.TokenAddress, input, "RewardToken.approve", transition) -} - -// callContract calls given smart contract function, encoded in input parameter -func callContract(from, to types.Address, input []byte, contractName string, transition *state.Transition) error { - result := transition.Call2(from, to, input, big.NewInt(0), contractCallGasLimit) - if result.Failed() { - if result.Reverted() { - if revertReason, err := abi.UnpackRevertError(result.ReturnValue); err == nil { - return fmt.Errorf("%s contract call was reverted: %s", contractName, revertReason) - } - } - - return fmt.Errorf("%s contract call failed: %w", contractName, result.Err) - } - - return nil -} - -// isNativeRewardToken returns true in case a native token is used as a reward token as well -func isNativeRewardToken(cfg PolyBFTConfig) bool { - return cfg.RewardConfig.TokenAddress == contracts.NativeERC20TokenContract -} diff --git a/consensus/polybft/contractsapi/artifact/artifacts.go b/consensus/polybft/contractsapi/artifact/artifacts.go deleted file mode 100644 index 358d416..0000000 --- a/consensus/polybft/contractsapi/artifact/artifacts.go +++ /dev/null @@ -1,47 +0,0 @@ -package artifact - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/umbracle/ethgo/abi" - "github.com/xgr-network/xgr-node/helper/hex" -) - -func ReadArtifactData(rootFolder, contractPath, contractName string) ([]byte, error) { - fileName := filepath.Join(rootFolder, contractPath, fmt.Sprintf("%s.json", contractName)) - - absolutePath, err := filepath.Abs(fileName) - if err != nil { - return nil, err - } - - return os.ReadFile(filepath.Clean(absolutePath)) -} - -func DecodeArtifact(data []byte) (*Artifact, error) { - var hexRes HexArtifact - if err := json.Unmarshal(data, &hexRes); err != nil { - return nil, fmt.Errorf("artifact found but no correct format: %w", err) - } - - return &Artifact{ - Abi: hexRes.Abi, - Bytecode: hex.MustDecodeHex(hexRes.ByteCode), - DeployedBytecode: hex.MustDecodeHex(hexRes.DeployedBytecode), - }, nil -} - -type HexArtifact struct { - Abi *abi.ABI - ByteCode string - DeployedBytecode string -} - -type Artifact struct { - Abi *abi.ABI - Bytecode []byte - DeployedBytecode []byte -} diff --git a/consensus/polybft/contractsapi/artifacts-gen/main.go b/consensus/polybft/contractsapi/artifacts-gen/main.go deleted file mode 100644 index 206dcc7..0000000 --- a/consensus/polybft/contractsapi/artifacts-gen/main.go +++ /dev/null @@ -1,237 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "go/format" - "log" - "os" - "path" - "runtime" - "strings" - - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi/artifact" - "github.com/xgr-network/xgr-node/helper/common" -) - -const ( - extension = ".sol" -) - -func main() { - _, filename, _, _ := runtime.Caller(0) //nolint: dogsled - currentPath := path.Dir(filename) - scpath := path.Join(currentPath, "../../../../core-contracts/artifacts/contracts/") - - readContracts := []struct { - Path string - Name string - }{ - { - "child/L2StateSender.sol", - "L2StateSender", - }, - { - "child/StateReceiver.sol", - "StateReceiver", - }, - { - "child/NativeERC20.sol", - "NativeERC20", - }, - { - "child/NativeERC20Mintable.sol", - "NativeERC20Mintable", - }, - { - "child/ChildERC20.sol", - "ChildERC20", - }, - { - "child/ChildERC20Predicate.sol", - "ChildERC20Predicate", - }, - { - "child/ChildERC20PredicateAccessList.sol", - "ChildERC20PredicateACL", - }, - { - "child/RootMintableERC20Predicate.sol", - "RootMintableERC20Predicate", - }, - { - "child/RootMintableERC20PredicateAccessList.sol", - "RootMintableERC20PredicateACL", - }, - { - "child/ChildERC721.sol", - "ChildERC721", - }, - { - "child/ChildERC721Predicate.sol", - "ChildERC721Predicate", - }, - { - "child/ChildERC721PredicateAccessList.sol", - "ChildERC721PredicateACL", - }, - { - "child/RootMintableERC721Predicate.sol", - "RootMintableERC721Predicate", - }, - { - "child/RootMintableERC721PredicateAccessList.sol", - "RootMintableERC721PredicateACL", - }, - { - "child/ChildERC1155.sol", - "ChildERC1155", - }, - { - "child/ChildERC1155Predicate.sol", - "ChildERC1155Predicate", - }, - { - "child/ChildERC1155PredicateAccessList.sol", - "ChildERC1155PredicateACL", - }, - { - "child/RootMintableERC1155Predicate.sol", - "RootMintableERC1155Predicate", - }, - { - "child/RootMintableERC1155PredicateAccessList.sol", - "RootMintableERC1155PredicateACL", - }, - { - "child/System.sol", - "System", - }, - { - "common/BLS.sol", - "BLS", - }, - { - "common/BN256G2.sol", - "BN256G2", - }, - { - "common/Merkle.sol", - "Merkle", - }, - { - "root/CheckpointManager.sol", - "CheckpointManager", - }, - { - "root/ExitHelper.sol", - "ExitHelper", - }, - { - "root/StateSender.sol", - "StateSender", - }, - { - "mocks/MockERC20.sol", - "MockERC20", - }, - { - "root/RootERC20Predicate.sol", - "RootERC20Predicate", - }, - { - "root/ChildMintableERC20Predicate.sol", - "ChildMintableERC20Predicate", - }, - { - "mocks/MockERC721.sol", - "MockERC721", - }, - { - "root/RootERC721Predicate.sol", - "RootERC721Predicate", - }, - { - "root/ChildMintableERC721Predicate.sol", - "ChildMintableERC721Predicate", - }, - { - "mocks/MockERC1155.sol", - "MockERC1155", - }, - { - "root/RootERC1155Predicate.sol", - "RootERC1155Predicate", - }, - { - "root/ChildMintableERC1155Predicate.sol", - "ChildMintableERC1155Predicate", - }, - { - "root/staking/CustomSupernetManager.sol", - "CustomSupernetManager", - }, - { - "root/staking/StakeManager.sol", - "StakeManager", - }, - { - "child/validator/RewardPool.sol", - "RewardPool", - }, - { - "child/validator/ValidatorSet.sol", - "ValidatorSet", - }, - { - "child/EIP1559Burn.sol", - "EIP1559Burn", - }, - { - "lib/GenesisProxy.sol", - "GenesisProxy", - }, - { - "../@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol", - "TransparentUpgradeableProxy", - }, - } - - str := `// This is auto-generated file. DO NOT EDIT. -package contractsapi - -` - - for _, v := range readContracts { - artifactBytes, err := artifact.ReadArtifactData(scpath, v.Path, getContractName(v.Path)) - if err != nil { - log.Fatal(err) - } - - dst := &bytes.Buffer{} - if err = json.Compact(dst, artifactBytes); err != nil { - log.Fatal(err) - } - - str += fmt.Sprintf("var %sArtifact string = `%s`\n", v.Name, dst.String()) - } - - output, err := format.Source([]byte(str)) - if err != nil { - fmt.Println(str) - log.Fatal(err) - } - - if err = common.SaveFileSafe(currentPath+"/../gen_sc_data.go", output, 0600); err != nil { - log.Fatal(err) - } -} - -// getContractName extracts smart contract name from provided path -func getContractName(path string) string { - pathSegments := strings.Split(path, string([]rune{os.PathSeparator})) - nameSegment := pathSegments[len(pathSegments)-1] - - return strings.Split(nameSegment, extension)[0] -} diff --git a/consensus/polybft/contractsapi/bindings-gen/main.go b/consensus/polybft/contractsapi/bindings-gen/main.go deleted file mode 100644 index b4dc8dd..0000000 --- a/consensus/polybft/contractsapi/bindings-gen/main.go +++ /dev/null @@ -1,805 +0,0 @@ -package main - -import ( - "bytes" - "errors" - "fmt" - "go/format" - "log" - "regexp" - "strconv" - "strings" - "text/template" - - "github.com/umbracle/ethgo/abi" - - gensc "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi/artifact" - "github.com/xgr-network/xgr-node/helper/common" -) - -const ( - abiTypeNameFormat = "var %sABIType = abi.MustNewType(\"%s\")" - eventNameFormat = "%sEvent" - functionNameFormat = "%sFn" -) - -var ( - signatureFunctionFormat = regexp.MustCompile(`^(.*)\((.*)\)$`) -) - -type generatedData struct { - resultString []string - structs []string -} - -func main() { - cases := []struct { - contractName string - artifact *artifact.Artifact - generateConstructor bool - functions []string - events []string - }{ - { - "StateReceiver", - gensc.StateReceiver, - false, - []string{ - "commit", - "execute", - "batchExecute", - }, - []string{ - "StateSyncResult", - "NewCommitment", - }, - }, - { - "StateSender", - gensc.StateSender, - false, - []string{ - "syncState", - }, - []string{ - "StateSynced", - }, - }, - { - "L2StateSender", - gensc.L2StateSender, - false, - []string{}, - []string{ - "L2StateSynced", - }, - }, - { - "CheckpointManager", - gensc.CheckpointManager, - true, - []string{ - "submit", - "initialize", - "getCheckpointBlock", - }, - []string{}, - }, - { - "ExitHelper", - gensc.ExitHelper, - false, - []string{ - "initialize", - "exit", - }, - []string{}, - }, - { - "ChildERC20Predicate", - gensc.ChildERC20Predicate, - false, - []string{ - "initialize", - "withdrawTo", - }, - []string{}, - }, - { - "ChildERC20PredicateACL", - gensc.ChildERC20PredicateACL, - false, - []string{ - "initialize(address,address,address,address,address,bool,bool,address)", - "withdrawTo", - }, - []string{}, - }, - { - "RootMintableERC20Predicate", - gensc.RootMintableERC20Predicate, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "RootMintableERC20PredicateACL", - gensc.RootMintableERC20PredicateACL, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "NativeERC20", - gensc.NativeERC20, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "NativeERC20Mintable", - gensc.NativeERC20Mintable, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "RootERC20Predicate", - gensc.RootERC20Predicate, - false, - []string{ - "initialize", - "depositTo", - }, - []string{ - "TokenMapped", - }, - }, - { - "ChildMintableERC20Predicate", - gensc.ChildMintableERC20Predicate, - false, - []string{ - "initialize", - }, - []string{ - "MintableTokenMapped", - }, - }, - { - "RootERC20", - gensc.RootERC20, - false, - []string{ - "balanceOf", - "approve", - "mint", - }, - []string{}, - }, - { - "RootERC1155Predicate", - gensc.RootERC1155Predicate, - false, - []string{ - "initialize", - "depositBatch", - }, - []string{}, - }, - { - "ChildMintableERC1155Predicate", - gensc.ChildMintableERC1155Predicate, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "RootERC1155", - gensc.RootERC1155, - false, - []string{ - "setApprovalForAll", - "mintBatch", - "balanceOf", - }, - []string{}, - }, - { - "ChildERC1155Predicate", - gensc.ChildERC1155Predicate, - false, - []string{ - "initialize", - "withdrawBatch", - }, - []string{}, - }, - { - "ChildERC1155PredicateACL", - gensc.ChildERC1155PredicateACL, - false, - []string{ - "initialize", - "withdrawBatch", - }, - []string{}, - }, - { - "RootMintableERC1155Predicate", - gensc.RootMintableERC1155Predicate, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "RootMintableERC1155PredicateACL", - gensc.RootMintableERC1155PredicateACL, - false, - []string{ - "initialize", - }, - []string{ - "L2MintableTokenMapped", - }, - }, - { - "ChildERC1155", - gensc.ChildERC1155, - false, - []string{ - "initialize", - "balanceOf", - }, - []string{}, - }, - { - "RootERC721Predicate", - gensc.RootERC721Predicate, - false, - []string{ - "initialize", - "depositBatch", - }, - []string{}, - }, - { - "ChildMintableERC721Predicate", - gensc.ChildMintableERC721Predicate, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "RootERC721", - gensc.RootERC721, - false, - []string{ - "setApprovalForAll", - "mint", - }, - []string{}, - }, - { - "ChildERC721Predicate", - gensc.ChildERC721Predicate, - false, - []string{ - "initialize", - "withdrawBatch", - }, - []string{}, - }, - { - "ChildERC721PredicateACL", - gensc.ChildERC721PredicateACL, - false, - []string{ - "initialize", - "withdrawBatch", - }, - []string{}, - }, - { - "RootMintableERC721Predicate", - gensc.RootMintableERC721Predicate, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "RootMintableERC721PredicateACL", - gensc.RootMintableERC721PredicateACL, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "ChildERC721", - gensc.ChildERC721, - false, - []string{ - "initialize", - "ownerOf", - }, - []string{}, - }, - { - "CustomSupernetManager", - gensc.CustomSupernetManager, - false, - []string{ - "initialize", - "whitelistValidators", - "register", - "getValidator", - "addGenesisBalance", - }, - []string{ - "ValidatorRegistered", - "AddedToWhitelist", - }, - }, - { - "StakeManager", - gensc.StakeManager, - false, - []string{ - "initialize", - "registerChildChain", - "stakeFor", - "releaseStakeOf", - "withdrawStake", - "stakeOf", - }, - []string{ - "ChildManagerRegistered", - "StakeAdded", - "StakeWithdrawn", - }, - }, - { - "ValidatorSet", - gensc.ValidatorSet, - false, - []string{ - "commitEpoch", - "unstake", - "initialize", - }, - []string{ - "Transfer", - "WithdrawalRegistered", - "Withdrawal", - }, - }, - { - "RewardPool", - gensc.RewardPool, - false, - []string{ - "initialize", - "distributeRewardFor", - }, - []string{}, - }, - { - "EIP1559Burn", - gensc.EIP1559Burn, - false, - []string{ - "initialize", - }, - []string{}, - }, - { - "GenesisProxy", - gensc.GenesisProxy, - false, - []string{ - "protectSetUpProxy", - "setUpProxy", - }, - []string{}, - }, - { - "TransparentUpgradeableProxy", - gensc.TransparentUpgradeableProxy, - true, - []string{}, - []string{}, - }, - } - - generatedData := &generatedData{} - - for _, c := range cases { - if c.generateConstructor { - if err := generateConstructor(generatedData, c.contractName, c.artifact.Abi.Constructor); err != nil { - log.Fatal(err) - } - } - - for _, methodRaw := range c.functions { - // There could be two objects with the same name in the generated JSON ABI (hardhat bug). - // This case can be fixed by specifying a function signature instead of just name - // e.g. "myFunc(address,bool,uint256)" instead of just "myFunc" - var ( - method *abi.Method - resolvedBySignature = false - ) - - if signatureFunctionFormat.MatchString(methodRaw) { - method = c.artifact.Abi.GetMethodBySignature(methodRaw) - resolvedBySignature = true - } else { - method = c.artifact.Abi.GetMethod(methodRaw) - } - - if err := generateFunction(generatedData, c.contractName, method, resolvedBySignature); err != nil { - log.Fatal(err) - } - } - - for _, event := range c.events { - if err := generateEvent(generatedData, c.contractName, c.artifact.Abi.Events[event]); err != nil { - log.Fatal(err) - } - } - } - - str := `// Code generated by scapi/gen. DO NOT EDIT. -package contractsapi - -import ( - "math/big" - - "github.com/xgr-network/xgr-node/types" - "github.com/umbracle/ethgo/abi" - "github.com/umbracle/ethgo" -) - -` - str += strings.Join(generatedData.resultString, "\n") - - output, err := format.Source([]byte(str)) - if err != nil { - fmt.Println(str) - log.Fatal(err) - } - - if err = common.SaveFileSafe("./consensus/polybft/contractsapi/contractsapi.go", output, 0600); err != nil { - log.Fatal(err) - } -} - -func getInternalType(paramName string, paramAbiType *abi.Type) string { - internalType := paramAbiType.InternalType() - if internalType == "" { - internalType = strings.Title(paramName) - } else { - internalType = strings.TrimSuffix(internalType, "[]") // remove [] if it's struct array - internalType = strings.TrimPrefix(internalType, "struct ") // remove struct prefix - // if struct is taken from an interface (ICheckpoint.Validator), remove interface - parts := strings.Split(internalType, ".") - if len(parts) > 1 { - internalType = parts[1] - } - } - - return internalType -} - -// generateType generates code for structs used in smart contract functions and events -func generateType(generatedData *generatedData, name string, obj *abi.Type, res *[]string) (string, error) { - if obj.Kind() != abi.KindTuple { - return "", errors.New("type not expected") - } - - internalType := getInternalType(name, obj) - generatedData.structs = append(generatedData.structs, internalType) - - str := []string{ - "type " + internalType + " struct {", - } - - for _, tupleElem := range obj.TupleElems() { - elem := tupleElem.Elem - - var typ string - - if elem.Kind() == abi.KindTuple { - // Struct - nestedType, err := generateNestedType(generatedData, tupleElem.Name, elem, res) - if err != nil { - return "", err - } - - typ = nestedType - } else if elem.Kind() == abi.KindSlice && elem.Elem().Kind() == abi.KindTuple { - // []Struct - nestedType, err := generateNestedType(generatedData, getInternalType(tupleElem.Name, elem), elem.Elem(), res) - if err != nil { - return "", err - } - - typ = "[]" + nestedType - } else if elem.Kind() == abi.KindArray && elem.Elem().Kind() == abi.KindTuple { - // [n]Struct - nestedType, err := generateNestedType(generatedData, getInternalType(tupleElem.Name, elem), elem.Elem(), res) - if err != nil { - return "", err - } - - typ = "[" + strconv.Itoa(elem.Size()) + "]" + nestedType - } else if elem.Kind() == abi.KindAddress { - // for address use the native `types.Address` type instead of `ethgo.Address`. Note that - // this only works for simple types and not for []address inputs. This is good enough since - // there are no kinds like that in our smart contracts. - typ = "types.Address" - } else { - // for the rest of the types use the go type returned by abi - typ = elem.GoType().String() - } - - // []byte and [n]byte get rendered as []uint68 and [n]uint8, since we do not have any - // uint8 internally in polybft, we can use regexp to replace those values with the - // correct byte representation - typ = strings.Replace(typ, "[32]uint8", "types.Hash", -1) - typ = strings.Replace(typ, "]uint8", "]byte", -1) - - // Trim the leading _ from name if it exists - fieldName := strings.TrimPrefix(tupleElem.Name, "_") - - // Replacement of Id for ID to make the linter happy - fieldName = strings.Title(fieldName) - fieldName = strings.Replace(fieldName, "Id", "ID", -1) - - str = append(str, fmt.Sprintf("%s %s `abi:\"%s\"`", fieldName, typ, tupleElem.Name)) - } - - str = append(str, "}") - *res = append(*res, strings.Join(str, "\n")) - - return internalType, nil -} - -// generateNestedType generates code for nested types found in smart contracts structs -func generateNestedType(generatedData *generatedData, name string, obj *abi.Type, res *[]string) (string, error) { - for _, s := range generatedData.structs { - if s == name { - // do not generate the same type again if it's already generated - // this happens when two functions use the same struct type as one of its parameters - return "*" + name, nil - } - } - - result, err := generateType(generatedData, name, obj, res) - if err != nil { - return "", err - } - - *res = append(*res, fmt.Sprintf(abiTypeNameFormat, result, obj.Format(true))) - - nestedTypeFunctions, err := generateAbiFuncsForNestedType(result) - if err != nil { - return "", err - } - - *res = append(*res, nestedTypeFunctions) - - return "*" + result, nil -} - -// generateAbiFuncsForNestedType generates necessary functions for nested types smart contracts interaction -func generateAbiFuncsForNestedType(name string) (string, error) { - tmpl := `func ({{.Sig}} *{{.TName}}) EncodeAbi() ([]byte, error) { - return {{.Name}}ABIType.Encode({{.Sig}}) - } - - func ({{.Sig}} *{{.TName}}) DecodeAbi(buf []byte) error { - return decodeStruct({{.Name}}ABIType, buf, &{{.Sig}}) - }` - - title := strings.Title(name) - - inputs := map[string]interface{}{ - "Sig": strings.ToLower(string(name[0])), - "Name": title, - "TName": title, - } - - return renderTmpl(tmpl, inputs) -} - -// generateEvent generates code for smart contract events -func generateEvent(generatedData *generatedData, contractName string, event *abi.Event) error { - name := fmt.Sprintf(eventNameFormat, event.Name) - res := []string{} - - _, err := generateType(generatedData, name, event.Inputs, &res) - if err != nil { - return err - } - - // write encode/decode functions - tmplStr := ` -{{range .Structs}} - {{.}} -{{ end }} - -func (*{{.TName}}) Sig() ethgo.Hash { - return {{.ContractName}}.Abi.Events["{{.Name}}"].ID() -} - -func ({{.Sig}} *{{.TName}}) Encode() ([]byte, error) { - return {{.ContractName}}.Abi.Events["{{.Name}}"].Inputs.Encode({{.Sig}}) -} - -func ({{.Sig}} *{{.TName}}) ParseLog(log *ethgo.Log) (bool, error) { - if (!{{.ContractName}}.Abi.Events["{{.Name}}"].Match(log)) { - return false, nil - } - - return true, decodeEvent({{.ContractName}}.Abi.Events["{{.Name}}"], log, {{.Sig}}) -} - -func ({{.Sig}} *{{.TName}}) Decode(input []byte) error { - return {{.ContractName}}.Abi.Events["{{.Name}}"].Inputs.DecodeStruct(input, &{{.Sig}}) -} -` - - inputs := map[string]interface{}{ - "Structs": res, - "Sig": strings.ToLower(string(name[0])), - "Name": event.Name, - "TName": strings.Title(name), - "ContractName": contractName, - } - - renderedString, err := renderTmpl(tmplStr, inputs) - if err != nil { - return err - } - - generatedData.resultString = append(generatedData.resultString, renderedString) - - return nil -} - -// generateConstruct generates stubs for a smart contract constructor -func generateConstructor(generatedData *generatedData, - contractName string, constructor *abi.Method) error { - methodName := fmt.Sprintf(functionNameFormat, strings.Title(contractName+"Constructor")) - res := []string{} - - _, err := generateType(generatedData, methodName, constructor.Inputs, &res) - if err != nil { - return err - } - - // write encode/decode functions - tmplStr := ` -{{range .Structs}} - {{.}} -{{ end }} - -func ({{.Sig}} *{{.TName}}) Sig() []byte { - return {{.ContractName}}.Abi.Constructor.ID() -} - -func ({{.Sig}} *{{.TName}}) EncodeAbi() ([]byte, error) { - return {{.ContractName}}.Abi.Constructor.Inputs.Encode({{.Sig}}) -} - -func ({{.Sig}} *{{.TName}}) DecodeAbi(buf []byte) error { - return decodeMethod({{.ContractName}}.Abi.Constructor, buf, {{.Sig}}) -}` - - inputs := map[string]interface{}{ - "Structs": res, - "Sig": strings.ToLower(string(methodName[0])), - "ContractName": contractName, - "TName": strings.Title(methodName), - } - - renderedString, err := renderTmpl(tmplStr, inputs) - if err != nil { - return err - } - - generatedData.resultString = append(generatedData.resultString, renderedString) - - return nil -} - -// generateFunction generates code for smart contract function and its parameters -func generateFunction(generatedData *generatedData, contractName string, - method *abi.Method, fnSigResolution bool) error { - methodName := fmt.Sprintf(functionNameFormat, strings.Title(method.Name+contractName)) - res := []string{} - - _, err := generateType(generatedData, methodName, method.Inputs, &res) - if err != nil { - return err - } - - // write encode/decode functions - - tmplString := ` - {{range .Structs}} - {{.}} - {{ end }} - - func ({{.Sig}} *{{.TName}}) Sig() []byte { - return {{.ContractName}}.Abi.{{.MethodGetter}}["{{.Name}}"].ID() - } - - func ({{.Sig}} *{{.TName}}) EncodeAbi() ([]byte, error) { - return {{.ContractName}}.Abi.{{.MethodGetter}}["{{.Name}}"].Encode({{.Sig}}) - } - - func ({{.Sig}} *{{.TName}}) DecodeAbi(buf []byte) error { - return decodeMethod({{.ContractName}}.Abi.{{.MethodGetter}}["{{.Name}}"], buf, {{.Sig}}) - }` - - methodGetter := "Methods" - if fnSigResolution { - methodGetter = "MethodsBySignature" - } - - inputs := map[string]interface{}{ - "Structs": res, - "Sig": strings.ToLower(string(methodName[0])), - "Name": method.Name, - "ContractName": contractName, - "TName": strings.Title(methodName), - "MethodGetter": methodGetter, - } - - if fnSigResolution { - inputs["Name"] = method.Sig() - } - - renderedString, err := renderTmpl(tmplString, inputs) - if err != nil { - return err - } - - generatedData.resultString = append(generatedData.resultString, renderedString) - - return nil -} - -func renderTmpl(tmplStr string, inputs map[string]interface{}) (string, error) { - tmpl, err := template.New("name").Parse(tmplStr) - if err != nil { - return "", fmt.Errorf("failed to load template: %w", err) - } - - var tpl bytes.Buffer - if err = tmpl.Execute(&tpl, inputs); err != nil { - return "", fmt.Errorf("failed to render template: %w", err) - } - - return tpl.String(), nil -} diff --git a/consensus/polybft/contractsapi/contractsapi.go b/consensus/polybft/contractsapi/contractsapi.go deleted file mode 100644 index 4981a34..0000000 --- a/consensus/polybft/contractsapi/contractsapi.go +++ /dev/null @@ -1,1816 +0,0 @@ -// Code generated by scapi/gen. DO NOT EDIT. -package contractsapi - -import ( - "math/big" - - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - "github.com/xgr-network/xgr-node/types" -) - -type StateSyncCommitment struct { - StartID *big.Int `abi:"startId"` - EndID *big.Int `abi:"endId"` - Root types.Hash `abi:"root"` -} - -var StateSyncCommitmentABIType = abi.MustNewType("tuple(uint256 startId,uint256 endId,bytes32 root)") - -func (s *StateSyncCommitment) EncodeAbi() ([]byte, error) { - return StateSyncCommitmentABIType.Encode(s) -} - -func (s *StateSyncCommitment) DecodeAbi(buf []byte) error { - return decodeStruct(StateSyncCommitmentABIType, buf, &s) -} - -type CommitStateReceiverFn struct { - Commitment *StateSyncCommitment `abi:"commitment"` - Signature []byte `abi:"signature"` - Bitmap []byte `abi:"bitmap"` -} - -func (c *CommitStateReceiverFn) Sig() []byte { - return StateReceiver.Abi.Methods["commit"].ID() -} - -func (c *CommitStateReceiverFn) EncodeAbi() ([]byte, error) { - return StateReceiver.Abi.Methods["commit"].Encode(c) -} - -func (c *CommitStateReceiverFn) DecodeAbi(buf []byte) error { - return decodeMethod(StateReceiver.Abi.Methods["commit"], buf, c) -} - -type StateSync struct { - ID *big.Int `abi:"id"` - Sender types.Address `abi:"sender"` - Receiver types.Address `abi:"receiver"` - Data []byte `abi:"data"` -} - -var StateSyncABIType = abi.MustNewType("tuple(uint256 id,address sender,address receiver,bytes data)") - -func (s *StateSync) EncodeAbi() ([]byte, error) { - return StateSyncABIType.Encode(s) -} - -func (s *StateSync) DecodeAbi(buf []byte) error { - return decodeStruct(StateSyncABIType, buf, &s) -} - -type ExecuteStateReceiverFn struct { - Proof []types.Hash `abi:"proof"` - Obj *StateSync `abi:"obj"` -} - -func (e *ExecuteStateReceiverFn) Sig() []byte { - return StateReceiver.Abi.Methods["execute"].ID() -} - -func (e *ExecuteStateReceiverFn) EncodeAbi() ([]byte, error) { - return StateReceiver.Abi.Methods["execute"].Encode(e) -} - -func (e *ExecuteStateReceiverFn) DecodeAbi(buf []byte) error { - return decodeMethod(StateReceiver.Abi.Methods["execute"], buf, e) -} - -type BatchExecuteStateReceiverFn struct { - Proofs [][]types.Hash `abi:"proofs"` - Objs []*StateSync `abi:"objs"` -} - -func (b *BatchExecuteStateReceiverFn) Sig() []byte { - return StateReceiver.Abi.Methods["batchExecute"].ID() -} - -func (b *BatchExecuteStateReceiverFn) EncodeAbi() ([]byte, error) { - return StateReceiver.Abi.Methods["batchExecute"].Encode(b) -} - -func (b *BatchExecuteStateReceiverFn) DecodeAbi(buf []byte) error { - return decodeMethod(StateReceiver.Abi.Methods["batchExecute"], buf, b) -} - -type StateSyncResultEvent struct { - Counter *big.Int `abi:"counter"` - Status bool `abi:"status"` - Message []byte `abi:"message"` -} - -func (*StateSyncResultEvent) Sig() ethgo.Hash { - return StateReceiver.Abi.Events["StateSyncResult"].ID() -} - -func (s *StateSyncResultEvent) Encode() ([]byte, error) { - return StateReceiver.Abi.Events["StateSyncResult"].Inputs.Encode(s) -} - -func (s *StateSyncResultEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !StateReceiver.Abi.Events["StateSyncResult"].Match(log) { - return false, nil - } - - return true, decodeEvent(StateReceiver.Abi.Events["StateSyncResult"], log, s) -} - -func (s *StateSyncResultEvent) Decode(input []byte) error { - return StateReceiver.Abi.Events["StateSyncResult"].Inputs.DecodeStruct(input, &s) -} - -type NewCommitmentEvent struct { - StartID *big.Int `abi:"startId"` - EndID *big.Int `abi:"endId"` - Root types.Hash `abi:"root"` -} - -func (*NewCommitmentEvent) Sig() ethgo.Hash { - return StateReceiver.Abi.Events["NewCommitment"].ID() -} - -func (n *NewCommitmentEvent) Encode() ([]byte, error) { - return StateReceiver.Abi.Events["NewCommitment"].Inputs.Encode(n) -} - -func (n *NewCommitmentEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !StateReceiver.Abi.Events["NewCommitment"].Match(log) { - return false, nil - } - - return true, decodeEvent(StateReceiver.Abi.Events["NewCommitment"], log, n) -} - -func (n *NewCommitmentEvent) Decode(input []byte) error { - return StateReceiver.Abi.Events["NewCommitment"].Inputs.DecodeStruct(input, &n) -} - -type SyncStateStateSenderFn struct { - Receiver types.Address `abi:"receiver"` - Data []byte `abi:"data"` -} - -func (s *SyncStateStateSenderFn) Sig() []byte { - return StateSender.Abi.Methods["syncState"].ID() -} - -func (s *SyncStateStateSenderFn) EncodeAbi() ([]byte, error) { - return StateSender.Abi.Methods["syncState"].Encode(s) -} - -func (s *SyncStateStateSenderFn) DecodeAbi(buf []byte) error { - return decodeMethod(StateSender.Abi.Methods["syncState"], buf, s) -} - -type StateSyncedEvent struct { - ID *big.Int `abi:"id"` - Sender types.Address `abi:"sender"` - Receiver types.Address `abi:"receiver"` - Data []byte `abi:"data"` -} - -func (*StateSyncedEvent) Sig() ethgo.Hash { - return StateSender.Abi.Events["StateSynced"].ID() -} - -func (s *StateSyncedEvent) Encode() ([]byte, error) { - return StateSender.Abi.Events["StateSynced"].Inputs.Encode(s) -} - -func (s *StateSyncedEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !StateSender.Abi.Events["StateSynced"].Match(log) { - return false, nil - } - - return true, decodeEvent(StateSender.Abi.Events["StateSynced"], log, s) -} - -func (s *StateSyncedEvent) Decode(input []byte) error { - return StateSender.Abi.Events["StateSynced"].Inputs.DecodeStruct(input, &s) -} - -type L2StateSyncedEvent struct { - ID *big.Int `abi:"id"` - Sender types.Address `abi:"sender"` - Receiver types.Address `abi:"receiver"` - Data []byte `abi:"data"` -} - -func (*L2StateSyncedEvent) Sig() ethgo.Hash { - return L2StateSender.Abi.Events["L2StateSynced"].ID() -} - -func (l *L2StateSyncedEvent) Encode() ([]byte, error) { - return L2StateSender.Abi.Events["L2StateSynced"].Inputs.Encode(l) -} - -func (l *L2StateSyncedEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !L2StateSender.Abi.Events["L2StateSynced"].Match(log) { - return false, nil - } - - return true, decodeEvent(L2StateSender.Abi.Events["L2StateSynced"], log, l) -} - -func (l *L2StateSyncedEvent) Decode(input []byte) error { - return L2StateSender.Abi.Events["L2StateSynced"].Inputs.DecodeStruct(input, &l) -} - -type CheckpointManagerConstructorFn struct { - Initiator types.Address `abi:"initiator"` -} - -func (c *CheckpointManagerConstructorFn) Sig() []byte { - return CheckpointManager.Abi.Constructor.ID() -} - -func (c *CheckpointManagerConstructorFn) EncodeAbi() ([]byte, error) { - return CheckpointManager.Abi.Constructor.Inputs.Encode(c) -} - -func (c *CheckpointManagerConstructorFn) DecodeAbi(buf []byte) error { - return decodeMethod(CheckpointManager.Abi.Constructor, buf, c) -} - -type CheckpointMetadata struct { - BlockHash types.Hash `abi:"blockHash"` - BlockRound *big.Int `abi:"blockRound"` - CurrentValidatorSetHash types.Hash `abi:"currentValidatorSetHash"` -} - -var CheckpointMetadataABIType = abi.MustNewType("tuple(bytes32 blockHash,uint256 blockRound,bytes32 currentValidatorSetHash)") - -func (c *CheckpointMetadata) EncodeAbi() ([]byte, error) { - return CheckpointMetadataABIType.Encode(c) -} - -func (c *CheckpointMetadata) DecodeAbi(buf []byte) error { - return decodeStruct(CheckpointMetadataABIType, buf, &c) -} - -type Checkpoint struct { - Epoch *big.Int `abi:"epoch"` - BlockNumber *big.Int `abi:"blockNumber"` - EventRoot types.Hash `abi:"eventRoot"` -} - -var CheckpointABIType = abi.MustNewType("tuple(uint256 epoch,uint256 blockNumber,bytes32 eventRoot)") - -func (c *Checkpoint) EncodeAbi() ([]byte, error) { - return CheckpointABIType.Encode(c) -} - -func (c *Checkpoint) DecodeAbi(buf []byte) error { - return decodeStruct(CheckpointABIType, buf, &c) -} - -type Validator struct { - Address types.Address `abi:"_address"` - BlsKey [4]*big.Int `abi:"blsKey"` - VotingPower *big.Int `abi:"votingPower"` -} - -var ValidatorABIType = abi.MustNewType("tuple(address _address,uint256[4] blsKey,uint256 votingPower)") - -func (v *Validator) EncodeAbi() ([]byte, error) { - return ValidatorABIType.Encode(v) -} - -func (v *Validator) DecodeAbi(buf []byte) error { - return decodeStruct(ValidatorABIType, buf, &v) -} - -type SubmitCheckpointManagerFn struct { - CheckpointMetadata *CheckpointMetadata `abi:"checkpointMetadata"` - Checkpoint *Checkpoint `abi:"checkpoint"` - Signature [2]*big.Int `abi:"signature"` - NewValidatorSet []*Validator `abi:"newValidatorSet"` - Bitmap []byte `abi:"bitmap"` -} - -func (s *SubmitCheckpointManagerFn) Sig() []byte { - return CheckpointManager.Abi.Methods["submit"].ID() -} - -func (s *SubmitCheckpointManagerFn) EncodeAbi() ([]byte, error) { - return CheckpointManager.Abi.Methods["submit"].Encode(s) -} - -func (s *SubmitCheckpointManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(CheckpointManager.Abi.Methods["submit"], buf, s) -} - -type InitializeCheckpointManagerFn struct { - NewBls types.Address `abi:"newBls"` - NewBn256G2 types.Address `abi:"newBn256G2"` - ChainID_ *big.Int `abi:"chainId_"` - NewValidatorSet []*Validator `abi:"newValidatorSet"` -} - -func (i *InitializeCheckpointManagerFn) Sig() []byte { - return CheckpointManager.Abi.Methods["initialize"].ID() -} - -func (i *InitializeCheckpointManagerFn) EncodeAbi() ([]byte, error) { - return CheckpointManager.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeCheckpointManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(CheckpointManager.Abi.Methods["initialize"], buf, i) -} - -type GetCheckpointBlockCheckpointManagerFn struct { - BlockNumber *big.Int `abi:"blockNumber"` -} - -func (g *GetCheckpointBlockCheckpointManagerFn) Sig() []byte { - return CheckpointManager.Abi.Methods["getCheckpointBlock"].ID() -} - -func (g *GetCheckpointBlockCheckpointManagerFn) EncodeAbi() ([]byte, error) { - return CheckpointManager.Abi.Methods["getCheckpointBlock"].Encode(g) -} - -func (g *GetCheckpointBlockCheckpointManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(CheckpointManager.Abi.Methods["getCheckpointBlock"], buf, g) -} - -type InitializeExitHelperFn struct { - NewCheckpointManager types.Address `abi:"newCheckpointManager"` -} - -func (i *InitializeExitHelperFn) Sig() []byte { - return ExitHelper.Abi.Methods["initialize"].ID() -} - -func (i *InitializeExitHelperFn) EncodeAbi() ([]byte, error) { - return ExitHelper.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeExitHelperFn) DecodeAbi(buf []byte) error { - return decodeMethod(ExitHelper.Abi.Methods["initialize"], buf, i) -} - -type ExitExitHelperFn struct { - BlockNumber *big.Int `abi:"blockNumber"` - LeafIndex *big.Int `abi:"leafIndex"` - UnhashedLeaf []byte `abi:"unhashedLeaf"` - Proof []types.Hash `abi:"proof"` -} - -func (e *ExitExitHelperFn) Sig() []byte { - return ExitHelper.Abi.Methods["exit"].ID() -} - -func (e *ExitExitHelperFn) EncodeAbi() ([]byte, error) { - return ExitHelper.Abi.Methods["exit"].Encode(e) -} - -func (e *ExitExitHelperFn) DecodeAbi(buf []byte) error { - return decodeMethod(ExitHelper.Abi.Methods["exit"], buf, e) -} - -type InitializeChildERC20PredicateFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewRootERC20Predicate types.Address `abi:"newRootERC20Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` - NewNativeTokenRootAddress types.Address `abi:"newNativeTokenRootAddress"` -} - -func (i *InitializeChildERC20PredicateFn) Sig() []byte { - return ChildERC20Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildERC20PredicateFn) EncodeAbi() ([]byte, error) { - return ChildERC20Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildERC20PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC20Predicate.Abi.Methods["initialize"], buf, i) -} - -type WithdrawToChildERC20PredicateFn struct { - ChildToken types.Address `abi:"childToken"` - Receiver types.Address `abi:"receiver"` - Amount *big.Int `abi:"amount"` -} - -func (w *WithdrawToChildERC20PredicateFn) Sig() []byte { - return ChildERC20Predicate.Abi.Methods["withdrawTo"].ID() -} - -func (w *WithdrawToChildERC20PredicateFn) EncodeAbi() ([]byte, error) { - return ChildERC20Predicate.Abi.Methods["withdrawTo"].Encode(w) -} - -func (w *WithdrawToChildERC20PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC20Predicate.Abi.Methods["withdrawTo"], buf, w) -} - -type InitializeChildERC20PredicateACLFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewRootERC20Predicate types.Address `abi:"newRootERC20Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` - NewNativeTokenRootAddress types.Address `abi:"newNativeTokenRootAddress"` - NewUseAllowList bool `abi:"newUseAllowList"` - NewUseBlockList bool `abi:"newUseBlockList"` - NewOwner types.Address `abi:"newOwner"` -} - -func (i *InitializeChildERC20PredicateACLFn) Sig() []byte { - return ChildERC20PredicateACL.Abi.MethodsBySignature["initialize(address,address,address,address,address,bool,bool,address)"].ID() -} - -func (i *InitializeChildERC20PredicateACLFn) EncodeAbi() ([]byte, error) { - return ChildERC20PredicateACL.Abi.MethodsBySignature["initialize(address,address,address,address,address,bool,bool,address)"].Encode(i) -} - -func (i *InitializeChildERC20PredicateACLFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC20PredicateACL.Abi.MethodsBySignature["initialize(address,address,address,address,address,bool,bool,address)"], buf, i) -} - -type WithdrawToChildERC20PredicateACLFn struct { - ChildToken types.Address `abi:"childToken"` - Receiver types.Address `abi:"receiver"` - Amount *big.Int `abi:"amount"` -} - -func (w *WithdrawToChildERC20PredicateACLFn) Sig() []byte { - return ChildERC20PredicateACL.Abi.Methods["withdrawTo"].ID() -} - -func (w *WithdrawToChildERC20PredicateACLFn) EncodeAbi() ([]byte, error) { - return ChildERC20PredicateACL.Abi.Methods["withdrawTo"].Encode(w) -} - -func (w *WithdrawToChildERC20PredicateACLFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC20PredicateACL.Abi.Methods["withdrawTo"], buf, w) -} - -type InitializeRootMintableERC20PredicateFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewChildERC20Predicate types.Address `abi:"newChildERC20Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeRootMintableERC20PredicateFn) Sig() []byte { - return RootMintableERC20Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRootMintableERC20PredicateFn) EncodeAbi() ([]byte, error) { - return RootMintableERC20Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRootMintableERC20PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootMintableERC20Predicate.Abi.Methods["initialize"], buf, i) -} - -type InitializeRootMintableERC20PredicateACLFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewChildERC20Predicate types.Address `abi:"newChildERC20Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` - NewUseAllowList bool `abi:"newUseAllowList"` - NewUseBlockList bool `abi:"newUseBlockList"` - NewOwner types.Address `abi:"newOwner"` -} - -func (i *InitializeRootMintableERC20PredicateACLFn) Sig() []byte { - return RootMintableERC20PredicateACL.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRootMintableERC20PredicateACLFn) EncodeAbi() ([]byte, error) { - return RootMintableERC20PredicateACL.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRootMintableERC20PredicateACLFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootMintableERC20PredicateACL.Abi.Methods["initialize"], buf, i) -} - -type InitializeNativeERC20Fn struct { - Predicate_ types.Address `abi:"predicate_"` - RootToken_ types.Address `abi:"rootToken_"` - Name_ string `abi:"name_"` - Symbol_ string `abi:"symbol_"` - Decimals_ uint8 `abi:"decimals_"` - TokenSupply_ *big.Int `abi:"tokenSupply_"` -} - -func (i *InitializeNativeERC20Fn) Sig() []byte { - return NativeERC20.Abi.Methods["initialize"].ID() -} - -func (i *InitializeNativeERC20Fn) EncodeAbi() ([]byte, error) { - return NativeERC20.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeNativeERC20Fn) DecodeAbi(buf []byte) error { - return decodeMethod(NativeERC20.Abi.Methods["initialize"], buf, i) -} - -type InitializeNativeERC20MintableFn struct { - Predicate_ types.Address `abi:"predicate_"` - Owner_ types.Address `abi:"owner_"` - RootToken_ types.Address `abi:"rootToken_"` - Name_ string `abi:"name_"` - Symbol_ string `abi:"symbol_"` - Decimals_ uint8 `abi:"decimals_"` - TokenSupply_ *big.Int `abi:"tokenSupply_"` -} - -func (i *InitializeNativeERC20MintableFn) Sig() []byte { - return NativeERC20Mintable.Abi.Methods["initialize"].ID() -} - -func (i *InitializeNativeERC20MintableFn) EncodeAbi() ([]byte, error) { - return NativeERC20Mintable.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeNativeERC20MintableFn) DecodeAbi(buf []byte) error { - return decodeMethod(NativeERC20Mintable.Abi.Methods["initialize"], buf, i) -} - -type InitializeRootERC20PredicateFn struct { - NewStateSender types.Address `abi:"newStateSender"` - NewExitHelper types.Address `abi:"newExitHelper"` - NewChildERC20Predicate types.Address `abi:"newChildERC20Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` - NewNativeTokenRoot types.Address `abi:"newNativeTokenRoot"` -} - -func (i *InitializeRootERC20PredicateFn) Sig() []byte { - return RootERC20Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRootERC20PredicateFn) EncodeAbi() ([]byte, error) { - return RootERC20Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRootERC20PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC20Predicate.Abi.Methods["initialize"], buf, i) -} - -type DepositToRootERC20PredicateFn struct { - RootToken types.Address `abi:"rootToken"` - Receiver types.Address `abi:"receiver"` - Amount *big.Int `abi:"amount"` -} - -func (d *DepositToRootERC20PredicateFn) Sig() []byte { - return RootERC20Predicate.Abi.Methods["depositTo"].ID() -} - -func (d *DepositToRootERC20PredicateFn) EncodeAbi() ([]byte, error) { - return RootERC20Predicate.Abi.Methods["depositTo"].Encode(d) -} - -func (d *DepositToRootERC20PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC20Predicate.Abi.Methods["depositTo"], buf, d) -} - -type TokenMappedEvent struct { - RootToken types.Address `abi:"rootToken"` - ChildToken types.Address `abi:"childToken"` -} - -func (*TokenMappedEvent) Sig() ethgo.Hash { - return RootERC20Predicate.Abi.Events["TokenMapped"].ID() -} - -func (t *TokenMappedEvent) Encode() ([]byte, error) { - return RootERC20Predicate.Abi.Events["TokenMapped"].Inputs.Encode(t) -} - -func (t *TokenMappedEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !RootERC20Predicate.Abi.Events["TokenMapped"].Match(log) { - return false, nil - } - - return true, decodeEvent(RootERC20Predicate.Abi.Events["TokenMapped"], log, t) -} - -func (t *TokenMappedEvent) Decode(input []byte) error { - return RootERC20Predicate.Abi.Events["TokenMapped"].Inputs.DecodeStruct(input, &t) -} - -type InitializeChildMintableERC20PredicateFn struct { - NewStateSender types.Address `abi:"newStateSender"` - NewExitHelper types.Address `abi:"newExitHelper"` - NewRootERC20Predicate types.Address `abi:"newRootERC20Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeChildMintableERC20PredicateFn) Sig() []byte { - return ChildMintableERC20Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildMintableERC20PredicateFn) EncodeAbi() ([]byte, error) { - return ChildMintableERC20Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildMintableERC20PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildMintableERC20Predicate.Abi.Methods["initialize"], buf, i) -} - -type MintableTokenMappedEvent struct { - RootToken types.Address `abi:"rootToken"` - ChildToken types.Address `abi:"childToken"` -} - -func (*MintableTokenMappedEvent) Sig() ethgo.Hash { - return ChildMintableERC20Predicate.Abi.Events["MintableTokenMapped"].ID() -} - -func (m *MintableTokenMappedEvent) Encode() ([]byte, error) { - return ChildMintableERC20Predicate.Abi.Events["MintableTokenMapped"].Inputs.Encode(m) -} - -func (m *MintableTokenMappedEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !ChildMintableERC20Predicate.Abi.Events["MintableTokenMapped"].Match(log) { - return false, nil - } - - return true, decodeEvent(ChildMintableERC20Predicate.Abi.Events["MintableTokenMapped"], log, m) -} - -func (m *MintableTokenMappedEvent) Decode(input []byte) error { - return ChildMintableERC20Predicate.Abi.Events["MintableTokenMapped"].Inputs.DecodeStruct(input, &m) -} - -type BalanceOfRootERC20Fn struct { - Account types.Address `abi:"account"` -} - -func (b *BalanceOfRootERC20Fn) Sig() []byte { - return RootERC20.Abi.Methods["balanceOf"].ID() -} - -func (b *BalanceOfRootERC20Fn) EncodeAbi() ([]byte, error) { - return RootERC20.Abi.Methods["balanceOf"].Encode(b) -} - -func (b *BalanceOfRootERC20Fn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC20.Abi.Methods["balanceOf"], buf, b) -} - -type ApproveRootERC20Fn struct { - Spender types.Address `abi:"spender"` - Amount *big.Int `abi:"amount"` -} - -func (a *ApproveRootERC20Fn) Sig() []byte { - return RootERC20.Abi.Methods["approve"].ID() -} - -func (a *ApproveRootERC20Fn) EncodeAbi() ([]byte, error) { - return RootERC20.Abi.Methods["approve"].Encode(a) -} - -func (a *ApproveRootERC20Fn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC20.Abi.Methods["approve"], buf, a) -} - -type MintRootERC20Fn struct { - To types.Address `abi:"to"` - Amount *big.Int `abi:"amount"` -} - -func (m *MintRootERC20Fn) Sig() []byte { - return RootERC20.Abi.Methods["mint"].ID() -} - -func (m *MintRootERC20Fn) EncodeAbi() ([]byte, error) { - return RootERC20.Abi.Methods["mint"].Encode(m) -} - -func (m *MintRootERC20Fn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC20.Abi.Methods["mint"], buf, m) -} - -type InitializeRootERC1155PredicateFn struct { - NewStateSender types.Address `abi:"newStateSender"` - NewExitHelper types.Address `abi:"newExitHelper"` - NewChildERC1155Predicate types.Address `abi:"newChildERC1155Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeRootERC1155PredicateFn) Sig() []byte { - return RootERC1155Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRootERC1155PredicateFn) EncodeAbi() ([]byte, error) { - return RootERC1155Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRootERC1155PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC1155Predicate.Abi.Methods["initialize"], buf, i) -} - -type DepositBatchRootERC1155PredicateFn struct { - RootToken types.Address `abi:"rootToken"` - Receivers []ethgo.Address `abi:"receivers"` - TokenIDs []*big.Int `abi:"tokenIds"` - Amounts []*big.Int `abi:"amounts"` -} - -func (d *DepositBatchRootERC1155PredicateFn) Sig() []byte { - return RootERC1155Predicate.Abi.Methods["depositBatch"].ID() -} - -func (d *DepositBatchRootERC1155PredicateFn) EncodeAbi() ([]byte, error) { - return RootERC1155Predicate.Abi.Methods["depositBatch"].Encode(d) -} - -func (d *DepositBatchRootERC1155PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC1155Predicate.Abi.Methods["depositBatch"], buf, d) -} - -type InitializeChildMintableERC1155PredicateFn struct { - NewStateSender types.Address `abi:"newStateSender"` - NewExitHelper types.Address `abi:"newExitHelper"` - NewRootERC1155Predicate types.Address `abi:"newRootERC1155Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeChildMintableERC1155PredicateFn) Sig() []byte { - return ChildMintableERC1155Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildMintableERC1155PredicateFn) EncodeAbi() ([]byte, error) { - return ChildMintableERC1155Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildMintableERC1155PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildMintableERC1155Predicate.Abi.Methods["initialize"], buf, i) -} - -type SetApprovalForAllRootERC1155Fn struct { - Operator types.Address `abi:"operator"` - Approved bool `abi:"approved"` -} - -func (s *SetApprovalForAllRootERC1155Fn) Sig() []byte { - return RootERC1155.Abi.Methods["setApprovalForAll"].ID() -} - -func (s *SetApprovalForAllRootERC1155Fn) EncodeAbi() ([]byte, error) { - return RootERC1155.Abi.Methods["setApprovalForAll"].Encode(s) -} - -func (s *SetApprovalForAllRootERC1155Fn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC1155.Abi.Methods["setApprovalForAll"], buf, s) -} - -type MintBatchRootERC1155Fn struct { - To types.Address `abi:"to"` - IDs []*big.Int `abi:"ids"` - Amounts []*big.Int `abi:"amounts"` - Data []byte `abi:"data"` -} - -func (m *MintBatchRootERC1155Fn) Sig() []byte { - return RootERC1155.Abi.Methods["mintBatch"].ID() -} - -func (m *MintBatchRootERC1155Fn) EncodeAbi() ([]byte, error) { - return RootERC1155.Abi.Methods["mintBatch"].Encode(m) -} - -func (m *MintBatchRootERC1155Fn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC1155.Abi.Methods["mintBatch"], buf, m) -} - -type BalanceOfRootERC1155Fn struct { - Account types.Address `abi:"account"` - ID *big.Int `abi:"id"` -} - -func (b *BalanceOfRootERC1155Fn) Sig() []byte { - return RootERC1155.Abi.Methods["balanceOf"].ID() -} - -func (b *BalanceOfRootERC1155Fn) EncodeAbi() ([]byte, error) { - return RootERC1155.Abi.Methods["balanceOf"].Encode(b) -} - -func (b *BalanceOfRootERC1155Fn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC1155.Abi.Methods["balanceOf"], buf, b) -} - -type InitializeChildERC1155PredicateFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewRootERC1155Predicate types.Address `abi:"newRootERC1155Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeChildERC1155PredicateFn) Sig() []byte { - return ChildERC1155Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildERC1155PredicateFn) EncodeAbi() ([]byte, error) { - return ChildERC1155Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildERC1155PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC1155Predicate.Abi.Methods["initialize"], buf, i) -} - -type WithdrawBatchChildERC1155PredicateFn struct { - ChildToken types.Address `abi:"childToken"` - Receivers []ethgo.Address `abi:"receivers"` - TokenIDs []*big.Int `abi:"tokenIds"` - Amounts []*big.Int `abi:"amounts"` -} - -func (w *WithdrawBatchChildERC1155PredicateFn) Sig() []byte { - return ChildERC1155Predicate.Abi.Methods["withdrawBatch"].ID() -} - -func (w *WithdrawBatchChildERC1155PredicateFn) EncodeAbi() ([]byte, error) { - return ChildERC1155Predicate.Abi.Methods["withdrawBatch"].Encode(w) -} - -func (w *WithdrawBatchChildERC1155PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC1155Predicate.Abi.Methods["withdrawBatch"], buf, w) -} - -type InitializeChildERC1155PredicateACLFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewRootERC1155Predicate types.Address `abi:"newRootERC1155Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` - NewUseAllowList bool `abi:"newUseAllowList"` - NewUseBlockList bool `abi:"newUseBlockList"` - NewOwner types.Address `abi:"newOwner"` -} - -func (i *InitializeChildERC1155PredicateACLFn) Sig() []byte { - return ChildERC1155PredicateACL.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildERC1155PredicateACLFn) EncodeAbi() ([]byte, error) { - return ChildERC1155PredicateACL.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildERC1155PredicateACLFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC1155PredicateACL.Abi.Methods["initialize"], buf, i) -} - -type WithdrawBatchChildERC1155PredicateACLFn struct { - ChildToken types.Address `abi:"childToken"` - Receivers []ethgo.Address `abi:"receivers"` - TokenIDs []*big.Int `abi:"tokenIds"` - Amounts []*big.Int `abi:"amounts"` -} - -func (w *WithdrawBatchChildERC1155PredicateACLFn) Sig() []byte { - return ChildERC1155PredicateACL.Abi.Methods["withdrawBatch"].ID() -} - -func (w *WithdrawBatchChildERC1155PredicateACLFn) EncodeAbi() ([]byte, error) { - return ChildERC1155PredicateACL.Abi.Methods["withdrawBatch"].Encode(w) -} - -func (w *WithdrawBatchChildERC1155PredicateACLFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC1155PredicateACL.Abi.Methods["withdrawBatch"], buf, w) -} - -type InitializeRootMintableERC1155PredicateFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewChildERC1155Predicate types.Address `abi:"newChildERC1155Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeRootMintableERC1155PredicateFn) Sig() []byte { - return RootMintableERC1155Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRootMintableERC1155PredicateFn) EncodeAbi() ([]byte, error) { - return RootMintableERC1155Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRootMintableERC1155PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootMintableERC1155Predicate.Abi.Methods["initialize"], buf, i) -} - -type InitializeRootMintableERC1155PredicateACLFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewChildERC1155Predicate types.Address `abi:"newChildERC1155Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` - NewUseAllowList bool `abi:"newUseAllowList"` - NewUseBlockList bool `abi:"newUseBlockList"` - NewOwner types.Address `abi:"newOwner"` -} - -func (i *InitializeRootMintableERC1155PredicateACLFn) Sig() []byte { - return RootMintableERC1155PredicateACL.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRootMintableERC1155PredicateACLFn) EncodeAbi() ([]byte, error) { - return RootMintableERC1155PredicateACL.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRootMintableERC1155PredicateACLFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootMintableERC1155PredicateACL.Abi.Methods["initialize"], buf, i) -} - -type L2MintableTokenMappedEvent struct { - RootToken types.Address `abi:"rootToken"` - ChildToken types.Address `abi:"childToken"` -} - -func (*L2MintableTokenMappedEvent) Sig() ethgo.Hash { - return RootMintableERC1155PredicateACL.Abi.Events["L2MintableTokenMapped"].ID() -} - -func (l *L2MintableTokenMappedEvent) Encode() ([]byte, error) { - return RootMintableERC1155PredicateACL.Abi.Events["L2MintableTokenMapped"].Inputs.Encode(l) -} - -func (l *L2MintableTokenMappedEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !RootMintableERC1155PredicateACL.Abi.Events["L2MintableTokenMapped"].Match(log) { - return false, nil - } - - return true, decodeEvent(RootMintableERC1155PredicateACL.Abi.Events["L2MintableTokenMapped"], log, l) -} - -func (l *L2MintableTokenMappedEvent) Decode(input []byte) error { - return RootMintableERC1155PredicateACL.Abi.Events["L2MintableTokenMapped"].Inputs.DecodeStruct(input, &l) -} - -type InitializeChildERC1155Fn struct { - RootToken_ types.Address `abi:"rootToken_"` - Uri_ string `abi:"uri_"` -} - -func (i *InitializeChildERC1155Fn) Sig() []byte { - return ChildERC1155.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildERC1155Fn) EncodeAbi() ([]byte, error) { - return ChildERC1155.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildERC1155Fn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC1155.Abi.Methods["initialize"], buf, i) -} - -type BalanceOfChildERC1155Fn struct { - Account types.Address `abi:"account"` - ID *big.Int `abi:"id"` -} - -func (b *BalanceOfChildERC1155Fn) Sig() []byte { - return ChildERC1155.Abi.Methods["balanceOf"].ID() -} - -func (b *BalanceOfChildERC1155Fn) EncodeAbi() ([]byte, error) { - return ChildERC1155.Abi.Methods["balanceOf"].Encode(b) -} - -func (b *BalanceOfChildERC1155Fn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC1155.Abi.Methods["balanceOf"], buf, b) -} - -type InitializeRootERC721PredicateFn struct { - NewStateSender types.Address `abi:"newStateSender"` - NewExitHelper types.Address `abi:"newExitHelper"` - NewChildERC721Predicate types.Address `abi:"newChildERC721Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeRootERC721PredicateFn) Sig() []byte { - return RootERC721Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRootERC721PredicateFn) EncodeAbi() ([]byte, error) { - return RootERC721Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRootERC721PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC721Predicate.Abi.Methods["initialize"], buf, i) -} - -type DepositBatchRootERC721PredicateFn struct { - RootToken types.Address `abi:"rootToken"` - Receivers []ethgo.Address `abi:"receivers"` - TokenIDs []*big.Int `abi:"tokenIds"` -} - -func (d *DepositBatchRootERC721PredicateFn) Sig() []byte { - return RootERC721Predicate.Abi.Methods["depositBatch"].ID() -} - -func (d *DepositBatchRootERC721PredicateFn) EncodeAbi() ([]byte, error) { - return RootERC721Predicate.Abi.Methods["depositBatch"].Encode(d) -} - -func (d *DepositBatchRootERC721PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC721Predicate.Abi.Methods["depositBatch"], buf, d) -} - -type InitializeChildMintableERC721PredicateFn struct { - NewStateSender types.Address `abi:"newStateSender"` - NewExitHelper types.Address `abi:"newExitHelper"` - NewRootERC721Predicate types.Address `abi:"newRootERC721Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeChildMintableERC721PredicateFn) Sig() []byte { - return ChildMintableERC721Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildMintableERC721PredicateFn) EncodeAbi() ([]byte, error) { - return ChildMintableERC721Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildMintableERC721PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildMintableERC721Predicate.Abi.Methods["initialize"], buf, i) -} - -type SetApprovalForAllRootERC721Fn struct { - Operator types.Address `abi:"operator"` - Approved bool `abi:"approved"` -} - -func (s *SetApprovalForAllRootERC721Fn) Sig() []byte { - return RootERC721.Abi.Methods["setApprovalForAll"].ID() -} - -func (s *SetApprovalForAllRootERC721Fn) EncodeAbi() ([]byte, error) { - return RootERC721.Abi.Methods["setApprovalForAll"].Encode(s) -} - -func (s *SetApprovalForAllRootERC721Fn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC721.Abi.Methods["setApprovalForAll"], buf, s) -} - -type MintRootERC721Fn struct { - To types.Address `abi:"to"` -} - -func (m *MintRootERC721Fn) Sig() []byte { - return RootERC721.Abi.Methods["mint"].ID() -} - -func (m *MintRootERC721Fn) EncodeAbi() ([]byte, error) { - return RootERC721.Abi.Methods["mint"].Encode(m) -} - -func (m *MintRootERC721Fn) DecodeAbi(buf []byte) error { - return decodeMethod(RootERC721.Abi.Methods["mint"], buf, m) -} - -type InitializeChildERC721PredicateFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewRootERC721Predicate types.Address `abi:"newRootERC721Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeChildERC721PredicateFn) Sig() []byte { - return ChildERC721Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildERC721PredicateFn) EncodeAbi() ([]byte, error) { - return ChildERC721Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildERC721PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC721Predicate.Abi.Methods["initialize"], buf, i) -} - -type WithdrawBatchChildERC721PredicateFn struct { - ChildToken types.Address `abi:"childToken"` - Receivers []ethgo.Address `abi:"receivers"` - TokenIDs []*big.Int `abi:"tokenIds"` -} - -func (w *WithdrawBatchChildERC721PredicateFn) Sig() []byte { - return ChildERC721Predicate.Abi.Methods["withdrawBatch"].ID() -} - -func (w *WithdrawBatchChildERC721PredicateFn) EncodeAbi() ([]byte, error) { - return ChildERC721Predicate.Abi.Methods["withdrawBatch"].Encode(w) -} - -func (w *WithdrawBatchChildERC721PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC721Predicate.Abi.Methods["withdrawBatch"], buf, w) -} - -type InitializeChildERC721PredicateACLFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewRootERC721Predicate types.Address `abi:"newRootERC721Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` - NewUseAllowList bool `abi:"newUseAllowList"` - NewUseBlockList bool `abi:"newUseBlockList"` - NewOwner types.Address `abi:"newOwner"` -} - -func (i *InitializeChildERC721PredicateACLFn) Sig() []byte { - return ChildERC721PredicateACL.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildERC721PredicateACLFn) EncodeAbi() ([]byte, error) { - return ChildERC721PredicateACL.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildERC721PredicateACLFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC721PredicateACL.Abi.Methods["initialize"], buf, i) -} - -type WithdrawBatchChildERC721PredicateACLFn struct { - ChildToken types.Address `abi:"childToken"` - Receivers []ethgo.Address `abi:"receivers"` - TokenIDs []*big.Int `abi:"tokenIds"` -} - -func (w *WithdrawBatchChildERC721PredicateACLFn) Sig() []byte { - return ChildERC721PredicateACL.Abi.Methods["withdrawBatch"].ID() -} - -func (w *WithdrawBatchChildERC721PredicateACLFn) EncodeAbi() ([]byte, error) { - return ChildERC721PredicateACL.Abi.Methods["withdrawBatch"].Encode(w) -} - -func (w *WithdrawBatchChildERC721PredicateACLFn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC721PredicateACL.Abi.Methods["withdrawBatch"], buf, w) -} - -type InitializeRootMintableERC721PredicateFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewChildERC721Predicate types.Address `abi:"newChildERC721Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` -} - -func (i *InitializeRootMintableERC721PredicateFn) Sig() []byte { - return RootMintableERC721Predicate.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRootMintableERC721PredicateFn) EncodeAbi() ([]byte, error) { - return RootMintableERC721Predicate.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRootMintableERC721PredicateFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootMintableERC721Predicate.Abi.Methods["initialize"], buf, i) -} - -type InitializeRootMintableERC721PredicateACLFn struct { - NewL2StateSender types.Address `abi:"newL2StateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewChildERC721Predicate types.Address `abi:"newChildERC721Predicate"` - NewChildTokenTemplate types.Address `abi:"newChildTokenTemplate"` - NewUseAllowList bool `abi:"newUseAllowList"` - NewUseBlockList bool `abi:"newUseBlockList"` - NewOwner types.Address `abi:"newOwner"` -} - -func (i *InitializeRootMintableERC721PredicateACLFn) Sig() []byte { - return RootMintableERC721PredicateACL.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRootMintableERC721PredicateACLFn) EncodeAbi() ([]byte, error) { - return RootMintableERC721PredicateACL.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRootMintableERC721PredicateACLFn) DecodeAbi(buf []byte) error { - return decodeMethod(RootMintableERC721PredicateACL.Abi.Methods["initialize"], buf, i) -} - -type InitializeChildERC721Fn struct { - RootToken_ types.Address `abi:"rootToken_"` - Name_ string `abi:"name_"` - Symbol_ string `abi:"symbol_"` -} - -func (i *InitializeChildERC721Fn) Sig() []byte { - return ChildERC721.Abi.Methods["initialize"].ID() -} - -func (i *InitializeChildERC721Fn) EncodeAbi() ([]byte, error) { - return ChildERC721.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeChildERC721Fn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC721.Abi.Methods["initialize"], buf, i) -} - -type OwnerOfChildERC721Fn struct { - TokenID *big.Int `abi:"tokenId"` -} - -func (o *OwnerOfChildERC721Fn) Sig() []byte { - return ChildERC721.Abi.Methods["ownerOf"].ID() -} - -func (o *OwnerOfChildERC721Fn) EncodeAbi() ([]byte, error) { - return ChildERC721.Abi.Methods["ownerOf"].Encode(o) -} - -func (o *OwnerOfChildERC721Fn) DecodeAbi(buf []byte) error { - return decodeMethod(ChildERC721.Abi.Methods["ownerOf"], buf, o) -} - -type InitializeCustomSupernetManagerFn struct { - NewStakeManager types.Address `abi:"newStakeManager"` - NewBls types.Address `abi:"newBls"` - NewStateSender types.Address `abi:"newStateSender"` - NewMatic types.Address `abi:"newMatic"` - NewChildValidatorSet types.Address `abi:"newChildValidatorSet"` - NewExitHelper types.Address `abi:"newExitHelper"` - NewRootERC20Predicate types.Address `abi:"newRootERC20Predicate"` - NewDomain string `abi:"newDomain"` -} - -func (i *InitializeCustomSupernetManagerFn) Sig() []byte { - return CustomSupernetManager.Abi.Methods["initialize"].ID() -} - -func (i *InitializeCustomSupernetManagerFn) EncodeAbi() ([]byte, error) { - return CustomSupernetManager.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeCustomSupernetManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(CustomSupernetManager.Abi.Methods["initialize"], buf, i) -} - -type WhitelistValidatorsCustomSupernetManagerFn struct { - Validators_ []ethgo.Address `abi:"validators_"` -} - -func (w *WhitelistValidatorsCustomSupernetManagerFn) Sig() []byte { - return CustomSupernetManager.Abi.Methods["whitelistValidators"].ID() -} - -func (w *WhitelistValidatorsCustomSupernetManagerFn) EncodeAbi() ([]byte, error) { - return CustomSupernetManager.Abi.Methods["whitelistValidators"].Encode(w) -} - -func (w *WhitelistValidatorsCustomSupernetManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(CustomSupernetManager.Abi.Methods["whitelistValidators"], buf, w) -} - -type RegisterCustomSupernetManagerFn struct { - Signature [2]*big.Int `abi:"signature"` - Pubkey [4]*big.Int `abi:"pubkey"` -} - -func (r *RegisterCustomSupernetManagerFn) Sig() []byte { - return CustomSupernetManager.Abi.Methods["register"].ID() -} - -func (r *RegisterCustomSupernetManagerFn) EncodeAbi() ([]byte, error) { - return CustomSupernetManager.Abi.Methods["register"].Encode(r) -} - -func (r *RegisterCustomSupernetManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(CustomSupernetManager.Abi.Methods["register"], buf, r) -} - -type GetValidatorCustomSupernetManagerFn struct { - Validator_ types.Address `abi:"validator_"` -} - -func (g *GetValidatorCustomSupernetManagerFn) Sig() []byte { - return CustomSupernetManager.Abi.Methods["getValidator"].ID() -} - -func (g *GetValidatorCustomSupernetManagerFn) EncodeAbi() ([]byte, error) { - return CustomSupernetManager.Abi.Methods["getValidator"].Encode(g) -} - -func (g *GetValidatorCustomSupernetManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(CustomSupernetManager.Abi.Methods["getValidator"], buf, g) -} - -type AddGenesisBalanceCustomSupernetManagerFn struct { - Amount *big.Int `abi:"amount"` -} - -func (a *AddGenesisBalanceCustomSupernetManagerFn) Sig() []byte { - return CustomSupernetManager.Abi.Methods["addGenesisBalance"].ID() -} - -func (a *AddGenesisBalanceCustomSupernetManagerFn) EncodeAbi() ([]byte, error) { - return CustomSupernetManager.Abi.Methods["addGenesisBalance"].Encode(a) -} - -func (a *AddGenesisBalanceCustomSupernetManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(CustomSupernetManager.Abi.Methods["addGenesisBalance"], buf, a) -} - -type ValidatorRegisteredEvent struct { - Validator types.Address `abi:"validator"` - BlsKey [4]*big.Int `abi:"blsKey"` -} - -func (*ValidatorRegisteredEvent) Sig() ethgo.Hash { - return CustomSupernetManager.Abi.Events["ValidatorRegistered"].ID() -} - -func (v *ValidatorRegisteredEvent) Encode() ([]byte, error) { - return CustomSupernetManager.Abi.Events["ValidatorRegistered"].Inputs.Encode(v) -} - -func (v *ValidatorRegisteredEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !CustomSupernetManager.Abi.Events["ValidatorRegistered"].Match(log) { - return false, nil - } - - return true, decodeEvent(CustomSupernetManager.Abi.Events["ValidatorRegistered"], log, v) -} - -func (v *ValidatorRegisteredEvent) Decode(input []byte) error { - return CustomSupernetManager.Abi.Events["ValidatorRegistered"].Inputs.DecodeStruct(input, &v) -} - -type AddedToWhitelistEvent struct { - Validator types.Address `abi:"validator"` -} - -func (*AddedToWhitelistEvent) Sig() ethgo.Hash { - return CustomSupernetManager.Abi.Events["AddedToWhitelist"].ID() -} - -func (a *AddedToWhitelistEvent) Encode() ([]byte, error) { - return CustomSupernetManager.Abi.Events["AddedToWhitelist"].Inputs.Encode(a) -} - -func (a *AddedToWhitelistEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !CustomSupernetManager.Abi.Events["AddedToWhitelist"].Match(log) { - return false, nil - } - - return true, decodeEvent(CustomSupernetManager.Abi.Events["AddedToWhitelist"], log, a) -} - -func (a *AddedToWhitelistEvent) Decode(input []byte) error { - return CustomSupernetManager.Abi.Events["AddedToWhitelist"].Inputs.DecodeStruct(input, &a) -} - -type InitializeStakeManagerFn struct { - NewStakingToken types.Address `abi:"newStakingToken"` -} - -func (i *InitializeStakeManagerFn) Sig() []byte { - return StakeManager.Abi.Methods["initialize"].ID() -} - -func (i *InitializeStakeManagerFn) EncodeAbi() ([]byte, error) { - return StakeManager.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeStakeManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(StakeManager.Abi.Methods["initialize"], buf, i) -} - -type RegisterChildChainStakeManagerFn struct { - Manager types.Address `abi:"manager"` -} - -func (r *RegisterChildChainStakeManagerFn) Sig() []byte { - return StakeManager.Abi.Methods["registerChildChain"].ID() -} - -func (r *RegisterChildChainStakeManagerFn) EncodeAbi() ([]byte, error) { - return StakeManager.Abi.Methods["registerChildChain"].Encode(r) -} - -func (r *RegisterChildChainStakeManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(StakeManager.Abi.Methods["registerChildChain"], buf, r) -} - -type StakeForStakeManagerFn struct { - ID *big.Int `abi:"id"` - Amount *big.Int `abi:"amount"` -} - -func (s *StakeForStakeManagerFn) Sig() []byte { - return StakeManager.Abi.Methods["stakeFor"].ID() -} - -func (s *StakeForStakeManagerFn) EncodeAbi() ([]byte, error) { - return StakeManager.Abi.Methods["stakeFor"].Encode(s) -} - -func (s *StakeForStakeManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(StakeManager.Abi.Methods["stakeFor"], buf, s) -} - -type ReleaseStakeOfStakeManagerFn struct { - Validator types.Address `abi:"validator"` - Amount *big.Int `abi:"amount"` -} - -func (r *ReleaseStakeOfStakeManagerFn) Sig() []byte { - return StakeManager.Abi.Methods["releaseStakeOf"].ID() -} - -func (r *ReleaseStakeOfStakeManagerFn) EncodeAbi() ([]byte, error) { - return StakeManager.Abi.Methods["releaseStakeOf"].Encode(r) -} - -func (r *ReleaseStakeOfStakeManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(StakeManager.Abi.Methods["releaseStakeOf"], buf, r) -} - -type WithdrawStakeStakeManagerFn struct { - To types.Address `abi:"to"` - Amount *big.Int `abi:"amount"` -} - -func (w *WithdrawStakeStakeManagerFn) Sig() []byte { - return StakeManager.Abi.Methods["withdrawStake"].ID() -} - -func (w *WithdrawStakeStakeManagerFn) EncodeAbi() ([]byte, error) { - return StakeManager.Abi.Methods["withdrawStake"].Encode(w) -} - -func (w *WithdrawStakeStakeManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(StakeManager.Abi.Methods["withdrawStake"], buf, w) -} - -type StakeOfStakeManagerFn struct { - Validator types.Address `abi:"validator"` - ID *big.Int `abi:"id"` -} - -func (s *StakeOfStakeManagerFn) Sig() []byte { - return StakeManager.Abi.Methods["stakeOf"].ID() -} - -func (s *StakeOfStakeManagerFn) EncodeAbi() ([]byte, error) { - return StakeManager.Abi.Methods["stakeOf"].Encode(s) -} - -func (s *StakeOfStakeManagerFn) DecodeAbi(buf []byte) error { - return decodeMethod(StakeManager.Abi.Methods["stakeOf"], buf, s) -} - -type ChildManagerRegisteredEvent struct { - ID *big.Int `abi:"id"` - Manager types.Address `abi:"manager"` -} - -func (*ChildManagerRegisteredEvent) Sig() ethgo.Hash { - return StakeManager.Abi.Events["ChildManagerRegistered"].ID() -} - -func (c *ChildManagerRegisteredEvent) Encode() ([]byte, error) { - return StakeManager.Abi.Events["ChildManagerRegistered"].Inputs.Encode(c) -} - -func (c *ChildManagerRegisteredEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !StakeManager.Abi.Events["ChildManagerRegistered"].Match(log) { - return false, nil - } - - return true, decodeEvent(StakeManager.Abi.Events["ChildManagerRegistered"], log, c) -} - -func (c *ChildManagerRegisteredEvent) Decode(input []byte) error { - return StakeManager.Abi.Events["ChildManagerRegistered"].Inputs.DecodeStruct(input, &c) -} - -type StakeAddedEvent struct { - ID *big.Int `abi:"id"` - Validator types.Address `abi:"validator"` - Amount *big.Int `abi:"amount"` -} - -func (*StakeAddedEvent) Sig() ethgo.Hash { - return StakeManager.Abi.Events["StakeAdded"].ID() -} - -func (s *StakeAddedEvent) Encode() ([]byte, error) { - return StakeManager.Abi.Events["StakeAdded"].Inputs.Encode(s) -} - -func (s *StakeAddedEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !StakeManager.Abi.Events["StakeAdded"].Match(log) { - return false, nil - } - - return true, decodeEvent(StakeManager.Abi.Events["StakeAdded"], log, s) -} - -func (s *StakeAddedEvent) Decode(input []byte) error { - return StakeManager.Abi.Events["StakeAdded"].Inputs.DecodeStruct(input, &s) -} - -type StakeWithdrawnEvent struct { - Validator types.Address `abi:"validator"` - Recipient types.Address `abi:"recipient"` - Amount *big.Int `abi:"amount"` -} - -func (*StakeWithdrawnEvent) Sig() ethgo.Hash { - return StakeManager.Abi.Events["StakeWithdrawn"].ID() -} - -func (s *StakeWithdrawnEvent) Encode() ([]byte, error) { - return StakeManager.Abi.Events["StakeWithdrawn"].Inputs.Encode(s) -} - -func (s *StakeWithdrawnEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !StakeManager.Abi.Events["StakeWithdrawn"].Match(log) { - return false, nil - } - - return true, decodeEvent(StakeManager.Abi.Events["StakeWithdrawn"], log, s) -} - -func (s *StakeWithdrawnEvent) Decode(input []byte) error { - return StakeManager.Abi.Events["StakeWithdrawn"].Inputs.DecodeStruct(input, &s) -} - -type Epoch struct { - StartBlock *big.Int `abi:"startBlock"` - EndBlock *big.Int `abi:"endBlock"` - EpochRoot types.Hash `abi:"epochRoot"` -} - -var EpochABIType = abi.MustNewType("tuple(uint256 startBlock,uint256 endBlock,bytes32 epochRoot)") - -func (e *Epoch) EncodeAbi() ([]byte, error) { - return EpochABIType.Encode(e) -} - -func (e *Epoch) DecodeAbi(buf []byte) error { - return decodeStruct(EpochABIType, buf, &e) -} - -type CommitEpochValidatorSetFn struct { - ID *big.Int `abi:"id"` - Epoch *Epoch `abi:"epoch"` -} - -func (c *CommitEpochValidatorSetFn) Sig() []byte { - return ValidatorSet.Abi.Methods["commitEpoch"].ID() -} - -func (c *CommitEpochValidatorSetFn) EncodeAbi() ([]byte, error) { - return ValidatorSet.Abi.Methods["commitEpoch"].Encode(c) -} - -func (c *CommitEpochValidatorSetFn) DecodeAbi(buf []byte) error { - return decodeMethod(ValidatorSet.Abi.Methods["commitEpoch"], buf, c) -} - -type UnstakeValidatorSetFn struct { - Amount *big.Int `abi:"amount"` -} - -func (u *UnstakeValidatorSetFn) Sig() []byte { - return ValidatorSet.Abi.Methods["unstake"].ID() -} - -func (u *UnstakeValidatorSetFn) EncodeAbi() ([]byte, error) { - return ValidatorSet.Abi.Methods["unstake"].Encode(u) -} - -func (u *UnstakeValidatorSetFn) DecodeAbi(buf []byte) error { - return decodeMethod(ValidatorSet.Abi.Methods["unstake"], buf, u) -} - -type ValidatorInit struct { - Addr types.Address `abi:"addr"` - Stake *big.Int `abi:"stake"` -} - -var ValidatorInitABIType = abi.MustNewType("tuple(address addr,uint256 stake)") - -func (v *ValidatorInit) EncodeAbi() ([]byte, error) { - return ValidatorInitABIType.Encode(v) -} - -func (v *ValidatorInit) DecodeAbi(buf []byte) error { - return decodeStruct(ValidatorInitABIType, buf, &v) -} - -type InitializeValidatorSetFn struct { - NewStateSender types.Address `abi:"newStateSender"` - NewStateReceiver types.Address `abi:"newStateReceiver"` - NewRootChainManager types.Address `abi:"newRootChainManager"` - NewEpochSize *big.Int `abi:"newEpochSize"` - InitialValidators []*ValidatorInit `abi:"initialValidators"` -} - -func (i *InitializeValidatorSetFn) Sig() []byte { - return ValidatorSet.Abi.Methods["initialize"].ID() -} - -func (i *InitializeValidatorSetFn) EncodeAbi() ([]byte, error) { - return ValidatorSet.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeValidatorSetFn) DecodeAbi(buf []byte) error { - return decodeMethod(ValidatorSet.Abi.Methods["initialize"], buf, i) -} - -type TransferEvent struct { - From types.Address `abi:"from"` - To types.Address `abi:"to"` - Value *big.Int `abi:"value"` -} - -func (*TransferEvent) Sig() ethgo.Hash { - return ValidatorSet.Abi.Events["Transfer"].ID() -} - -func (t *TransferEvent) Encode() ([]byte, error) { - return ValidatorSet.Abi.Events["Transfer"].Inputs.Encode(t) -} - -func (t *TransferEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !ValidatorSet.Abi.Events["Transfer"].Match(log) { - return false, nil - } - - return true, decodeEvent(ValidatorSet.Abi.Events["Transfer"], log, t) -} - -func (t *TransferEvent) Decode(input []byte) error { - return ValidatorSet.Abi.Events["Transfer"].Inputs.DecodeStruct(input, &t) -} - -type WithdrawalRegisteredEvent struct { - Account types.Address `abi:"account"` - Amount *big.Int `abi:"amount"` -} - -func (*WithdrawalRegisteredEvent) Sig() ethgo.Hash { - return ValidatorSet.Abi.Events["WithdrawalRegistered"].ID() -} - -func (w *WithdrawalRegisteredEvent) Encode() ([]byte, error) { - return ValidatorSet.Abi.Events["WithdrawalRegistered"].Inputs.Encode(w) -} - -func (w *WithdrawalRegisteredEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !ValidatorSet.Abi.Events["WithdrawalRegistered"].Match(log) { - return false, nil - } - - return true, decodeEvent(ValidatorSet.Abi.Events["WithdrawalRegistered"], log, w) -} - -func (w *WithdrawalRegisteredEvent) Decode(input []byte) error { - return ValidatorSet.Abi.Events["WithdrawalRegistered"].Inputs.DecodeStruct(input, &w) -} - -type WithdrawalEvent struct { - Account types.Address `abi:"account"` - Amount *big.Int `abi:"amount"` -} - -func (*WithdrawalEvent) Sig() ethgo.Hash { - return ValidatorSet.Abi.Events["Withdrawal"].ID() -} - -func (w *WithdrawalEvent) Encode() ([]byte, error) { - return ValidatorSet.Abi.Events["Withdrawal"].Inputs.Encode(w) -} - -func (w *WithdrawalEvent) ParseLog(log *ethgo.Log) (bool, error) { - if !ValidatorSet.Abi.Events["Withdrawal"].Match(log) { - return false, nil - } - - return true, decodeEvent(ValidatorSet.Abi.Events["Withdrawal"], log, w) -} - -func (w *WithdrawalEvent) Decode(input []byte) error { - return ValidatorSet.Abi.Events["Withdrawal"].Inputs.DecodeStruct(input, &w) -} - -type InitializeRewardPoolFn struct { - NewRewardToken types.Address `abi:"newRewardToken"` - NewRewardWallet types.Address `abi:"newRewardWallet"` - NewValidatorSet types.Address `abi:"newValidatorSet"` - NewBaseReward *big.Int `abi:"newBaseReward"` -} - -func (i *InitializeRewardPoolFn) Sig() []byte { - return RewardPool.Abi.Methods["initialize"].ID() -} - -func (i *InitializeRewardPoolFn) EncodeAbi() ([]byte, error) { - return RewardPool.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeRewardPoolFn) DecodeAbi(buf []byte) error { - return decodeMethod(RewardPool.Abi.Methods["initialize"], buf, i) -} - -type Uptime struct { - Validator types.Address `abi:"validator"` - SignedBlocks *big.Int `abi:"signedBlocks"` -} - -var UptimeABIType = abi.MustNewType("tuple(address validator,uint256 signedBlocks)") - -func (u *Uptime) EncodeAbi() ([]byte, error) { - return UptimeABIType.Encode(u) -} - -func (u *Uptime) DecodeAbi(buf []byte) error { - return decodeStruct(UptimeABIType, buf, &u) -} - -type DistributeRewardForRewardPoolFn struct { - EpochID *big.Int `abi:"epochId"` - Uptime []*Uptime `abi:"uptime"` -} - -func (d *DistributeRewardForRewardPoolFn) Sig() []byte { - return RewardPool.Abi.Methods["distributeRewardFor"].ID() -} - -func (d *DistributeRewardForRewardPoolFn) EncodeAbi() ([]byte, error) { - return RewardPool.Abi.Methods["distributeRewardFor"].Encode(d) -} - -func (d *DistributeRewardForRewardPoolFn) DecodeAbi(buf []byte) error { - return decodeMethod(RewardPool.Abi.Methods["distributeRewardFor"], buf, d) -} - -type InitializeEIP1559BurnFn struct { - NewChildERC20Predicate types.Address `abi:"newChildERC20Predicate"` - NewBurnDestination types.Address `abi:"newBurnDestination"` -} - -func (i *InitializeEIP1559BurnFn) Sig() []byte { - return EIP1559Burn.Abi.Methods["initialize"].ID() -} - -func (i *InitializeEIP1559BurnFn) EncodeAbi() ([]byte, error) { - return EIP1559Burn.Abi.Methods["initialize"].Encode(i) -} - -func (i *InitializeEIP1559BurnFn) DecodeAbi(buf []byte) error { - return decodeMethod(EIP1559Burn.Abi.Methods["initialize"], buf, i) -} - -type ProtectSetUpProxyGenesisProxyFn struct { - Initiator types.Address `abi:"initiator"` -} - -func (p *ProtectSetUpProxyGenesisProxyFn) Sig() []byte { - return GenesisProxy.Abi.Methods["protectSetUpProxy"].ID() -} - -func (p *ProtectSetUpProxyGenesisProxyFn) EncodeAbi() ([]byte, error) { - return GenesisProxy.Abi.Methods["protectSetUpProxy"].Encode(p) -} - -func (p *ProtectSetUpProxyGenesisProxyFn) DecodeAbi(buf []byte) error { - return decodeMethod(GenesisProxy.Abi.Methods["protectSetUpProxy"], buf, p) -} - -type SetUpProxyGenesisProxyFn struct { - Logic types.Address `abi:"logic"` - Admin types.Address `abi:"admin"` - Data []byte `abi:"data"` -} - -func (s *SetUpProxyGenesisProxyFn) Sig() []byte { - return GenesisProxy.Abi.Methods["setUpProxy"].ID() -} - -func (s *SetUpProxyGenesisProxyFn) EncodeAbi() ([]byte, error) { - return GenesisProxy.Abi.Methods["setUpProxy"].Encode(s) -} - -func (s *SetUpProxyGenesisProxyFn) DecodeAbi(buf []byte) error { - return decodeMethod(GenesisProxy.Abi.Methods["setUpProxy"], buf, s) -} - -type TransparentUpgradeableProxyConstructorFn struct { - Logic types.Address `abi:"_logic"` - Admin_ types.Address `abi:"admin_"` - Data []byte `abi:"_data"` -} - -func (t *TransparentUpgradeableProxyConstructorFn) Sig() []byte { - return TransparentUpgradeableProxy.Abi.Constructor.ID() -} - -func (t *TransparentUpgradeableProxyConstructorFn) EncodeAbi() ([]byte, error) { - return TransparentUpgradeableProxy.Abi.Constructor.Inputs.Encode(t) -} - -func (t *TransparentUpgradeableProxyConstructorFn) DecodeAbi(buf []byte) error { - return decodeMethod(TransparentUpgradeableProxy.Abi.Constructor, buf, t) -} diff --git a/consensus/polybft/contractsapi/contractsapi_test.go b/consensus/polybft/contractsapi/contractsapi_test.go deleted file mode 100644 index fae755f..0000000 --- a/consensus/polybft/contractsapi/contractsapi_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package contractsapi - -import ( - "math/big" - "reflect" - "testing" - - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/types" -) - -type method interface { - EncodeAbi() ([]byte, error) - DecodeAbi(buf []byte) error -} - -func TestEncoding_Method(t *testing.T) { - t.Parallel() - - cases := []method{ - // empty commit - &CommitStateReceiverFn{ - Commitment: &StateSyncCommitment{ - StartID: big.NewInt(1), - EndID: big.NewInt(1), - Root: types.EmptyRootHash, - }, - Signature: []byte{}, - Bitmap: []byte{}, - }, - // empty commit epoch - &CommitEpochValidatorSetFn{ - ID: big.NewInt(1), - Epoch: &Epoch{ - StartBlock: big.NewInt(1), - EndBlock: big.NewInt(1), - }, - }, - } - - for _, c := range cases { - res, err := c.EncodeAbi() - require.NoError(t, err) - - // use reflection to create another type and decode - val := reflect.New(reflect.TypeOf(c).Elem()).Interface() - obj, ok := val.(method) - require.True(t, ok) - - err = obj.DecodeAbi(res) - require.NoError(t, err) - require.Equal(t, obj, c) - } -} - -func TestEncoding_Struct(t *testing.T) { - t.Parallel() - - commitment := &StateSyncCommitment{ - StartID: big.NewInt(1), - EndID: big.NewInt(10), - Root: types.StringToHash("hash"), - } - - encoding, err := commitment.EncodeAbi() - require.NoError(t, err) - - var commitmentDecoded StateSyncCommitment - - require.NoError(t, commitmentDecoded.DecodeAbi(encoding)) - require.Equal(t, commitment.StartID, commitmentDecoded.StartID) - require.Equal(t, commitment.EndID, commitmentDecoded.EndID) - require.Equal(t, commitment.Root, commitmentDecoded.Root) -} - -func TestEncodingAndParsingEvent(t *testing.T) { - t.Parallel() - - var ( - exitEventAPI L2StateSyncedEvent - stateSyncEventAPI StateSyncedEvent - ) - - topics := make([]ethgo.Hash, 4) - topics[0] = exitEventAPI.Sig() - topics[1] = ethgo.BytesToHash(common.EncodeUint64ToBytes(11)) - topics[2] = ethgo.BytesToHash(types.StringToAddress("0x1111").Bytes()) - topics[3] = ethgo.BytesToHash(types.StringToAddress("0x2222").Bytes()) - someType := abi.MustNewType("tuple(string firstName, string lastName)") - encodedData, err := someType.Encode(map[string]string{"firstName": "John", "lastName": "Doe"}) - require.NoError(t, err) - - log := ðgo.Log{ - Address: ethgo.Address(contracts.L2StateSenderContract), - Topics: topics, - Data: encodedData, - } - - var exitEvent L2StateSyncedEvent - - // log matches event - doesMatch, err := exitEvent.ParseLog(log) - require.NoError(t, err) - require.True(t, doesMatch) - require.Equal(t, uint64(11), exitEvent.ID.Uint64()) - - // change exit event id - log.Topics[1] = ethgo.BytesToHash(common.EncodeUint64ToBytes(22)) - doesMatch, err = exitEvent.ParseLog(log) - require.NoError(t, err) - require.True(t, doesMatch) - require.Equal(t, uint64(22), exitEvent.ID.Uint64()) - - // log does not match event - log.Topics[0] = stateSyncEventAPI.Sig() - doesMatch, err = exitEvent.ParseLog(log) - require.NoError(t, err) - require.False(t, doesMatch) - - // error on parsing log - log.Topics[0] = exitEventAPI.Sig() - log.Topics = log.Topics[:3] - doesMatch, err = exitEvent.ParseLog(log) - require.Error(t, err) - require.True(t, doesMatch) -} diff --git a/consensus/polybft/contractsapi/decoder.go b/consensus/polybft/contractsapi/decoder.go deleted file mode 100644 index 2a3852a..0000000 --- a/consensus/polybft/contractsapi/decoder.go +++ /dev/null @@ -1,99 +0,0 @@ -package contractsapi - -import ( - "bytes" - "fmt" - "math/big" - "reflect" - - "github.com/mitchellh/mapstructure" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" -) - -const abiMethodIDLength = 4 - -func decodeEvent(event *abi.Event, log *ethgo.Log, out interface{}) error { - val, err := event.ParseLog(log) - if err != nil { - return err - } - - return decodeImpl(val, out) -} - -func decodeMethod(method *abi.Method, input []byte, out interface{}) error { - if len(input) < abiMethodIDLength { - return fmt.Errorf("invalid method data, len = %d", len(input)) - } - - sig := method.ID() - if !bytes.HasPrefix(input, sig) { - return fmt.Errorf("prefix is not correct") - } - - val, err := abi.Decode(method.Inputs, input[abiMethodIDLength:]) - if err != nil { - return err - } - - return decodeImpl(val, out) -} - -func decodeStruct(t *abi.Type, input []byte, out interface{}) error { - if len(input) < abiMethodIDLength { - return fmt.Errorf("invalid struct data, len = %d", len(input)) - } - - val, err := abi.Decode(t, input) - if err != nil { - return err - } - - return decodeImpl(val, out) -} - -func decodeImpl(input interface{}, out interface{}) error { - metadata := &mapstructure.Metadata{} - dc := &mapstructure.DecoderConfig{ - Result: out, - TagName: "abi", - Metadata: metadata, - DecodeHook: customHook, - } - - ms, err := mapstructure.NewDecoder(dc) - if err != nil { - return err - } - - if err = ms.Decode(input); err != nil { - return err - } - - if len(metadata.Unused) != 0 { - return fmt.Errorf("some keys not used: %v", metadata.Unused) - } - - return nil -} - -var bigTyp = reflect.TypeOf(new(big.Int)) - -func customHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { - if f == bigTyp && t.Kind() == reflect.Uint64 { - // convert big.Int to uint64 (if possible) - b, ok := data.(*big.Int) - if !ok { - return nil, fmt.Errorf("data not a big.Int") - } - - if !b.IsUint64() { - return nil, fmt.Errorf("cannot format big.Int to uint64") - } - - return b.Uint64(), nil - } - - return data, nil -} diff --git a/consensus/polybft/contractsapi/gen_sc_data.go b/consensus/polybft/contractsapi/gen_sc_data.go deleted file mode 100644 index 0f26c30..0000000 --- a/consensus/polybft/contractsapi/gen_sc_data.go +++ /dev/null @@ -1,45 +0,0 @@ -// This is auto-generated file. DO NOT EDIT. -package contractsapi - -var L2StateSenderArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"L2StateSender","sourceName":"contracts/child/L2StateSender.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"L2StateSynced","type":"event"},{"inputs":[],"name":"MAX_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"counter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"syncState","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610297806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806316f198311461004657806361bc221a1461005b578063a6f9885c14610076575b600080fd5b61005961005436600461017a565b61007f565b005b61006460005481565b60405190815260200160405180910390f35b61006461080081565b6001600160a01b0383166100cd5760405162461bcd60e51b815260206004820152601060248201526f24a72b20a624a22fa922a1a2a4ab22a960811b60448201526064015b60405180910390fd5b6108008111156101145760405162461bcd60e51b815260206004820152601260248201527108ab0868a8a88a6be9a82b0be988a9c8ea8960731b60448201526064016100c4565b826001600160a01b0316336001600160a01b031660008081546101369061020b565b9190508190557fedaf3c471ebd67d60c29efe34b639ede7d6a1d92eaeb3f503e784971e67118a5858560405161016d929190610232565b60405180910390a4505050565b60008060006040848603121561018f57600080fd5b83356001600160a01b03811681146101a657600080fd5b9250602084013567ffffffffffffffff808211156101c357600080fd5b818601915086601f8301126101d757600080fd5b8135818111156101e657600080fd5b8760208285010111156101f857600080fd5b6020830194508093505050509250925092565b60006001820161022b57634e487b7160e01b600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f1916010191905056fea264697066735822122041815082e2111df07ce129b83f0000ecca6c4886247dffeae7090f8f048b665164736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100415760003560e01c806316f198311461004657806361bc221a1461005b578063a6f9885c14610076575b600080fd5b61005961005436600461017a565b61007f565b005b61006460005481565b60405190815260200160405180910390f35b61006461080081565b6001600160a01b0383166100cd5760405162461bcd60e51b815260206004820152601060248201526f24a72b20a624a22fa922a1a2a4ab22a960811b60448201526064015b60405180910390fd5b6108008111156101145760405162461bcd60e51b815260206004820152601260248201527108ab0868a8a88a6be9a82b0be988a9c8ea8960731b60448201526064016100c4565b826001600160a01b0316336001600160a01b031660008081546101369061020b565b9190508190557fedaf3c471ebd67d60c29efe34b639ede7d6a1d92eaeb3f503e784971e67118a5858560405161016d929190610232565b60405180910390a4505050565b60008060006040848603121561018f57600080fd5b83356001600160a01b03811681146101a657600080fd5b9250602084013567ffffffffffffffff808211156101c357600080fd5b818601915086601f8301126101d757600080fd5b8135818111156101e657600080fd5b8760208285010111156101f857600080fd5b6020830194508093505050509250925092565b60006001820161022b57634e487b7160e01b600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f1916010191905056fea264697066735822122041815082e2111df07ce129b83f0000ecca6c4886247dffeae7090f8f048b665164736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var StateReceiverArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"StateReceiver","sourceName":"contracts/child/StateReceiver.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"startId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"endId","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"NewCommitment","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"counter","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"},{"indexed":false,"internalType":"bytes","name":"message","type":"bytes"}],"name":"StateSyncResult","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[][]","name":"proofs","type":"bytes32[][]"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct StateReceiver.StateSync[]","name":"objs","type":"tuple[]"}],"name":"batchExecute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"},{"internalType":"bytes32","name":"root","type":"bytes32"}],"internalType":"struct StateReceiver.StateSyncCommitment","name":"commitment","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"bitmap","type":"bytes"}],"name":"commit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"commitmentCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"commitmentIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"commitments","outputs":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"},{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct StateReceiver.StateSync","name":"obj","type":"tuple"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getCommitmentByStateSyncId","outputs":[{"components":[{"internalType":"uint256","name":"startId","type":"uint256"},{"internalType":"uint256","name":"endId","type":"uint256"},{"internalType":"bytes32","name":"root","type":"bytes32"}],"internalType":"struct StateReceiver.StateSyncCommitment","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getRootByStateSyncId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastCommittedId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"processedStateSyncs","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611244806100206000396000f3fe608060405234801561001057600080fd5b50600436106101215760003560e01c806355b01e4d116100ad578063ad240c2a11610071578063ad240c2a1461026a578063c59a18f714610273578063c6df461714610286578063e0563ab114610299578063eb70ef44146102a257600080fd5b806355b01e4d1461022d5780635ea5df791461023b5780639017c12714610244578063947287cf1461025757806397e5230d1461026057600080fd5b80633b878c22116100f45780633b878c221461018457806349ce89971461018d57806350d5b95b146101d757806351351d53146101ec578063544c5e0f146101fa57600080fd5b806307b3e25214610126578063196f1b2d1461015157806323e281cf14610172578063284017f51461017b575b600080fd5b6101346004600360981b0181565b6040516001600160a01b0390911681526020015b60405180910390f35b61016461015f366004610cd6565b6102d7565b604051908152602001610148565b61016460325481565b61013461202081565b61013461101081565b6101bc61019b366004610cd6565b60356020526000908152604090208054600182015460029092015490919083565b60408051938452602084019290925290820152606001610148565b6101ea6101e5366004610d3a565b610356565b005b6101346002600160a01b0381565b61021d610208366004610cd6565b60346020526000908152604090205460ff1681565b6040519015158152602001610148565b6101346004600160991b0181565b61016461138881565b6101ea610252366004610da9565b61041d565b61016461520881565b610164620249f081565b61016460335481565b610164610281366004610cd6565b6105bd565b6101ea610294366004610e55565b6105de565b61013461203081565b6102b56102b0366004610cd6565b6107c2565b6040805182518152602080840151908201529181015190820152606001610148565b6000806035816102e8603686610885565b81526020810191909152604001600020600201549050806103505760405162461bcd60e51b815260206004820152601d60248201527f537461746552656365697665723a204e4f5f524f4f545f464f525f494400000060448201526064015b60405180910390fd5b92915050565b600061036282356107c2565b80519091506103d290610376908435610ef3565b825160208401516103879190610ef3565b610392906001610f06565b83604001518787876040516020016103aa9190610f5e565b604051602081830303815290604052805190602001206109329095949392919063ffffffff16565b61040e5760405162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22fa82927a7a360991b6044820152606401610347565b61041782610995565b50505050565b828181146104805760405162461bcd60e51b815260206004820152602a60248201527f537461746552656365697665723a20554e4d4154434845445f4c454e4754485f604482015269504152414d455445525360b01b6064820152608401610347565b60005b818110156105b55760006104ba8585848181106104a2576104a2610ffa565b90506020028101906104b49190611010565b356107c2565b9050600061056e82600001518787868181106104d8576104d8610ffa565b90506020028101906104ea9190611010565b6104f5919035610ef3565b835160208501516105069190610ef3565b610511906001610f06565b84604001518b8b8881811061052857610528610ffa565b905060200281019061053a9190611030565b8b8b8a81811061054c5761054c610ffa565b905060200281019061055e9190611010565b6040516020016103aa9190610f5e565b90508061057f575050600101610483565b6105ab86868581811061059457610594610ffa565b90506020028101906105a69190611010565b610995565b5050600101610483565b505050505050565b603681815481106105cd57600080fd5b600091825260209091200154905081565b336002600160a01b03146106225760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610347565b603354610630906001610f06565b8535146106725760405162461bcd60e51b815260206004820152601060248201526f1253959053125117d4d510549517d25160821b6044820152606401610347565b8435602086013510156106b85760405162461bcd60e51b815260206004820152600e60248201526d1253959053125117d1539117d25160921b6044820152606401610347565b604080518635602080830191909152870135818301529086013560608201526106fd906080016040516020818303038152906040528051906020012085858585610bb0565b6032805486916035916000918261071383611079565b90915550815260208082019290925260409081016000208335815591830135600183015582013560028201555050603680546001810182556000919091526020868101357f4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b89092018290556033829055604080519088013581528735917f11efd893530b26afc66d488ff54cb15df117cb6e0e4a08c6dcb166d766c3bf3b910160405180910390a35050505050565b60408051606081018252600080825260208201819052918101829052906107ea603684610885565b603654909150810361084a5760405162461bcd60e51b815260206004820152602360248201527f537461746552656365697665723a204e4f5f434f4d4d49544d454e545f464f5260448201526217d25160ea1b6064820152608401610347565b600090815260356020908152604091829020825160608101845281548152600182015492810192909252600201549181019190915292915050565b8154600090810361089857506000610350565b82546000905b808210156108e55760006108b28383610cb4565b600087815260209020909150859082015411156108d1578091506108df565b6108dc816001610f06565b92505b5061089e565b60008211801561091157508361090e86610900600186610ef3565b600091825260209091200190565b54145b1561092a57610921600183610ef3565b92505050610350565b509392505050565b60008115610971578160051b8301835b8760011660051b8981528135602082185250604060002098508760011c97506020810190508181106109425750505b85158488141660018084031b8611600180851b018710161690509695505050505050565b803560009081526034602052604090205460ff1615610a055760405162461bcd60e51b815260206004820152602660248201527f537461746552656365697665723a2053544154455f53594e435f49535f50524f60448201526510d154d4d15160d21b6064820152608401610347565b610a156060820160408301611092565b6001600160a01b03163b600003610a69576040805160208082526000908201819052918335917f31c652130602f3ce96ceaf8a4c2b8b49f049166c6fcf2eb31943a75ec7c936ae910160405180910390a350565b8035600090815260346020526040808220805460ff191660011790558190610a979060608501908501611092565b6001600160a01b03168335610ab26040860160208701611092565b610abf60608701876110ad565b604051602401610ad294939291906110f3565b60408051601f198184030181529181526020820180516001600160e01b031663eeb4994560e01b17905251610b07919061114c565b6000604051808303816000865af19150503d8060008114610b44576040519150601f19603f3d011682016040523d82523d6000602084013e610b49565b606091505b509150915081610b6c5782356000908152603460205260409020805460ff191690555b81151583600001357f31c652130602f3ce96ceaf8a4c2b8b49f049166c6fcf2eb31943a75ec7c936ae83604051610ba3919061115e565b60405180910390a3505050565b6000806120306001600160a01b0316620249f08888888888604051602001610bdc959493929190611191565b60408051601f1981840301815290829052610bf69161114c565b6000604051808303818686fa925050503d8060008114610c32576040519150601f19603f3d011682016040523d82523d6000602084013e610c37565b606091505b5091509150600081806020019051810190610c5291906111ca565b9050828015610c5e5750805b610caa5760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c45440000006044820152606401610347565b5050505050505050565b6000610cc360028484186111ec565b610ccf90848416610f06565b9392505050565b600060208284031215610ce857600080fd5b5035919050565b60008083601f840112610d0157600080fd5b5081356001600160401b03811115610d1857600080fd5b6020830191508360208260051b8501011115610d3357600080fd5b9250929050565b600080600060408486031215610d4f57600080fd5b83356001600160401b0380821115610d6657600080fd5b610d7287838801610cef565b90955093506020860135915080821115610d8b57600080fd5b50840160808187031215610d9e57600080fd5b809150509250925092565b60008060008060408587031215610dbf57600080fd5b84356001600160401b0380821115610dd657600080fd5b610de288838901610cef565b90965094506020870135915080821115610dfb57600080fd5b50610e0887828801610cef565b95989497509550505050565b60008083601f840112610e2657600080fd5b5081356001600160401b03811115610e3d57600080fd5b602083019150836020828501011115610d3357600080fd5b600080600080600085870360a0811215610e6e57600080fd5b6060811215610e7c57600080fd5b5085945060608601356001600160401b0380821115610e9a57600080fd5b610ea689838a01610e14565b90965094506080880135915080821115610ebf57600080fd5b50610ecc88828901610e14565b969995985093965092949392505050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561035057610350610edd565b8082018082111561035057610350610edd565b80356001600160a01b0381168114610f3057600080fd5b919050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60208152813560208201526000610f7760208401610f19565b60018060a01b03808216604085015280610f9360408701610f19565b16606085015250506060830135601e19843603018112610fb257600080fd5b83016020810190356001600160401b03811115610fce57600080fd5b803603821315610fdd57600080fd5b608080850152610ff160a085018284610f35565b95945050505050565b634e487b7160e01b600052603260045260246000fd5b60008235607e1983360301811261102657600080fd5b9190910192915050565b6000808335601e1984360301811261104757600080fd5b8301803591506001600160401b0382111561106157600080fd5b6020019150600581901b3603821315610d3357600080fd5b60006001820161108b5761108b610edd565b5060010190565b6000602082840312156110a457600080fd5b610ccf82610f19565b6000808335601e198436030181126110c457600080fd5b8301803591506001600160401b038211156110de57600080fd5b602001915036819003821315610d3357600080fd5b8481526001600160a01b038416602082015260606040820181905260009061111e9083018486610f35565b9695505050505050565b60005b8381101561114357818101518382015260200161112b565b50506000910152565b60008251611026818460208701611128565b602081526000825180602084015261117d816040850160208701611128565b601f01601f19169190910160400192915050565b8581526060602082015260006111ab606083018688610f35565b82810360408401526111be818587610f35565b98975050505050505050565b6000602082840312156111dc57600080fd5b81518015158114610ccf57600080fd5b60008261120957634e487b7160e01b600052601260045260246000fd5b50049056fea26469706673582212205e465487d9414dfe77390f61a0c38958de52ee524ceeb4baa7548b12c5998a1564736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101215760003560e01c806355b01e4d116100ad578063ad240c2a11610071578063ad240c2a1461026a578063c59a18f714610273578063c6df461714610286578063e0563ab114610299578063eb70ef44146102a257600080fd5b806355b01e4d1461022d5780635ea5df791461023b5780639017c12714610244578063947287cf1461025757806397e5230d1461026057600080fd5b80633b878c22116100f45780633b878c221461018457806349ce89971461018d57806350d5b95b146101d757806351351d53146101ec578063544c5e0f146101fa57600080fd5b806307b3e25214610126578063196f1b2d1461015157806323e281cf14610172578063284017f51461017b575b600080fd5b6101346004600360981b0181565b6040516001600160a01b0390911681526020015b60405180910390f35b61016461015f366004610cd6565b6102d7565b604051908152602001610148565b61016460325481565b61013461202081565b61013461101081565b6101bc61019b366004610cd6565b60356020526000908152604090208054600182015460029092015490919083565b60408051938452602084019290925290820152606001610148565b6101ea6101e5366004610d3a565b610356565b005b6101346002600160a01b0381565b61021d610208366004610cd6565b60346020526000908152604090205460ff1681565b6040519015158152602001610148565b6101346004600160991b0181565b61016461138881565b6101ea610252366004610da9565b61041d565b61016461520881565b610164620249f081565b61016460335481565b610164610281366004610cd6565b6105bd565b6101ea610294366004610e55565b6105de565b61013461203081565b6102b56102b0366004610cd6565b6107c2565b6040805182518152602080840151908201529181015190820152606001610148565b6000806035816102e8603686610885565b81526020810191909152604001600020600201549050806103505760405162461bcd60e51b815260206004820152601d60248201527f537461746552656365697665723a204e4f5f524f4f545f464f525f494400000060448201526064015b60405180910390fd5b92915050565b600061036282356107c2565b80519091506103d290610376908435610ef3565b825160208401516103879190610ef3565b610392906001610f06565b83604001518787876040516020016103aa9190610f5e565b604051602081830303815290604052805190602001206109329095949392919063ffffffff16565b61040e5760405162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22fa82927a7a360991b6044820152606401610347565b61041782610995565b50505050565b828181146104805760405162461bcd60e51b815260206004820152602a60248201527f537461746552656365697665723a20554e4d4154434845445f4c454e4754485f604482015269504152414d455445525360b01b6064820152608401610347565b60005b818110156105b55760006104ba8585848181106104a2576104a2610ffa565b90506020028101906104b49190611010565b356107c2565b9050600061056e82600001518787868181106104d8576104d8610ffa565b90506020028101906104ea9190611010565b6104f5919035610ef3565b835160208501516105069190610ef3565b610511906001610f06565b84604001518b8b8881811061052857610528610ffa565b905060200281019061053a9190611030565b8b8b8a81811061054c5761054c610ffa565b905060200281019061055e9190611010565b6040516020016103aa9190610f5e565b90508061057f575050600101610483565b6105ab86868581811061059457610594610ffa565b90506020028101906105a69190611010565b610995565b5050600101610483565b505050505050565b603681815481106105cd57600080fd5b600091825260209091200154905081565b336002600160a01b03146106225760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610347565b603354610630906001610f06565b8535146106725760405162461bcd60e51b815260206004820152601060248201526f1253959053125117d4d510549517d25160821b6044820152606401610347565b8435602086013510156106b85760405162461bcd60e51b815260206004820152600e60248201526d1253959053125117d1539117d25160921b6044820152606401610347565b604080518635602080830191909152870135818301529086013560608201526106fd906080016040516020818303038152906040528051906020012085858585610bb0565b6032805486916035916000918261071383611079565b90915550815260208082019290925260409081016000208335815591830135600183015582013560028201555050603680546001810182556000919091526020868101357f4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b89092018290556033829055604080519088013581528735917f11efd893530b26afc66d488ff54cb15df117cb6e0e4a08c6dcb166d766c3bf3b910160405180910390a35050505050565b60408051606081018252600080825260208201819052918101829052906107ea603684610885565b603654909150810361084a5760405162461bcd60e51b815260206004820152602360248201527f537461746552656365697665723a204e4f5f434f4d4d49544d454e545f464f5260448201526217d25160ea1b6064820152608401610347565b600090815260356020908152604091829020825160608101845281548152600182015492810192909252600201549181019190915292915050565b8154600090810361089857506000610350565b82546000905b808210156108e55760006108b28383610cb4565b600087815260209020909150859082015411156108d1578091506108df565b6108dc816001610f06565b92505b5061089e565b60008211801561091157508361090e86610900600186610ef3565b600091825260209091200190565b54145b1561092a57610921600183610ef3565b92505050610350565b509392505050565b60008115610971578160051b8301835b8760011660051b8981528135602082185250604060002098508760011c97506020810190508181106109425750505b85158488141660018084031b8611600180851b018710161690509695505050505050565b803560009081526034602052604090205460ff1615610a055760405162461bcd60e51b815260206004820152602660248201527f537461746552656365697665723a2053544154455f53594e435f49535f50524f60448201526510d154d4d15160d21b6064820152608401610347565b610a156060820160408301611092565b6001600160a01b03163b600003610a69576040805160208082526000908201819052918335917f31c652130602f3ce96ceaf8a4c2b8b49f049166c6fcf2eb31943a75ec7c936ae910160405180910390a350565b8035600090815260346020526040808220805460ff191660011790558190610a979060608501908501611092565b6001600160a01b03168335610ab26040860160208701611092565b610abf60608701876110ad565b604051602401610ad294939291906110f3565b60408051601f198184030181529181526020820180516001600160e01b031663eeb4994560e01b17905251610b07919061114c565b6000604051808303816000865af19150503d8060008114610b44576040519150601f19603f3d011682016040523d82523d6000602084013e610b49565b606091505b509150915081610b6c5782356000908152603460205260409020805460ff191690555b81151583600001357f31c652130602f3ce96ceaf8a4c2b8b49f049166c6fcf2eb31943a75ec7c936ae83604051610ba3919061115e565b60405180910390a3505050565b6000806120306001600160a01b0316620249f08888888888604051602001610bdc959493929190611191565b60408051601f1981840301815290829052610bf69161114c565b6000604051808303818686fa925050503d8060008114610c32576040519150601f19603f3d011682016040523d82523d6000602084013e610c37565b606091505b5091509150600081806020019051810190610c5291906111ca565b9050828015610c5e5750805b610caa5760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c45440000006044820152606401610347565b5050505050505050565b6000610cc360028484186111ec565b610ccf90848416610f06565b9392505050565b600060208284031215610ce857600080fd5b5035919050565b60008083601f840112610d0157600080fd5b5081356001600160401b03811115610d1857600080fd5b6020830191508360208260051b8501011115610d3357600080fd5b9250929050565b600080600060408486031215610d4f57600080fd5b83356001600160401b0380821115610d6657600080fd5b610d7287838801610cef565b90955093506020860135915080821115610d8b57600080fd5b50840160808187031215610d9e57600080fd5b809150509250925092565b60008060008060408587031215610dbf57600080fd5b84356001600160401b0380821115610dd657600080fd5b610de288838901610cef565b90965094506020870135915080821115610dfb57600080fd5b50610e0887828801610cef565b95989497509550505050565b60008083601f840112610e2657600080fd5b5081356001600160401b03811115610e3d57600080fd5b602083019150836020828501011115610d3357600080fd5b600080600080600085870360a0811215610e6e57600080fd5b6060811215610e7c57600080fd5b5085945060608601356001600160401b0380821115610e9a57600080fd5b610ea689838a01610e14565b90965094506080880135915080821115610ebf57600080fd5b50610ecc88828901610e14565b969995985093965092949392505050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561035057610350610edd565b8082018082111561035057610350610edd565b80356001600160a01b0381168114610f3057600080fd5b919050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60208152813560208201526000610f7760208401610f19565b60018060a01b03808216604085015280610f9360408701610f19565b16606085015250506060830135601e19843603018112610fb257600080fd5b83016020810190356001600160401b03811115610fce57600080fd5b803603821315610fdd57600080fd5b608080850152610ff160a085018284610f35565b95945050505050565b634e487b7160e01b600052603260045260246000fd5b60008235607e1983360301811261102657600080fd5b9190910192915050565b6000808335601e1984360301811261104757600080fd5b8301803591506001600160401b0382111561106157600080fd5b6020019150600581901b3603821315610d3357600080fd5b60006001820161108b5761108b610edd565b5060010190565b6000602082840312156110a457600080fd5b610ccf82610f19565b6000808335601e198436030181126110c457600080fd5b8301803591506001600160401b038211156110de57600080fd5b602001915036819003821315610d3357600080fd5b8481526001600160a01b038416602082015260606040820181905260009061111e9083018486610f35565b9695505050505050565b60005b8381101561114357818101518382015260200161112b565b50506000910152565b60008251611026818460208701611128565b602081526000825180602084015261117d816040850160208701611128565b601f01601f19169190910160400192915050565b8581526060602082015260006111ab606083018688610f35565b82810360408401526111be818587610f35565b98975050505050505050565b6000602082840312156111dc57600080fd5b81518015158114610ccf57600080fd5b60008261120957634e487b7160e01b600052601260045260246000fd5b50049056fea26469706673582212205e465487d9414dfe77390f61a0c38958de52ee524ceeb4baa7548b12c5998a1564736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var NativeERC20Artifact string = `{"_format":"hh-sol-artifact-1","contractName":"NativeERC20","sourceName":"contracts/child/NativeERC20.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"predicate_","type":"address"},{"internalType":"address","name":"rootToken_","type":"address"},{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"uint8","name":"decimals_","type":"uint8"},{"internalType":"uint256","name":"tokenSupply_","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rootToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b5061121c806100206000396000f3fe608060405234801561001057600080fd5b50600436106101635760003560e01c806355b01e4d116100ce57806397e5230d1161008757806397e5230d146102b85780639dc29fac146102c2578063a457c2d7146102d5578063a9059cbb146102e8578063dd62ed3e146102fb578063e0563ab11461030e578063e61987051461031757600080fd5b806355b01e4d146102605780635ea5df791461026e57806370a0823114610277578063740b08dc14610292578063947287cf146102a757806395d89b41146102b057600080fd5b8063284017f511610120578063284017f514610205578063313ce5671461020e57806339509351146102235780633b878c221461023657806340c10f191461023f57806351351d531461025257600080fd5b806306fdde031461016857806307b3e25214610186578063095ea7b3146101ac57806318160ddd146101cf5780631f2d0065146101e157806323b872dd146101f2575b600080fd5b610170610328565b60405161017d9190610d4d565b60405180910390f35b6101946004600360981b0181565b6040516001600160a01b03909116815260200161017d565b6101bf6101ba366004610d9c565b6103ba565b604051901515815260200161017d565b6034545b60405190815260200161017d565b6036546001600160a01b0316610194565b6101bf610200366004610dc6565b6103d4565b61019461202081565b60395460405160ff909116815260200161017d565b6101bf610231366004610d9c565b6103f8565b61019461101081565b6101bf61024d366004610d9c565b61041a565b6101946002600160a01b0381565b6101946004600160991b0181565b6101d361138881565b6101d3610285366004610e02565b6001600160a01b03163190565b6102a56102a0366004610e6d565b610463565b005b6101d361520881565b610170610618565b6101d3620249f081565b6101bf6102d0366004610d9c565b610627565b6101bf6102e3366004610d9c565b61065e565b6101bf6102f6366004610d9c565b6106d9565b6101d3610309366004610f21565b6106e7565b61019461203081565b6035546001600160a01b0316610194565b60606037805461033790610f54565b80601f016020809104026020016040519081016040528092919081815260200182805461036390610f54565b80156103b05780601f10610385576101008083540402835291602001916103b0565b820191906000526020600020905b81548152906001019060200180831161039357829003601f168201915b5050505050905090565b6000336103c8818585610712565b60019150505b92915050565b6000336103e2858285610836565b6103ed8585856108b0565b506001949350505050565b6000336103c881858561040b83836106e7565b6104159190610fa4565b610712565b6035546000906001600160a01b031633146104505760405162461bcd60e51b815260040161044790610fb7565b60405180910390fd5b61045a8383610a75565b50600192915050565b600054610100900460ff16158080156104835750600054600160ff909116105b8061049d5750303b15801561049d575060005460ff166001145b6105005760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610447565b6000805460ff191660011790558015610523576000805461ff0019166101001790555b336002600160a01b03146105675760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610447565b603580546001600160a01b03808c166001600160a01b03199283161790925560368054928b169290911691909117905560376105a4878983611060565b5060386105b2858783611060565b506039805460ff191660ff85161790556034829055801561060d576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b60606038805461033790610f54565b6035546000906001600160a01b031633146106545760405162461bcd60e51b815260040161044790610fb7565b61045a8383610bcf565b6000338161066c82866106e7565b9050838110156106cc5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610447565b6103ed8286868403610712565b6000336103c88185856108b0565b6001600160a01b03918216600090815260336020908152604080832093909416825291909152205490565b6001600160a01b0383166107745760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610447565b6001600160a01b0382166107d55760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610447565b6001600160a01b0383811660008181526033602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b600061084284846106e7565b905060001981146108aa578181101561089d5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610447565b6108aa8484848403610712565b50505050565b6001600160a01b0383166109145760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610447565b6001600160a01b0382166109765760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610447565b6000806120206001600160a01b031685858560405160200161099a93929190611121565b60408051601f19818403018152908290526109b491611145565b6000604051808303816000865af19150503d80600081146109f1576040519150601f19603f3d011682016040523d82523d6000602084013e6109f6565b606091505b5091509150818015610a17575080806020019051810190610a179190611161565b610a335760405162461bcd60e51b815260040161044790611183565b836001600160a01b0316856001600160a01b03166000805160206111c783398151915285604051610a6691815260200190565b60405180910390a35050505050565b6001600160a01b038216610acb5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610447565b8060346000828254610add9190610fa4565b9091555050604051600090819061202090610b0090839087908790602001611121565b60408051601f1981840301815290829052610b1a91611145565b6000604051808303816000865af19150503d8060008114610b57576040519150601f19603f3d011682016040523d82523d6000602084013e610b5c565b606091505b5091509150818015610b7d575080806020019051810190610b7d9190611161565b610b995760405162461bcd60e51b815260040161044790611183565b6040518381526001600160a01b038516906000906000805160206111c7833981519152906020015b60405180910390a350505050565b6001600160a01b038216610c2f5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610447565b8060346000828254610c4191906111b3565b9091555050604051600090819061202090610c6490869084908790602001611121565b60408051601f1981840301815290829052610c7e91611145565b6000604051808303816000865af19150503d8060008114610cbb576040519150601f19603f3d011682016040523d82523d6000602084013e610cc0565b606091505b5091509150818015610ce1575080806020019051810190610ce19190611161565b610cfd5760405162461bcd60e51b815260040161044790611183565b6040518381526000906001600160a01b038616906000805160206111c783398151915290602001610bc1565b60005b83811015610d44578181015183820152602001610d2c565b50506000910152565b6020815260008251806020840152610d6c816040850160208701610d29565b601f01601f19169190910160400192915050565b80356001600160a01b0381168114610d9757600080fd5b919050565b60008060408385031215610daf57600080fd5b610db883610d80565b946020939093013593505050565b600080600060608486031215610ddb57600080fd5b610de484610d80565b9250610df260208501610d80565b9150604084013590509250925092565b600060208284031215610e1457600080fd5b610e1d82610d80565b9392505050565b60008083601f840112610e3657600080fd5b50813567ffffffffffffffff811115610e4e57600080fd5b602083019150836020828501011115610e6657600080fd5b9250929050565b60008060008060008060008060c0898b031215610e8957600080fd5b610e9289610d80565b9750610ea060208a01610d80565b9650604089013567ffffffffffffffff80821115610ebd57600080fd5b610ec98c838d01610e24565b909850965060608b0135915080821115610ee257600080fd5b50610eef8b828c01610e24565b909550935050608089013560ff81168114610f0957600080fd5b8092505060a089013590509295985092959890939650565b60008060408385031215610f3457600080fd5b610f3d83610d80565b9150610f4b60208401610d80565b90509250929050565b600181811c90821680610f6857607f821691505b602082108103610f8857634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b808201808211156103ce576103ce610f8e565b60208082526024908201527f4e617469766545524332303a204f6e6c79207072656469636174652063616e2060408201526318d85b1b60e21b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b601f82111561105b57600081815260208120601f850160051c810160208610156110385750805b601f850160051c820191505b8181101561105757828155600101611044565b5050505b505050565b67ffffffffffffffff83111561107857611078610ffb565b61108c836110868354610f54565b83611011565b6000601f8411600181146110c057600085156110a85750838201355b600019600387901b1c1916600186901b17835561111a565b600083815260209020601f19861690835b828110156110f157868501358255602094850194600190920191016110d1565b508682101561110e5760001960f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60008251611157818460208701610d29565b9190910192915050565b60006020828403121561117357600080fd5b81518015158114610e1d57600080fd5b60208082526016908201527514149150d3d35412531157d0d0531317d1905253115160521b604082015260600190565b818103818111156103ce576103ce610f8e56feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212203e253e3b3f3175f58563f905e4d35905dc8b894d96a80a43930b6a730edd9a2a64736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101635760003560e01c806355b01e4d116100ce57806397e5230d1161008757806397e5230d146102b85780639dc29fac146102c2578063a457c2d7146102d5578063a9059cbb146102e8578063dd62ed3e146102fb578063e0563ab11461030e578063e61987051461031757600080fd5b806355b01e4d146102605780635ea5df791461026e57806370a0823114610277578063740b08dc14610292578063947287cf146102a757806395d89b41146102b057600080fd5b8063284017f511610120578063284017f514610205578063313ce5671461020e57806339509351146102235780633b878c221461023657806340c10f191461023f57806351351d531461025257600080fd5b806306fdde031461016857806307b3e25214610186578063095ea7b3146101ac57806318160ddd146101cf5780631f2d0065146101e157806323b872dd146101f2575b600080fd5b610170610328565b60405161017d9190610d4d565b60405180910390f35b6101946004600360981b0181565b6040516001600160a01b03909116815260200161017d565b6101bf6101ba366004610d9c565b6103ba565b604051901515815260200161017d565b6034545b60405190815260200161017d565b6036546001600160a01b0316610194565b6101bf610200366004610dc6565b6103d4565b61019461202081565b60395460405160ff909116815260200161017d565b6101bf610231366004610d9c565b6103f8565b61019461101081565b6101bf61024d366004610d9c565b61041a565b6101946002600160a01b0381565b6101946004600160991b0181565b6101d361138881565b6101d3610285366004610e02565b6001600160a01b03163190565b6102a56102a0366004610e6d565b610463565b005b6101d361520881565b610170610618565b6101d3620249f081565b6101bf6102d0366004610d9c565b610627565b6101bf6102e3366004610d9c565b61065e565b6101bf6102f6366004610d9c565b6106d9565b6101d3610309366004610f21565b6106e7565b61019461203081565b6035546001600160a01b0316610194565b60606037805461033790610f54565b80601f016020809104026020016040519081016040528092919081815260200182805461036390610f54565b80156103b05780601f10610385576101008083540402835291602001916103b0565b820191906000526020600020905b81548152906001019060200180831161039357829003601f168201915b5050505050905090565b6000336103c8818585610712565b60019150505b92915050565b6000336103e2858285610836565b6103ed8585856108b0565b506001949350505050565b6000336103c881858561040b83836106e7565b6104159190610fa4565b610712565b6035546000906001600160a01b031633146104505760405162461bcd60e51b815260040161044790610fb7565b60405180910390fd5b61045a8383610a75565b50600192915050565b600054610100900460ff16158080156104835750600054600160ff909116105b8061049d5750303b15801561049d575060005460ff166001145b6105005760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610447565b6000805460ff191660011790558015610523576000805461ff0019166101001790555b336002600160a01b03146105675760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610447565b603580546001600160a01b03808c166001600160a01b03199283161790925560368054928b169290911691909117905560376105a4878983611060565b5060386105b2858783611060565b506039805460ff191660ff85161790556034829055801561060d576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b60606038805461033790610f54565b6035546000906001600160a01b031633146106545760405162461bcd60e51b815260040161044790610fb7565b61045a8383610bcf565b6000338161066c82866106e7565b9050838110156106cc5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610447565b6103ed8286868403610712565b6000336103c88185856108b0565b6001600160a01b03918216600090815260336020908152604080832093909416825291909152205490565b6001600160a01b0383166107745760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610447565b6001600160a01b0382166107d55760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610447565b6001600160a01b0383811660008181526033602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b600061084284846106e7565b905060001981146108aa578181101561089d5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610447565b6108aa8484848403610712565b50505050565b6001600160a01b0383166109145760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610447565b6001600160a01b0382166109765760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610447565b6000806120206001600160a01b031685858560405160200161099a93929190611121565b60408051601f19818403018152908290526109b491611145565b6000604051808303816000865af19150503d80600081146109f1576040519150601f19603f3d011682016040523d82523d6000602084013e6109f6565b606091505b5091509150818015610a17575080806020019051810190610a179190611161565b610a335760405162461bcd60e51b815260040161044790611183565b836001600160a01b0316856001600160a01b03166000805160206111c783398151915285604051610a6691815260200190565b60405180910390a35050505050565b6001600160a01b038216610acb5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610447565b8060346000828254610add9190610fa4565b9091555050604051600090819061202090610b0090839087908790602001611121565b60408051601f1981840301815290829052610b1a91611145565b6000604051808303816000865af19150503d8060008114610b57576040519150601f19603f3d011682016040523d82523d6000602084013e610b5c565b606091505b5091509150818015610b7d575080806020019051810190610b7d9190611161565b610b995760405162461bcd60e51b815260040161044790611183565b6040518381526001600160a01b038516906000906000805160206111c7833981519152906020015b60405180910390a350505050565b6001600160a01b038216610c2f5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610447565b8060346000828254610c4191906111b3565b9091555050604051600090819061202090610c6490869084908790602001611121565b60408051601f1981840301815290829052610c7e91611145565b6000604051808303816000865af19150503d8060008114610cbb576040519150601f19603f3d011682016040523d82523d6000602084013e610cc0565b606091505b5091509150818015610ce1575080806020019051810190610ce19190611161565b610cfd5760405162461bcd60e51b815260040161044790611183565b6040518381526000906001600160a01b038616906000805160206111c783398151915290602001610bc1565b60005b83811015610d44578181015183820152602001610d2c565b50506000910152565b6020815260008251806020840152610d6c816040850160208701610d29565b601f01601f19169190910160400192915050565b80356001600160a01b0381168114610d9757600080fd5b919050565b60008060408385031215610daf57600080fd5b610db883610d80565b946020939093013593505050565b600080600060608486031215610ddb57600080fd5b610de484610d80565b9250610df260208501610d80565b9150604084013590509250925092565b600060208284031215610e1457600080fd5b610e1d82610d80565b9392505050565b60008083601f840112610e3657600080fd5b50813567ffffffffffffffff811115610e4e57600080fd5b602083019150836020828501011115610e6657600080fd5b9250929050565b60008060008060008060008060c0898b031215610e8957600080fd5b610e9289610d80565b9750610ea060208a01610d80565b9650604089013567ffffffffffffffff80821115610ebd57600080fd5b610ec98c838d01610e24565b909850965060608b0135915080821115610ee257600080fd5b50610eef8b828c01610e24565b909550935050608089013560ff81168114610f0957600080fd5b8092505060a089013590509295985092959890939650565b60008060408385031215610f3457600080fd5b610f3d83610d80565b9150610f4b60208401610d80565b90509250929050565b600181811c90821680610f6857607f821691505b602082108103610f8857634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b808201808211156103ce576103ce610f8e565b60208082526024908201527f4e617469766545524332303a204f6e6c79207072656469636174652063616e2060408201526318d85b1b60e21b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b601f82111561105b57600081815260208120601f850160051c810160208610156110385750805b601f850160051c820191505b8181101561105757828155600101611044565b5050505b505050565b67ffffffffffffffff83111561107857611078610ffb565b61108c836110868354610f54565b83611011565b6000601f8411600181146110c057600085156110a85750838201355b600019600387901b1c1916600186901b17835561111a565b600083815260209020601f19861690835b828110156110f157868501358255602094850194600190920191016110d1565b508682101561110e5760001960f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60008251611157818460208701610d29565b9190910192915050565b60006020828403121561117357600080fd5b81518015158114610e1d57600080fd5b60208082526016908201527514149150d3d35412531157d0d0531317d1905253115160521b604082015260600190565b818103818111156103ce576103ce610f8e56feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212203e253e3b3f3175f58563f905e4d35905dc8b894d96a80a43930b6a730edd9a2a64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var NativeERC20MintableArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"NativeERC20Mintable","sourceName":"contracts/child/NativeERC20Mintable.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"predicate_","type":"address"},{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"rootToken_","type":"address"},{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"uint8","name":"decimals_","type":"uint8"},{"internalType":"uint256","name":"tokenSupply_","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b5061001a3361001f565b61008d565b603480546001600160a01b03191690556100388161003b565b50565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b61150a8061009c6000396000f3fe608060405234801561001057600080fd5b506004361061019a5760003560e01c806370a08231116100e45780639dc29fac116100925780639dc29fac1461031a578063a457c2d71461032d578063a9059cbb14610340578063dd62ed3e14610353578063e0563ab114610366578063e30c39781461036f578063e619870514610380578063f2fde38b1461039157600080fd5b806370a08231146102c3578063715018a6146102de57806379ba5097146102e65780638da5cb5b146102ee578063947287cf146102ff57806395d89b411461030857806397e5230d1461031057600080fd5b8063313ce5671161014c578063313ce56714610245578063395093511461025a5780633b878c221461026d57806340c10f191461027657806351351d531461028957806355b01e4d146102975780635ea5df79146102a55780636091636b146102ae57600080fd5b806306fdde031461019f57806307b3e252146101bd578063095ea7b3146101e357806318160ddd146102065780631f2d00651461021857806323b872dd14610229578063284017f51461023c575b600080fd5b6101a76103a4565b6040516101b49190611022565b60405180910390f35b6101cb6004600360981b0181565b6040516001600160a01b0390911681526020016101b4565b6101f66101f1366004611071565b610436565b60405190151581526020016101b4565b6036545b6040519081526020016101b4565b6038546001600160a01b03166101cb565b6101f661023736600461109b565b610450565b6101cb61202081565b603b5460405160ff90911681526020016101b4565b6101f6610268366004611071565b610474565b6101cb61101081565b6101f6610284366004611071565b610496565b6101cb6002600160a01b0381565b6101cb6004600160991b0181565b61020a61138881565b6102c16102bc366004611120565b6104f4565b005b61020a6102d13660046111e4565b6001600160a01b03163190565b6102c1610714565b6102c1610728565b6033546001600160a01b03166101cb565b61020a61520881565b6101a76107a2565b61020a620249f081565b6101f6610328366004611071565b6107b1565b6101f661033b366004611071565b6107fd565b6101f661034e366004611071565b610878565b61020a610361366004611206565b610886565b6101cb61203081565b6034546001600160a01b03166101cb565b6037546001600160a01b03166101cb565b6102c161039f3660046111e4565b6108b1565b6060603980546103b390611239565b80601f01602080910402602001604051908101604052809291908181526020018280546103df90611239565b801561042c5780601f106104015761010080835404028352916020019161042c565b820191906000526020600020905b81548152906001019060200180831161040f57829003601f168201915b5050505050905090565b600033610444818585610922565b60019150505b92915050565b60003361045e858285610a46565b610469858585610ac0565b506001949350505050565b6000336104448185856104878383610886565b6104919190611289565b610922565b6037546000906001600160a01b03163314806104bc57506033546001600160a01b031633145b6104e15760405162461bcd60e51b81526004016104d89061129c565b60405180910390fd5b6104eb8383610c85565b50600192915050565b600054610100900460ff16158080156105145750600054600160ff909116105b8061052e5750303b15801561052e575060005460ff166001145b6105915760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104d8565b6000805460ff1916600117905580156105b4576000805461ff0019166101001790555b336002600160a01b03146105f85760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064016104d8565b6001600160a01b0389166106595760405162461bcd60e51b815260206004820152602260248201527f4e617469766545524332303a20496e76616c6964206f776e6572206164647265604482015261737360f01b60648201526084016104d8565b603780546001600160a01b03808d166001600160a01b03199283161790925560388054928b1692909116919091179055603961069687898361134e565b50603a6106a485878361134e565b50603b805460ff191660ff851617905560368290556106c289610ddf565b8015610708576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050505050565b61071c610df8565b6107266000610ddf565b565b60345433906001600160a01b031681146107965760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016104d8565b61079f81610ddf565b50565b6060603a80546103b390611239565b6037546000906001600160a01b03163314806107d757506033546001600160a01b031633145b6107f35760405162461bcd60e51b81526004016104d89061129c565b6104eb8383610e52565b6000338161080b8286610886565b90508381101561086b5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104d8565b6104698286868403610922565b600033610444818585610ac0565b6001600160a01b03918216600090815260356020908152604080832093909416825291909152205490565b6108b9610df8565b603480546001600160a01b0383166001600160a01b031990911681179091556108ea6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6001600160a01b0383166109845760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104d8565b6001600160a01b0382166109e55760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104d8565b6001600160a01b0383811660008181526035602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b6000610a528484610886565b90506000198114610aba5781811015610aad5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016104d8565b610aba8484848403610922565b50505050565b6001600160a01b038316610b245760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104d8565b6001600160a01b038216610b865760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104d8565b6000806120206001600160a01b0316858585604051602001610baa9392919061140f565b60408051601f1981840301815290829052610bc491611433565b6000604051808303816000865af19150503d8060008114610c01576040519150601f19603f3d011682016040523d82523d6000602084013e610c06565b606091505b5091509150818015610c27575080806020019051810190610c27919061144f565b610c435760405162461bcd60e51b81526004016104d890611471565b836001600160a01b0316856001600160a01b03166000805160206114b583398151915285604051610c7691815260200190565b60405180910390a35050505050565b6001600160a01b038216610cdb5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104d8565b8060366000828254610ced9190611289565b9091555050604051600090819061202090610d109083908790879060200161140f565b60408051601f1981840301815290829052610d2a91611433565b6000604051808303816000865af19150503d8060008114610d67576040519150601f19603f3d011682016040523d82523d6000602084013e610d6c565b606091505b5091509150818015610d8d575080806020019051810190610d8d919061144f565b610da95760405162461bcd60e51b81526004016104d890611471565b6040518381526001600160a01b038516906000906000805160206114b5833981519152906020015b60405180910390a350505050565b603480546001600160a01b031916905561079f81610fac565b6033546001600160a01b031633146107265760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104d8565b6001600160a01b038216610eb25760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016104d8565b8060366000828254610ec491906114a1565b9091555050604051600090819061202090610ee79086908490879060200161140f565b60408051601f1981840301815290829052610f0191611433565b6000604051808303816000865af19150503d8060008114610f3e576040519150601f19603f3d011682016040523d82523d6000602084013e610f43565b606091505b5091509150818015610f64575080806020019051810190610f64919061144f565b610f805760405162461bcd60e51b81526004016104d890611471565b6040518381526000906001600160a01b038616906000805160206114b583398151915290602001610dd1565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60005b83811015611019578181015183820152602001611001565b50506000910152565b6020815260008251806020840152611041816040850160208701610ffe565b601f01601f19169190910160400192915050565b80356001600160a01b038116811461106c57600080fd5b919050565b6000806040838503121561108457600080fd5b61108d83611055565b946020939093013593505050565b6000806000606084860312156110b057600080fd5b6110b984611055565b92506110c760208501611055565b9150604084013590509250925092565b60008083601f8401126110e957600080fd5b50813567ffffffffffffffff81111561110157600080fd5b60208301915083602082850101111561111957600080fd5b9250929050565b600080600080600080600080600060e08a8c03121561113e57600080fd5b6111478a611055565b985061115560208b01611055565b975061116360408b01611055565b965060608a013567ffffffffffffffff8082111561118057600080fd5b61118c8d838e016110d7565b909850965060808c01359150808211156111a557600080fd5b506111b28c828d016110d7565b90955093505060a08a013560ff811681146111cc57600080fd5b8092505060c08a013590509295985092959850929598565b6000602082840312156111f657600080fd5b6111ff82611055565b9392505050565b6000806040838503121561121957600080fd5b61122283611055565b915061123060208401611055565b90509250929050565b600181811c9082168061124d57607f821691505b60208210810361126d57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8082018082111561044a5761044a611273565b6020808252602d908201527f4e617469766545524332303a204f6e6c7920707265646963617465206f72206f60408201526c1ddb995c8818d85b8818d85b1b609a1b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b601f82111561134957600081815260208120601f850160051c810160208610156113265750805b601f850160051c820191505b8181101561134557828155600101611332565b5050505b505050565b67ffffffffffffffff831115611366576113666112e9565b61137a836113748354611239565b836112ff565b6000601f8411600181146113ae57600085156113965750838201355b600019600387901b1c1916600186901b178355611408565b600083815260209020601f19861690835b828110156113df57868501358255602094850194600190920191016113bf565b50868210156113fc5760001960f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60008251611445818460208701610ffe565b9190910192915050565b60006020828403121561146157600080fd5b815180151581146111ff57600080fd5b60208082526016908201527514149150d3d35412531157d0d0531317d1905253115160521b604082015260600190565b8181038181111561044a5761044a61127356feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220c841ea6b2ede0321e38158aa4ac46f255ea0caf34a5429d64b9287ce7df103b364736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061019a5760003560e01c806370a08231116100e45780639dc29fac116100925780639dc29fac1461031a578063a457c2d71461032d578063a9059cbb14610340578063dd62ed3e14610353578063e0563ab114610366578063e30c39781461036f578063e619870514610380578063f2fde38b1461039157600080fd5b806370a08231146102c3578063715018a6146102de57806379ba5097146102e65780638da5cb5b146102ee578063947287cf146102ff57806395d89b411461030857806397e5230d1461031057600080fd5b8063313ce5671161014c578063313ce56714610245578063395093511461025a5780633b878c221461026d57806340c10f191461027657806351351d531461028957806355b01e4d146102975780635ea5df79146102a55780636091636b146102ae57600080fd5b806306fdde031461019f57806307b3e252146101bd578063095ea7b3146101e357806318160ddd146102065780631f2d00651461021857806323b872dd14610229578063284017f51461023c575b600080fd5b6101a76103a4565b6040516101b49190611022565b60405180910390f35b6101cb6004600360981b0181565b6040516001600160a01b0390911681526020016101b4565b6101f66101f1366004611071565b610436565b60405190151581526020016101b4565b6036545b6040519081526020016101b4565b6038546001600160a01b03166101cb565b6101f661023736600461109b565b610450565b6101cb61202081565b603b5460405160ff90911681526020016101b4565b6101f6610268366004611071565b610474565b6101cb61101081565b6101f6610284366004611071565b610496565b6101cb6002600160a01b0381565b6101cb6004600160991b0181565b61020a61138881565b6102c16102bc366004611120565b6104f4565b005b61020a6102d13660046111e4565b6001600160a01b03163190565b6102c1610714565b6102c1610728565b6033546001600160a01b03166101cb565b61020a61520881565b6101a76107a2565b61020a620249f081565b6101f6610328366004611071565b6107b1565b6101f661033b366004611071565b6107fd565b6101f661034e366004611071565b610878565b61020a610361366004611206565b610886565b6101cb61203081565b6034546001600160a01b03166101cb565b6037546001600160a01b03166101cb565b6102c161039f3660046111e4565b6108b1565b6060603980546103b390611239565b80601f01602080910402602001604051908101604052809291908181526020018280546103df90611239565b801561042c5780601f106104015761010080835404028352916020019161042c565b820191906000526020600020905b81548152906001019060200180831161040f57829003601f168201915b5050505050905090565b600033610444818585610922565b60019150505b92915050565b60003361045e858285610a46565b610469858585610ac0565b506001949350505050565b6000336104448185856104878383610886565b6104919190611289565b610922565b6037546000906001600160a01b03163314806104bc57506033546001600160a01b031633145b6104e15760405162461bcd60e51b81526004016104d89061129c565b60405180910390fd5b6104eb8383610c85565b50600192915050565b600054610100900460ff16158080156105145750600054600160ff909116105b8061052e5750303b15801561052e575060005460ff166001145b6105915760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104d8565b6000805460ff1916600117905580156105b4576000805461ff0019166101001790555b336002600160a01b03146105f85760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064016104d8565b6001600160a01b0389166106595760405162461bcd60e51b815260206004820152602260248201527f4e617469766545524332303a20496e76616c6964206f776e6572206164647265604482015261737360f01b60648201526084016104d8565b603780546001600160a01b03808d166001600160a01b03199283161790925560388054928b1692909116919091179055603961069687898361134e565b50603a6106a485878361134e565b50603b805460ff191660ff851617905560368290556106c289610ddf565b8015610708576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050505050565b61071c610df8565b6107266000610ddf565b565b60345433906001600160a01b031681146107965760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016104d8565b61079f81610ddf565b50565b6060603a80546103b390611239565b6037546000906001600160a01b03163314806107d757506033546001600160a01b031633145b6107f35760405162461bcd60e51b81526004016104d89061129c565b6104eb8383610e52565b6000338161080b8286610886565b90508381101561086b5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104d8565b6104698286868403610922565b600033610444818585610ac0565b6001600160a01b03918216600090815260356020908152604080832093909416825291909152205490565b6108b9610df8565b603480546001600160a01b0383166001600160a01b031990911681179091556108ea6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6001600160a01b0383166109845760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104d8565b6001600160a01b0382166109e55760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104d8565b6001600160a01b0383811660008181526035602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b6000610a528484610886565b90506000198114610aba5781811015610aad5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016104d8565b610aba8484848403610922565b50505050565b6001600160a01b038316610b245760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104d8565b6001600160a01b038216610b865760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104d8565b6000806120206001600160a01b0316858585604051602001610baa9392919061140f565b60408051601f1981840301815290829052610bc491611433565b6000604051808303816000865af19150503d8060008114610c01576040519150601f19603f3d011682016040523d82523d6000602084013e610c06565b606091505b5091509150818015610c27575080806020019051810190610c27919061144f565b610c435760405162461bcd60e51b81526004016104d890611471565b836001600160a01b0316856001600160a01b03166000805160206114b583398151915285604051610c7691815260200190565b60405180910390a35050505050565b6001600160a01b038216610cdb5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104d8565b8060366000828254610ced9190611289565b9091555050604051600090819061202090610d109083908790879060200161140f565b60408051601f1981840301815290829052610d2a91611433565b6000604051808303816000865af19150503d8060008114610d67576040519150601f19603f3d011682016040523d82523d6000602084013e610d6c565b606091505b5091509150818015610d8d575080806020019051810190610d8d919061144f565b610da95760405162461bcd60e51b81526004016104d890611471565b6040518381526001600160a01b038516906000906000805160206114b5833981519152906020015b60405180910390a350505050565b603480546001600160a01b031916905561079f81610fac565b6033546001600160a01b031633146107265760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104d8565b6001600160a01b038216610eb25760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016104d8565b8060366000828254610ec491906114a1565b9091555050604051600090819061202090610ee79086908490879060200161140f565b60408051601f1981840301815290829052610f0191611433565b6000604051808303816000865af19150503d8060008114610f3e576040519150601f19603f3d011682016040523d82523d6000602084013e610f43565b606091505b5091509150818015610f64575080806020019051810190610f64919061144f565b610f805760405162461bcd60e51b81526004016104d890611471565b6040518381526000906001600160a01b038616906000805160206114b583398151915290602001610dd1565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60005b83811015611019578181015183820152602001611001565b50506000910152565b6020815260008251806020840152611041816040850160208701610ffe565b601f01601f19169190910160400192915050565b80356001600160a01b038116811461106c57600080fd5b919050565b6000806040838503121561108457600080fd5b61108d83611055565b946020939093013593505050565b6000806000606084860312156110b057600080fd5b6110b984611055565b92506110c760208501611055565b9150604084013590509250925092565b60008083601f8401126110e957600080fd5b50813567ffffffffffffffff81111561110157600080fd5b60208301915083602082850101111561111957600080fd5b9250929050565b600080600080600080600080600060e08a8c03121561113e57600080fd5b6111478a611055565b985061115560208b01611055565b975061116360408b01611055565b965060608a013567ffffffffffffffff8082111561118057600080fd5b61118c8d838e016110d7565b909850965060808c01359150808211156111a557600080fd5b506111b28c828d016110d7565b90955093505060a08a013560ff811681146111cc57600080fd5b8092505060c08a013590509295985092959850929598565b6000602082840312156111f657600080fd5b6111ff82611055565b9392505050565b6000806040838503121561121957600080fd5b61122283611055565b915061123060208401611055565b90509250929050565b600181811c9082168061124d57607f821691505b60208210810361126d57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8082018082111561044a5761044a611273565b6020808252602d908201527f4e617469766545524332303a204f6e6c7920707265646963617465206f72206f60408201526c1ddb995c8818d85b8818d85b1b609a1b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b601f82111561134957600081815260208120601f850160051c810160208610156113265750805b601f850160051c820191505b8181101561134557828155600101611332565b5050505b505050565b67ffffffffffffffff831115611366576113666112e9565b61137a836113748354611239565b836112ff565b6000601f8411600181146113ae57600085156113965750838201355b600019600387901b1c1916600186901b178355611408565b600083815260209020601f19861690835b828110156113df57868501358255602094850194600190920191016113bf565b50868210156113fc5760001960f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60008251611445818460208701610ffe565b9190910192915050565b60006020828403121561146157600080fd5b815180151581146111ff57600080fd5b60208082526016908201527514149150d3d35412531157d0d0531317d1905253115160521b604082015260600190565b8181038181111561044a5761044a61127356feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220c841ea6b2ede0321e38158aa4ac46f255ea0caf34a5429d64b9287ce7df103b364736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildERC20Artifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildERC20","sourceName":"contracts/child/ChildERC20.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rootToken_","type":"address"},{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"uint8","name":"decimals_","type":"uint8"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"}],"name":"invalidateNext","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rootToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611920806100206000396000f3fe608060405234801561001057600080fd5b50600436106101215760003560e01c806340c10f19116100ad578063a457c2d711610071578063a457c2d71461028b578063a9059cbb1461029e578063dd62ed3e146102b1578063e6198705146102c4578063f6d2ee86146102d557600080fd5b806340c10f191461021f57806370a082311461023257806395d89b411461025b5780639b77ef11146102635780639dc29fac1461027857600080fd5b80631f2d0065116100f45780631f2d00651461018c57806323b872dd146101b15780632d0335ab146101c4578063313ce567146101ed578063395093511461020c57600080fd5b806306fdde0314610126578063095ea7b3146101445780630c53c51c1461016757806318160ddd1461017a575b600080fd5b61012e6102e8565b60405161013b9190611395565b60405180910390f35b6101576101523660046113cb565b61037a565b604051901515815260200161013b565b61012e61017536600461144f565b61039e565b60a0545b60405190815260200161013b565b60d1546001600160a01b03165b6040516001600160a01b03909116815260200161013b565b6101576101bf3660046114c5565b610681565b61017e6101d2366004611501565b6001600160a01b031660009081526038602052604090205490565b60d154600160a01b900460ff1660405160ff909116815260200161013b565b61015761021a3660046113cb565b6106af565b61015761022d3660046113cb565b6106db565b61017e610240366004611501565b6001600160a01b03166000908152609e602052604090205490565b61012e61071b565b61027661027136600461151c565b61072a565b005b6101576102863660046113cb565b610751565b6101576102993660046113cb565b610788565b6101576102ac3660046113cb565b61080e565b61017e6102bf366004611535565b610826565b60d0546001600160a01b0316610199565b6102766102e3366004611568565b610851565b606060a180546102f7906115ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610323906115ec565b80156103705780601f1061034557610100808354040283529160200191610370565b820191906000526020600020905b81548152906001019060200180831161035357829003601f168201915b5050505050905090565b600080610385610ad1565b9050610392818585610ae0565b60019150505b92915050565b606060006103e187878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610c0592505050565b90506001600160e01b031960003581169082160361046c5760405162461bcd60e51b815260206004820152603d60248201527f66756e6374696f6e5369676e61747572652063616e206e6f74206265206f662060448201527f657865637574654d6574615472616e73616374696f6e206d6574686f6400000060648201526084015b60405180910390fd5b604080516060810182526001600160a01b038a16600081815260386020908152848220548452808401929092528351601f8b0183900483028101830185528a815290938301918b908b9081908401838280828437600092019190915250505091525090506104dd8982888888610c20565b6105335760405162461bcd60e51b815260206004820152602160248201527f5369676e657220616e64207369676e617475726520646f206e6f74206d6174636044820152600d60fb1b6064820152608401610463565b603860008a6001600160a01b03166001600160a01b031681526020019081526020016000206000815460010191905081905550600080306001600160a01b03168a8a8d60405160200161058893929190611626565b60408051601f19818403018152908290526105a29161164c565b6000604051808303816000865af19150503d80600081146105df576040519150601f19603f3d011682016040523d82523d6000602084013e6105e4565b606091505b5091509150816106365760405162461bcd60e51b815260206004820152601c60248201527f46756e6374696f6e2063616c6c206e6f74207375636365737366756c000000006044820152606401610463565b7f5845892132946850460bff5a0083f71031bc5bf9aadcd40f1de79423eac9b10b8b338c8c60405161066b9493929190611668565b60405180910390a19a9950505050505050505050565b60008061068c610ad1565b9050610699858285610cfc565b6106a4858585610d76565b506001949350505050565b6000806106ba610ad1565b90506103928185856106cc8589610826565b6106d691906116b4565b610ae0565b60d0546000906001600160a01b031633146107085760405162461bcd60e51b8152600401610463906116d5565b6107128383610f0f565b50600192915050565b606060a280546102f7906115ec565b33600090815260386020526040812080548392906107499084906116b4565b909155505050565b60d0546000906001600160a01b0316331461077e5760405162461bcd60e51b8152600401610463906116d5565b6107128383610fbf565b600080610793610ad1565b905060006107a18286610826565b9050838110156108015760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610463565b6106a48286868403610ae0565b600080610819610ad1565b9050610392818585610d76565b6001600160a01b039182166000908152609f6020908152604080832093909416825291909152205490565b606b54610100900460ff16158080156108715750606b54600160ff909116105b8061088b5750303b15801561088b5750606b5460ff166001145b6108ee5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610463565b606b805460ff19166001179055801561091157606b805461ff0019166101001790555b6001600160a01b0387161580159061092857508415155b801561093357508215155b61097f5760405162461bcd60e51b815260206004820152601e60248201527f4368696c6445524332303a204241445f494e495449414c495a4154494f4e00006044820152606401610463565b60d1805460ff8416600160a01b026001600160a81b03199091166001600160a01b038a161717905560d080546001600160a01b03191633179055604080516020601f8801819004810282018101909252868152610a2a91889088908190840183828082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506110de92505050565b610a8286868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250506040805180820190915260018152603160f81b6020820152915061110f9050565b8015610ac857606b805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050565b6000610adb61117b565b905090565b6001600160a01b038316610b425760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610463565b6001600160a01b038216610ba35760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610463565b6001600160a01b038381166000818152609f602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b60008151600003610c1857506000919050565b506020015190565b6000806001610c36610c31886111d7565b611254565b6040805160008152602081018083529290925260ff861690820152606081018790526080810186905260a0016020604051602081039080840390855afa158015610c84573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610cdb5760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964207369676e617475726560781b6044820152606401610463565b866001600160a01b0316816001600160a01b03161491505095945050505050565b6000610d088484610826565b90506000198114610d705781811015610d635760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610463565b610d708484848403610ae0565b50505050565b6001600160a01b038316610dda5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610463565b6001600160a01b038216610e3c5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610463565b6001600160a01b0383166000908152609e602052604090205481811015610eb45760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610463565b6001600160a01b038085166000818152609e602052604080822086860390559286168082529083902080548601905591516000805160206118cb83398151915290610f029086815260200190565b60405180910390a3610d70565b6001600160a01b038216610f655760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610463565b8060a06000828254610f7791906116b4565b90915550506001600160a01b0382166000818152609e60209081526040808320805486019055518481526000805160206118cb833981519152910160405180910390a35b5050565b6001600160a01b03821661101f5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610463565b6001600160a01b0382166000908152609e6020526040902054818110156110935760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610463565b6001600160a01b0383166000818152609e60209081526040808320868603905560a080548790039055518581529192916000805160206118cb8339815191529101610bf8565b505050565b606b54610100900460ff166111055760405162461bcd60e51b815260040161046390611718565b610fbb8282611281565b815160208084019190912082519183019190912060038290556004819055466001557f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f61115d8184846112c1565b600055600280546001600160a01b0319163017905560055550505050565b60003033036111d157600080368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050503601516001600160a01b031691506111d49050565b50335b90565b60006040518060800160405280604381526020016118886043913980516020918201208351848301516040808701518051908601209051611237950193845260208401929092526001600160a01b03166040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b600061039861126161130a565b8360405161190160f01b8152600281019290925260228201526042902090565b606b54610100900460ff166112a85760405162461bcd60e51b815260040161046390611718565b60a16112b483826117c7565b5060a26110d982826117c7565b6040805160208101859052908101839052606081018290524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090509392505050565b6002546000906001600160a01b031630148015611328575060015446145b15611334575060005490565b610adb6005546003546004546112c1565b60005b83811015611360578181015183820152602001611348565b50506000910152565b60008151808452611381816020860160208601611345565b601f01601f19169290920160200192915050565b6020815260006113a86020830184611369565b9392505050565b80356001600160a01b03811681146113c657600080fd5b919050565b600080604083850312156113de57600080fd5b6113e7836113af565b946020939093013593505050565b60008083601f84011261140757600080fd5b50813567ffffffffffffffff81111561141f57600080fd5b60208301915083602082850101111561143757600080fd5b9250929050565b803560ff811681146113c657600080fd5b60008060008060008060a0878903121561146857600080fd5b611471876113af565b9550602087013567ffffffffffffffff81111561148d57600080fd5b61149989828a016113f5565b90965094505060408701359250606087013591506114b96080880161143e565b90509295509295509295565b6000806000606084860312156114da57600080fd5b6114e3846113af565b92506114f1602085016113af565b9150604084013590509250925092565b60006020828403121561151357600080fd5b6113a8826113af565b60006020828403121561152e57600080fd5b5035919050565b6000806040838503121561154857600080fd5b611551836113af565b915061155f602084016113af565b90509250929050565b6000806000806000806080878903121561158157600080fd5b61158a876113af565b9550602087013567ffffffffffffffff808211156115a757600080fd5b6115b38a838b016113f5565b909750955060408901359150808211156115cc57600080fd5b506115d989828a016113f5565b90945092506114b990506060880161143e565b600181811c9082168061160057607f821691505b60208210810361162057634e487b7160e01b600052602260045260246000fd5b50919050565b8284823760609190911b6bffffffffffffffffffffffff19169101908152601401919050565b6000825161165e818460208701611345565b9190910192915050565b6001600160a01b0385811682528416602082015260606040820181905281018290526000828460808401376000608084840101526080601f19601f850116830101905095945050505050565b8082018082111561039857634e487b7160e01b600052601160045260246000fd5b60208082526023908201527f4368696c6445524332303a204f6e6c79207072656469636174652063616e2063604082015262185b1b60ea1b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b601f8211156110d957600081815260208120601f850160051c810160208610156117a05750805b601f850160051c820191505b818110156117bf578281556001016117ac565b505050505050565b815167ffffffffffffffff8111156117e1576117e1611763565b6117f5816117ef84546115ec565b84611779565b602080601f83116001811461182a57600084156118125750858301515b600019600386901b1c1916600185901b1785556117bf565b600085815260208120601f198616915b828110156118595788860151825594840194600190910190840161183a565b50858210156118775787850151600019600388901b60f8161c191681555b5050505050600190811b0190555056fe4d6574615472616e73616374696f6e2875696e74323536206e6f6e63652c616464726573732066726f6d2c62797465732066756e6374696f6e5369676e617475726529ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220b322ab3a17e671324178982233e8fdd83f1d7487c05f56ee80ebf7cb64b9b2c964736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101215760003560e01c806340c10f19116100ad578063a457c2d711610071578063a457c2d71461028b578063a9059cbb1461029e578063dd62ed3e146102b1578063e6198705146102c4578063f6d2ee86146102d557600080fd5b806340c10f191461021f57806370a082311461023257806395d89b411461025b5780639b77ef11146102635780639dc29fac1461027857600080fd5b80631f2d0065116100f45780631f2d00651461018c57806323b872dd146101b15780632d0335ab146101c4578063313ce567146101ed578063395093511461020c57600080fd5b806306fdde0314610126578063095ea7b3146101445780630c53c51c1461016757806318160ddd1461017a575b600080fd5b61012e6102e8565b60405161013b9190611395565b60405180910390f35b6101576101523660046113cb565b61037a565b604051901515815260200161013b565b61012e61017536600461144f565b61039e565b60a0545b60405190815260200161013b565b60d1546001600160a01b03165b6040516001600160a01b03909116815260200161013b565b6101576101bf3660046114c5565b610681565b61017e6101d2366004611501565b6001600160a01b031660009081526038602052604090205490565b60d154600160a01b900460ff1660405160ff909116815260200161013b565b61015761021a3660046113cb565b6106af565b61015761022d3660046113cb565b6106db565b61017e610240366004611501565b6001600160a01b03166000908152609e602052604090205490565b61012e61071b565b61027661027136600461151c565b61072a565b005b6101576102863660046113cb565b610751565b6101576102993660046113cb565b610788565b6101576102ac3660046113cb565b61080e565b61017e6102bf366004611535565b610826565b60d0546001600160a01b0316610199565b6102766102e3366004611568565b610851565b606060a180546102f7906115ec565b80601f0160208091040260200160405190810160405280929190818152602001828054610323906115ec565b80156103705780601f1061034557610100808354040283529160200191610370565b820191906000526020600020905b81548152906001019060200180831161035357829003601f168201915b5050505050905090565b600080610385610ad1565b9050610392818585610ae0565b60019150505b92915050565b606060006103e187878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610c0592505050565b90506001600160e01b031960003581169082160361046c5760405162461bcd60e51b815260206004820152603d60248201527f66756e6374696f6e5369676e61747572652063616e206e6f74206265206f662060448201527f657865637574654d6574615472616e73616374696f6e206d6574686f6400000060648201526084015b60405180910390fd5b604080516060810182526001600160a01b038a16600081815260386020908152848220548452808401929092528351601f8b0183900483028101830185528a815290938301918b908b9081908401838280828437600092019190915250505091525090506104dd8982888888610c20565b6105335760405162461bcd60e51b815260206004820152602160248201527f5369676e657220616e64207369676e617475726520646f206e6f74206d6174636044820152600d60fb1b6064820152608401610463565b603860008a6001600160a01b03166001600160a01b031681526020019081526020016000206000815460010191905081905550600080306001600160a01b03168a8a8d60405160200161058893929190611626565b60408051601f19818403018152908290526105a29161164c565b6000604051808303816000865af19150503d80600081146105df576040519150601f19603f3d011682016040523d82523d6000602084013e6105e4565b606091505b5091509150816106365760405162461bcd60e51b815260206004820152601c60248201527f46756e6374696f6e2063616c6c206e6f74207375636365737366756c000000006044820152606401610463565b7f5845892132946850460bff5a0083f71031bc5bf9aadcd40f1de79423eac9b10b8b338c8c60405161066b9493929190611668565b60405180910390a19a9950505050505050505050565b60008061068c610ad1565b9050610699858285610cfc565b6106a4858585610d76565b506001949350505050565b6000806106ba610ad1565b90506103928185856106cc8589610826565b6106d691906116b4565b610ae0565b60d0546000906001600160a01b031633146107085760405162461bcd60e51b8152600401610463906116d5565b6107128383610f0f565b50600192915050565b606060a280546102f7906115ec565b33600090815260386020526040812080548392906107499084906116b4565b909155505050565b60d0546000906001600160a01b0316331461077e5760405162461bcd60e51b8152600401610463906116d5565b6107128383610fbf565b600080610793610ad1565b905060006107a18286610826565b9050838110156108015760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610463565b6106a48286868403610ae0565b600080610819610ad1565b9050610392818585610d76565b6001600160a01b039182166000908152609f6020908152604080832093909416825291909152205490565b606b54610100900460ff16158080156108715750606b54600160ff909116105b8061088b5750303b15801561088b5750606b5460ff166001145b6108ee5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610463565b606b805460ff19166001179055801561091157606b805461ff0019166101001790555b6001600160a01b0387161580159061092857508415155b801561093357508215155b61097f5760405162461bcd60e51b815260206004820152601e60248201527f4368696c6445524332303a204241445f494e495449414c495a4154494f4e00006044820152606401610463565b60d1805460ff8416600160a01b026001600160a81b03199091166001600160a01b038a161717905560d080546001600160a01b03191633179055604080516020601f8801819004810282018101909252868152610a2a91889088908190840183828082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152506110de92505050565b610a8286868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250506040805180820190915260018152603160f81b6020820152915061110f9050565b8015610ac857606b805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050565b6000610adb61117b565b905090565b6001600160a01b038316610b425760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610463565b6001600160a01b038216610ba35760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610463565b6001600160a01b038381166000818152609f602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b60008151600003610c1857506000919050565b506020015190565b6000806001610c36610c31886111d7565b611254565b6040805160008152602081018083529290925260ff861690820152606081018790526080810186905260a0016020604051602081039080840390855afa158015610c84573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610cdb5760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964207369676e617475726560781b6044820152606401610463565b866001600160a01b0316816001600160a01b03161491505095945050505050565b6000610d088484610826565b90506000198114610d705781811015610d635760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610463565b610d708484848403610ae0565b50505050565b6001600160a01b038316610dda5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610463565b6001600160a01b038216610e3c5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610463565b6001600160a01b0383166000908152609e602052604090205481811015610eb45760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610463565b6001600160a01b038085166000818152609e602052604080822086860390559286168082529083902080548601905591516000805160206118cb83398151915290610f029086815260200190565b60405180910390a3610d70565b6001600160a01b038216610f655760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610463565b8060a06000828254610f7791906116b4565b90915550506001600160a01b0382166000818152609e60209081526040808320805486019055518481526000805160206118cb833981519152910160405180910390a35b5050565b6001600160a01b03821661101f5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610463565b6001600160a01b0382166000908152609e6020526040902054818110156110935760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610463565b6001600160a01b0383166000818152609e60209081526040808320868603905560a080548790039055518581529192916000805160206118cb8339815191529101610bf8565b505050565b606b54610100900460ff166111055760405162461bcd60e51b815260040161046390611718565b610fbb8282611281565b815160208084019190912082519183019190912060038290556004819055466001557f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f61115d8184846112c1565b600055600280546001600160a01b0319163017905560055550505050565b60003033036111d157600080368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050503601516001600160a01b031691506111d49050565b50335b90565b60006040518060800160405280604381526020016118886043913980516020918201208351848301516040808701518051908601209051611237950193845260208401929092526001600160a01b03166040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b600061039861126161130a565b8360405161190160f01b8152600281019290925260228201526042902090565b606b54610100900460ff166112a85760405162461bcd60e51b815260040161046390611718565b60a16112b483826117c7565b5060a26110d982826117c7565b6040805160208101859052908101839052606081018290524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090509392505050565b6002546000906001600160a01b031630148015611328575060015446145b15611334575060005490565b610adb6005546003546004546112c1565b60005b83811015611360578181015183820152602001611348565b50506000910152565b60008151808452611381816020860160208601611345565b601f01601f19169290920160200192915050565b6020815260006113a86020830184611369565b9392505050565b80356001600160a01b03811681146113c657600080fd5b919050565b600080604083850312156113de57600080fd5b6113e7836113af565b946020939093013593505050565b60008083601f84011261140757600080fd5b50813567ffffffffffffffff81111561141f57600080fd5b60208301915083602082850101111561143757600080fd5b9250929050565b803560ff811681146113c657600080fd5b60008060008060008060a0878903121561146857600080fd5b611471876113af565b9550602087013567ffffffffffffffff81111561148d57600080fd5b61149989828a016113f5565b90965094505060408701359250606087013591506114b96080880161143e565b90509295509295509295565b6000806000606084860312156114da57600080fd5b6114e3846113af565b92506114f1602085016113af565b9150604084013590509250925092565b60006020828403121561151357600080fd5b6113a8826113af565b60006020828403121561152e57600080fd5b5035919050565b6000806040838503121561154857600080fd5b611551836113af565b915061155f602084016113af565b90509250929050565b6000806000806000806080878903121561158157600080fd5b61158a876113af565b9550602087013567ffffffffffffffff808211156115a757600080fd5b6115b38a838b016113f5565b909750955060408901359150808211156115cc57600080fd5b506115d989828a016113f5565b90945092506114b990506060880161143e565b600181811c9082168061160057607f821691505b60208210810361162057634e487b7160e01b600052602260045260246000fd5b50919050565b8284823760609190911b6bffffffffffffffffffffffff19169101908152601401919050565b6000825161165e818460208701611345565b9190910192915050565b6001600160a01b0385811682528416602082015260606040820181905281018290526000828460808401376000608084840101526080601f19601f850116830101905095945050505050565b8082018082111561039857634e487b7160e01b600052601160045260246000fd5b60208082526023908201527f4368696c6445524332303a204f6e6c79207072656469636174652063616e2063604082015262185b1b60ea1b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b601f8211156110d957600081815260208120601f850160051c810160208610156117a05750805b601f850160051c820191505b818110156117bf578281556001016117ac565b505050505050565b815167ffffffffffffffff8111156117e1576117e1611763565b6117f5816117ef84546115ec565b84611779565b602080601f83116001811461182a57600084156118125750858301515b600019600386901b1c1916600185901b1785556117bf565b600085815260208120601f198616915b828110156118595788860151825594840194600190910190840161183a565b50858210156118775787850151600019600388901b60f8161c191681555b5050505050600190811b0190555056fe4d6574615472616e73616374696f6e2875696e74323536206e6f6e63652c616464726573732066726f6d2c62797465732066756e6374696f6e5369676e617475726529ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220b322ab3a17e671324178982233e8fdd83f1d7487c05f56ee80ebf7cb64b9b2c964736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildERC20PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildERC20Predicate","sourceName":"contracts/child/ChildERC20Predicate.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2ERC20Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2ERC20Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2TokenMapped","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootERC20Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"},{"internalType":"address","name":"newNativeTokenRootAddress","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootERC20Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IChildERC20","name":"childToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC20","name":"childToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506114ea806100206000396000f3fe608060405234801561001057600080fd5b50600436106101375760003560e01c80637efab4f5116100b8578063c3b35a7e1161007c578063c3b35a7e14610270578063d41f177114610283578063e0563ab1146102aa578063eeb49945146102b3578063f3fef3a3146102c6578063f6451255146102d957600080fd5b80637efab4f5146101fa578063947287cf1461022357806397e5230d1461022c578063b176806514610236578063b68ad1e41461025d57600080fd5b80633b878c22116100ff5780633b878c22146101ab57806351351d53146101b457806355b01e4d146101c25780635ea5df79146101d057806371cf93b7146101e757600080fd5b806305dc2e8f1461013c57806307b3e2521461016c5780631459457a1461017a5780631bc114ba1461018f578063284017f5146101a2575b600080fd5b60345461014f906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61014f6004600360981b0181565b61018d610188366004610ff2565b610300565b005b60335461014f906001600160a01b031681565b61014f61202081565b61014f61101081565b61014f6002600160a01b0381565b61014f6004600160991b0181565b6101d961138881565b604051908152602001610163565b60355461014f906001600160a01b031681565b61014f610208366004611063565b6037602052600090815260409020546001600160a01b031681565b6101d961520881565b6101d9620249f081565b6101d97f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60365461014f906001600160a01b031681565b61018d61027e366004611087565b610464565b6101d97f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b61014f61203081565b61018d6102c13660046110c8565b610474565b61018d6102d4366004611151565b610648565b6101d97f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b336002600160a01b03146103495760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b600054610100900460ff16158080156103695750600054600160ff909116105b806103835750303b158015610383575060005460ff166001145b6103e65760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610340565b6000805460ff191660011790558015610409576000805461ff0019166101001790555b6104168686868686610657565b801561045c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b61046f8383836107a7565b505050565b6034546001600160a01b031633146104df5760405162461bcd60e51b815260206004820152602860248201527f4368696c6445524332305072656469636174653a204f4e4c595f53544154455f6044820152672922a1a2a4ab22a960c11b6064820152608401610340565b6035546001600160a01b0384811691161461054d5760405162461bcd60e51b815260206004820152602860248201527f4368696c6445524332305072656469636174653a204f4e4c595f524f4f545f50604482015267524544494341544560c01b6064820152608401610340565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82161057c60206000848661117d565b610585916111a7565b036105a45761059f61059a826020818661117d565b610b1d565b610642565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad6105d360206000848661117d565b6105dc916111a7565b036105eb5761059f8282610dc8565b60405162461bcd60e51b815260206004820152602660248201527f4368696c6445524332305072656469636174653a20494e56414c49445f5349476044820152654e415455524560d01b6064820152608401610340565b50505050565b6106538233836107a7565b5050565b6001600160a01b0385161580159061067757506001600160a01b03841615155b801561068b57506001600160a01b03831615155b801561069f57506001600160a01b03821615155b6106fb5760405162461bcd60e51b815260206004820152602760248201527f4368696c6445524332305072656469636174653a204241445f494e495449414c60448201526624ad20aa24a7a760c91b6064820152608401610340565b603380546001600160a01b03199081166001600160a01b0388811691909117909255603480548216878416179055603580548216868416179055603680549091168483161790558116156107a0576001600160a01b03811660008181526037602052604080822080546001600160a01b03191661101090811790915590519092917f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d91a35b5050505050565b826001600160a01b03163b60000361080b5760405162461bcd60e51b815260206004820152602160248201527f4368696c6445524332305072656469636174653a204e4f545f434f4e545241436044820152601560fa1b6064820152608401610340565b6000836001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561084b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086f91906111c5565b6001600160a01b038181166000908152603760205260409020549192508581169116146108ae5760405162461bcd60e51b8152600401610340906111e2565b6001600160a01b0381166108c4576108c4611225565b306001600160a01b0316846001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561090c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061093091906111c5565b6001600160a01b03161461094657610946611225565b604051632770a7eb60e21b81526001600160a01b03851690639dc29fac90610974903390869060040161123b565b6020604051808303816000875af1158015610993573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b79190611254565b610a035760405162461bcd60e51b815260206004820181905260248201527f4368696c6445524332305072656469636174653a204255524e5f4641494c45446044820152606401610340565b603354603554604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610a8e9391169160c4016112bc565b600060405180830381600087803b158015610aa857600080fd5b505af1158015610abc573d6000803e3d6000fd5b50505050826001600160a01b0316846001600160a01b0316826001600160a01b03167fa0923f060a16fc784558d43de424ffde7b01643de5e5d335851b9df94c76bb273386604051610b0f92919061123b565b60405180910390a450505050565b6000808080610b2e858701876112e8565b6001600160a01b0380851660009081526037602052604090205494985092965090945092501680610b715760405162461bcd60e51b8152600401610340906111e2565b806001600160a01b03163b600003610b8b57610b8b611225565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bcb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bef91906111c5565b9050856001600160a01b0316816001600160a01b031614610c1257610c12611225565b6001600160a01b038116610c2857610c28611225565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c9491906111c5565b6001600160a01b031614610caa57610caa611225565b6040516340c10f1960e01b81526001600160a01b038316906340c10f1990610cd8908790879060040161123b565b6020604051808303816000875af1158015610cf7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1b9190611254565b610d675760405162461bcd60e51b815260206004820181905260248201527f4368696c6445524332305072656469636174653a204d494e545f4641494c45446044820152606401610340565b836001600160a01b0316826001600160a01b0316876001600160a01b03167fdf34f3a3ed8bedc14a4b284ebaee5374d55b64bac6a84c270dabe8fd6b4cdafd8887604051610db692919061123b565b60405180910390a45050505050505050565b6000808080610dd9858701876113dc565b92975090955093509150506001600160a01b038416610dfa57610dfa611225565b6001600160a01b038481166000908152603760205260409020541615610e2257610e22611225565b6036546040516bffffffffffffffffffffffff19606087901b166020820152600091610e72916001600160a01b039091169060340160405160208183030381529060405280519060200120610f3d565b6001600160a01b038681166000908152603760205260409081902080546001600160a01b031916928416928317905551637b69774360e11b81529192509063f6d2ee8690610eca908890889088908890600401611469565b600060405180830381600087803b158015610ee457600080fd5b505af1158015610ef8573d6000803e3d6000fd5b50506040516001600160a01b038085169350881691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a350505050505050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116610fd45760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b6044820152606401610340565b92915050565b6001600160a01b0381168114610fef57600080fd5b50565b600080600080600060a0868803121561100a57600080fd5b853561101581610fda565b9450602086013561102581610fda565b9350604086013561103581610fda565b9250606086013561104581610fda565b9150608086013561105581610fda565b809150509295509295909350565b60006020828403121561107557600080fd5b813561108081610fda565b9392505050565b60008060006060848603121561109c57600080fd5b83356110a781610fda565b925060208401356110b781610fda565b929592945050506040919091013590565b600080600080606085870312156110de57600080fd5b8435935060208501356110f081610fda565b9250604085013567ffffffffffffffff8082111561110d57600080fd5b818701915087601f83011261112157600080fd5b81358181111561113057600080fd5b88602082850101111561114257600080fd5b95989497505060200194505050565b6000806040838503121561116457600080fd5b823561116f81610fda565b946020939093013593505050565b6000808585111561118d57600080fd5b8386111561119a57600080fd5b5050820193919092039150565b80356020831015610fd457600019602084900360031b1b1692915050565b6000602082840312156111d757600080fd5b815161108081610fda565b60208082526023908201527f4368696c6445524332305072656469636174653a20554e4d41505045445f544f60408201526225a2a760e91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b60006020828403121561126657600080fd5b8151801515811461108057600080fd5b6000815180845260005b8181101561129c57602081850181015186830182015201611280565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190526000906112e090830184611276565b949350505050565b600080600080608085870312156112fe57600080fd5b843561130981610fda565b9350602085013561131981610fda565b9250604085013561132981610fda565b9396929550929360600135925050565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261136057600080fd5b813567ffffffffffffffff8082111561137b5761137b611339565b604051601f8301601f19908116603f011681019082821181831017156113a3576113a3611339565b816040528381528660208588010111156113bc57600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600080600060a086880312156113f457600080fd5b85359450602086013561140681610fda565b9350604086013567ffffffffffffffff8082111561142357600080fd5b61142f89838a0161134f565b9450606088013591508082111561144557600080fd5b506114528882890161134f565b925050608086013560ff8116811461105557600080fd5b6001600160a01b038516815260806020820181905260009061148d90830186611276565b828103604084015261149f8186611276565b91505060ff831660608301529594505050505056fea2646970667358221220e0f44ad7883c5cd2dbd85a60832f6c9874795bf975cefd366724cc91bbc22dcd64736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101375760003560e01c80637efab4f5116100b8578063c3b35a7e1161007c578063c3b35a7e14610270578063d41f177114610283578063e0563ab1146102aa578063eeb49945146102b3578063f3fef3a3146102c6578063f6451255146102d957600080fd5b80637efab4f5146101fa578063947287cf1461022357806397e5230d1461022c578063b176806514610236578063b68ad1e41461025d57600080fd5b80633b878c22116100ff5780633b878c22146101ab57806351351d53146101b457806355b01e4d146101c25780635ea5df79146101d057806371cf93b7146101e757600080fd5b806305dc2e8f1461013c57806307b3e2521461016c5780631459457a1461017a5780631bc114ba1461018f578063284017f5146101a2575b600080fd5b60345461014f906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61014f6004600360981b0181565b61018d610188366004610ff2565b610300565b005b60335461014f906001600160a01b031681565b61014f61202081565b61014f61101081565b61014f6002600160a01b0381565b61014f6004600160991b0181565b6101d961138881565b604051908152602001610163565b60355461014f906001600160a01b031681565b61014f610208366004611063565b6037602052600090815260409020546001600160a01b031681565b6101d961520881565b6101d9620249f081565b6101d97f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60365461014f906001600160a01b031681565b61018d61027e366004611087565b610464565b6101d97f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b61014f61203081565b61018d6102c13660046110c8565b610474565b61018d6102d4366004611151565b610648565b6101d97f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b336002600160a01b03146103495760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b600054610100900460ff16158080156103695750600054600160ff909116105b806103835750303b158015610383575060005460ff166001145b6103e65760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610340565b6000805460ff191660011790558015610409576000805461ff0019166101001790555b6104168686868686610657565b801561045c576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b61046f8383836107a7565b505050565b6034546001600160a01b031633146104df5760405162461bcd60e51b815260206004820152602860248201527f4368696c6445524332305072656469636174653a204f4e4c595f53544154455f6044820152672922a1a2a4ab22a960c11b6064820152608401610340565b6035546001600160a01b0384811691161461054d5760405162461bcd60e51b815260206004820152602860248201527f4368696c6445524332305072656469636174653a204f4e4c595f524f4f545f50604482015267524544494341544560c01b6064820152608401610340565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82161057c60206000848661117d565b610585916111a7565b036105a45761059f61059a826020818661117d565b610b1d565b610642565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad6105d360206000848661117d565b6105dc916111a7565b036105eb5761059f8282610dc8565b60405162461bcd60e51b815260206004820152602660248201527f4368696c6445524332305072656469636174653a20494e56414c49445f5349476044820152654e415455524560d01b6064820152608401610340565b50505050565b6106538233836107a7565b5050565b6001600160a01b0385161580159061067757506001600160a01b03841615155b801561068b57506001600160a01b03831615155b801561069f57506001600160a01b03821615155b6106fb5760405162461bcd60e51b815260206004820152602760248201527f4368696c6445524332305072656469636174653a204241445f494e495449414c60448201526624ad20aa24a7a760c91b6064820152608401610340565b603380546001600160a01b03199081166001600160a01b0388811691909117909255603480548216878416179055603580548216868416179055603680549091168483161790558116156107a0576001600160a01b03811660008181526037602052604080822080546001600160a01b03191661101090811790915590519092917f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d91a35b5050505050565b826001600160a01b03163b60000361080b5760405162461bcd60e51b815260206004820152602160248201527f4368696c6445524332305072656469636174653a204e4f545f434f4e545241436044820152601560fa1b6064820152608401610340565b6000836001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561084b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086f91906111c5565b6001600160a01b038181166000908152603760205260409020549192508581169116146108ae5760405162461bcd60e51b8152600401610340906111e2565b6001600160a01b0381166108c4576108c4611225565b306001600160a01b0316846001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561090c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061093091906111c5565b6001600160a01b03161461094657610946611225565b604051632770a7eb60e21b81526001600160a01b03851690639dc29fac90610974903390869060040161123b565b6020604051808303816000875af1158015610993573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b79190611254565b610a035760405162461bcd60e51b815260206004820181905260248201527f4368696c6445524332305072656469636174653a204255524e5f4641494c45446044820152606401610340565b603354603554604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610a8e9391169160c4016112bc565b600060405180830381600087803b158015610aa857600080fd5b505af1158015610abc573d6000803e3d6000fd5b50505050826001600160a01b0316846001600160a01b0316826001600160a01b03167fa0923f060a16fc784558d43de424ffde7b01643de5e5d335851b9df94c76bb273386604051610b0f92919061123b565b60405180910390a450505050565b6000808080610b2e858701876112e8565b6001600160a01b0380851660009081526037602052604090205494985092965090945092501680610b715760405162461bcd60e51b8152600401610340906111e2565b806001600160a01b03163b600003610b8b57610b8b611225565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bcb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bef91906111c5565b9050856001600160a01b0316816001600160a01b031614610c1257610c12611225565b6001600160a01b038116610c2857610c28611225565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c9491906111c5565b6001600160a01b031614610caa57610caa611225565b6040516340c10f1960e01b81526001600160a01b038316906340c10f1990610cd8908790879060040161123b565b6020604051808303816000875af1158015610cf7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1b9190611254565b610d675760405162461bcd60e51b815260206004820181905260248201527f4368696c6445524332305072656469636174653a204d494e545f4641494c45446044820152606401610340565b836001600160a01b0316826001600160a01b0316876001600160a01b03167fdf34f3a3ed8bedc14a4b284ebaee5374d55b64bac6a84c270dabe8fd6b4cdafd8887604051610db692919061123b565b60405180910390a45050505050505050565b6000808080610dd9858701876113dc565b92975090955093509150506001600160a01b038416610dfa57610dfa611225565b6001600160a01b038481166000908152603760205260409020541615610e2257610e22611225565b6036546040516bffffffffffffffffffffffff19606087901b166020820152600091610e72916001600160a01b039091169060340160405160208183030381529060405280519060200120610f3d565b6001600160a01b038681166000908152603760205260409081902080546001600160a01b031916928416928317905551637b69774360e11b81529192509063f6d2ee8690610eca908890889088908890600401611469565b600060405180830381600087803b158015610ee457600080fd5b505af1158015610ef8573d6000803e3d6000fd5b50506040516001600160a01b038085169350881691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a350505050505050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116610fd45760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b6044820152606401610340565b92915050565b6001600160a01b0381168114610fef57600080fd5b50565b600080600080600060a0868803121561100a57600080fd5b853561101581610fda565b9450602086013561102581610fda565b9350604086013561103581610fda565b9250606086013561104581610fda565b9150608086013561105581610fda565b809150509295509295909350565b60006020828403121561107557600080fd5b813561108081610fda565b9392505050565b60008060006060848603121561109c57600080fd5b83356110a781610fda565b925060208401356110b781610fda565b929592945050506040919091013590565b600080600080606085870312156110de57600080fd5b8435935060208501356110f081610fda565b9250604085013567ffffffffffffffff8082111561110d57600080fd5b818701915087601f83011261112157600080fd5b81358181111561113057600080fd5b88602082850101111561114257600080fd5b95989497505060200194505050565b6000806040838503121561116457600080fd5b823561116f81610fda565b946020939093013593505050565b6000808585111561118d57600080fd5b8386111561119a57600080fd5b5050820193919092039150565b80356020831015610fd457600019602084900360031b1b1692915050565b6000602082840312156111d757600080fd5b815161108081610fda565b60208082526023908201527f4368696c6445524332305072656469636174653a20554e4d41505045445f544f60408201526225a2a760e91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b60006020828403121561126657600080fd5b8151801515811461108057600080fd5b6000815180845260005b8181101561129c57602081850181015186830182015201611280565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190526000906112e090830184611276565b949350505050565b600080600080608085870312156112fe57600080fd5b843561130981610fda565b9350602085013561131981610fda565b9250604085013561132981610fda565b9396929550929360600135925050565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261136057600080fd5b813567ffffffffffffffff8082111561137b5761137b611339565b604051601f8301601f19908116603f011681019082821181831017156113a3576113a3611339565b816040528381528660208588010111156113bc57600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600080600060a086880312156113f457600080fd5b85359450602086013561140681610fda565b9350604086013567ffffffffffffffff8082111561142357600080fd5b61142f89838a0161134f565b9450606088013591508082111561144557600080fd5b506114528882890161134f565b925050608086013560ff8116811461105557600080fd5b6001600160a01b038516815260806020820181905260009061148d90830186611276565b828103604084015261149f8186611276565b91505060ff831660608301529594505050505056fea2646970667358221220e0f44ad7883c5cd2dbd85a60832f6c9874795bf975cefd366724cc91bbc22dcd64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildERC20PredicateACLArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildERC20PredicateAccessList","sourceName":"contracts/child/ChildERC20PredicateAccessList.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"AllowListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"BlockListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2ERC20Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2ERC20Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2TokenMapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootERC20Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"},{"internalType":"address","name":"newNativeTokenRootAddress","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootERC20Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"},{"internalType":"address","name":"newNativeTokenRootAddress","type":"address"},{"internalType":"bool","name":"newUseAllowList","type":"bool"},{"internalType":"bool","name":"newUseBlockList","type":"bool"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootERC20Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseAllowList","type":"bool"}],"name":"setAllowList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseBlockList","type":"bool"}],"name":"setBlockList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC20","name":"childToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC20","name":"childToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611c8e806100206000396000f3fe608060405234801561001057600080fd5b506004361061018f5760003560e01c80638da5cb5b116100e4578063c5e4683a11610092578063c5e4683a14610323578063d41f177114610336578063e0563ab11461035d578063e30c397814610366578063eeb4994514610377578063f2fde38b1461038a578063f3fef3a31461039d578063f6451255146103b057600080fd5b80638da5cb5b1461029f578063947287cf146102b057806397e5230d146102b9578063b1768065146102c3578063b68ad1e4146102ea578063c1225a20146102fd578063c3b35a7e1461031057600080fd5b806354f8abad1161014157806354f8abad1461021a57806355b01e4d1461022d5780635ea5df791461023b578063715018a61461025257806371cf93b71461025a57806379ba50971461026d5780637efab4f51461027557600080fd5b806305dc2e8f1461019457806307b3e252146101c45780631459457a146101d25780631bc114ba146101e7578063284017f5146101fa5780633b878c221461020357806351351d531461020c575b600080fd5b60fd546101a7906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101a76004600360981b0181565b6101e56101e0366004611616565b6103d7565b005b60fc546101a7906001600160a01b031681565b6101a761202081565b6101a761101081565b6101a76002600160a01b0381565b6101e5610228366004611695565b6104db565b6101a76004600160991b0181565b61024461138881565b6040519081526020016101bb565b6101e56105fe565b60fe546101a7906001600160a01b031681565b6101e5610612565b6101a761028336600461173e565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b03166101a7565b61024461520881565b610244620249f081565b6102447f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff546101a7906001600160a01b031681565b6101e561030b366004611762565b61068c565b6101e561031e36600461177f565b6106db565b6101e5610331366004611762565b6106f3565b6102447f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101a761203081565b6065546001600160a01b03166101a7565b6101e56103853660046117c0565b61073a565b6101e561039836600461173e565b61090e565b6101e56103ab366004611849565b61097f565b6102447f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b336002600160a01b03146104075760405163973d02cb60e01b81526004016103fe90611875565b60405180910390fd5b600054610100900460ff16158080156104275750600054600160ff909116105b806104415750303b158015610441575060005460ff166001145b61045d5760405162461bcd60e51b81526004016103fe90611899565b6000805460ff191660011790558015610480576000805461ff0019166101001790555b61048d8686868686610996565b80156104d3576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b336002600160a01b03146105025760405163973d02cb60e01b81526004016103fe90611875565b600054610100900460ff16158080156105225750600054600160ff909116105b8061053c5750303b15801561053c575060005460ff166001145b6105585760405162461bcd60e51b81526004016103fe90611899565b6000805460ff19166001179055801561057b576000805461ff0019166101001790555b6105888989898989610996565b60c9805461ffff191685151561ff00191617610100851515021790556105ad82610ae7565b80156105f3576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b610606610b00565b6106106000610ae7565b565b60655433906001600160a01b031681146106805760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016103fe565b61068981610ae7565b50565b610694610b00565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b6106e3610b5a565b6106ee838383610b62565b505050565b6106fb610b00565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b60fd546001600160a01b031633146107a55760405162461bcd60e51b815260206004820152602860248201527f4368696c6445524332305072656469636174653a204f4e4c595f53544154455f6044820152672922a1a2a4ab22a960c11b60648201526084016103fe565b60fe546001600160a01b038481169116146108135760405162461bcd60e51b815260206004820152602860248201527f4368696c6445524332305072656469636174653a204f4e4c595f524f4f545f50604482015267524544494341544560c01b60648201526084016103fe565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216108426020600084866118e7565b61084b91611911565b0361086a5761086561086082602081866118e7565b610ed9565b610908565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad6108996020600084866118e7565b6108a291611911565b036108b1576108658282611185565b60405162461bcd60e51b815260206004820152602660248201527f4368696c6445524332305072656469636174653a20494e56414c49445f5349476044820152654e415455524560d01b60648201526084016103fe565b50505050565b610916610b00565b606580546001600160a01b0383166001600160a01b031990911681179091556109476033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b610987610b5a565b610992823383610b62565b5050565b6001600160a01b038516158015906109b657506001600160a01b03841615155b80156109ca57506001600160a01b03831615155b80156109de57506001600160a01b03821615155b610a3a5760405162461bcd60e51b815260206004820152602760248201527f4368696c6445524332305072656469636174653a204241445f494e495449414c60448201526624ad20aa24a7a760c91b60648201526084016103fe565b60fc80546001600160a01b03199081166001600160a01b038881169190911790925560fd8054821687841617905560fe8054821686841617905560ff8054909116848316179055811615610ae0576001600160a01b0381166000818152610100602052604080822080546001600160a01b03191661101090811790915590519092917f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d91a35b5050505050565b606580546001600160a01b0319169055610689816112fc565b6033546001600160a01b031633146106105760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103fe565b61061061134e565b826001600160a01b03163b600003610bc65760405162461bcd60e51b815260206004820152602160248201527f4368696c6445524332305072656469636174653a204e4f545f434f4e545241436044820152601560fa1b60648201526084016103fe565b6000836001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c2a919061192f565b6001600160a01b0381811660009081526101006020526040902054919250858116911614610c6a5760405162461bcd60e51b81526004016103fe9061194c565b6001600160a01b038116610c8057610c8061198f565b306001600160a01b0316846001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cec919061192f565b6001600160a01b031614610d0257610d0261198f565b604051632770a7eb60e21b81526001600160a01b03851690639dc29fac90610d3090339086906004016119a5565b6020604051808303816000875af1158015610d4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d7391906119be565b610dbf5760405162461bcd60e51b815260206004820181905260248201527f4368696c6445524332305072656469636174653a204255524e5f4641494c454460448201526064016103fe565b60fc5460fe54604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610e4a9391169160c401611a2b565b600060405180830381600087803b158015610e6457600080fd5b505af1158015610e78573d6000803e3d6000fd5b50505050826001600160a01b0316846001600160a01b0316826001600160a01b03167fa0923f060a16fc784558d43de424ffde7b01643de5e5d335851b9df94c76bb273386604051610ecb9291906119a5565b60405180910390a450505050565b6000808080610eea85870187611a57565b6001600160a01b038085166000908152610100602052604090205494985092965090945092501680610f2e5760405162461bcd60e51b81526004016103fe9061194c565b806001600160a01b03163b600003610f4857610f4861198f565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f88573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fac919061192f565b9050856001600160a01b0316816001600160a01b031614610fcf57610fcf61198f565b6001600160a01b038116610fe557610fe561198f565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561102d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611051919061192f565b6001600160a01b0316146110675761106761198f565b6040516340c10f1960e01b81526001600160a01b038316906340c10f199061109590879087906004016119a5565b6020604051808303816000875af11580156110b4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110d891906119be565b6111245760405162461bcd60e51b815260206004820181905260248201527f4368696c6445524332305072656469636174653a204d494e545f4641494c454460448201526064016103fe565b836001600160a01b0316826001600160a01b0316876001600160a01b03167fdf34f3a3ed8bedc14a4b284ebaee5374d55b64bac6a84c270dabe8fd6b4cdafd88876040516111739291906119a5565b60405180910390a45050505050505050565b600080808061119685870187611b4b565b92975090955093509150506001600160a01b0384166111b7576111b761198f565b6001600160a01b038481166000908152610100602052604090205416156111e0576111e061198f565b60ff546040516bffffffffffffffffffffffff19606087901b166020820152600091611230916001600160a01b039091169060340160405160208183030381529060405280519060200120611564565b6001600160a01b03868116600090815261010060205260409081902080546001600160a01b031916928416928317905551637b69774360e11b81529192509063f6d2ee8690611289908890889088908890600401611bd8565b600060405180830381600087803b1580156112a357600080fd5b505af11580156112b7573d6000803e3d6000fd5b50506040516001600160a01b038085169350881691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a350505050505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60c95460ff161561145957604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b0191611388916113b09190611c23565b6000604051808303818686fa925050503d80600081146113ec576040519150601f19603f3d011682016040523d82523d6000602084013e6113f1565b606091505b509150915081801561141657506000818060200190518101906114149190611c3f565b115b6114565760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b60448201526064016103fe565b50505b60c954610100900460ff161561061057604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b0191611388916114c09190611c23565b6000604051808303818686fa925050503d80600081146114fc576040519150601f19603f3d011682016040523d82523d6000602084013e611501565b606091505b50915091508180156115275750808060200190518101906115229190611c3f565b600114155b6109925760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b60448201526064016103fe565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b0381166115fb5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b60448201526064016103fe565b92915050565b6001600160a01b038116811461068957600080fd5b600080600080600060a0868803121561162e57600080fd5b853561163981611601565b9450602086013561164981611601565b9350604086013561165981611601565b9250606086013561166981611601565b9150608086013561167981611601565b809150509295509295909350565b801515811461068957600080fd5b600080600080600080600080610100898b0312156116b257600080fd5b88356116bd81611601565b975060208901356116cd81611601565b965060408901356116dd81611601565b955060608901356116ed81611601565b945060808901356116fd81611601565b935060a089013561170d81611687565b925060c089013561171d81611687565b915060e089013561172d81611601565b809150509295985092959890939650565b60006020828403121561175057600080fd5b813561175b81611601565b9392505050565b60006020828403121561177457600080fd5b813561175b81611687565b60008060006060848603121561179457600080fd5b833561179f81611601565b925060208401356117af81611601565b929592945050506040919091013590565b600080600080606085870312156117d657600080fd5b8435935060208501356117e881611601565b9250604085013567ffffffffffffffff8082111561180557600080fd5b818701915087601f83011261181957600080fd5b81358181111561182857600080fd5b88602082850101111561183a57600080fd5b95989497505060200194505050565b6000806040838503121561185c57600080fd5b823561186781611601565b946020939093013593505050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b600080858511156118f757600080fd5b8386111561190457600080fd5b5050820193919092039150565b803560208310156115fb57600019602084900360031b1b1692915050565b60006020828403121561194157600080fd5b815161175b81611601565b60208082526023908201527f4368696c6445524332305072656469636174653a20554e4d41505045445f544f60408201526225a2a760e91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156119d057600080fd5b815161175b81611687565b60005b838110156119f65781810151838201526020016119de565b50506000910152565b60008151808452611a178160208601602086016119db565b601f01601f19169290920160200192915050565b6001600160a01b0383168152604060208201819052600090611a4f908301846119ff565b949350505050565b60008060008060808587031215611a6d57600080fd5b8435611a7881611601565b93506020850135611a8881611601565b92506040850135611a9881611601565b9396929550929360600135925050565b634e487b7160e01b600052604160045260246000fd5b600082601f830112611acf57600080fd5b813567ffffffffffffffff80821115611aea57611aea611aa8565b604051601f8301601f19908116603f01168101908282118183101715611b1257611b12611aa8565b81604052838152866020858801011115611b2b57600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600080600060a08688031215611b6357600080fd5b853594506020860135611b7581611601565b9350604086013567ffffffffffffffff80821115611b9257600080fd5b611b9e89838a01611abe565b94506060880135915080821115611bb457600080fd5b50611bc188828901611abe565b925050608086013560ff8116811461167957600080fd5b6001600160a01b0385168152608060208201819052600090611bfc908301866119ff565b8281036040840152611c0e81866119ff565b91505060ff8316606083015295945050505050565b60008251611c358184602087016119db565b9190910192915050565b600060208284031215611c5157600080fd5b505191905056fea26469706673582212206ea7447ee1c5e059d53f0d5e7f2241d867a55a3968ed1a50039de2f2d55b996664736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061018f5760003560e01c80638da5cb5b116100e4578063c5e4683a11610092578063c5e4683a14610323578063d41f177114610336578063e0563ab11461035d578063e30c397814610366578063eeb4994514610377578063f2fde38b1461038a578063f3fef3a31461039d578063f6451255146103b057600080fd5b80638da5cb5b1461029f578063947287cf146102b057806397e5230d146102b9578063b1768065146102c3578063b68ad1e4146102ea578063c1225a20146102fd578063c3b35a7e1461031057600080fd5b806354f8abad1161014157806354f8abad1461021a57806355b01e4d1461022d5780635ea5df791461023b578063715018a61461025257806371cf93b71461025a57806379ba50971461026d5780637efab4f51461027557600080fd5b806305dc2e8f1461019457806307b3e252146101c45780631459457a146101d25780631bc114ba146101e7578063284017f5146101fa5780633b878c221461020357806351351d531461020c575b600080fd5b60fd546101a7906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101a76004600360981b0181565b6101e56101e0366004611616565b6103d7565b005b60fc546101a7906001600160a01b031681565b6101a761202081565b6101a761101081565b6101a76002600160a01b0381565b6101e5610228366004611695565b6104db565b6101a76004600160991b0181565b61024461138881565b6040519081526020016101bb565b6101e56105fe565b60fe546101a7906001600160a01b031681565b6101e5610612565b6101a761028336600461173e565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b03166101a7565b61024461520881565b610244620249f081565b6102447f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff546101a7906001600160a01b031681565b6101e561030b366004611762565b61068c565b6101e561031e36600461177f565b6106db565b6101e5610331366004611762565b6106f3565b6102447f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101a761203081565b6065546001600160a01b03166101a7565b6101e56103853660046117c0565b61073a565b6101e561039836600461173e565b61090e565b6101e56103ab366004611849565b61097f565b6102447f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b336002600160a01b03146104075760405163973d02cb60e01b81526004016103fe90611875565b60405180910390fd5b600054610100900460ff16158080156104275750600054600160ff909116105b806104415750303b158015610441575060005460ff166001145b61045d5760405162461bcd60e51b81526004016103fe90611899565b6000805460ff191660011790558015610480576000805461ff0019166101001790555b61048d8686868686610996565b80156104d3576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b336002600160a01b03146105025760405163973d02cb60e01b81526004016103fe90611875565b600054610100900460ff16158080156105225750600054600160ff909116105b8061053c5750303b15801561053c575060005460ff166001145b6105585760405162461bcd60e51b81526004016103fe90611899565b6000805460ff19166001179055801561057b576000805461ff0019166101001790555b6105888989898989610996565b60c9805461ffff191685151561ff00191617610100851515021790556105ad82610ae7565b80156105f3576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b610606610b00565b6106106000610ae7565b565b60655433906001600160a01b031681146106805760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016103fe565b61068981610ae7565b50565b610694610b00565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b6106e3610b5a565b6106ee838383610b62565b505050565b6106fb610b00565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b60fd546001600160a01b031633146107a55760405162461bcd60e51b815260206004820152602860248201527f4368696c6445524332305072656469636174653a204f4e4c595f53544154455f6044820152672922a1a2a4ab22a960c11b60648201526084016103fe565b60fe546001600160a01b038481169116146108135760405162461bcd60e51b815260206004820152602860248201527f4368696c6445524332305072656469636174653a204f4e4c595f524f4f545f50604482015267524544494341544560c01b60648201526084016103fe565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216108426020600084866118e7565b61084b91611911565b0361086a5761086561086082602081866118e7565b610ed9565b610908565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad6108996020600084866118e7565b6108a291611911565b036108b1576108658282611185565b60405162461bcd60e51b815260206004820152602660248201527f4368696c6445524332305072656469636174653a20494e56414c49445f5349476044820152654e415455524560d01b60648201526084016103fe565b50505050565b610916610b00565b606580546001600160a01b0383166001600160a01b031990911681179091556109476033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b610987610b5a565b610992823383610b62565b5050565b6001600160a01b038516158015906109b657506001600160a01b03841615155b80156109ca57506001600160a01b03831615155b80156109de57506001600160a01b03821615155b610a3a5760405162461bcd60e51b815260206004820152602760248201527f4368696c6445524332305072656469636174653a204241445f494e495449414c60448201526624ad20aa24a7a760c91b60648201526084016103fe565b60fc80546001600160a01b03199081166001600160a01b038881169190911790925560fd8054821687841617905560fe8054821686841617905560ff8054909116848316179055811615610ae0576001600160a01b0381166000818152610100602052604080822080546001600160a01b03191661101090811790915590519092917f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d91a35b5050505050565b606580546001600160a01b0319169055610689816112fc565b6033546001600160a01b031633146106105760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103fe565b61061061134e565b826001600160a01b03163b600003610bc65760405162461bcd60e51b815260206004820152602160248201527f4368696c6445524332305072656469636174653a204e4f545f434f4e545241436044820152601560fa1b60648201526084016103fe565b6000836001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c2a919061192f565b6001600160a01b0381811660009081526101006020526040902054919250858116911614610c6a5760405162461bcd60e51b81526004016103fe9061194c565b6001600160a01b038116610c8057610c8061198f565b306001600160a01b0316846001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cec919061192f565b6001600160a01b031614610d0257610d0261198f565b604051632770a7eb60e21b81526001600160a01b03851690639dc29fac90610d3090339086906004016119a5565b6020604051808303816000875af1158015610d4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d7391906119be565b610dbf5760405162461bcd60e51b815260206004820181905260248201527f4368696c6445524332305072656469636174653a204255524e5f4641494c454460448201526064016103fe565b60fc5460fe54604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610e4a9391169160c401611a2b565b600060405180830381600087803b158015610e6457600080fd5b505af1158015610e78573d6000803e3d6000fd5b50505050826001600160a01b0316846001600160a01b0316826001600160a01b03167fa0923f060a16fc784558d43de424ffde7b01643de5e5d335851b9df94c76bb273386604051610ecb9291906119a5565b60405180910390a450505050565b6000808080610eea85870187611a57565b6001600160a01b038085166000908152610100602052604090205494985092965090945092501680610f2e5760405162461bcd60e51b81526004016103fe9061194c565b806001600160a01b03163b600003610f4857610f4861198f565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f88573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fac919061192f565b9050856001600160a01b0316816001600160a01b031614610fcf57610fcf61198f565b6001600160a01b038116610fe557610fe561198f565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561102d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611051919061192f565b6001600160a01b0316146110675761106761198f565b6040516340c10f1960e01b81526001600160a01b038316906340c10f199061109590879087906004016119a5565b6020604051808303816000875af11580156110b4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110d891906119be565b6111245760405162461bcd60e51b815260206004820181905260248201527f4368696c6445524332305072656469636174653a204d494e545f4641494c454460448201526064016103fe565b836001600160a01b0316826001600160a01b0316876001600160a01b03167fdf34f3a3ed8bedc14a4b284ebaee5374d55b64bac6a84c270dabe8fd6b4cdafd88876040516111739291906119a5565b60405180910390a45050505050505050565b600080808061119685870187611b4b565b92975090955093509150506001600160a01b0384166111b7576111b761198f565b6001600160a01b038481166000908152610100602052604090205416156111e0576111e061198f565b60ff546040516bffffffffffffffffffffffff19606087901b166020820152600091611230916001600160a01b039091169060340160405160208183030381529060405280519060200120611564565b6001600160a01b03868116600090815261010060205260409081902080546001600160a01b031916928416928317905551637b69774360e11b81529192509063f6d2ee8690611289908890889088908890600401611bd8565b600060405180830381600087803b1580156112a357600080fd5b505af11580156112b7573d6000803e3d6000fd5b50506040516001600160a01b038085169350881691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a350505050505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60c95460ff161561145957604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b0191611388916113b09190611c23565b6000604051808303818686fa925050503d80600081146113ec576040519150601f19603f3d011682016040523d82523d6000602084013e6113f1565b606091505b509150915081801561141657506000818060200190518101906114149190611c3f565b115b6114565760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b60448201526064016103fe565b50505b60c954610100900460ff161561061057604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b0191611388916114c09190611c23565b6000604051808303818686fa925050503d80600081146114fc576040519150601f19603f3d011682016040523d82523d6000602084013e611501565b606091505b50915091508180156115275750808060200190518101906115229190611c3f565b600114155b6109925760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b60448201526064016103fe565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b0381166115fb5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b60448201526064016103fe565b92915050565b6001600160a01b038116811461068957600080fd5b600080600080600060a0868803121561162e57600080fd5b853561163981611601565b9450602086013561164981611601565b9350604086013561165981611601565b9250606086013561166981611601565b9150608086013561167981611601565b809150509295509295909350565b801515811461068957600080fd5b600080600080600080600080610100898b0312156116b257600080fd5b88356116bd81611601565b975060208901356116cd81611601565b965060408901356116dd81611601565b955060608901356116ed81611601565b945060808901356116fd81611601565b935060a089013561170d81611687565b925060c089013561171d81611687565b915060e089013561172d81611601565b809150509295985092959890939650565b60006020828403121561175057600080fd5b813561175b81611601565b9392505050565b60006020828403121561177457600080fd5b813561175b81611687565b60008060006060848603121561179457600080fd5b833561179f81611601565b925060208401356117af81611601565b929592945050506040919091013590565b600080600080606085870312156117d657600080fd5b8435935060208501356117e881611601565b9250604085013567ffffffffffffffff8082111561180557600080fd5b818701915087601f83011261181957600080fd5b81358181111561182857600080fd5b88602082850101111561183a57600080fd5b95989497505060200194505050565b6000806040838503121561185c57600080fd5b823561186781611601565b946020939093013593505050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b600080858511156118f757600080fd5b8386111561190457600080fd5b5050820193919092039150565b803560208310156115fb57600019602084900360031b1b1692915050565b60006020828403121561194157600080fd5b815161175b81611601565b60208082526023908201527f4368696c6445524332305072656469636174653a20554e4d41505045445f544f60408201526225a2a760e91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156119d057600080fd5b815161175b81611687565b60005b838110156119f65781810151838201526020016119de565b50506000910152565b60008151808452611a178160208601602086016119db565b601f01601f19169290920160200192915050565b6001600160a01b0383168152604060208201819052600090611a4f908301846119ff565b949350505050565b60008060008060808587031215611a6d57600080fd5b8435611a7881611601565b93506020850135611a8881611601565b92506040850135611a9881611601565b9396929550929360600135925050565b634e487b7160e01b600052604160045260246000fd5b600082601f830112611acf57600080fd5b813567ffffffffffffffff80821115611aea57611aea611aa8565b604051601f8301601f19908116603f01168101908282118183101715611b1257611b12611aa8565b81604052838152866020858801011115611b2b57600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600080600060a08688031215611b6357600080fd5b853594506020860135611b7581611601565b9350604086013567ffffffffffffffff80821115611b9257600080fd5b611b9e89838a01611abe565b94506060880135915080821115611bb457600080fd5b50611bc188828901611abe565b925050608086013560ff8116811461167957600080fd5b6001600160a01b0385168152608060208201819052600090611bfc908301866119ff565b8281036040840152611c0e81866119ff565b91505060ff8316606083015295945050505050565b60008251611c358184602087016119db565b9190910192915050565b600060208284031215611c5157600080fd5b505191905056fea26469706673582212206ea7447ee1c5e059d53f0d5e7f2241d867a55a3968ed1a50039de2f2d55b996664736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RootMintableERC20PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RootMintableERC20Predicate","sourceName":"contracts/child/RootMintableERC20Predicate.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2MintableERC20Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2MintableERC20Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2MintableTokenMapped","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childERC20Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"rootToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"rootToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newChildERC20Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"rootToken","type":"address"}],"name":"mapToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b5061149f806100206000396000f3fe608060405234801561001057600080fd5b50600436106101425760003560e01c806397e5230d116100b8578063e0563ab11161007c578063e0563ab1146102a2578063eeb49945146102ab578063f213159c146102be578063f4a120f7146102d1578063f6451255146102e4578063f8c8765e1461030b57600080fd5b806397e5230d14610224578063b17680651461022e578063b68ad1e414610255578063d41f177114610268578063d57184e41461028f57600080fd5b806347e7ef241161010a57806347e7ef24146101aa57806351351d53146101bf57806355b01e4d146101cd5780635ea5df79146101db5780637efab4f5146101f2578063947287cf1461021b57600080fd5b806305dc2e8f1461014757806307b3e252146101775780631bc114ba14610185578063284017f5146101985780633b878c22146101a1575b600080fd5b60345461015a906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61015a6004600360981b0181565b60335461015a906001600160a01b031681565b61015a61202081565b61015a61101081565b6101bd6101b8366004611038565b61031e565b005b61015a6002600160a01b0381565b61015a6004600160991b0181565b6101e461138881565b60405190815260200161016e565b61015a610200366004611064565b6037602052600090815260409020546001600160a01b031681565b6101e461520881565b6101e4620249f081565b6101e47f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60365461015a906001600160a01b031681565b6101e47f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b60355461015a906001600160a01b031681565b61015a61203081565b6101bd6102b9366004611088565b61032d565b6101bd6102cc366004611111565b6104d5565b61015a6102df366004611064565b6104e5565b6101e47f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b6101bd610319366004611152565b6108b1565b610329823383610a0e565b5050565b6034546001600160a01b031633146103a45760405162461bcd60e51b815260206004820152602f60248201527f526f6f744d696e7461626c6545524332305072656469636174653a204f4e4c5960448201526e2fa9aa20aa22afa922a1a2a4ab22a960891b60648201526084015b60405180910390fd5b6035546001600160a01b0384811691161461041a5760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c6545524332305072656469636174653a204f4e4c5960448201526f5f4348494c445f50524544494341544560801b606482015260840161039b565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e9828696104496020600084866111ae565b610452916111d8565b036104715761046c61046782602081866111ae565b610b7e565b6104cf565b60405162461bcd60e51b815260206004820152602d60248201527f526f6f744d696e7461626c6545524332305072656469636174653a20494e564160448201526c4c49445f5349474e415455524560981b606482015260840161039b565b50505050565b6104e0838383610a0e565b505050565b60006001600160a01b03821661054f5760405162461bcd60e51b815260206004820152602960248201527f526f6f744d696e7461626c6545524332305072656469636174653a20494e56416044820152682624a22faa27a5a2a760b91b606482015260840161039b565b6001600160a01b0382811660009081526037602052604090205416156105ca5760405162461bcd60e51b815260206004820152602a60248201527f526f6f744d696e7461626c6545524332305072656469636174653a20414c524560448201526910511657d3505414115160b21b606482015260840161039b565b6035546036546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b039283169260009261067892911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b0385811660008181526037602052604080822080546001600160a01b03191686861617905560335481516306fdde0360e01b81529151959650909316936316f198319387937f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad938b9391926306fdde0392600480830193928290030181865afa158015610710573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526107389190810190611231565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610776573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261079e9190810190611231565b8a6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061080091906112d3565b604051602001610814959493929190611322565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610840929190611374565b600060405180830381600087803b15801561085a57600080fd5b505af115801561086e573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a39392505050565b336002600160a01b03146108f55760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b604482015260640161039b565b600054610100900460ff16158080156109155750600054600160ff909116105b8061092f5750303b15801561092f575060005460ff166001145b6109925760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161039b565b6000805460ff1916600117905580156109b5576000805461ff0019166101001790555b6109c185858585610c36565b8015610a07576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6001600160a01b038084166000908152603760205260409020541680610a3a57610a37846104e5565b90505b6001600160a01b038116610a5057610a50611398565b610a656001600160a01b038516333085610d31565b603354603554604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b0388811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610af09391169160c401611374565b600060405180830381600087803b158015610b0a57600080fd5b505af1158015610b1e573d6000803e3d6000fd5b50505050826001600160a01b0316816001600160a01b0316856001600160a01b03167f1666a3f7b8a7494f2ebcebe646a5187ae55b1db3a068097377d90cd64a258ce93386604051610b719291906113ae565b60405180910390a46104cf565b6000808080610b8f858701876113c7565b6001600160a01b0380851660009081526037602052604090205494985092965090945092501680610bc257610bc2611398565b610bd66001600160a01b0386168484610d9c565b826001600160a01b0316816001600160a01b0316866001600160a01b03167fb9f935478aae5b1da868de596dafe0a2b41eeb61311fdd60c6dbbe46a1debee88786604051610c259291906113ae565b60405180910390a450505050505050565b6001600160a01b03841615801590610c5657506001600160a01b03831615155b8015610c6a57506001600160a01b03821615155b8015610c7e57506001600160a01b03811615155b610ce15760405162461bcd60e51b815260206004820152602e60248201527f526f6f744d696e7461626c6545524332305072656469636174653a204241445f60448201526d24a724aa24a0a624ad20aa24a7a760911b606482015260840161039b565b603380546001600160a01b039586166001600160a01b0319918216179091556034805494861694821694909417909355603580549285169284169290921790915560368054919093169116179055565b6040516001600160a01b03808516602483015283166044820152606481018290526104cf9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610dbb565b6104e08363a9059cbb60e01b8484604051602401610d659291906113ae565b6000610e10826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610e909092919063ffffffff16565b9050805160001480610e31575080806020019051810190610e319190611418565b6104e05760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161039b565b6060610e9f8484600085610ea7565b949350505050565b606082471015610f085760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161039b565b600080866001600160a01b03168587604051610f24919061143a565b60006040518083038185875af1925050503d8060008114610f61576040519150601f19603f3d011682016040523d82523d6000602084013e610f66565b606091505b5091509150610f7787838387610f82565b979650505050505050565b60608315610ff1578251600003610fea576001600160a01b0385163b610fea5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161039b565b5081610e9f565b610e9f83838151156110065781518083602001fd5b8060405162461bcd60e51b815260040161039b9190611456565b6001600160a01b038116811461103557600080fd5b50565b6000806040838503121561104b57600080fd5b823561105681611020565b946020939093013593505050565b60006020828403121561107657600080fd5b813561108181611020565b9392505050565b6000806000806060858703121561109e57600080fd5b8435935060208501356110b081611020565b9250604085013567ffffffffffffffff808211156110cd57600080fd5b818701915087601f8301126110e157600080fd5b8135818111156110f057600080fd5b88602082850101111561110257600080fd5b95989497505060200194505050565b60008060006060848603121561112657600080fd5b833561113181611020565b9250602084013561114181611020565b929592945050506040919091013590565b6000806000806080858703121561116857600080fd5b843561117381611020565b9350602085013561118381611020565b9250604085013561119381611020565b915060608501356111a381611020565b939692955090935050565b600080858511156111be57600080fd5b838611156111cb57600080fd5b5050820193919092039150565b803560208310156111f157600019602084900360031b1b165b92915050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015611228578181015183820152602001611210565b50506000910152565b60006020828403121561124357600080fd5b815167ffffffffffffffff8082111561125b57600080fd5b818401915084601f83011261126f57600080fd5b815181811115611281576112816111f7565b604051601f8201601f19908116603f011681019083821181831017156112a9576112a96111f7565b816040528281528760208487010111156112c257600080fd5b610f7783602083016020880161120d565b6000602082840312156112e557600080fd5b815160ff8116811461108157600080fd5b6000815180845261130e81602086016020860161120d565b601f01601f19169290920160200192915050565b8581526001600160a01b038516602082015260a06040820181905260009061134c908301866112f6565b828103606084015261135e81866112f6565b91505060ff831660808301529695505050505050565b6001600160a01b0383168152604060208201819052600090610e9f908301846112f6565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b600080600080608085870312156113dd57600080fd5b84356113e881611020565b935060208501356113f881611020565b9250604085013561140881611020565b9396929550929360600135925050565b60006020828403121561142a57600080fd5b8151801515811461108157600080fd5b6000825161144c81846020870161120d565b9190910192915050565b60208152600061108160208301846112f656fea264697066735822122084ec8ad36f0d03abcfc5bde4447836ff970764edfea496ff308f22046530682b64736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101425760003560e01c806397e5230d116100b8578063e0563ab11161007c578063e0563ab1146102a2578063eeb49945146102ab578063f213159c146102be578063f4a120f7146102d1578063f6451255146102e4578063f8c8765e1461030b57600080fd5b806397e5230d14610224578063b17680651461022e578063b68ad1e414610255578063d41f177114610268578063d57184e41461028f57600080fd5b806347e7ef241161010a57806347e7ef24146101aa57806351351d53146101bf57806355b01e4d146101cd5780635ea5df79146101db5780637efab4f5146101f2578063947287cf1461021b57600080fd5b806305dc2e8f1461014757806307b3e252146101775780631bc114ba14610185578063284017f5146101985780633b878c22146101a1575b600080fd5b60345461015a906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61015a6004600360981b0181565b60335461015a906001600160a01b031681565b61015a61202081565b61015a61101081565b6101bd6101b8366004611038565b61031e565b005b61015a6002600160a01b0381565b61015a6004600160991b0181565b6101e461138881565b60405190815260200161016e565b61015a610200366004611064565b6037602052600090815260409020546001600160a01b031681565b6101e461520881565b6101e4620249f081565b6101e47f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60365461015a906001600160a01b031681565b6101e47f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b60355461015a906001600160a01b031681565b61015a61203081565b6101bd6102b9366004611088565b61032d565b6101bd6102cc366004611111565b6104d5565b61015a6102df366004611064565b6104e5565b6101e47f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b6101bd610319366004611152565b6108b1565b610329823383610a0e565b5050565b6034546001600160a01b031633146103a45760405162461bcd60e51b815260206004820152602f60248201527f526f6f744d696e7461626c6545524332305072656469636174653a204f4e4c5960448201526e2fa9aa20aa22afa922a1a2a4ab22a960891b60648201526084015b60405180910390fd5b6035546001600160a01b0384811691161461041a5760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c6545524332305072656469636174653a204f4e4c5960448201526f5f4348494c445f50524544494341544560801b606482015260840161039b565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e9828696104496020600084866111ae565b610452916111d8565b036104715761046c61046782602081866111ae565b610b7e565b6104cf565b60405162461bcd60e51b815260206004820152602d60248201527f526f6f744d696e7461626c6545524332305072656469636174653a20494e564160448201526c4c49445f5349474e415455524560981b606482015260840161039b565b50505050565b6104e0838383610a0e565b505050565b60006001600160a01b03821661054f5760405162461bcd60e51b815260206004820152602960248201527f526f6f744d696e7461626c6545524332305072656469636174653a20494e56416044820152682624a22faa27a5a2a760b91b606482015260840161039b565b6001600160a01b0382811660009081526037602052604090205416156105ca5760405162461bcd60e51b815260206004820152602a60248201527f526f6f744d696e7461626c6545524332305072656469636174653a20414c524560448201526910511657d3505414115160b21b606482015260840161039b565b6035546036546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b039283169260009261067892911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b0385811660008181526037602052604080822080546001600160a01b03191686861617905560335481516306fdde0360e01b81529151959650909316936316f198319387937f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad938b9391926306fdde0392600480830193928290030181865afa158015610710573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526107389190810190611231565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610776573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261079e9190810190611231565b8a6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061080091906112d3565b604051602001610814959493929190611322565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610840929190611374565b600060405180830381600087803b15801561085a57600080fd5b505af115801561086e573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a39392505050565b336002600160a01b03146108f55760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b604482015260640161039b565b600054610100900460ff16158080156109155750600054600160ff909116105b8061092f5750303b15801561092f575060005460ff166001145b6109925760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161039b565b6000805460ff1916600117905580156109b5576000805461ff0019166101001790555b6109c185858585610c36565b8015610a07576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6001600160a01b038084166000908152603760205260409020541680610a3a57610a37846104e5565b90505b6001600160a01b038116610a5057610a50611398565b610a656001600160a01b038516333085610d31565b603354603554604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b0388811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610af09391169160c401611374565b600060405180830381600087803b158015610b0a57600080fd5b505af1158015610b1e573d6000803e3d6000fd5b50505050826001600160a01b0316816001600160a01b0316856001600160a01b03167f1666a3f7b8a7494f2ebcebe646a5187ae55b1db3a068097377d90cd64a258ce93386604051610b719291906113ae565b60405180910390a46104cf565b6000808080610b8f858701876113c7565b6001600160a01b0380851660009081526037602052604090205494985092965090945092501680610bc257610bc2611398565b610bd66001600160a01b0386168484610d9c565b826001600160a01b0316816001600160a01b0316866001600160a01b03167fb9f935478aae5b1da868de596dafe0a2b41eeb61311fdd60c6dbbe46a1debee88786604051610c259291906113ae565b60405180910390a450505050505050565b6001600160a01b03841615801590610c5657506001600160a01b03831615155b8015610c6a57506001600160a01b03821615155b8015610c7e57506001600160a01b03811615155b610ce15760405162461bcd60e51b815260206004820152602e60248201527f526f6f744d696e7461626c6545524332305072656469636174653a204241445f60448201526d24a724aa24a0a624ad20aa24a7a760911b606482015260840161039b565b603380546001600160a01b039586166001600160a01b0319918216179091556034805494861694821694909417909355603580549285169284169290921790915560368054919093169116179055565b6040516001600160a01b03808516602483015283166044820152606481018290526104cf9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610dbb565b6104e08363a9059cbb60e01b8484604051602401610d659291906113ae565b6000610e10826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610e909092919063ffffffff16565b9050805160001480610e31575080806020019051810190610e319190611418565b6104e05760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161039b565b6060610e9f8484600085610ea7565b949350505050565b606082471015610f085760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161039b565b600080866001600160a01b03168587604051610f24919061143a565b60006040518083038185875af1925050503d8060008114610f61576040519150601f19603f3d011682016040523d82523d6000602084013e610f66565b606091505b5091509150610f7787838387610f82565b979650505050505050565b60608315610ff1578251600003610fea576001600160a01b0385163b610fea5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161039b565b5081610e9f565b610e9f83838151156110065781518083602001fd5b8060405162461bcd60e51b815260040161039b9190611456565b6001600160a01b038116811461103557600080fd5b50565b6000806040838503121561104b57600080fd5b823561105681611020565b946020939093013593505050565b60006020828403121561107657600080fd5b813561108181611020565b9392505050565b6000806000806060858703121561109e57600080fd5b8435935060208501356110b081611020565b9250604085013567ffffffffffffffff808211156110cd57600080fd5b818701915087601f8301126110e157600080fd5b8135818111156110f057600080fd5b88602082850101111561110257600080fd5b95989497505060200194505050565b60008060006060848603121561112657600080fd5b833561113181611020565b9250602084013561114181611020565b929592945050506040919091013590565b6000806000806080858703121561116857600080fd5b843561117381611020565b9350602085013561118381611020565b9250604085013561119381611020565b915060608501356111a381611020565b939692955090935050565b600080858511156111be57600080fd5b838611156111cb57600080fd5b5050820193919092039150565b803560208310156111f157600019602084900360031b1b165b92915050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015611228578181015183820152602001611210565b50506000910152565b60006020828403121561124357600080fd5b815167ffffffffffffffff8082111561125b57600080fd5b818401915084601f83011261126f57600080fd5b815181811115611281576112816111f7565b604051601f8201601f19908116603f011681019083821181831017156112a9576112a96111f7565b816040528281528760208487010111156112c257600080fd5b610f7783602083016020880161120d565b6000602082840312156112e557600080fd5b815160ff8116811461108157600080fd5b6000815180845261130e81602086016020860161120d565b601f01601f19169290920160200192915050565b8581526001600160a01b038516602082015260a06040820181905260009061134c908301866112f6565b828103606084015261135e81866112f6565b91505060ff831660808301529695505050505050565b6001600160a01b0383168152604060208201819052600090610e9f908301846112f6565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b600080600080608085870312156113dd57600080fd5b84356113e881611020565b935060208501356113f881611020565b9250604085013561140881611020565b9396929550929360600135925050565b60006020828403121561142a57600080fd5b8151801515811461108157600080fd5b6000825161144c81846020870161120d565b9190910192915050565b60208152600061108160208301846112f656fea264697066735822122084ec8ad36f0d03abcfc5bde4447836ff970764edfea496ff308f22046530682b64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RootMintableERC20PredicateACLArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RootMintableERC20PredicateAccessList","sourceName":"contracts/child/RootMintableERC20PredicateAccessList.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"AllowListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"BlockListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2MintableERC20Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2MintableERC20Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2MintableTokenMapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"childERC20Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"rootToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"rootToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newChildERC20Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"},{"internalType":"bool","name":"newUseAllowList","type":"bool"},{"internalType":"bool","name":"newUseBlockList","type":"bool"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newChildERC20Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"rootToken","type":"address"}],"name":"mapToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseAllowList","type":"bool"}],"name":"setAllowList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseBlockList","type":"bool"}],"name":"setBlockList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611bfb806100206000396000f3fe608060405234801561001057600080fd5b506004361061019a5760003560e01c8063b1768065116100e4578063e0563ab111610092578063e0563ab114610355578063e30c39781461035e578063eeb499451461036f578063f213159c14610382578063f2fde38b14610395578063f4a120f7146103a8578063f6451255146103bb578063f8c8765e146103e257600080fd5b8063b1768065146102a8578063b68ad1e4146102cf578063c1225a20146102e2578063c5e4683a146102f5578063d41f177114610308578063d57184e41461032f578063d8dd17731461034257600080fd5b806355b01e4d1161014c57806355b01e4d146102255780635ea5df7914610233578063715018a61461024a57806379ba5097146102525780637efab4f51461025a5780638da5cb5b14610284578063947287cf1461029557806397e5230d1461029e57600080fd5b806305dc2e8f1461019f57806307b3e252146101cf5780631bc114ba146101dd578063284017f5146101f05780633b878c22146101f957806347e7ef241461020257806351351d5314610217575b600080fd5b60fd546101b2906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101b26004600360981b0181565b60fc546101b2906001600160a01b031681565b6101b261202081565b6101b261101081565b61021561021036600461164d565b6103f5565b005b6101b26002600160a01b0381565b6101b26004600160991b0181565b61023c61138881565b6040519081526020016101c6565b610215610404565b610215610418565b6101b2610268366004611679565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b03166101b2565b61023c61520881565b61023c620249f081565b61023c7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff546101b2906001600160a01b031681565b6102156102f03660046116ab565b610497565b6102156103033660046116ab565b6104e6565b61023c7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b60fe546101b2906001600160a01b031681565b6102156103503660046116c8565b61052d565b6101b261203081565b6065546001600160a01b03166101b2565b61021561037d36600461175e565b61064e565b6102156103903660046117e7565b6107f1565b6102156103a3366004611679565b610801565b6101b26103b6366004611679565b610872565b61023c7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b6102156103f0366004611828565b610c40565b610400823383610d39565b5050565b61040c610eb2565b6104166000610f0c565b565b60655433906001600160a01b0316811461048b5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b61049481610f0c565b50565b61049f610eb2565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b6104ee610eb2565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b03146105545760405163973d02cb60e01b815260040161048290611884565b600054610100900460ff16158080156105745750600054600160ff909116105b8061058e5750303b15801561058e575060005460ff166001145b6105aa5760405162461bcd60e51b8152600401610482906118a8565b6000805460ff1916600117905580156105cd576000805461ff0019166101001790555b6105d988888888610f25565b60c9805461ffff191685151561ff00191617610100851515021790556105fe82610f0c565b8015610644576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146106c05760405162461bcd60e51b815260206004820152602f60248201527f526f6f744d696e7461626c6545524332305072656469636174653a204f4e4c5960448201526e2fa9aa20aa22afa922a1a2a4ab22a960891b6064820152608401610482565b60fe546001600160a01b038481169116146107365760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c6545524332305072656469636174653a204f4e4c5960448201526f5f4348494c445f50524544494341544560801b6064820152608401610482565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e9828696107656020600084866118f6565b61076e91611920565b0361078d5761078861078382602081866118f6565b611020565b6107eb565b60405162461bcd60e51b815260206004820152602d60248201527f526f6f744d696e7461626c6545524332305072656469636174653a20494e564160448201526c4c49445f5349474e415455524560981b6064820152608401610482565b50505050565b6107fc838383610d39565b505050565b610809610eb2565b606580546001600160a01b0383166001600160a01b0319909116811790915561083a6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60006001600160a01b0382166108dc5760405162461bcd60e51b815260206004820152602960248201527f526f6f744d696e7461626c6545524332305072656469636174653a20494e56416044820152682624a22faa27a5a2a760b91b6064820152608401610482565b6001600160a01b038281166000908152610100602052604090205416156109585760405162461bcd60e51b815260206004820152602a60248201527f526f6f744d696e7461626c6545524332305072656469636174653a20414c524560448201526910511657d3505414115160b21b6064820152608401610482565b60fe5460ff546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b0392831692600092610a0692911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b038581166000818152610100602052604080822080546001600160a01b03191686861617905560fc5481516306fdde0360e01b81529151959650909316936316f198319387937f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad938b9391926306fdde0392600480830193928290030181865afa158015610a9f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610ac79190810190611979565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610b05573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610b2d9190810190611979565b8a6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b6b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8f9190611a1b565b604051602001610ba3959493929190611a6a565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610bcf929190611abc565b600060405180830381600087803b158015610be957600080fd5b505af1158015610bfd573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a39392505050565b336002600160a01b0314610c675760405163973d02cb60e01b815260040161048290611884565b600054610100900460ff1615808015610c875750600054600160ff909116105b80610ca15750303b158015610ca1575060005460ff166001145b610cbd5760405162461bcd60e51b8152600401610482906118a8565b6000805460ff191660011790558015610ce0576000805461ff0019166101001790555b610cec85858585610f25565b8015610d32576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b610d416110d9565b6001600160a01b03808416600090815261010060205260409020541680610d6e57610d6b84610872565b90505b6001600160a01b038116610d8457610d84611ae0565b610d996001600160a01b0385163330856110e1565b60fc5460fe54604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b0388811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610e249391169160c401611abc565b600060405180830381600087803b158015610e3e57600080fd5b505af1158015610e52573d6000803e3d6000fd5b50505050826001600160a01b0316816001600160a01b0316856001600160a01b03167f1666a3f7b8a7494f2ebcebe646a5187ae55b1db3a068097377d90cd64a258ce93386604051610ea5929190611af6565b60405180910390a46107eb565b6033546001600160a01b031633146104165760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610482565b606580546001600160a01b03191690556104948161114c565b6001600160a01b03841615801590610f4557506001600160a01b03831615155b8015610f5957506001600160a01b03821615155b8015610f6d57506001600160a01b03811615155b610fd05760405162461bcd60e51b815260206004820152602e60248201527f526f6f744d696e7461626c6545524332305072656469636174653a204241445f60448201526d24a724aa24a0a624ad20aa24a7a760911b6064820152608401610482565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b600080808061103185870187611b0f565b6001600160a01b03808516600090815261010060205260409020549498509296509094509250168061106557611065611ae0565b6110796001600160a01b038616848461119e565b826001600160a01b0316816001600160a01b0316866001600160a01b03167fb9f935478aae5b1da868de596dafe0a2b41eeb61311fdd60c6dbbe46a1debee887866040516110c8929190611af6565b60405180910390a450505050505050565b6104166111bd565b6040516001600160a01b03808516602483015283166044820152606481018290526107eb9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526113d3565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6107fc8363a9059cbb60e01b8484604051602401611115929190611af6565b60c95460ff16156112c857604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161121f9190611b60565b6000604051808303818686fa925050503d806000811461125b576040519150601f19603f3d011682016040523d82523d6000602084013e611260565b606091505b509150915081801561128557506000818060200190518101906112839190611b7c565b115b6112c55760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b6044820152606401610482565b50505b60c954610100900460ff161561041657604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b01916113889161132f9190611b60565b6000604051808303818686fa925050503d806000811461136b576040519150601f19603f3d011682016040523d82523d6000602084013e611370565b606091505b50915091508180156113965750808060200190518101906113919190611b7c565b600114155b6104005760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b6044820152606401610482565b6000611428826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166114a89092919063ffffffff16565b90508051600014806114495750808060200190518101906114499190611b95565b6107fc5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610482565b60606114b784846000856114bf565b949350505050565b6060824710156115205760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610482565b600080866001600160a01b0316858760405161153c9190611b60565b60006040518083038185875af1925050503d8060008114611579576040519150601f19603f3d011682016040523d82523d6000602084013e61157e565b606091505b509150915061158f8783838761159a565b979650505050505050565b60608315611609578251600003611602576001600160a01b0385163b6116025760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610482565b50816114b7565b6114b7838381511561161e5781518083602001fd5b8060405162461bcd60e51b81526004016104829190611bb2565b6001600160a01b038116811461049457600080fd5b6000806040838503121561166057600080fd5b823561166b81611638565b946020939093013593505050565b60006020828403121561168b57600080fd5b813561169681611638565b9392505050565b801515811461049457600080fd5b6000602082840312156116bd57600080fd5b81356116968161169d565b600080600080600080600060e0888a0312156116e357600080fd5b87356116ee81611638565b965060208801356116fe81611638565b9550604088013561170e81611638565b9450606088013561171e81611638565b9350608088013561172e8161169d565b925060a088013561173e8161169d565b915060c088013561174e81611638565b8091505092959891949750929550565b6000806000806060858703121561177457600080fd5b84359350602085013561178681611638565b9250604085013567ffffffffffffffff808211156117a357600080fd5b818701915087601f8301126117b757600080fd5b8135818111156117c657600080fd5b8860208285010111156117d857600080fd5b95989497505060200194505050565b6000806000606084860312156117fc57600080fd5b833561180781611638565b9250602084013561181781611638565b929592945050506040919091013590565b6000806000806080858703121561183e57600080fd5b843561184981611638565b9350602085013561185981611638565b9250604085013561186981611638565b9150606085013561187981611638565b939692955090935050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6000808585111561190657600080fd5b8386111561191357600080fd5b5050820193919092039150565b8035602083101561193957600019602084900360031b1b165b92915050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015611970578181015183820152602001611958565b50506000910152565b60006020828403121561198b57600080fd5b815167ffffffffffffffff808211156119a357600080fd5b818401915084601f8301126119b757600080fd5b8151818111156119c9576119c961193f565b604051601f8201601f19908116603f011681019083821181831017156119f1576119f161193f565b81604052828152876020848701011115611a0a57600080fd5b61158f836020830160208801611955565b600060208284031215611a2d57600080fd5b815160ff8116811461169657600080fd5b60008151808452611a56816020860160208601611955565b601f01601f19169290920160200192915050565b8581526001600160a01b038516602082015260a060408201819052600090611a9490830186611a3e565b8281036060840152611aa68186611a3e565b91505060ff831660808301529695505050505050565b6001600160a01b03831681526040602082018190526000906114b790830184611a3e565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b60008060008060808587031215611b2557600080fd5b8435611b3081611638565b93506020850135611b4081611638565b92506040850135611b5081611638565b9396929550929360600135925050565b60008251611b72818460208701611955565b9190910192915050565b600060208284031215611b8e57600080fd5b5051919050565b600060208284031215611ba757600080fd5b81516116968161169d565b6020815260006116966020830184611a3e56fea2646970667358221220176d1307d83cd6036f83b91e4ae24d7832bdefc3a6d526f5cfd719424ef4671864736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061019a5760003560e01c8063b1768065116100e4578063e0563ab111610092578063e0563ab114610355578063e30c39781461035e578063eeb499451461036f578063f213159c14610382578063f2fde38b14610395578063f4a120f7146103a8578063f6451255146103bb578063f8c8765e146103e257600080fd5b8063b1768065146102a8578063b68ad1e4146102cf578063c1225a20146102e2578063c5e4683a146102f5578063d41f177114610308578063d57184e41461032f578063d8dd17731461034257600080fd5b806355b01e4d1161014c57806355b01e4d146102255780635ea5df7914610233578063715018a61461024a57806379ba5097146102525780637efab4f51461025a5780638da5cb5b14610284578063947287cf1461029557806397e5230d1461029e57600080fd5b806305dc2e8f1461019f57806307b3e252146101cf5780631bc114ba146101dd578063284017f5146101f05780633b878c22146101f957806347e7ef241461020257806351351d5314610217575b600080fd5b60fd546101b2906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101b26004600360981b0181565b60fc546101b2906001600160a01b031681565b6101b261202081565b6101b261101081565b61021561021036600461164d565b6103f5565b005b6101b26002600160a01b0381565b6101b26004600160991b0181565b61023c61138881565b6040519081526020016101c6565b610215610404565b610215610418565b6101b2610268366004611679565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b03166101b2565b61023c61520881565b61023c620249f081565b61023c7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff546101b2906001600160a01b031681565b6102156102f03660046116ab565b610497565b6102156103033660046116ab565b6104e6565b61023c7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b60fe546101b2906001600160a01b031681565b6102156103503660046116c8565b61052d565b6101b261203081565b6065546001600160a01b03166101b2565b61021561037d36600461175e565b61064e565b6102156103903660046117e7565b6107f1565b6102156103a3366004611679565b610801565b6101b26103b6366004611679565b610872565b61023c7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b6102156103f0366004611828565b610c40565b610400823383610d39565b5050565b61040c610eb2565b6104166000610f0c565b565b60655433906001600160a01b0316811461048b5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b61049481610f0c565b50565b61049f610eb2565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b6104ee610eb2565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b03146105545760405163973d02cb60e01b815260040161048290611884565b600054610100900460ff16158080156105745750600054600160ff909116105b8061058e5750303b15801561058e575060005460ff166001145b6105aa5760405162461bcd60e51b8152600401610482906118a8565b6000805460ff1916600117905580156105cd576000805461ff0019166101001790555b6105d988888888610f25565b60c9805461ffff191685151561ff00191617610100851515021790556105fe82610f0c565b8015610644576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146106c05760405162461bcd60e51b815260206004820152602f60248201527f526f6f744d696e7461626c6545524332305072656469636174653a204f4e4c5960448201526e2fa9aa20aa22afa922a1a2a4ab22a960891b6064820152608401610482565b60fe546001600160a01b038481169116146107365760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c6545524332305072656469636174653a204f4e4c5960448201526f5f4348494c445f50524544494341544560801b6064820152608401610482565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e9828696107656020600084866118f6565b61076e91611920565b0361078d5761078861078382602081866118f6565b611020565b6107eb565b60405162461bcd60e51b815260206004820152602d60248201527f526f6f744d696e7461626c6545524332305072656469636174653a20494e564160448201526c4c49445f5349474e415455524560981b6064820152608401610482565b50505050565b6107fc838383610d39565b505050565b610809610eb2565b606580546001600160a01b0383166001600160a01b0319909116811790915561083a6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60006001600160a01b0382166108dc5760405162461bcd60e51b815260206004820152602960248201527f526f6f744d696e7461626c6545524332305072656469636174653a20494e56416044820152682624a22faa27a5a2a760b91b6064820152608401610482565b6001600160a01b038281166000908152610100602052604090205416156109585760405162461bcd60e51b815260206004820152602a60248201527f526f6f744d696e7461626c6545524332305072656469636174653a20414c524560448201526910511657d3505414115160b21b6064820152608401610482565b60fe5460ff546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b0392831692600092610a0692911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b038581166000818152610100602052604080822080546001600160a01b03191686861617905560fc5481516306fdde0360e01b81529151959650909316936316f198319387937f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad938b9391926306fdde0392600480830193928290030181865afa158015610a9f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610ac79190810190611979565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610b05573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610b2d9190810190611979565b8a6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b6b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8f9190611a1b565b604051602001610ba3959493929190611a6a565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610bcf929190611abc565b600060405180830381600087803b158015610be957600080fd5b505af1158015610bfd573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a39392505050565b336002600160a01b0314610c675760405163973d02cb60e01b815260040161048290611884565b600054610100900460ff1615808015610c875750600054600160ff909116105b80610ca15750303b158015610ca1575060005460ff166001145b610cbd5760405162461bcd60e51b8152600401610482906118a8565b6000805460ff191660011790558015610ce0576000805461ff0019166101001790555b610cec85858585610f25565b8015610d32576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b610d416110d9565b6001600160a01b03808416600090815261010060205260409020541680610d6e57610d6b84610872565b90505b6001600160a01b038116610d8457610d84611ae0565b610d996001600160a01b0385163330856110e1565b60fc5460fe54604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b0388811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610e249391169160c401611abc565b600060405180830381600087803b158015610e3e57600080fd5b505af1158015610e52573d6000803e3d6000fd5b50505050826001600160a01b0316816001600160a01b0316856001600160a01b03167f1666a3f7b8a7494f2ebcebe646a5187ae55b1db3a068097377d90cd64a258ce93386604051610ea5929190611af6565b60405180910390a46107eb565b6033546001600160a01b031633146104165760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610482565b606580546001600160a01b03191690556104948161114c565b6001600160a01b03841615801590610f4557506001600160a01b03831615155b8015610f5957506001600160a01b03821615155b8015610f6d57506001600160a01b03811615155b610fd05760405162461bcd60e51b815260206004820152602e60248201527f526f6f744d696e7461626c6545524332305072656469636174653a204241445f60448201526d24a724aa24a0a624ad20aa24a7a760911b6064820152608401610482565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b600080808061103185870187611b0f565b6001600160a01b03808516600090815261010060205260409020549498509296509094509250168061106557611065611ae0565b6110796001600160a01b038616848461119e565b826001600160a01b0316816001600160a01b0316866001600160a01b03167fb9f935478aae5b1da868de596dafe0a2b41eeb61311fdd60c6dbbe46a1debee887866040516110c8929190611af6565b60405180910390a450505050505050565b6104166111bd565b6040516001600160a01b03808516602483015283166044820152606481018290526107eb9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526113d3565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6107fc8363a9059cbb60e01b8484604051602401611115929190611af6565b60c95460ff16156112c857604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161121f9190611b60565b6000604051808303818686fa925050503d806000811461125b576040519150601f19603f3d011682016040523d82523d6000602084013e611260565b606091505b509150915081801561128557506000818060200190518101906112839190611b7c565b115b6112c55760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b6044820152606401610482565b50505b60c954610100900460ff161561041657604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b01916113889161132f9190611b60565b6000604051808303818686fa925050503d806000811461136b576040519150601f19603f3d011682016040523d82523d6000602084013e611370565b606091505b50915091508180156113965750808060200190518101906113919190611b7c565b600114155b6104005760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b6044820152606401610482565b6000611428826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166114a89092919063ffffffff16565b90508051600014806114495750808060200190518101906114499190611b95565b6107fc5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610482565b60606114b784846000856114bf565b949350505050565b6060824710156115205760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610482565b600080866001600160a01b0316858760405161153c9190611b60565b60006040518083038185875af1925050503d8060008114611579576040519150601f19603f3d011682016040523d82523d6000602084013e61157e565b606091505b509150915061158f8783838761159a565b979650505050505050565b60608315611609578251600003611602576001600160a01b0385163b6116025760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610482565b50816114b7565b6114b7838381511561161e5781518083602001fd5b8060405162461bcd60e51b81526004016104829190611bb2565b6001600160a01b038116811461049457600080fd5b6000806040838503121561166057600080fd5b823561166b81611638565b946020939093013593505050565b60006020828403121561168b57600080fd5b813561169681611638565b9392505050565b801515811461049457600080fd5b6000602082840312156116bd57600080fd5b81356116968161169d565b600080600080600080600060e0888a0312156116e357600080fd5b87356116ee81611638565b965060208801356116fe81611638565b9550604088013561170e81611638565b9450606088013561171e81611638565b9350608088013561172e8161169d565b925060a088013561173e8161169d565b915060c088013561174e81611638565b8091505092959891949750929550565b6000806000806060858703121561177457600080fd5b84359350602085013561178681611638565b9250604085013567ffffffffffffffff808211156117a357600080fd5b818701915087601f8301126117b757600080fd5b8135818111156117c657600080fd5b8860208285010111156117d857600080fd5b95989497505060200194505050565b6000806000606084860312156117fc57600080fd5b833561180781611638565b9250602084013561181781611638565b929592945050506040919091013590565b6000806000806080858703121561183e57600080fd5b843561184981611638565b9350602085013561185981611638565b9250604085013561186981611638565b9150606085013561187981611638565b939692955090935050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6000808585111561190657600080fd5b8386111561191357600080fd5b5050820193919092039150565b8035602083101561193957600019602084900360031b1b165b92915050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015611970578181015183820152602001611958565b50506000910152565b60006020828403121561198b57600080fd5b815167ffffffffffffffff808211156119a357600080fd5b818401915084601f8301126119b757600080fd5b8151818111156119c9576119c961193f565b604051601f8201601f19908116603f011681019083821181831017156119f1576119f161193f565b81604052828152876020848701011115611a0a57600080fd5b61158f836020830160208801611955565b600060208284031215611a2d57600080fd5b815160ff8116811461169657600080fd5b60008151808452611a56816020860160208601611955565b601f01601f19169290920160200192915050565b8581526001600160a01b038516602082015260a060408201819052600090611a9490830186611a3e565b8281036060840152611aa68186611a3e565b91505060ff831660808301529695505050505050565b6001600160a01b03831681526040602082018190526000906114b790830184611a3e565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b60008060008060808587031215611b2557600080fd5b8435611b3081611638565b93506020850135611b4081611638565b92506040850135611b5081611638565b9396929550929360600135925050565b60008251611b72818460208701611955565b9190910192915050565b600060208284031215611b8e57600080fd5b5051919050565b600060208284031215611ba757600080fd5b81516116968161169d565b6020815260006116966020830184611a3e56fea2646970667358221220176d1307d83cd6036f83b91e4ae24d7832bdefc3a6d526f5cfd719424ef4671864736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildERC721Artifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildERC721","sourceName":"contracts/child/ChildERC721.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"burnBatch","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rootToken_","type":"address"},{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"}],"name":"invalidateNext","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"accounts","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"mintBatch","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rootToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50612488806100206000396000f3fe608060405234801561001057600080fd5b506004361061014d5760003560e01c806370a08231116100c3578063a22cb4651161007c578063a22cb465146102de578063b2dc5dc3146102f1578063b88d4fde14610304578063c87b56dd14610317578063e61987051461032a578063e985e9c51461033c57600080fd5b806370a08231146102775780637c88e3d91461028a578063906571471461029d57806395d89b41146102b05780639b77ef11146102b85780639dc29fac146102cb57600080fd5b80631f2d0065116101155780631f2d0065146101e257806323b872dd146101f45780632d0335ab1461020757806340c10f191461023e57806342842e0e146102515780636352211e1461026457600080fd5b806301ffc9a71461015257806306fdde031461017a578063081812fc1461018f578063095ea7b3146101ba5780630c53c51c146101cf575b600080fd5b610165610160366004611a73565b61034f565b60405190151581526020015b60405180910390f35b6101826103a1565b6040516101719190611ae0565b6101a261019d366004611af3565b610433565b6040516001600160a01b039091168152602001610171565b6101cd6101c8366004611b28565b61045a565b005b6101826101dd366004611b9a565b610586565b610103546001600160a01b03166101a2565b6101cd610202366004611c19565b610864565b610230610215366004611c55565b6001600160a01b031660009081526038602052604090205490565b604051908152602001610171565b61016561024c366004611b28565b61089c565b6101cd61025f366004611c19565b6108dd565b6101a2610272366004611af3565b6108f8565b610230610285366004611c55565b61092d565b610165610298366004611cb4565b6109b3565b6101cd6102ab366004611d1f565b610a9b565b610182610d0b565b6101cd6102c6366004611af3565b610d1a565b6101656102d9366004611b28565b610d41565b6101cd6102ec366004611d9f565b610db1565b6101656102ff366004611ddb565b610dc7565b6101cd610312366004611e43565b610e75565b610182610325366004611af3565b610eb4565b610102546001600160a01b03166101a2565b61016561034a366004611f1e565b610f28565b60006001600160e01b031982166380ac58cd60e01b148061038057506001600160e01b03198216635b5e139f60e01b145b8061039b57506301ffc9a760e01b6001600160e01b03198316145b92915050565b606060d080546103b090611f51565b80601f01602080910402602001604051908101604052809291908181526020018280546103dc90611f51565b80156104295780601f106103fe57610100808354040283529160200191610429565b820191906000526020600020905b81548152906001019060200180831161040c57829003601f168201915b5050505050905090565b600061043e82610f56565b50600090815260d460205260409020546001600160a01b031690565b6000610465826108f8565b9050806001600160a01b0316836001600160a01b0316036104d75760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e656044820152603960f91b60648201526084015b60405180910390fd5b806001600160a01b03166104e9610f7e565b6001600160a01b0316148061050557506105058161034a610f7e565b6105775760405162461bcd60e51b815260206004820152603d60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c00000060648201526084016104ce565b6105818383610f8d565b505050565b606060006105c987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610ffb92505050565b90506001600160e01b031960003581169082160361064f5760405162461bcd60e51b815260206004820152603d60248201527f66756e6374696f6e5369676e61747572652063616e206e6f74206265206f662060448201527f657865637574654d6574615472616e73616374696f6e206d6574686f6400000060648201526084016104ce565b604080516060810182526001600160a01b038a16600081815260386020908152848220548452808401929092528351601f8b0183900483028101830185528a815290938301918b908b9081908401838280828437600092019190915250505091525090506106c08982888888611016565b6107165760405162461bcd60e51b815260206004820152602160248201527f5369676e657220616e64207369676e617475726520646f206e6f74206d6174636044820152600d60fb1b60648201526084016104ce565b603860008a6001600160a01b03166001600160a01b031681526020019081526020016000206000815460010191905081905550600080306001600160a01b03168a8a8d60405160200161076b93929190611f8b565b60408051601f198184030181529082905261078591611fb1565b6000604051808303816000865af19150503d80600081146107c2576040519150601f19603f3d011682016040523d82523d6000602084013e6107c7565b606091505b5091509150816108195760405162461bcd60e51b815260206004820152601c60248201527f46756e6374696f6e2063616c6c206e6f74207375636365737366756c0000000060448201526064016104ce565b7f5845892132946850460bff5a0083f71031bc5bf9aadcd40f1de79423eac9b10b8b338c8c60405161084e9493929190611fcd565b60405180910390a19a9950505050505050505050565b61087561086f610f7e565b826110f2565b6108915760405162461bcd60e51b81526004016104ce90612019565b610581838383611150565b610102546000906001600160a01b031633146108ca5760405162461bcd60e51b81526004016104ce90612066565b6108d483836112a2565b50600192915050565b61058183838360405180602001604052806000815250610e75565b600081815260d260205260408120546001600160a01b03168061039b5760405162461bcd60e51b81526004016104ce906120aa565b60006001600160a01b0382166109975760405162461bcd60e51b815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f7420612076616044820152683634b21037bbb732b960b91b60648201526084016104ce565b506001600160a01b0316600090815260d3602052604090205490565b610102546000906001600160a01b031633146109e15760405162461bcd60e51b81526004016104ce90612066565b83828114610a315760405162461bcd60e51b815260206004820152601f60248201527f4368696c644552433732313a204172726179206c656e206d69736d617463680060448201526064016104ce565b60005b81811015610a8c57610a84878783818110610a5157610a516120dc565b9050602002016020810190610a669190611c55565b868684818110610a7857610a786120dc565b905060200201356112a2565b600101610a34565b5060019150505b949350505050565b606b54610100900460ff1615808015610abb5750606b54600160ff909116105b80610ad55750303b158015610ad55750606b5460ff166001145b610b385760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104ce565b606b805460ff191660011790558015610b5b57606b805461ff0019166101001790555b6001600160a01b03861615801590610b7257508315155b8015610b7d57508115155b610bc95760405162461bcd60e51b815260206004820152601f60248201527f4368696c644552433732313a2042616420696e697469616c697a6174696f6e0060448201526064016104ce565b61010380546001600160a01b0388166001600160a01b031991821617909155610102805490911633179055604080516020601f8701819004810282018101909252858152610c6591879087908190840183828082843760009201919091525050604080516020601f890181900481028201810190925287815292508791508690819084018382808284376000920191909152506112bc92505050565b610cbd85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250506040805180820190915260018152603160f81b602082015291506112ed9050565b8015610d0357606b805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b606060d180546103b090611f51565b3360009081526038602052604081208054839290610d399084906120f2565b909155505050565b610102546000906001600160a01b03163314610d6f5760405162461bcd60e51b81526004016104ce90612066565b610d78826108f8565b6001600160a01b0316836001600160a01b031614610da85760405162461bcd60e51b81526004016104ce90612113565b6108d482611359565b610dc3610dbc610f7e565b83836113dc565b5050565b610102546000906001600160a01b03163314610df55760405162461bcd60e51b81526004016104ce90612066565b8160005b81811015610e69576000858583818110610e1557610e156120dc565b905060200201359050610e27816108f8565b6001600160a01b0316876001600160a01b031614610e575760405162461bcd60e51b81526004016104ce90612113565b610e6081611359565b50600101610df9565b50600195945050505050565b610e86610e80610f7e565b836110f2565b610ea25760405162461bcd60e51b81526004016104ce90612019565b610eae848484846114a6565b50505050565b6060610ebf82610f56565b6000610ed660408051602081019091526000815290565b90506000815111610ef65760405180602001604052806000815250610f21565b80610f00846114d9565b604051602001610f11929190612148565b6040516020818303038152906040525b9392505050565b6001600160a01b03918216600090815260d56020908152604080832093909416825291909152205460ff1690565b610f5f8161156b565b610f7b5760405162461bcd60e51b81526004016104ce906120aa565b50565b6000610f88611588565b905090565b600081815260d46020526040902080546001600160a01b0319166001600160a01b0384169081179091558190610fc2826108f8565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000815160000361100e57506000919050565b506020015190565b600080600161102c611027886115e4565b611661565b6040805160008152602081018083529290925260ff861690820152606081018790526080810186905260a0016020604051602081039080840390855afa15801561107a573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166110d15760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964207369676e617475726560781b60448201526064016104ce565b866001600160a01b0316816001600160a01b03161491505095945050505050565b6000806110fe836108f8565b9050806001600160a01b0316846001600160a01b0316148061112557506111258185610f28565b80610a935750836001600160a01b031661113e84610433565b6001600160a01b031614949350505050565b826001600160a01b0316611163826108f8565b6001600160a01b0316146111895760405162461bcd60e51b81526004016104ce90612177565b6001600160a01b0382166111eb5760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f206164646044820152637265737360e01b60648201526084016104ce565b826001600160a01b03166111fe826108f8565b6001600160a01b0316146112245760405162461bcd60e51b81526004016104ce90612177565b600081815260d46020908152604080832080546001600160a01b03199081169091556001600160a01b0387811680865260d3855283862080546000190190559087168086528386208054600101905586865260d2909452828520805490921684179091559051849360008051602061243383398151915291a4505050565b610dc382826040518060200160405280600081525061168e565b606b54610100900460ff166112e35760405162461bcd60e51b81526004016104ce906121bc565b610dc382826116c1565b815160208084019190912082519183019190912060038290556004819055466001557f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f61133b818484611701565b600055600280546001600160a01b0319163017905560055550505050565b6000611364826108f8565b905061136f826108f8565b600083815260d46020908152604080832080546001600160a01b03199081169091556001600160a01b03851680855260d38452828520805460001901905587855260d290935281842080549091169055519293508492600080516020612433833981519152908390a45050565b816001600160a01b0316836001600160a01b0316036114395760405162461bcd60e51b815260206004820152601960248201527822a9219b99189d1030b8383937bb32903a379031b0b63632b960391b60448201526064016104ce565b6001600160a01b03838116600081815260d56020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6114b1848484611150565b6114bd8484848461174a565b610eae5760405162461bcd60e51b81526004016104ce90612207565b606060006114e68361184f565b60010190506000816001600160401b0381111561150557611505611e2d565b6040519080825280601f01601f19166020018201604052801561152f576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461153957509392505050565b600090815260d260205260409020546001600160a01b0316151590565b60003033036115de57600080368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050503601516001600160a01b031691506115e19050565b50335b90565b60006040518060800160405280604381526020016123f06043913980516020918201208351848301516040808701518051908601209051611644950193845260208401929092526001600160a01b03166040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b600061039b61166e611927565b8360405161190160f01b8152600281019290925260228201526042902090565b6116988383611962565b6116a5600084848461174a565b6105815760405162461bcd60e51b81526004016104ce90612207565b606b54610100900460ff166116e85760405162461bcd60e51b81526004016104ce906121bc565b60d06116f4838261229f565b5060d1610581828261229f565b6040805160208101859052908101839052606081018290524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090509392505050565b60006001600160a01b0384163b1561184757836001600160a01b031663150b7a02611773610f7e565b8786866040518563ffffffff1660e01b8152600401611795949392919061235e565b6020604051808303816000875af19250505080156117d0575060408051601f3d908101601f191682019092526117cd9181019061239b565b60015b61182d573d8080156117fe576040519150601f19603f3d011682016040523d82523d6000602084013e611803565b606091505b5080516000036118255760405162461bcd60e51b81526004016104ce90612207565b805181602001fd5b6001600160e01b031916630a85bd0160e11b149050610a93565b506001610a93565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b831061188e5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef810000000083106118ba576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106118d857662386f26fc10000830492506010015b6305f5e10083106118f0576305f5e100830492506008015b612710831061190457612710830492506004015b60648310611916576064830492506002015b600a831061039b5760010192915050565b6002546000906001600160a01b031630148015611945575060015446145b15611951575060005490565b610f88600554600354600454611701565b6001600160a01b0382166119b85760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f206164647265737360448201526064016104ce565b6119c18161156b565b156119de5760405162461bcd60e51b81526004016104ce906123b8565b6119e78161156b565b15611a045760405162461bcd60e51b81526004016104ce906123b8565b6001600160a01b038216600081815260d3602090815260408083208054600101905584835260d290915280822080546001600160a01b031916841790555183929190600080516020612433833981519152908290a45050565b6001600160e01b031981168114610f7b57600080fd5b600060208284031215611a8557600080fd5b8135610f2181611a5d565b60005b83811015611aab578181015183820152602001611a93565b50506000910152565b60008151808452611acc816020860160208601611a90565b601f01601f19169290920160200192915050565b602081526000610f216020830184611ab4565b600060208284031215611b0557600080fd5b5035919050565b80356001600160a01b0381168114611b2357600080fd5b919050565b60008060408385031215611b3b57600080fd5b611b4483611b0c565b946020939093013593505050565b60008083601f840112611b6457600080fd5b5081356001600160401b03811115611b7b57600080fd5b602083019150836020828501011115611b9357600080fd5b9250929050565b60008060008060008060a08789031215611bb357600080fd5b611bbc87611b0c565b955060208701356001600160401b03811115611bd757600080fd5b611be389828a01611b52565b9096509450506040870135925060608701359150608087013560ff81168114611c0b57600080fd5b809150509295509295509295565b600080600060608486031215611c2e57600080fd5b611c3784611b0c565b9250611c4560208501611b0c565b9150604084013590509250925092565b600060208284031215611c6757600080fd5b610f2182611b0c565b60008083601f840112611c8257600080fd5b5081356001600160401b03811115611c9957600080fd5b6020830191508360208260051b8501011115611b9357600080fd5b60008060008060408587031215611cca57600080fd5b84356001600160401b0380821115611ce157600080fd5b611ced88838901611c70565b90965094506020870135915080821115611d0657600080fd5b50611d1387828801611c70565b95989497509550505050565b600080600080600060608688031215611d3757600080fd5b611d4086611b0c565b945060208601356001600160401b0380821115611d5c57600080fd5b611d6889838a01611b52565b90965094506040880135915080821115611d8157600080fd5b50611d8e88828901611b52565b969995985093965092949392505050565b60008060408385031215611db257600080fd5b611dbb83611b0c565b915060208301358015158114611dd057600080fd5b809150509250929050565b600080600060408486031215611df057600080fd5b611df984611b0c565b925060208401356001600160401b03811115611e1457600080fd5b611e2086828701611c70565b9497909650939450505050565b634e487b7160e01b600052604160045260246000fd5b60008060008060808587031215611e5957600080fd5b611e6285611b0c565b9350611e7060208601611b0c565b92506040850135915060608501356001600160401b0380821115611e9357600080fd5b818701915087601f830112611ea757600080fd5b813581811115611eb957611eb9611e2d565b604051601f8201601f19908116603f01168101908382118183101715611ee157611ee1611e2d565b816040528281528a6020848701011115611efa57600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b60008060408385031215611f3157600080fd5b611f3a83611b0c565b9150611f4860208401611b0c565b90509250929050565b600181811c90821680611f6557607f821691505b602082108103611f8557634e487b7160e01b600052602260045260246000fd5b50919050565b8284823760609190911b6bffffffffffffffffffffffff19169101908152601401919050565b60008251611fc3818460208701611a90565b9190910192915050565b6001600160a01b0385811682528416602082015260606040820181905281018290526000828460808401376000608084840101526080601f19601f850116830101905095945050505050565b6020808252602d908201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560408201526c1c881bdc88185c1c1c9bdd9959609a1b606082015260800190565b60208082526024908201527f4368696c644552433732313a204f6e6c79207072656469636174652063616e2060408201526318d85b1b60e21b606082015260800190565b602080825260189082015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b8082018082111561039b57634e487b7160e01b600052601160045260246000fd5b6020808252818101527f4368696c644552433732313a204f6e6c79206f776e65722063616e206275726e604082015260600190565b6000835161215a818460208801611a90565b83519083019061216e818360208801611a90565b01949350505050565b60208082526025908201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060408201526437bbb732b960d91b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b60208082526032908201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560408201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b606082015260800190565b601f82111561058157600081815260208120601f850160051c810160208610156122805750805b601f850160051c820191505b81811015610d035782815560010161228c565b81516001600160401b038111156122b8576122b8611e2d565b6122cc816122c68454611f51565b84612259565b602080601f83116001811461230157600084156122e95750858301515b600019600386901b1c1916600185901b178555610d03565b600085815260208120601f198616915b8281101561233057888601518255948401946001909101908401612311565b508582101561234e5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061239190830184611ab4565b9695505050505050565b6000602082840312156123ad57600080fd5b8151610f2181611a5d565b6020808252601c908201527f4552433732313a20746f6b656e20616c7265616479206d696e7465640000000060408201526060019056fe4d6574615472616e73616374696f6e2875696e74323536206e6f6e63652c616464726573732066726f6d2c62797465732066756e6374696f6e5369676e617475726529ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220bd40f4c587cb7d8ae1bc3b96cf69daefceacb0a939f4075b779cabc6e8e56d3964736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061014d5760003560e01c806370a08231116100c3578063a22cb4651161007c578063a22cb465146102de578063b2dc5dc3146102f1578063b88d4fde14610304578063c87b56dd14610317578063e61987051461032a578063e985e9c51461033c57600080fd5b806370a08231146102775780637c88e3d91461028a578063906571471461029d57806395d89b41146102b05780639b77ef11146102b85780639dc29fac146102cb57600080fd5b80631f2d0065116101155780631f2d0065146101e257806323b872dd146101f45780632d0335ab1461020757806340c10f191461023e57806342842e0e146102515780636352211e1461026457600080fd5b806301ffc9a71461015257806306fdde031461017a578063081812fc1461018f578063095ea7b3146101ba5780630c53c51c146101cf575b600080fd5b610165610160366004611a73565b61034f565b60405190151581526020015b60405180910390f35b6101826103a1565b6040516101719190611ae0565b6101a261019d366004611af3565b610433565b6040516001600160a01b039091168152602001610171565b6101cd6101c8366004611b28565b61045a565b005b6101826101dd366004611b9a565b610586565b610103546001600160a01b03166101a2565b6101cd610202366004611c19565b610864565b610230610215366004611c55565b6001600160a01b031660009081526038602052604090205490565b604051908152602001610171565b61016561024c366004611b28565b61089c565b6101cd61025f366004611c19565b6108dd565b6101a2610272366004611af3565b6108f8565b610230610285366004611c55565b61092d565b610165610298366004611cb4565b6109b3565b6101cd6102ab366004611d1f565b610a9b565b610182610d0b565b6101cd6102c6366004611af3565b610d1a565b6101656102d9366004611b28565b610d41565b6101cd6102ec366004611d9f565b610db1565b6101656102ff366004611ddb565b610dc7565b6101cd610312366004611e43565b610e75565b610182610325366004611af3565b610eb4565b610102546001600160a01b03166101a2565b61016561034a366004611f1e565b610f28565b60006001600160e01b031982166380ac58cd60e01b148061038057506001600160e01b03198216635b5e139f60e01b145b8061039b57506301ffc9a760e01b6001600160e01b03198316145b92915050565b606060d080546103b090611f51565b80601f01602080910402602001604051908101604052809291908181526020018280546103dc90611f51565b80156104295780601f106103fe57610100808354040283529160200191610429565b820191906000526020600020905b81548152906001019060200180831161040c57829003601f168201915b5050505050905090565b600061043e82610f56565b50600090815260d460205260409020546001600160a01b031690565b6000610465826108f8565b9050806001600160a01b0316836001600160a01b0316036104d75760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e656044820152603960f91b60648201526084015b60405180910390fd5b806001600160a01b03166104e9610f7e565b6001600160a01b0316148061050557506105058161034a610f7e565b6105775760405162461bcd60e51b815260206004820152603d60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c00000060648201526084016104ce565b6105818383610f8d565b505050565b606060006105c987878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610ffb92505050565b90506001600160e01b031960003581169082160361064f5760405162461bcd60e51b815260206004820152603d60248201527f66756e6374696f6e5369676e61747572652063616e206e6f74206265206f662060448201527f657865637574654d6574615472616e73616374696f6e206d6574686f6400000060648201526084016104ce565b604080516060810182526001600160a01b038a16600081815260386020908152848220548452808401929092528351601f8b0183900483028101830185528a815290938301918b908b9081908401838280828437600092019190915250505091525090506106c08982888888611016565b6107165760405162461bcd60e51b815260206004820152602160248201527f5369676e657220616e64207369676e617475726520646f206e6f74206d6174636044820152600d60fb1b60648201526084016104ce565b603860008a6001600160a01b03166001600160a01b031681526020019081526020016000206000815460010191905081905550600080306001600160a01b03168a8a8d60405160200161076b93929190611f8b565b60408051601f198184030181529082905261078591611fb1565b6000604051808303816000865af19150503d80600081146107c2576040519150601f19603f3d011682016040523d82523d6000602084013e6107c7565b606091505b5091509150816108195760405162461bcd60e51b815260206004820152601c60248201527f46756e6374696f6e2063616c6c206e6f74207375636365737366756c0000000060448201526064016104ce565b7f5845892132946850460bff5a0083f71031bc5bf9aadcd40f1de79423eac9b10b8b338c8c60405161084e9493929190611fcd565b60405180910390a19a9950505050505050505050565b61087561086f610f7e565b826110f2565b6108915760405162461bcd60e51b81526004016104ce90612019565b610581838383611150565b610102546000906001600160a01b031633146108ca5760405162461bcd60e51b81526004016104ce90612066565b6108d483836112a2565b50600192915050565b61058183838360405180602001604052806000815250610e75565b600081815260d260205260408120546001600160a01b03168061039b5760405162461bcd60e51b81526004016104ce906120aa565b60006001600160a01b0382166109975760405162461bcd60e51b815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f7420612076616044820152683634b21037bbb732b960b91b60648201526084016104ce565b506001600160a01b0316600090815260d3602052604090205490565b610102546000906001600160a01b031633146109e15760405162461bcd60e51b81526004016104ce90612066565b83828114610a315760405162461bcd60e51b815260206004820152601f60248201527f4368696c644552433732313a204172726179206c656e206d69736d617463680060448201526064016104ce565b60005b81811015610a8c57610a84878783818110610a5157610a516120dc565b9050602002016020810190610a669190611c55565b868684818110610a7857610a786120dc565b905060200201356112a2565b600101610a34565b5060019150505b949350505050565b606b54610100900460ff1615808015610abb5750606b54600160ff909116105b80610ad55750303b158015610ad55750606b5460ff166001145b610b385760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104ce565b606b805460ff191660011790558015610b5b57606b805461ff0019166101001790555b6001600160a01b03861615801590610b7257508315155b8015610b7d57508115155b610bc95760405162461bcd60e51b815260206004820152601f60248201527f4368696c644552433732313a2042616420696e697469616c697a6174696f6e0060448201526064016104ce565b61010380546001600160a01b0388166001600160a01b031991821617909155610102805490911633179055604080516020601f8701819004810282018101909252858152610c6591879087908190840183828082843760009201919091525050604080516020601f890181900481028201810190925287815292508791508690819084018382808284376000920191909152506112bc92505050565b610cbd85858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250506040805180820190915260018152603160f81b602082015291506112ed9050565b8015610d0357606b805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b606060d180546103b090611f51565b3360009081526038602052604081208054839290610d399084906120f2565b909155505050565b610102546000906001600160a01b03163314610d6f5760405162461bcd60e51b81526004016104ce90612066565b610d78826108f8565b6001600160a01b0316836001600160a01b031614610da85760405162461bcd60e51b81526004016104ce90612113565b6108d482611359565b610dc3610dbc610f7e565b83836113dc565b5050565b610102546000906001600160a01b03163314610df55760405162461bcd60e51b81526004016104ce90612066565b8160005b81811015610e69576000858583818110610e1557610e156120dc565b905060200201359050610e27816108f8565b6001600160a01b0316876001600160a01b031614610e575760405162461bcd60e51b81526004016104ce90612113565b610e6081611359565b50600101610df9565b50600195945050505050565b610e86610e80610f7e565b836110f2565b610ea25760405162461bcd60e51b81526004016104ce90612019565b610eae848484846114a6565b50505050565b6060610ebf82610f56565b6000610ed660408051602081019091526000815290565b90506000815111610ef65760405180602001604052806000815250610f21565b80610f00846114d9565b604051602001610f11929190612148565b6040516020818303038152906040525b9392505050565b6001600160a01b03918216600090815260d56020908152604080832093909416825291909152205460ff1690565b610f5f8161156b565b610f7b5760405162461bcd60e51b81526004016104ce906120aa565b50565b6000610f88611588565b905090565b600081815260d46020526040902080546001600160a01b0319166001600160a01b0384169081179091558190610fc2826108f8565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000815160000361100e57506000919050565b506020015190565b600080600161102c611027886115e4565b611661565b6040805160008152602081018083529290925260ff861690820152606081018790526080810186905260a0016020604051602081039080840390855afa15801561107a573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166110d15760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964207369676e617475726560781b60448201526064016104ce565b866001600160a01b0316816001600160a01b03161491505095945050505050565b6000806110fe836108f8565b9050806001600160a01b0316846001600160a01b0316148061112557506111258185610f28565b80610a935750836001600160a01b031661113e84610433565b6001600160a01b031614949350505050565b826001600160a01b0316611163826108f8565b6001600160a01b0316146111895760405162461bcd60e51b81526004016104ce90612177565b6001600160a01b0382166111eb5760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f206164646044820152637265737360e01b60648201526084016104ce565b826001600160a01b03166111fe826108f8565b6001600160a01b0316146112245760405162461bcd60e51b81526004016104ce90612177565b600081815260d46020908152604080832080546001600160a01b03199081169091556001600160a01b0387811680865260d3855283862080546000190190559087168086528386208054600101905586865260d2909452828520805490921684179091559051849360008051602061243383398151915291a4505050565b610dc382826040518060200160405280600081525061168e565b606b54610100900460ff166112e35760405162461bcd60e51b81526004016104ce906121bc565b610dc382826116c1565b815160208084019190912082519183019190912060038290556004819055466001557f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f61133b818484611701565b600055600280546001600160a01b0319163017905560055550505050565b6000611364826108f8565b905061136f826108f8565b600083815260d46020908152604080832080546001600160a01b03199081169091556001600160a01b03851680855260d38452828520805460001901905587855260d290935281842080549091169055519293508492600080516020612433833981519152908390a45050565b816001600160a01b0316836001600160a01b0316036114395760405162461bcd60e51b815260206004820152601960248201527822a9219b99189d1030b8383937bb32903a379031b0b63632b960391b60448201526064016104ce565b6001600160a01b03838116600081815260d56020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6114b1848484611150565b6114bd8484848461174a565b610eae5760405162461bcd60e51b81526004016104ce90612207565b606060006114e68361184f565b60010190506000816001600160401b0381111561150557611505611e2d565b6040519080825280601f01601f19166020018201604052801561152f576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461153957509392505050565b600090815260d260205260409020546001600160a01b0316151590565b60003033036115de57600080368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050503601516001600160a01b031691506115e19050565b50335b90565b60006040518060800160405280604381526020016123f06043913980516020918201208351848301516040808701518051908601209051611644950193845260208401929092526001600160a01b03166040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b600061039b61166e611927565b8360405161190160f01b8152600281019290925260228201526042902090565b6116988383611962565b6116a5600084848461174a565b6105815760405162461bcd60e51b81526004016104ce90612207565b606b54610100900460ff166116e85760405162461bcd60e51b81526004016104ce906121bc565b60d06116f4838261229f565b5060d1610581828261229f565b6040805160208101859052908101839052606081018290524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090509392505050565b60006001600160a01b0384163b1561184757836001600160a01b031663150b7a02611773610f7e565b8786866040518563ffffffff1660e01b8152600401611795949392919061235e565b6020604051808303816000875af19250505080156117d0575060408051601f3d908101601f191682019092526117cd9181019061239b565b60015b61182d573d8080156117fe576040519150601f19603f3d011682016040523d82523d6000602084013e611803565b606091505b5080516000036118255760405162461bcd60e51b81526004016104ce90612207565b805181602001fd5b6001600160e01b031916630a85bd0160e11b149050610a93565b506001610a93565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b831061188e5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef810000000083106118ba576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106118d857662386f26fc10000830492506010015b6305f5e10083106118f0576305f5e100830492506008015b612710831061190457612710830492506004015b60648310611916576064830492506002015b600a831061039b5760010192915050565b6002546000906001600160a01b031630148015611945575060015446145b15611951575060005490565b610f88600554600354600454611701565b6001600160a01b0382166119b85760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f206164647265737360448201526064016104ce565b6119c18161156b565b156119de5760405162461bcd60e51b81526004016104ce906123b8565b6119e78161156b565b15611a045760405162461bcd60e51b81526004016104ce906123b8565b6001600160a01b038216600081815260d3602090815260408083208054600101905584835260d290915280822080546001600160a01b031916841790555183929190600080516020612433833981519152908290a45050565b6001600160e01b031981168114610f7b57600080fd5b600060208284031215611a8557600080fd5b8135610f2181611a5d565b60005b83811015611aab578181015183820152602001611a93565b50506000910152565b60008151808452611acc816020860160208601611a90565b601f01601f19169290920160200192915050565b602081526000610f216020830184611ab4565b600060208284031215611b0557600080fd5b5035919050565b80356001600160a01b0381168114611b2357600080fd5b919050565b60008060408385031215611b3b57600080fd5b611b4483611b0c565b946020939093013593505050565b60008083601f840112611b6457600080fd5b5081356001600160401b03811115611b7b57600080fd5b602083019150836020828501011115611b9357600080fd5b9250929050565b60008060008060008060a08789031215611bb357600080fd5b611bbc87611b0c565b955060208701356001600160401b03811115611bd757600080fd5b611be389828a01611b52565b9096509450506040870135925060608701359150608087013560ff81168114611c0b57600080fd5b809150509295509295509295565b600080600060608486031215611c2e57600080fd5b611c3784611b0c565b9250611c4560208501611b0c565b9150604084013590509250925092565b600060208284031215611c6757600080fd5b610f2182611b0c565b60008083601f840112611c8257600080fd5b5081356001600160401b03811115611c9957600080fd5b6020830191508360208260051b8501011115611b9357600080fd5b60008060008060408587031215611cca57600080fd5b84356001600160401b0380821115611ce157600080fd5b611ced88838901611c70565b90965094506020870135915080821115611d0657600080fd5b50611d1387828801611c70565b95989497509550505050565b600080600080600060608688031215611d3757600080fd5b611d4086611b0c565b945060208601356001600160401b0380821115611d5c57600080fd5b611d6889838a01611b52565b90965094506040880135915080821115611d8157600080fd5b50611d8e88828901611b52565b969995985093965092949392505050565b60008060408385031215611db257600080fd5b611dbb83611b0c565b915060208301358015158114611dd057600080fd5b809150509250929050565b600080600060408486031215611df057600080fd5b611df984611b0c565b925060208401356001600160401b03811115611e1457600080fd5b611e2086828701611c70565b9497909650939450505050565b634e487b7160e01b600052604160045260246000fd5b60008060008060808587031215611e5957600080fd5b611e6285611b0c565b9350611e7060208601611b0c565b92506040850135915060608501356001600160401b0380821115611e9357600080fd5b818701915087601f830112611ea757600080fd5b813581811115611eb957611eb9611e2d565b604051601f8201601f19908116603f01168101908382118183101715611ee157611ee1611e2d565b816040528281528a6020848701011115611efa57600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b60008060408385031215611f3157600080fd5b611f3a83611b0c565b9150611f4860208401611b0c565b90509250929050565b600181811c90821680611f6557607f821691505b602082108103611f8557634e487b7160e01b600052602260045260246000fd5b50919050565b8284823760609190911b6bffffffffffffffffffffffff19169101908152601401919050565b60008251611fc3818460208701611a90565b9190910192915050565b6001600160a01b0385811682528416602082015260606040820181905281018290526000828460808401376000608084840101526080601f19601f850116830101905095945050505050565b6020808252602d908201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560408201526c1c881bdc88185c1c1c9bdd9959609a1b606082015260800190565b60208082526024908201527f4368696c644552433732313a204f6e6c79207072656469636174652063616e2060408201526318d85b1b60e21b606082015260800190565b602080825260189082015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b8082018082111561039b57634e487b7160e01b600052601160045260246000fd5b6020808252818101527f4368696c644552433732313a204f6e6c79206f776e65722063616e206275726e604082015260600190565b6000835161215a818460208801611a90565b83519083019061216e818360208801611a90565b01949350505050565b60208082526025908201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060408201526437bbb732b960d91b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b60208082526032908201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560408201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b606082015260800190565b601f82111561058157600081815260208120601f850160051c810160208610156122805750805b601f850160051c820191505b81811015610d035782815560010161228c565b81516001600160401b038111156122b8576122b8611e2d565b6122cc816122c68454611f51565b84612259565b602080601f83116001811461230157600084156122e95750858301515b600019600386901b1c1916600185901b178555610d03565b600085815260208120601f198616915b8281101561233057888601518255948401946001909101908401612311565b508582101561234e5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061239190830184611ab4565b9695505050505050565b6000602082840312156123ad57600080fd5b8151610f2181611a5d565b6020808252601c908201527f4552433732313a20746f6b656e20616c7265616479206d696e7465640000000060408201526060019056fe4d6574615472616e73616374696f6e2875696e74323536206e6f6e63652c616464726573732066726f6d2c62797465732066756e6374696f6e5369676e617475726529ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220bd40f4c587cb7d8ae1bc3b96cf69daefceacb0a939f4075b779cabc6e8e56d3964736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildERC721PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildERC721Predicate","sourceName":"contracts/child/ChildERC721Predicate.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"L2ERC721Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"L2ERC721DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"L2ERC721Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"L2ERC721WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2TokenMapped","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootERC721Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootERC721Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IChildERC721","name":"childToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC721","name":"childToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC721","name":"childToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611fc4806100206000396000f3fe608060405234801561001057600080fd5b50600436106101585760003560e01c8063b1768065116100c3578063e0563ab11161007c578063e0563ab114610306578063eeb499451461030f578063f3fef3a314610322578063f645125514610335578063f691325c1461035c578063f8c8765e1461036f57600080fd5b8063b176806514610244578063b68ad1e41461026b578063c3b35a7e1461027e578063c5ac2b1c14610291578063d41f1771146102b8578063d7c9e3ec146102df57600080fd5b806355b01e4d1161011557806355b01e4d146101ce5780635ea5df79146101dc5780636f33e695146101f35780637efab4f514610208578063947287cf1461023157806397e5230d1461023a57600080fd5b806305dc2e8f1461015d57806307b3e2521461018d5780631bc114ba1461019b578063284017f5146101ae5780633b878c22146101b757806351351d53146101c0575b600080fd5b603454610170906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101706004600360981b0181565b603354610170906001600160a01b031681565b61017061202081565b61017061101081565b6101706002600160a01b0381565b6101706004600160991b0181565b6101e561138881565b604051908152602001610184565b61020661020136600461166f565b610382565b005b6101706102163660046116f1565b6037602052600090815260409020546001600160a01b031681565b6101e561520881565b6101e5620249f081565b6101e57f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b603654610170906001600160a01b031681565b61020661028c366004611715565b610396565b6101e57faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6101e57f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101e57f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61017061203081565b61020661031d366004611756565b6103a6565b6102066103303660046117de565b6105c9565b6101e57f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b603554610170906001600160a01b031681565b61020661037d36600461180a565b6105d8565b61038f8585858585610734565b5050505050565b6103a1838383610a98565b505050565b6034546001600160a01b031633146104175760405162461bcd60e51b815260206004820152602960248201527f4368696c644552433732315072656469636174653a204f4e4c595f53544154456044820152682fa922a1a2a4ab22a960b91b60648201526084015b60405180910390fd5b6035546001600160a01b038481169116146104865760405162461bcd60e51b815260206004820152602960248201527f4368696c644552433732315072656469636174653a204f4e4c595f524f4f545f60448201526850524544494341544560b81b606482015260840161040e565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216104b5602060008486611866565b6104be91611890565b036104dd576104d86104d38260208186611866565b610da1565b6105c3565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1861050c602060008486611866565b61051591611890565b03610524576104d88282611017565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad610553602060008486611866565b61055c91611890565b0361056b576104d8828261127d565b60405162461bcd60e51b815260206004820152602760248201527f4368696c644552433732315072656469636174653a20494e56414c49445f5349604482015266474e415455524560c81b606482015260840161040e565b50505050565b6105d4823383610a98565b5050565b336002600160a01b031461061c5760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b604482015260640161040e565b600054610100900460ff161580801561063c5750600054600160ff909116105b806106565750303b158015610656575060005460ff166001145b6106b95760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161040e565b6000805460ff1916600117905580156106dc576000805461ff0019166101001790555b6106e8858585856113eb565b801561038f576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050505050565b8461073e816114e0565b61075a5760405162461bcd60e51b815260040161040e906118ae565b6000866001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561079a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107be91906118f0565b6001600160a01b038181166000908152603760205260409020549192508881169116146107fd5760405162461bcd60e51b815260040161040e9061190d565b6001600160a01b03811661081357610813611951565b306001600160a01b0316876001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561085b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061087f91906118f0565b6001600160a01b03161461089557610895611951565b8483146108f05760405162461bcd60e51b8152602060048201526024808201527f4368696c644552433732315072656469636174653a20494e56414c49445f4c4560448201526309c8ea8960e31b606482015260840161040e565b60405163b2dc5dc360e01b81526001600160a01b0388169063b2dc5dc39061092090339088908890600401611999565b6020604051808303816000875af115801561093f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061096391906119c7565b61097f5760405162461bcd60e51b815260040161040e906119e9565b6033546035546040516001600160a01b03928316926316f198319216906109d6907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908d908d908d908d90602001611a73565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610a02929190611b0d565b600060405180830381600087803b158015610a1c57600080fd5b505af1158015610a30573d6000803e3d6000fd5b50505050336001600160a01b0316876001600160a01b0316826001600160a01b03167fa80bc76d6e1849a9088a9c00a2aeaa54eeb78f15565a18da3e8873438976f52289898989604051610a879493929190611b39565b60405180910390a450505050505050565b82610aa2816114e0565b610abe5760405162461bcd60e51b815260040161040e906118ae565b6000846001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610afe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b2291906118f0565b6001600160a01b03818116600090815260376020526040902054919250868116911614610b615760405162461bcd60e51b815260040161040e9061190d565b6001600160a01b038116610b7757610b77611951565b306001600160a01b0316856001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bbf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610be391906118f0565b6001600160a01b031614610bf957610bf9611951565b604051632770a7eb60e21b81526001600160a01b03861690639dc29fac90610c279033908790600401611b6b565b6020604051808303816000875af1158015610c46573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c6a91906119c7565b610c865760405162461bcd60e51b815260040161040e906119e9565b603354603554604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152888116608083015260a08083018990528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610d119391169160c401611b0d565b600060405180830381600087803b158015610d2b57600080fd5b505af1158015610d3f573d6000803e3d6000fd5b50505050836001600160a01b0316856001600160a01b0316826001600160a01b03167f1e0ef6131232b1090efc3ec1cf7b53aa17f4b7cd8a4f9e033b49ee237379b0133387604051610d92929190611b6b565b60405180910390a45050505050565b6000808080610db285870187611b84565b6001600160a01b0380851660009081526037602052604090205494985092965090945092501680610df55760405162461bcd60e51b815260040161040e9061190d565b610dfe816114e0565b610e0a57610e0a611951565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e6e91906118f0565b9050856001600160a01b0316816001600160a01b031614610e9157610e91611951565b6001600160a01b038116610ea757610ea7611951565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610eef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f1391906118f0565b6001600160a01b031614610f2957610f29611951565b6040516340c10f1960e01b81526001600160a01b038316906340c10f1990610f579087908790600401611b6b565b6020604051808303816000875af1158015610f76573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f9a91906119c7565b610fb65760405162461bcd60e51b815260040161040e90611bd5565b836001600160a01b0316826001600160a01b0316876001600160a01b03167f37589fd8c906c19ea68eeb7e6b3e03efc06ff8aa4b1830588eba75f4375b16118887604051611005929190611b6b565b60405180910390a45050505050505050565b600080808061102885870187611cea565b6001600160a01b0380851660009081526037602052604090205494995092975090955093501690508061106d5760405162461bcd60e51b815260040161040e9061190d565b611076816114e0565b61108257611082611951565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110e691906118f0565b9050856001600160a01b0316816001600160a01b03161461110957611109611951565b6001600160a01b03811661111f5761111f611951565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015611167573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061118b91906118f0565b6001600160a01b0316146111a1576111a1611951565b604051637c88e3d960e01b81526001600160a01b03831690637c88e3d9906111cf9087908790600401611ddc565b6020604051808303816000875af11580156111ee573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061121291906119c7565b61122e5760405162461bcd60e51b815260040161040e90611bd5565b846001600160a01b0316826001600160a01b0316876001600160a01b03167fc1b1a5c1b97cc8e5ac82b47496f5ebdadf9c7d119b30a116e2bdafd56f6ed4758787604051611005929190611ddc565b6000808061128d84860186611ecf565b91955093509150506001600160a01b0383166112ab576112ab611951565b6001600160a01b0383811660009081526037602052604090205416156112d3576112d3611951565b6036546040516bffffffffffffffffffffffff19606086901b166020820152600091611323916001600160a01b039091169060340160405160208183030381529060405280519060200120611575565b6001600160a01b038581166000908152603760205260409081902080546001600160a01b031916928416928317905551639065714760e01b81529192509063906571479061137990879087908790600401611f4e565b600060405180830381600087803b15801561139357600080fd5b505af11580156113a7573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a3505050505050565b6001600160a01b0384161580159061140b57506001600160a01b03831615155b801561141f57506001600160a01b03821615155b801561143357506001600160a01b03811615155b6114905760405162461bcd60e51b815260206004820152602860248201527f4368696c644552433732315072656469636174653a204241445f494e495449416044820152672624ad20aa24a7a760c11b606482015260840161040e565b603380546001600160a01b039586166001600160a01b0319918216179091556034805494861694821694909417909355603580549285169284169290921790915560368054919093169116179055565b6000816001600160a01b03163b6000036114fc57506000919050565b6040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611563575060408051601f3d908101601f19168201909252611560918101906119c7565b60015b61156f57506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b03811661156f5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b604482015260640161040e565b6001600160a01b038116811461162157600080fd5b50565b60008083601f84011261163657600080fd5b5081356001600160401b0381111561164d57600080fd5b6020830191508360208260051b850101111561166857600080fd5b9250929050565b60008060008060006060868803121561168757600080fd5b85356116928161160c565b945060208601356001600160401b03808211156116ae57600080fd5b6116ba89838a01611624565b909650945060408801359150808211156116d357600080fd5b506116e088828901611624565b969995985093965092949392505050565b60006020828403121561170357600080fd5b813561170e8161160c565b9392505050565b60008060006060848603121561172a57600080fd5b83356117358161160c565b925060208401356117458161160c565b929592945050506040919091013590565b6000806000806060858703121561176c57600080fd5b84359350602085013561177e8161160c565b925060408501356001600160401b038082111561179a57600080fd5b818701915087601f8301126117ae57600080fd5b8135818111156117bd57600080fd5b8860208285010111156117cf57600080fd5b95989497505060200194505050565b600080604083850312156117f157600080fd5b82356117fc8161160c565b946020939093013593505050565b6000806000806080858703121561182057600080fd5b843561182b8161160c565b9350602085013561183b8161160c565b9250604085013561184b8161160c565b9150606085013561185b8161160c565b939692955090935050565b6000808585111561187657600080fd5b8386111561188357600080fd5b5050820193919092039150565b8035602083101561156f57600019602084900360031b1b1692915050565b60208082526022908201527f4368696c644552433732315072656469636174653a204e4f545f434f4e54524160408201526110d560f21b606082015260800190565b60006020828403121561190257600080fd5b815161170e8161160c565b60208082526024908201527f4368696c644552433732315072656469636174653a20554e4d41505045445f5460408201526327a5a2a760e11b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b0383111561198057600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b03841681526040602082018190526000906119be9083018486611967565b95945050505050565b6000602082840312156119d957600080fd5b8151801515811461170e57600080fd5b60208082526021908201527f4368696c644552433732315072656469636174653a204255524e5f4641494c456040820152601160fa1b606082015260800190565b8183526000602080850194508260005b85811015611a68578135611a4d8161160c565b6001600160a01b031687529582019590820190600101611a3a565b509495945050505050565b8781526001600160a01b0387811660208301528616604082015260a060608201819052600090611aa69083018688611a2a565b8281036080840152611ab9818587611967565b9a9950505050505050505050565b6000815180845260005b81811015611aed57602081850181015186830182015201611ad1565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b0383168152604060208201819052600090611b3190830184611ac7565b949350505050565b604081526000611b4d604083018688611a2a565b8281036020840152611b60818587611967565b979650505050505050565b6001600160a01b03929092168252602082015260400190565b60008060008060808587031215611b9a57600080fd5b8435611ba58161160c565b93506020850135611bb58161160c565b92506040850135611bc58161160c565b9396929550929360600135925050565b60208082526021908201527f4368696c644552433732315072656469636174653a204d494e545f4641494c456040820152601160fa1b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611c5457611c54611c16565b604052919050565b60006001600160401b03821115611c7557611c75611c16565b5060051b60200190565b600082601f830112611c9057600080fd5b81356020611ca5611ca083611c5c565b611c2c565b82815260059290921b84018101918181019086841115611cc457600080fd5b8286015b84811015611cdf5780358352918301918301611cc8565b509695505050505050565b600080600080600060a08688031215611d0257600080fd5b85359450602080870135611d158161160c565b94506040870135611d258161160c565b935060608701356001600160401b0380821115611d4157600080fd5b818901915089601f830112611d5557600080fd5b8135611d63611ca082611c5c565b81815260059190911b8301840190848101908c831115611d8257600080fd5b938501935b82851015611da9578435611d9a8161160c565b82529385019390850190611d87565b965050506080890135925080831115611dc157600080fd5b5050611dcf88828901611c7f565b9150509295509295909350565b604080825283519082018190526000906020906060840190828701845b82811015611e1e5781516001600160a01b031684529284019290840190600101611df9565b5050508381038285015284518082528583019183019060005b81811015611e5357835183529284019291840191600101611e37565b5090979650505050505050565b600082601f830112611e7157600080fd5b81356001600160401b03811115611e8a57611e8a611c16565b611e9d601f8201601f1916602001611c2c565b818152846020838601011115611eb257600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060808587031215611ee557600080fd5b843593506020850135611ef78161160c565b925060408501356001600160401b0380821115611f1357600080fd5b611f1f88838901611e60565b93506060870135915080821115611f3557600080fd5b50611f4287828801611e60565b91505092959194509250565b6001600160a01b0384168152606060208201819052600090611f7290830185611ac7565b8281036040840152611f848185611ac7565b969550505050505056fea264697066735822122081e46b99b49bcb8dea5f01175766e4aca327da04a0662d01672b6f776a5e701164736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101585760003560e01c8063b1768065116100c3578063e0563ab11161007c578063e0563ab114610306578063eeb499451461030f578063f3fef3a314610322578063f645125514610335578063f691325c1461035c578063f8c8765e1461036f57600080fd5b8063b176806514610244578063b68ad1e41461026b578063c3b35a7e1461027e578063c5ac2b1c14610291578063d41f1771146102b8578063d7c9e3ec146102df57600080fd5b806355b01e4d1161011557806355b01e4d146101ce5780635ea5df79146101dc5780636f33e695146101f35780637efab4f514610208578063947287cf1461023157806397e5230d1461023a57600080fd5b806305dc2e8f1461015d57806307b3e2521461018d5780631bc114ba1461019b578063284017f5146101ae5780633b878c22146101b757806351351d53146101c0575b600080fd5b603454610170906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101706004600360981b0181565b603354610170906001600160a01b031681565b61017061202081565b61017061101081565b6101706002600160a01b0381565b6101706004600160991b0181565b6101e561138881565b604051908152602001610184565b61020661020136600461166f565b610382565b005b6101706102163660046116f1565b6037602052600090815260409020546001600160a01b031681565b6101e561520881565b6101e5620249f081565b6101e57f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b603654610170906001600160a01b031681565b61020661028c366004611715565b610396565b6101e57faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6101e57f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101e57f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61017061203081565b61020661031d366004611756565b6103a6565b6102066103303660046117de565b6105c9565b6101e57f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b603554610170906001600160a01b031681565b61020661037d36600461180a565b6105d8565b61038f8585858585610734565b5050505050565b6103a1838383610a98565b505050565b6034546001600160a01b031633146104175760405162461bcd60e51b815260206004820152602960248201527f4368696c644552433732315072656469636174653a204f4e4c595f53544154456044820152682fa922a1a2a4ab22a960b91b60648201526084015b60405180910390fd5b6035546001600160a01b038481169116146104865760405162461bcd60e51b815260206004820152602960248201527f4368696c644552433732315072656469636174653a204f4e4c595f524f4f545f60448201526850524544494341544560b81b606482015260840161040e565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216104b5602060008486611866565b6104be91611890565b036104dd576104d86104d38260208186611866565b610da1565b6105c3565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1861050c602060008486611866565b61051591611890565b03610524576104d88282611017565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad610553602060008486611866565b61055c91611890565b0361056b576104d8828261127d565b60405162461bcd60e51b815260206004820152602760248201527f4368696c644552433732315072656469636174653a20494e56414c49445f5349604482015266474e415455524560c81b606482015260840161040e565b50505050565b6105d4823383610a98565b5050565b336002600160a01b031461061c5760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b604482015260640161040e565b600054610100900460ff161580801561063c5750600054600160ff909116105b806106565750303b158015610656575060005460ff166001145b6106b95760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161040e565b6000805460ff1916600117905580156106dc576000805461ff0019166101001790555b6106e8858585856113eb565b801561038f576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050505050565b8461073e816114e0565b61075a5760405162461bcd60e51b815260040161040e906118ae565b6000866001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561079a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107be91906118f0565b6001600160a01b038181166000908152603760205260409020549192508881169116146107fd5760405162461bcd60e51b815260040161040e9061190d565b6001600160a01b03811661081357610813611951565b306001600160a01b0316876001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561085b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061087f91906118f0565b6001600160a01b03161461089557610895611951565b8483146108f05760405162461bcd60e51b8152602060048201526024808201527f4368696c644552433732315072656469636174653a20494e56414c49445f4c4560448201526309c8ea8960e31b606482015260840161040e565b60405163b2dc5dc360e01b81526001600160a01b0388169063b2dc5dc39061092090339088908890600401611999565b6020604051808303816000875af115801561093f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061096391906119c7565b61097f5760405162461bcd60e51b815260040161040e906119e9565b6033546035546040516001600160a01b03928316926316f198319216906109d6907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908d908d908d908d90602001611a73565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610a02929190611b0d565b600060405180830381600087803b158015610a1c57600080fd5b505af1158015610a30573d6000803e3d6000fd5b50505050336001600160a01b0316876001600160a01b0316826001600160a01b03167fa80bc76d6e1849a9088a9c00a2aeaa54eeb78f15565a18da3e8873438976f52289898989604051610a879493929190611b39565b60405180910390a450505050505050565b82610aa2816114e0565b610abe5760405162461bcd60e51b815260040161040e906118ae565b6000846001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610afe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b2291906118f0565b6001600160a01b03818116600090815260376020526040902054919250868116911614610b615760405162461bcd60e51b815260040161040e9061190d565b6001600160a01b038116610b7757610b77611951565b306001600160a01b0316856001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bbf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610be391906118f0565b6001600160a01b031614610bf957610bf9611951565b604051632770a7eb60e21b81526001600160a01b03861690639dc29fac90610c279033908790600401611b6b565b6020604051808303816000875af1158015610c46573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c6a91906119c7565b610c865760405162461bcd60e51b815260040161040e906119e9565b603354603554604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152888116608083015260a08083018990528351808403909101815260c08301938490526316f1983160e01b909352938416936316f1983193610d119391169160c401611b0d565b600060405180830381600087803b158015610d2b57600080fd5b505af1158015610d3f573d6000803e3d6000fd5b50505050836001600160a01b0316856001600160a01b0316826001600160a01b03167f1e0ef6131232b1090efc3ec1cf7b53aa17f4b7cd8a4f9e033b49ee237379b0133387604051610d92929190611b6b565b60405180910390a45050505050565b6000808080610db285870187611b84565b6001600160a01b0380851660009081526037602052604090205494985092965090945092501680610df55760405162461bcd60e51b815260040161040e9061190d565b610dfe816114e0565b610e0a57610e0a611951565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e6e91906118f0565b9050856001600160a01b0316816001600160a01b031614610e9157610e91611951565b6001600160a01b038116610ea757610ea7611951565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610eef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f1391906118f0565b6001600160a01b031614610f2957610f29611951565b6040516340c10f1960e01b81526001600160a01b038316906340c10f1990610f579087908790600401611b6b565b6020604051808303816000875af1158015610f76573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f9a91906119c7565b610fb65760405162461bcd60e51b815260040161040e90611bd5565b836001600160a01b0316826001600160a01b0316876001600160a01b03167f37589fd8c906c19ea68eeb7e6b3e03efc06ff8aa4b1830588eba75f4375b16118887604051611005929190611b6b565b60405180910390a45050505050505050565b600080808061102885870187611cea565b6001600160a01b0380851660009081526037602052604090205494995092975090955093501690508061106d5760405162461bcd60e51b815260040161040e9061190d565b611076816114e0565b61108257611082611951565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110e691906118f0565b9050856001600160a01b0316816001600160a01b03161461110957611109611951565b6001600160a01b03811661111f5761111f611951565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015611167573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061118b91906118f0565b6001600160a01b0316146111a1576111a1611951565b604051637c88e3d960e01b81526001600160a01b03831690637c88e3d9906111cf9087908790600401611ddc565b6020604051808303816000875af11580156111ee573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061121291906119c7565b61122e5760405162461bcd60e51b815260040161040e90611bd5565b846001600160a01b0316826001600160a01b0316876001600160a01b03167fc1b1a5c1b97cc8e5ac82b47496f5ebdadf9c7d119b30a116e2bdafd56f6ed4758787604051611005929190611ddc565b6000808061128d84860186611ecf565b91955093509150506001600160a01b0383166112ab576112ab611951565b6001600160a01b0383811660009081526037602052604090205416156112d3576112d3611951565b6036546040516bffffffffffffffffffffffff19606086901b166020820152600091611323916001600160a01b039091169060340160405160208183030381529060405280519060200120611575565b6001600160a01b038581166000908152603760205260409081902080546001600160a01b031916928416928317905551639065714760e01b81529192509063906571479061137990879087908790600401611f4e565b600060405180830381600087803b15801561139357600080fd5b505af11580156113a7573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a3505050505050565b6001600160a01b0384161580159061140b57506001600160a01b03831615155b801561141f57506001600160a01b03821615155b801561143357506001600160a01b03811615155b6114905760405162461bcd60e51b815260206004820152602860248201527f4368696c644552433732315072656469636174653a204241445f494e495449416044820152672624ad20aa24a7a760c11b606482015260840161040e565b603380546001600160a01b039586166001600160a01b0319918216179091556034805494861694821694909417909355603580549285169284169290921790915560368054919093169116179055565b6000816001600160a01b03163b6000036114fc57506000919050565b6040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611563575060408051601f3d908101601f19168201909252611560918101906119c7565b60015b61156f57506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b03811661156f5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b604482015260640161040e565b6001600160a01b038116811461162157600080fd5b50565b60008083601f84011261163657600080fd5b5081356001600160401b0381111561164d57600080fd5b6020830191508360208260051b850101111561166857600080fd5b9250929050565b60008060008060006060868803121561168757600080fd5b85356116928161160c565b945060208601356001600160401b03808211156116ae57600080fd5b6116ba89838a01611624565b909650945060408801359150808211156116d357600080fd5b506116e088828901611624565b969995985093965092949392505050565b60006020828403121561170357600080fd5b813561170e8161160c565b9392505050565b60008060006060848603121561172a57600080fd5b83356117358161160c565b925060208401356117458161160c565b929592945050506040919091013590565b6000806000806060858703121561176c57600080fd5b84359350602085013561177e8161160c565b925060408501356001600160401b038082111561179a57600080fd5b818701915087601f8301126117ae57600080fd5b8135818111156117bd57600080fd5b8860208285010111156117cf57600080fd5b95989497505060200194505050565b600080604083850312156117f157600080fd5b82356117fc8161160c565b946020939093013593505050565b6000806000806080858703121561182057600080fd5b843561182b8161160c565b9350602085013561183b8161160c565b9250604085013561184b8161160c565b9150606085013561185b8161160c565b939692955090935050565b6000808585111561187657600080fd5b8386111561188357600080fd5b5050820193919092039150565b8035602083101561156f57600019602084900360031b1b1692915050565b60208082526022908201527f4368696c644552433732315072656469636174653a204e4f545f434f4e54524160408201526110d560f21b606082015260800190565b60006020828403121561190257600080fd5b815161170e8161160c565b60208082526024908201527f4368696c644552433732315072656469636174653a20554e4d41505045445f5460408201526327a5a2a760e11b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b0383111561198057600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b03841681526040602082018190526000906119be9083018486611967565b95945050505050565b6000602082840312156119d957600080fd5b8151801515811461170e57600080fd5b60208082526021908201527f4368696c644552433732315072656469636174653a204255524e5f4641494c456040820152601160fa1b606082015260800190565b8183526000602080850194508260005b85811015611a68578135611a4d8161160c565b6001600160a01b031687529582019590820190600101611a3a565b509495945050505050565b8781526001600160a01b0387811660208301528616604082015260a060608201819052600090611aa69083018688611a2a565b8281036080840152611ab9818587611967565b9a9950505050505050505050565b6000815180845260005b81811015611aed57602081850181015186830182015201611ad1565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b0383168152604060208201819052600090611b3190830184611ac7565b949350505050565b604081526000611b4d604083018688611a2a565b8281036020840152611b60818587611967565b979650505050505050565b6001600160a01b03929092168252602082015260400190565b60008060008060808587031215611b9a57600080fd5b8435611ba58161160c565b93506020850135611bb58161160c565b92506040850135611bc58161160c565b9396929550929360600135925050565b60208082526021908201527f4368696c644552433732315072656469636174653a204d494e545f4641494c456040820152601160fa1b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611c5457611c54611c16565b604052919050565b60006001600160401b03821115611c7557611c75611c16565b5060051b60200190565b600082601f830112611c9057600080fd5b81356020611ca5611ca083611c5c565b611c2c565b82815260059290921b84018101918181019086841115611cc457600080fd5b8286015b84811015611cdf5780358352918301918301611cc8565b509695505050505050565b600080600080600060a08688031215611d0257600080fd5b85359450602080870135611d158161160c565b94506040870135611d258161160c565b935060608701356001600160401b0380821115611d4157600080fd5b818901915089601f830112611d5557600080fd5b8135611d63611ca082611c5c565b81815260059190911b8301840190848101908c831115611d8257600080fd5b938501935b82851015611da9578435611d9a8161160c565b82529385019390850190611d87565b965050506080890135925080831115611dc157600080fd5b5050611dcf88828901611c7f565b9150509295509295909350565b604080825283519082018190526000906020906060840190828701845b82811015611e1e5781516001600160a01b031684529284019290840190600101611df9565b5050508381038285015284518082528583019183019060005b81811015611e5357835183529284019291840191600101611e37565b5090979650505050505050565b600082601f830112611e7157600080fd5b81356001600160401b03811115611e8a57611e8a611c16565b611e9d601f8201601f1916602001611c2c565b818152846020838601011115611eb257600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060808587031215611ee557600080fd5b843593506020850135611ef78161160c565b925060408501356001600160401b0380821115611f1357600080fd5b611f1f88838901611e60565b93506060870135915080821115611f3557600080fd5b50611f4287828801611e60565b91505092959194509250565b6001600160a01b0384168152606060208201819052600090611f7290830185611ac7565b8281036040840152611f848185611ac7565b969550505050505056fea264697066735822122081e46b99b49bcb8dea5f01175766e4aca327da04a0662d01672b6f776a5e701164736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildERC721PredicateACLArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildERC721PredicateAccessList","sourceName":"contracts/child/ChildERC721PredicateAccessList.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"AllowListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"BlockListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"L2ERC721Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"L2ERC721DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"L2ERC721Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"L2ERC721WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2TokenMapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootERC721Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"},{"internalType":"bool","name":"newUseAllowList","type":"bool"},{"internalType":"bool","name":"newUseBlockList","type":"bool"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootERC721Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootERC721Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseAllowList","type":"bool"}],"name":"setAllowList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseBlockList","type":"bool"}],"name":"setBlockList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC721","name":"childToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC721","name":"childToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC721","name":"childToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50612758806100206000396000f3fe608060405234801561001057600080fd5b50600436106101b05760003560e01c8063b68ad1e4116100ef578063e0563ab111610092578063e0563ab1146103b9578063e30c3978146103c2578063eeb49945146103d3578063f2fde38b146103e6578063f3fef3a3146103f9578063f64512551461040c578063f691325c14610433578063f8c8765e1461044657600080fd5b8063b68ad1e4146102e5578063c1225a20146102f8578063c3b35a7e1461030b578063c5ac2b1c1461031e578063c5e4683a14610345578063d41f177114610358578063d7c9e3ec1461037f578063d8dd1773146103a657600080fd5b80636f33e695116101575780636f33e6951461024b578063715018a61461026057806379ba5097146102685780637efab4f5146102705780638da5cb5b1461029a578063947287cf146102ab57806397e5230d146102b4578063b1768065146102be57600080fd5b806305dc2e8f146101b557806307b3e252146101e55780631bc114ba146101f3578063284017f5146102065780633b878c221461020f57806351351d531461021857806355b01e4d146102265780635ea5df7914610234575b600080fd5b60fd546101c8906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101c86004600360981b0181565b60fc546101c8906001600160a01b031681565b6101c861202081565b6101c861101081565b6101c86002600160a01b0381565b6101c86004600160991b0181565b61023d61138881565b6040519081526020016101dc565b61025e610259366004611c96565b610459565b005b61025e610475565b61025e610489565b6101c861027e366004611d18565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b03166101c8565b61023d61520881565b61023d620249f081565b61023d7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff546101c8906001600160a01b031681565b61025e610306366004611d4a565b610508565b61025e610319366004611d67565b610557565b61023d7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b61025e610353366004611d4a565b61056f565b61023d7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b61023d7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61025e6103b4366004611da8565b6105b6565b6101c861203081565b6065546001600160a01b03166101c8565b61025e6103e1366004611e3e565b6106d7565b61025e6103f4366004611d18565b6108f5565b61025e610407366004611ec6565b610966565b61023d7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b60fe546101c8906001600160a01b031681565b61025e610454366004611ef2565b61097d565b610461610a75565b61046e8585858585610a7d565b5050505050565b61047d610de2565b6104876000610e3c565b565b60655433906001600160a01b031681146104fc5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b61050581610e3c565b50565b610510610de2565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b61055f610a75565b61056a838383610e55565b505050565b610577610de2565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b03146105dd5760405163973d02cb60e01b81526004016104f390611f4e565b600054610100900460ff16158080156105fd5750600054600160ff909116105b806106175750303b158015610617575060005460ff166001145b6106335760405162461bcd60e51b81526004016104f390611f72565b6000805460ff191660011790558015610656576000805461ff0019166101001790555b6106628888888861115f565b60c9805461ffff191685151561ff001916176101008515150217905561068782610e3c565b80156106cd576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146107435760405162461bcd60e51b815260206004820152602960248201527f4368696c644552433732315072656469636174653a204f4e4c595f53544154456044820152682fa922a1a2a4ab22a960b91b60648201526084016104f3565b60fe546001600160a01b038481169116146107b25760405162461bcd60e51b815260206004820152602960248201527f4368696c644552433732315072656469636174653a204f4e4c595f524f4f545f60448201526850524544494341544560b81b60648201526084016104f3565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216107e1602060008486611fc0565b6107ea91611fea565b03610809576108046107ff8260208186611fc0565b611254565b6108ef565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18610838602060008486611fc0565b61084191611fea565b036108505761080482826114cb565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad61087f602060008486611fc0565b61088891611fea565b03610897576108048282611732565b60405162461bcd60e51b815260206004820152602760248201527f4368696c644552433732315072656469636174653a20494e56414c49445f5349604482015266474e415455524560c81b60648201526084016104f3565b50505050565b6108fd610de2565b606580546001600160a01b0383166001600160a01b0319909116811790915561092e6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b61096e610a75565b610979823383610e55565b5050565b336002600160a01b03146109a45760405163973d02cb60e01b81526004016104f390611f4e565b600054610100900460ff16158080156109c45750600054600160ff909116105b806109de5750303b1580156109de575060005460ff166001145b6109fa5760405162461bcd60e51b81526004016104f390611f72565b6000805460ff191660011790558015610a1d576000805461ff0019166101001790555b610a298585858561115f565b801561046e576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050505050565b6104876118a2565b84610a8781611ab8565b610aa35760405162461bcd60e51b81526004016104f390612008565b6000866001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ae3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b07919061204a565b6001600160a01b0381811660009081526101006020526040902054919250888116911614610b475760405162461bcd60e51b81526004016104f390612067565b6001600160a01b038116610b5d57610b5d6120ab565b306001600160a01b0316876001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ba5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bc9919061204a565b6001600160a01b031614610bdf57610bdf6120ab565b848314610c3a5760405162461bcd60e51b8152602060048201526024808201527f4368696c644552433732315072656469636174653a20494e56414c49445f4c4560448201526309c8ea8960e31b60648201526084016104f3565b60405163b2dc5dc360e01b81526001600160a01b0388169063b2dc5dc390610c6a903390889088906004016120f3565b6020604051808303816000875af1158015610c89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cad9190612121565b610cc95760405162461bcd60e51b81526004016104f39061213e565b60fc5460fe546040516001600160a01b03928316926316f19831921690610d20907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908d908d908d908d906020016121c8565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610d4c92919061226c565b600060405180830381600087803b158015610d6657600080fd5b505af1158015610d7a573d6000803e3d6000fd5b50505050336001600160a01b0316876001600160a01b0316826001600160a01b03167fa80bc76d6e1849a9088a9c00a2aeaa54eeb78f15565a18da3e8873438976f52289898989604051610dd19493929190612298565b60405180910390a450505050505050565b6033546001600160a01b031633146104875760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104f3565b606580546001600160a01b031916905561050581611b4d565b82610e5f81611ab8565b610e7b5760405162461bcd60e51b81526004016104f390612008565b6000846001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ebb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610edf919061204a565b6001600160a01b0381811660009081526101006020526040902054919250868116911614610f1f5760405162461bcd60e51b81526004016104f390612067565b6001600160a01b038116610f3557610f356120ab565b306001600160a01b0316856001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f7d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fa1919061204a565b6001600160a01b031614610fb757610fb76120ab565b604051632770a7eb60e21b81526001600160a01b03861690639dc29fac90610fe590339087906004016122ca565b6020604051808303816000875af1158015611004573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110289190612121565b6110445760405162461bcd60e51b81526004016104f39061213e565b60fc5460fe54604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152888116608083015260a08083018990528351808403909101815260c08301938490526316f1983160e01b909352938416936316f19831936110cf9391169160c40161226c565b600060405180830381600087803b1580156110e957600080fd5b505af11580156110fd573d6000803e3d6000fd5b50505050836001600160a01b0316856001600160a01b0316826001600160a01b03167f1e0ef6131232b1090efc3ec1cf7b53aa17f4b7cd8a4f9e033b49ee237379b01333876040516111509291906122ca565b60405180910390a45050505050565b6001600160a01b0384161580159061117f57506001600160a01b03831615155b801561119357506001600160a01b03821615155b80156111a757506001600160a01b03811615155b6112045760405162461bcd60e51b815260206004820152602860248201527f4368696c644552433732315072656469636174653a204241445f494e495449416044820152672624ad20aa24a7a760c11b60648201526084016104f3565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b6000808080611265858701876122e3565b6001600160a01b0380851660009081526101006020526040902054949850929650909450925016806112a95760405162461bcd60e51b81526004016104f390612067565b6112b281611ab8565b6112be576112be6120ab565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112fe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611322919061204a565b9050856001600160a01b0316816001600160a01b031614611345576113456120ab565b6001600160a01b03811661135b5761135b6120ab565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113c7919061204a565b6001600160a01b0316146113dd576113dd6120ab565b6040516340c10f1960e01b81526001600160a01b038316906340c10f199061140b90879087906004016122ca565b6020604051808303816000875af115801561142a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061144e9190612121565b61146a5760405162461bcd60e51b81526004016104f390612334565b836001600160a01b0316826001600160a01b0316876001600160a01b03167f37589fd8c906c19ea68eeb7e6b3e03efc06ff8aa4b1830588eba75f4375b161188876040516114b99291906122ca565b60405180910390a45050505050505050565b60008080806114dc85870187612449565b6001600160a01b03808516600090815261010060205260409020549499509297509095509350169050806115225760405162461bcd60e51b81526004016104f390612067565b61152b81611ab8565b611537576115376120ab565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015611577573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061159b919061204a565b9050856001600160a01b0316816001600160a01b0316146115be576115be6120ab565b6001600160a01b0381166115d4576115d46120ab565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561161c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611640919061204a565b6001600160a01b031614611656576116566120ab565b604051637c88e3d960e01b81526001600160a01b03831690637c88e3d990611684908790879060040161253b565b6020604051808303816000875af11580156116a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116c79190612121565b6116e35760405162461bcd60e51b81526004016104f390612334565b846001600160a01b0316826001600160a01b0316876001600160a01b03167fc1b1a5c1b97cc8e5ac82b47496f5ebdadf9c7d119b30a116e2bdafd56f6ed47587876040516114b992919061253b565b600080806117428486018661262e565b91955093509150506001600160a01b038316611760576117606120ab565b6001600160a01b03838116600090815261010060205260409020541615611789576117896120ab565b60ff546040516bffffffffffffffffffffffff19606086901b1660208201526000916117d9916001600160a01b039091169060340160405160208183030381529060405280519060200120611b9f565b6001600160a01b03858116600090815261010060205260409081902080546001600160a01b031916928416928317905551639065714760e01b815291925090639065714790611830908790879087906004016126ad565b600060405180830381600087803b15801561184a57600080fd5b505af115801561185e573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a3505050505050565b60c95460ff16156119ad57604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161190491906126ed565b6000604051808303818686fa925050503d8060008114611940576040519150601f19603f3d011682016040523d82523d6000602084013e611945565b606091505b509150915081801561196a57506000818060200190518101906119689190612709565b115b6119aa5760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b60448201526064016104f3565b50505b60c954610100900460ff161561048757604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b019161138891611a1491906126ed565b6000604051808303818686fa925050503d8060008114611a50576040519150601f19603f3d011682016040523d82523d6000602084013e611a55565b606091505b5091509150818015611a7b575080806020019051810190611a769190612709565b600114155b6109795760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b60448201526064016104f3565b6000816001600160a01b03163b600003611ad457506000919050565b6040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611b3b575060408051601f3d908101601f19168201909252611b3891810190612121565b60015b611b4757506000919050565b92915050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116611b475760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b60448201526064016104f3565b6001600160a01b038116811461050557600080fd5b60008083601f840112611c5d57600080fd5b5081356001600160401b03811115611c7457600080fd5b6020830191508360208260051b8501011115611c8f57600080fd5b9250929050565b600080600080600060608688031215611cae57600080fd5b8535611cb981611c36565b945060208601356001600160401b0380821115611cd557600080fd5b611ce189838a01611c4b565b90965094506040880135915080821115611cfa57600080fd5b50611d0788828901611c4b565b969995985093965092949392505050565b600060208284031215611d2a57600080fd5b8135611d3581611c36565b9392505050565b801515811461050557600080fd5b600060208284031215611d5c57600080fd5b8135611d3581611d3c565b600080600060608486031215611d7c57600080fd5b8335611d8781611c36565b92506020840135611d9781611c36565b929592945050506040919091013590565b600080600080600080600060e0888a031215611dc357600080fd5b8735611dce81611c36565b96506020880135611dde81611c36565b95506040880135611dee81611c36565b94506060880135611dfe81611c36565b93506080880135611e0e81611d3c565b925060a0880135611e1e81611d3c565b915060c0880135611e2e81611c36565b8091505092959891949750929550565b60008060008060608587031215611e5457600080fd5b843593506020850135611e6681611c36565b925060408501356001600160401b0380821115611e8257600080fd5b818701915087601f830112611e9657600080fd5b813581811115611ea557600080fd5b886020828501011115611eb757600080fd5b95989497505060200194505050565b60008060408385031215611ed957600080fd5b8235611ee481611c36565b946020939093013593505050565b60008060008060808587031215611f0857600080fd5b8435611f1381611c36565b93506020850135611f2381611c36565b92506040850135611f3381611c36565b91506060850135611f4381611c36565b939692955090935050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008085851115611fd057600080fd5b83861115611fdd57600080fd5b5050820193919092039150565b80356020831015611b4757600019602084900360031b1b1692915050565b60208082526022908201527f4368696c644552433732315072656469636174653a204e4f545f434f4e54524160408201526110d560f21b606082015260800190565b60006020828403121561205c57600080fd5b8151611d3581611c36565b60208082526024908201527f4368696c644552433732315072656469636174653a20554e4d41505045445f5460408201526327a5a2a760e11b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b038311156120da57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b038416815260406020820181905260009061211890830184866120c1565b95945050505050565b60006020828403121561213357600080fd5b8151611d3581611d3c565b60208082526021908201527f4368696c644552433732315072656469636174653a204255524e5f4641494c456040820152601160fa1b606082015260800190565b8183526000602080850194508260005b858110156121bd5781356121a281611c36565b6001600160a01b03168752958201959082019060010161218f565b509495945050505050565b8781526001600160a01b0387811660208301528616604082015260a0606082018190526000906121fb908301868861217f565b828103608084015261220e8185876120c1565b9a9950505050505050505050565b60005b8381101561223757818101518382015260200161221f565b50506000910152565b6000815180845261225881602086016020860161221c565b601f01601f19169290920160200192915050565b6001600160a01b038316815260406020820181905260009061229090830184612240565b949350505050565b6040815260006122ac60408301868861217f565b82810360208401526122bf8185876120c1565b979650505050505050565b6001600160a01b03929092168252602082015260400190565b600080600080608085870312156122f957600080fd5b843561230481611c36565b9350602085013561231481611c36565b9250604085013561232481611c36565b9396929550929360600135925050565b60208082526021908201527f4368696c644552433732315072656469636174653a204d494e545f4641494c456040820152601160fa1b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156123b3576123b3612375565b604052919050565b60006001600160401b038211156123d4576123d4612375565b5060051b60200190565b600082601f8301126123ef57600080fd5b813560206124046123ff836123bb565b61238b565b82815260059290921b8401810191818101908684111561242357600080fd5b8286015b8481101561243e5780358352918301918301612427565b509695505050505050565b600080600080600060a0868803121561246157600080fd5b8535945060208087013561247481611c36565b9450604087013561248481611c36565b935060608701356001600160401b03808211156124a057600080fd5b818901915089601f8301126124b457600080fd5b81356124c26123ff826123bb565b81815260059190911b8301840190848101908c8311156124e157600080fd5b938501935b828510156125085784356124f981611c36565b825293850193908501906124e6565b96505050608089013592508083111561252057600080fd5b505061252e888289016123de565b9150509295509295909350565b604080825283519082018190526000906020906060840190828701845b8281101561257d5781516001600160a01b031684529284019290840190600101612558565b5050508381038285015284518082528583019183019060005b818110156125b257835183529284019291840191600101612596565b5090979650505050505050565b600082601f8301126125d057600080fd5b81356001600160401b038111156125e9576125e9612375565b6125fc601f8201601f191660200161238b565b81815284602083860101111561261157600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000806080858703121561264457600080fd5b84359350602085013561265681611c36565b925060408501356001600160401b038082111561267257600080fd5b61267e888389016125bf565b9350606087013591508082111561269457600080fd5b506126a1878288016125bf565b91505092959194509250565b6001600160a01b03841681526060602082018190526000906126d190830185612240565b82810360408401526126e38185612240565b9695505050505050565b600082516126ff81846020870161221c565b9190910192915050565b60006020828403121561271b57600080fd5b505191905056fea2646970667358221220d293b100fc5adddbb8fd0cb4e134ee7114418d77a3e89fc4f80cf959061a6cfc64736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101b05760003560e01c8063b68ad1e4116100ef578063e0563ab111610092578063e0563ab1146103b9578063e30c3978146103c2578063eeb49945146103d3578063f2fde38b146103e6578063f3fef3a3146103f9578063f64512551461040c578063f691325c14610433578063f8c8765e1461044657600080fd5b8063b68ad1e4146102e5578063c1225a20146102f8578063c3b35a7e1461030b578063c5ac2b1c1461031e578063c5e4683a14610345578063d41f177114610358578063d7c9e3ec1461037f578063d8dd1773146103a657600080fd5b80636f33e695116101575780636f33e6951461024b578063715018a61461026057806379ba5097146102685780637efab4f5146102705780638da5cb5b1461029a578063947287cf146102ab57806397e5230d146102b4578063b1768065146102be57600080fd5b806305dc2e8f146101b557806307b3e252146101e55780631bc114ba146101f3578063284017f5146102065780633b878c221461020f57806351351d531461021857806355b01e4d146102265780635ea5df7914610234575b600080fd5b60fd546101c8906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101c86004600360981b0181565b60fc546101c8906001600160a01b031681565b6101c861202081565b6101c861101081565b6101c86002600160a01b0381565b6101c86004600160991b0181565b61023d61138881565b6040519081526020016101dc565b61025e610259366004611c96565b610459565b005b61025e610475565b61025e610489565b6101c861027e366004611d18565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b03166101c8565b61023d61520881565b61023d620249f081565b61023d7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff546101c8906001600160a01b031681565b61025e610306366004611d4a565b610508565b61025e610319366004611d67565b610557565b61023d7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b61025e610353366004611d4a565b61056f565b61023d7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b61023d7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61025e6103b4366004611da8565b6105b6565b6101c861203081565b6065546001600160a01b03166101c8565b61025e6103e1366004611e3e565b6106d7565b61025e6103f4366004611d18565b6108f5565b61025e610407366004611ec6565b610966565b61023d7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b60fe546101c8906001600160a01b031681565b61025e610454366004611ef2565b61097d565b610461610a75565b61046e8585858585610a7d565b5050505050565b61047d610de2565b6104876000610e3c565b565b60655433906001600160a01b031681146104fc5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b61050581610e3c565b50565b610510610de2565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b61055f610a75565b61056a838383610e55565b505050565b610577610de2565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b03146105dd5760405163973d02cb60e01b81526004016104f390611f4e565b600054610100900460ff16158080156105fd5750600054600160ff909116105b806106175750303b158015610617575060005460ff166001145b6106335760405162461bcd60e51b81526004016104f390611f72565b6000805460ff191660011790558015610656576000805461ff0019166101001790555b6106628888888861115f565b60c9805461ffff191685151561ff001916176101008515150217905561068782610e3c565b80156106cd576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146107435760405162461bcd60e51b815260206004820152602960248201527f4368696c644552433732315072656469636174653a204f4e4c595f53544154456044820152682fa922a1a2a4ab22a960b91b60648201526084016104f3565b60fe546001600160a01b038481169116146107b25760405162461bcd60e51b815260206004820152602960248201527f4368696c644552433732315072656469636174653a204f4e4c595f524f4f545f60448201526850524544494341544560b81b60648201526084016104f3565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216107e1602060008486611fc0565b6107ea91611fea565b03610809576108046107ff8260208186611fc0565b611254565b6108ef565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18610838602060008486611fc0565b61084191611fea565b036108505761080482826114cb565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad61087f602060008486611fc0565b61088891611fea565b03610897576108048282611732565b60405162461bcd60e51b815260206004820152602760248201527f4368696c644552433732315072656469636174653a20494e56414c49445f5349604482015266474e415455524560c81b60648201526084016104f3565b50505050565b6108fd610de2565b606580546001600160a01b0383166001600160a01b0319909116811790915561092e6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b61096e610a75565b610979823383610e55565b5050565b336002600160a01b03146109a45760405163973d02cb60e01b81526004016104f390611f4e565b600054610100900460ff16158080156109c45750600054600160ff909116105b806109de5750303b1580156109de575060005460ff166001145b6109fa5760405162461bcd60e51b81526004016104f390611f72565b6000805460ff191660011790558015610a1d576000805461ff0019166101001790555b610a298585858561115f565b801561046e576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050505050565b6104876118a2565b84610a8781611ab8565b610aa35760405162461bcd60e51b81526004016104f390612008565b6000866001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ae3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b07919061204a565b6001600160a01b0381811660009081526101006020526040902054919250888116911614610b475760405162461bcd60e51b81526004016104f390612067565b6001600160a01b038116610b5d57610b5d6120ab565b306001600160a01b0316876001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ba5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bc9919061204a565b6001600160a01b031614610bdf57610bdf6120ab565b848314610c3a5760405162461bcd60e51b8152602060048201526024808201527f4368696c644552433732315072656469636174653a20494e56414c49445f4c4560448201526309c8ea8960e31b60648201526084016104f3565b60405163b2dc5dc360e01b81526001600160a01b0388169063b2dc5dc390610c6a903390889088906004016120f3565b6020604051808303816000875af1158015610c89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cad9190612121565b610cc95760405162461bcd60e51b81526004016104f39061213e565b60fc5460fe546040516001600160a01b03928316926316f19831921690610d20907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908d908d908d908d906020016121c8565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610d4c92919061226c565b600060405180830381600087803b158015610d6657600080fd5b505af1158015610d7a573d6000803e3d6000fd5b50505050336001600160a01b0316876001600160a01b0316826001600160a01b03167fa80bc76d6e1849a9088a9c00a2aeaa54eeb78f15565a18da3e8873438976f52289898989604051610dd19493929190612298565b60405180910390a450505050505050565b6033546001600160a01b031633146104875760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104f3565b606580546001600160a01b031916905561050581611b4d565b82610e5f81611ab8565b610e7b5760405162461bcd60e51b81526004016104f390612008565b6000846001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ebb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610edf919061204a565b6001600160a01b0381811660009081526101006020526040902054919250868116911614610f1f5760405162461bcd60e51b81526004016104f390612067565b6001600160a01b038116610f3557610f356120ab565b306001600160a01b0316856001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f7d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fa1919061204a565b6001600160a01b031614610fb757610fb76120ab565b604051632770a7eb60e21b81526001600160a01b03861690639dc29fac90610fe590339087906004016122ca565b6020604051808303816000875af1158015611004573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110289190612121565b6110445760405162461bcd60e51b81526004016104f39061213e565b60fc5460fe54604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152888116608083015260a08083018990528351808403909101815260c08301938490526316f1983160e01b909352938416936316f19831936110cf9391169160c40161226c565b600060405180830381600087803b1580156110e957600080fd5b505af11580156110fd573d6000803e3d6000fd5b50505050836001600160a01b0316856001600160a01b0316826001600160a01b03167f1e0ef6131232b1090efc3ec1cf7b53aa17f4b7cd8a4f9e033b49ee237379b01333876040516111509291906122ca565b60405180910390a45050505050565b6001600160a01b0384161580159061117f57506001600160a01b03831615155b801561119357506001600160a01b03821615155b80156111a757506001600160a01b03811615155b6112045760405162461bcd60e51b815260206004820152602860248201527f4368696c644552433732315072656469636174653a204241445f494e495449416044820152672624ad20aa24a7a760c11b60648201526084016104f3565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b6000808080611265858701876122e3565b6001600160a01b0380851660009081526101006020526040902054949850929650909450925016806112a95760405162461bcd60e51b81526004016104f390612067565b6112b281611ab8565b6112be576112be6120ab565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112fe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611322919061204a565b9050856001600160a01b0316816001600160a01b031614611345576113456120ab565b6001600160a01b03811661135b5761135b6120ab565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113c7919061204a565b6001600160a01b0316146113dd576113dd6120ab565b6040516340c10f1960e01b81526001600160a01b038316906340c10f199061140b90879087906004016122ca565b6020604051808303816000875af115801561142a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061144e9190612121565b61146a5760405162461bcd60e51b81526004016104f390612334565b836001600160a01b0316826001600160a01b0316876001600160a01b03167f37589fd8c906c19ea68eeb7e6b3e03efc06ff8aa4b1830588eba75f4375b161188876040516114b99291906122ca565b60405180910390a45050505050505050565b60008080806114dc85870187612449565b6001600160a01b03808516600090815261010060205260409020549499509297509095509350169050806115225760405162461bcd60e51b81526004016104f390612067565b61152b81611ab8565b611537576115376120ab565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015611577573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061159b919061204a565b9050856001600160a01b0316816001600160a01b0316146115be576115be6120ab565b6001600160a01b0381166115d4576115d46120ab565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561161c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611640919061204a565b6001600160a01b031614611656576116566120ab565b604051637c88e3d960e01b81526001600160a01b03831690637c88e3d990611684908790879060040161253b565b6020604051808303816000875af11580156116a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116c79190612121565b6116e35760405162461bcd60e51b81526004016104f390612334565b846001600160a01b0316826001600160a01b0316876001600160a01b03167fc1b1a5c1b97cc8e5ac82b47496f5ebdadf9c7d119b30a116e2bdafd56f6ed47587876040516114b992919061253b565b600080806117428486018661262e565b91955093509150506001600160a01b038316611760576117606120ab565b6001600160a01b03838116600090815261010060205260409020541615611789576117896120ab565b60ff546040516bffffffffffffffffffffffff19606086901b1660208201526000916117d9916001600160a01b039091169060340160405160208183030381529060405280519060200120611b9f565b6001600160a01b03858116600090815261010060205260409081902080546001600160a01b031916928416928317905551639065714760e01b815291925090639065714790611830908790879087906004016126ad565b600060405180830381600087803b15801561184a57600080fd5b505af115801561185e573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a3505050505050565b60c95460ff16156119ad57604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161190491906126ed565b6000604051808303818686fa925050503d8060008114611940576040519150601f19603f3d011682016040523d82523d6000602084013e611945565b606091505b509150915081801561196a57506000818060200190518101906119689190612709565b115b6119aa5760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b60448201526064016104f3565b50505b60c954610100900460ff161561048757604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b019161138891611a1491906126ed565b6000604051808303818686fa925050503d8060008114611a50576040519150601f19603f3d011682016040523d82523d6000602084013e611a55565b606091505b5091509150818015611a7b575080806020019051810190611a769190612709565b600114155b6109795760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b60448201526064016104f3565b6000816001600160a01b03163b600003611ad457506000919050565b6040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611b3b575060408051601f3d908101601f19168201909252611b3891810190612121565b60015b611b4757506000919050565b92915050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116611b475760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b60448201526064016104f3565b6001600160a01b038116811461050557600080fd5b60008083601f840112611c5d57600080fd5b5081356001600160401b03811115611c7457600080fd5b6020830191508360208260051b8501011115611c8f57600080fd5b9250929050565b600080600080600060608688031215611cae57600080fd5b8535611cb981611c36565b945060208601356001600160401b0380821115611cd557600080fd5b611ce189838a01611c4b565b90965094506040880135915080821115611cfa57600080fd5b50611d0788828901611c4b565b969995985093965092949392505050565b600060208284031215611d2a57600080fd5b8135611d3581611c36565b9392505050565b801515811461050557600080fd5b600060208284031215611d5c57600080fd5b8135611d3581611d3c565b600080600060608486031215611d7c57600080fd5b8335611d8781611c36565b92506020840135611d9781611c36565b929592945050506040919091013590565b600080600080600080600060e0888a031215611dc357600080fd5b8735611dce81611c36565b96506020880135611dde81611c36565b95506040880135611dee81611c36565b94506060880135611dfe81611c36565b93506080880135611e0e81611d3c565b925060a0880135611e1e81611d3c565b915060c0880135611e2e81611c36565b8091505092959891949750929550565b60008060008060608587031215611e5457600080fd5b843593506020850135611e6681611c36565b925060408501356001600160401b0380821115611e8257600080fd5b818701915087601f830112611e9657600080fd5b813581811115611ea557600080fd5b886020828501011115611eb757600080fd5b95989497505060200194505050565b60008060408385031215611ed957600080fd5b8235611ee481611c36565b946020939093013593505050565b60008060008060808587031215611f0857600080fd5b8435611f1381611c36565b93506020850135611f2381611c36565b92506040850135611f3381611c36565b91506060850135611f4381611c36565b939692955090935050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008085851115611fd057600080fd5b83861115611fdd57600080fd5b5050820193919092039150565b80356020831015611b4757600019602084900360031b1b1692915050565b60208082526022908201527f4368696c644552433732315072656469636174653a204e4f545f434f4e54524160408201526110d560f21b606082015260800190565b60006020828403121561205c57600080fd5b8151611d3581611c36565b60208082526024908201527f4368696c644552433732315072656469636174653a20554e4d41505045445f5460408201526327a5a2a760e11b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b038311156120da57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b038416815260406020820181905260009061211890830184866120c1565b95945050505050565b60006020828403121561213357600080fd5b8151611d3581611d3c565b60208082526021908201527f4368696c644552433732315072656469636174653a204255524e5f4641494c456040820152601160fa1b606082015260800190565b8183526000602080850194508260005b858110156121bd5781356121a281611c36565b6001600160a01b03168752958201959082019060010161218f565b509495945050505050565b8781526001600160a01b0387811660208301528616604082015260a0606082018190526000906121fb908301868861217f565b828103608084015261220e8185876120c1565b9a9950505050505050505050565b60005b8381101561223757818101518382015260200161221f565b50506000910152565b6000815180845261225881602086016020860161221c565b601f01601f19169290920160200192915050565b6001600160a01b038316815260406020820181905260009061229090830184612240565b949350505050565b6040815260006122ac60408301868861217f565b82810360208401526122bf8185876120c1565b979650505050505050565b6001600160a01b03929092168252602082015260400190565b600080600080608085870312156122f957600080fd5b843561230481611c36565b9350602085013561231481611c36565b9250604085013561232481611c36565b9396929550929360600135925050565b60208082526021908201527f4368696c644552433732315072656469636174653a204d494e545f4641494c456040820152601160fa1b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156123b3576123b3612375565b604052919050565b60006001600160401b038211156123d4576123d4612375565b5060051b60200190565b600082601f8301126123ef57600080fd5b813560206124046123ff836123bb565b61238b565b82815260059290921b8401810191818101908684111561242357600080fd5b8286015b8481101561243e5780358352918301918301612427565b509695505050505050565b600080600080600060a0868803121561246157600080fd5b8535945060208087013561247481611c36565b9450604087013561248481611c36565b935060608701356001600160401b03808211156124a057600080fd5b818901915089601f8301126124b457600080fd5b81356124c26123ff826123bb565b81815260059190911b8301840190848101908c8311156124e157600080fd5b938501935b828510156125085784356124f981611c36565b825293850193908501906124e6565b96505050608089013592508083111561252057600080fd5b505061252e888289016123de565b9150509295509295909350565b604080825283519082018190526000906020906060840190828701845b8281101561257d5781516001600160a01b031684529284019290840190600101612558565b5050508381038285015284518082528583019183019060005b818110156125b257835183529284019291840191600101612596565b5090979650505050505050565b600082601f8301126125d057600080fd5b81356001600160401b038111156125e9576125e9612375565b6125fc601f8201601f191660200161238b565b81815284602083860101111561261157600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000806080858703121561264457600080fd5b84359350602085013561265681611c36565b925060408501356001600160401b038082111561267257600080fd5b61267e888389016125bf565b9350606087013591508082111561269457600080fd5b506126a1878288016125bf565b91505092959194509250565b6001600160a01b03841681526060602082018190526000906126d190830185612240565b82810360408401526126e38185612240565b9695505050505050565b600082516126ff81846020870161221c565b9190910192915050565b60006020828403121561271b57600080fd5b505191905056fea2646970667358221220d293b100fc5adddbb8fd0cb4e134ee7114418d77a3e89fc4f80cf959061a6cfc64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RootMintableERC721PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RootMintableERC721Predicate","sourceName":"contracts/child/RootMintableERC721Predicate.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"L2MintableERC721Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"L2MintableERC721DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"L2MintableERC721Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"L2MintableERC721WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2MintableTokenMapped","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childERC721Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"depositBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newChildERC721Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"}],"name":"mapToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611a8f806100206000396000f3fe608060405234801561001057600080fd5b506004361061016e5760003560e01c806397e5230d116100ce578063e0563ab111610087578063e0563ab114610353578063eeb499451461035c578063f213159c1461036f578063f4a120f714610382578063f645125514610395578063f8c8765e146103bc578063ff6f870c146103cf57600080fd5b806397e5230d1461029a578063b1768065146102a4578063b68ad1e4146102cb578063c5ac2b1c146102de578063d41f177114610305578063d7c9e3ec1461032c57600080fd5b806347e7ef241161012b57806347e7ef241461020d5780634fdca69d1461022257806351351d531461023557806355b01e4d146102435780635ea5df79146102515780637efab4f514610268578063947287cf1461029157600080fd5b806305dc2e8f1461017357806307b3e252146101a3578063150b7a02146101b15780631bc114ba146101e8578063284017f5146101fb5780633b878c2214610204575b600080fd5b603454610186906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101866004600360981b0181565b6101cf6101bf366004611231565b630a85bd0160e11b949350505050565b6040516001600160e01b0319909116815260200161019a565b603354610186906001600160a01b031681565b61018661202081565b61018661101081565b61022061021b3660046112df565b6103e2565b005b603554610186906001600160a01b031681565b6101866002600160a01b0381565b6101866004600160991b0181565b61025a61138881565b60405190815260200161019a565b61018661027636600461130b565b6037602052600090815260409020546001600160a01b031681565b61025a61520881565b61025a620249f081565b61025a7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b603654610186906001600160a01b031681565b61025a7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b61025a7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b61025a7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61018661203081565b61022061036a36600461132f565b6103f1565b61022061037d3660046113b7565b6105d1565b61018661039036600461130b565b6105e1565b61025a7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b6102206103ca3660046113f8565b61093a565b6102206103dd36600461149f565b610a97565b6103ed823383610af5565b5050565b6034546001600160a01b031633146104695760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c654552433732315072656469636174653a204f4e4c60448201526f2cafa9aa20aa22afa922a1a2a4ab22a960811b60648201526084015b60405180910390fd5b6035546001600160a01b038481169116146104e05760405162461bcd60e51b815260206004820152603160248201527f526f6f744d696e7461626c654552433732315072656469636174653a204f4e4c604482015270595f4348494c445f50524544494341544560781b6064820152608401610460565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286961050f602060008486611521565b6105189161154b565b036105375761053261052d8260208186611521565b610c74565b6105cb565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed2610566602060008486611521565b61056f9161154b565b0361057e576105328282610d72565b60405162461bcd60e51b815260206004820152602e6024820152600080516020611a3a83398151915260448201526d414c49445f5349474e415455524560901b6064820152608401610460565b50505050565b6105dc838383610af5565b505050565b60006001600160a01b03821661063a5760405162461bcd60e51b815260206004820152602a6024820152600080516020611a3a83398151915260448201526920a624a22faa27a5a2a760b11b6064820152608401610460565b6001600160a01b0382811660009081526037602052604090205416156106b65760405162461bcd60e51b815260206004820152602b60248201527f526f6f744d696e7461626c654552433732315072656469636174653a20414c5260448201526a1150511657d3505414115160aa1b6064820152608401610460565b6035546036546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b039283169260009261076492911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b0385811660008181526037602052604080822080546001600160a01b03191686861617905560335481516306fdde0360e01b81529151959650909316936316f198319387937f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad938b9391926306fdde0392600480830193928290030181865afa1580156107fc573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610824919081019061158e565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610862573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261088a919081019061158e565b60405160200161089d9493929190611630565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016108c9929190611677565b600060405180830381600087803b1580156108e357600080fd5b505af11580156108f7573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a39392505050565b336002600160a01b031461097e5760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610460565b600054610100900460ff161580801561099e5750600054600160ff909116105b806109b85750303b1580156109b8575060005460ff166001145b610a1b5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610460565b6000805460ff191660011790558015610a3e576000805461ff0019166101001790555b610a4a85858585610eb3565b8015610a90576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b828114610ae85760405162461bcd60e51b815260206004820152602b6024820152600080516020611a3a83398151915260448201526a082989288be988a9c8ea8960ab1b6064820152608401610460565b610a908585858585610faf565b6000610b0084611165565b604051632142170760e11b81529091506001600160a01b038516906342842e0e90610b33903390309087906004016116a3565b600060405180830381600087803b158015610b4d57600080fd5b505af1158015610b61573d6000803e3d6000fd5b5050603354603554604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038a811682840152336060830152898116608083015260a08083018a90528351808403909101815260c08301938490526316f1983160e01b90935293841695506316f198319450610bf1939092169160c401611677565b600060405180830381600087803b158015610c0b57600080fd5b505af1158015610c1f573d6000803e3d6000fd5b505060408051338152602081018690526001600160a01b03808816945085811693508816917fe3abb9ec195b50242582cc6cb7abc990ac26439b9ed7635ed72f7bd720e7477a910160405180910390a46105cb565b6000808080610c85858701876116c7565b6001600160a01b0380851660009081526037602052604090205494985092965090945092501680610cb857610cb8611718565b604051632142170760e11b81526001600160a01b038616906342842e0e90610ce8903090879087906004016116a3565b600060405180830381600087803b158015610d0257600080fd5b505af1158015610d16573d6000803e3d6000fd5b5050604080516001600160a01b03888116825260208201879052808816945085811693508916917f80afffd84aa825dd9b3c1ba262eb55eef58f78616634437a367a263f3c48fe3d91015b60405180910390a450505050505050565b6000808080610d83858701876117b7565b6001600160a01b03808516600090815260376020526040902054949950929750909550935016905080610db857610db8611718565b60005b8251811015610e6357856001600160a01b03166342842e0e30868481518110610de657610de66118a9565b6020026020010151868581518110610e0057610e006118a9565b60200260200101516040518463ffffffff1660e01b8152600401610e26939291906116a3565b600060405180830381600087803b158015610e4057600080fd5b505af1158015610e54573d6000803e3d6000fd5b50505050806001019050610dbb565b50836001600160a01b0316816001600160a01b0316866001600160a01b03167ff64537485d843fa8a1f4354cb9937954c64282de0828be9f096355006222186b8686604051610d619291906118bf565b6001600160a01b03841615801590610ed357506001600160a01b03831615155b8015610ee757506001600160a01b03821615155b8015610efb57506001600160a01b03811615155b610f5f5760405162461bcd60e51b815260206004820152602f60248201527f526f6f744d696e7461626c654552433732315072656469636174653a2042414460448201526e2fa4a724aa24a0a624ad20aa24a7a760891b6064820152608401610460565b603380546001600160a01b039586166001600160a01b0319918216179091556034805494861694821694909417909355603580549285169284169290921790915560368054919093169116179055565b6000610fba86611165565b905060005b8281101561104c57866001600160a01b03166342842e0e3330878786818110610fea57610fea6118a9565b905060200201356040518463ffffffff1660e01b815260040161100f939291906116a3565b600060405180830381600087803b15801561102957600080fd5b505af115801561103d573d6000803e3d6000fd5b50505050806001019050610fbf565b506033546035546040516001600160a01b03928316926316f198319216906110a4907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908b9033908c908c908c908c906020016119be565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016110d0929190611677565b600060405180830381600087803b1580156110ea57600080fd5b505af11580156110fe573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316876001600160a01b03167f0ca5d99e0f7e7651c00c76347baa68a333790cbc9fb039a10843d1bd61f578c5888888886040516111559493929190611a12565b60405180910390a4505050505050565b6001600160a01b0380821660009081526037602052604090205416806111915761118e826105e1565b90505b6001600160a01b0381166111a7576111a7611718565b919050565b6001600160a01b03811681146111c157600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611202576112026111c4565b604052919050565b60006001600160401b03821115611223576112236111c4565b50601f01601f191660200190565b6000806000806080858703121561124757600080fd5b8435611252816111ac565b93506020850135611262816111ac565b92506040850135915060608501356001600160401b0381111561128457600080fd5b8501601f8101871361129557600080fd5b80356112a86112a38261120a565b6111da565b8181528860208385010111156112bd57600080fd5b8160208401602083013760006020838301015280935050505092959194509250565b600080604083850312156112f257600080fd5b82356112fd816111ac565b946020939093013593505050565b60006020828403121561131d57600080fd5b8135611328816111ac565b9392505050565b6000806000806060858703121561134557600080fd5b843593506020850135611357816111ac565b925060408501356001600160401b038082111561137357600080fd5b818701915087601f83011261138757600080fd5b81358181111561139657600080fd5b8860208285010111156113a857600080fd5b95989497505060200194505050565b6000806000606084860312156113cc57600080fd5b83356113d7816111ac565b925060208401356113e7816111ac565b929592945050506040919091013590565b6000806000806080858703121561140e57600080fd5b8435611419816111ac565b93506020850135611429816111ac565b92506040850135611439816111ac565b91506060850135611449816111ac565b939692955090935050565b60008083601f84011261146657600080fd5b5081356001600160401b0381111561147d57600080fd5b6020830191508360208260051b850101111561149857600080fd5b9250929050565b6000806000806000606086880312156114b757600080fd5b85356114c2816111ac565b945060208601356001600160401b03808211156114de57600080fd5b6114ea89838a01611454565b9096509450604088013591508082111561150357600080fd5b5061151088828901611454565b969995985093965092949392505050565b6000808585111561153157600080fd5b8386111561153e57600080fd5b5050820193919092039150565b8035602083101561156457600019602084900360031b1b165b92915050565b60005b8381101561158557818101518382015260200161156d565b50506000910152565b6000602082840312156115a057600080fd5b81516001600160401b038111156115b657600080fd5b8201601f810184136115c757600080fd5b80516115d56112a38261120a565b8181528560208385010111156115ea57600080fd5b6115fb82602083016020860161156a565b95945050505050565b6000815180845261161c81602086016020860161156a565b601f01601f19169290920160200192915050565b8481526001600160a01b038416602082015260806040820181905260009061165a90830185611604565b828103606084015261166c8185611604565b979650505050505050565b6001600160a01b038316815260406020820181905260009061169b90830184611604565b949350505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600080600080608085870312156116dd57600080fd5b84356116e8816111ac565b935060208501356116f8816111ac565b92506040850135611708816111ac565b9396929550929360600135925050565b634e487b7160e01b600052600160045260246000fd5b60006001600160401b03821115611747576117476111c4565b5060051b60200190565b600082601f83011261176257600080fd5b813560206117726112a38361172e565b82815260059290921b8401810191818101908684111561179157600080fd5b8286015b848110156117ac5780358352918301918301611795565b509695505050505050565b600080600080600060a086880312156117cf57600080fd5b853594506020808701356117e2816111ac565b945060408701356117f2816111ac565b935060608701356001600160401b038082111561180e57600080fd5b818901915089601f83011261182257600080fd5b81356118306112a38261172e565b81815260059190911b8301840190848101908c83111561184f57600080fd5b938501935b82851015611876578435611867816111ac565b82529385019390850190611854565b96505050608089013592508083111561188e57600080fd5b505061189c88828901611751565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b604080825283519082018190526000906020906060840190828701845b828110156119015781516001600160a01b0316845292840192908401906001016118dc565b5050508381038285015284518082528583019183019060005b818110156119365783518352928401929184019160010161191a565b5090979650505050505050565b8183526000602080850194508260005b85811015611981578135611966816111ac565b6001600160a01b031687529582019590820190600101611953565b509495945050505050565b81835260006001600160fb1b038311156119a557600080fd5b8260051b80836020870137939093016020019392505050565b8781526001600160a01b0387811660208301528616604082015260a0606082018190526000906119f19083018688611943565b8281036080840152611a0481858761198c565b9a9950505050505050505050565b604081526000611a26604083018688611943565b828103602084015261166c81858761198c56fe526f6f744d696e7461626c654552433732315072656469636174653a20494e56a2646970667358221220d56687504a65fbdf30d6fa40bc1481712f630a02ef2aa4dc136cf9be78742d2364736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061016e5760003560e01c806397e5230d116100ce578063e0563ab111610087578063e0563ab114610353578063eeb499451461035c578063f213159c1461036f578063f4a120f714610382578063f645125514610395578063f8c8765e146103bc578063ff6f870c146103cf57600080fd5b806397e5230d1461029a578063b1768065146102a4578063b68ad1e4146102cb578063c5ac2b1c146102de578063d41f177114610305578063d7c9e3ec1461032c57600080fd5b806347e7ef241161012b57806347e7ef241461020d5780634fdca69d1461022257806351351d531461023557806355b01e4d146102435780635ea5df79146102515780637efab4f514610268578063947287cf1461029157600080fd5b806305dc2e8f1461017357806307b3e252146101a3578063150b7a02146101b15780631bc114ba146101e8578063284017f5146101fb5780633b878c2214610204575b600080fd5b603454610186906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101866004600360981b0181565b6101cf6101bf366004611231565b630a85bd0160e11b949350505050565b6040516001600160e01b0319909116815260200161019a565b603354610186906001600160a01b031681565b61018661202081565b61018661101081565b61022061021b3660046112df565b6103e2565b005b603554610186906001600160a01b031681565b6101866002600160a01b0381565b6101866004600160991b0181565b61025a61138881565b60405190815260200161019a565b61018661027636600461130b565b6037602052600090815260409020546001600160a01b031681565b61025a61520881565b61025a620249f081565b61025a7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b603654610186906001600160a01b031681565b61025a7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b61025a7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b61025a7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61018661203081565b61022061036a36600461132f565b6103f1565b61022061037d3660046113b7565b6105d1565b61018661039036600461130b565b6105e1565b61025a7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b6102206103ca3660046113f8565b61093a565b6102206103dd36600461149f565b610a97565b6103ed823383610af5565b5050565b6034546001600160a01b031633146104695760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c654552433732315072656469636174653a204f4e4c60448201526f2cafa9aa20aa22afa922a1a2a4ab22a960811b60648201526084015b60405180910390fd5b6035546001600160a01b038481169116146104e05760405162461bcd60e51b815260206004820152603160248201527f526f6f744d696e7461626c654552433732315072656469636174653a204f4e4c604482015270595f4348494c445f50524544494341544560781b6064820152608401610460565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286961050f602060008486611521565b6105189161154b565b036105375761053261052d8260208186611521565b610c74565b6105cb565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed2610566602060008486611521565b61056f9161154b565b0361057e576105328282610d72565b60405162461bcd60e51b815260206004820152602e6024820152600080516020611a3a83398151915260448201526d414c49445f5349474e415455524560901b6064820152608401610460565b50505050565b6105dc838383610af5565b505050565b60006001600160a01b03821661063a5760405162461bcd60e51b815260206004820152602a6024820152600080516020611a3a83398151915260448201526920a624a22faa27a5a2a760b11b6064820152608401610460565b6001600160a01b0382811660009081526037602052604090205416156106b65760405162461bcd60e51b815260206004820152602b60248201527f526f6f744d696e7461626c654552433732315072656469636174653a20414c5260448201526a1150511657d3505414115160aa1b6064820152608401610460565b6035546036546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b039283169260009261076492911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b0385811660008181526037602052604080822080546001600160a01b03191686861617905560335481516306fdde0360e01b81529151959650909316936316f198319387937f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad938b9391926306fdde0392600480830193928290030181865afa1580156107fc573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610824919081019061158e565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610862573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261088a919081019061158e565b60405160200161089d9493929190611630565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016108c9929190611677565b600060405180830381600087803b1580156108e357600080fd5b505af11580156108f7573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a39392505050565b336002600160a01b031461097e5760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610460565b600054610100900460ff161580801561099e5750600054600160ff909116105b806109b85750303b1580156109b8575060005460ff166001145b610a1b5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610460565b6000805460ff191660011790558015610a3e576000805461ff0019166101001790555b610a4a85858585610eb3565b8015610a90576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b828114610ae85760405162461bcd60e51b815260206004820152602b6024820152600080516020611a3a83398151915260448201526a082989288be988a9c8ea8960ab1b6064820152608401610460565b610a908585858585610faf565b6000610b0084611165565b604051632142170760e11b81529091506001600160a01b038516906342842e0e90610b33903390309087906004016116a3565b600060405180830381600087803b158015610b4d57600080fd5b505af1158015610b61573d6000803e3d6000fd5b5050603354603554604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038a811682840152336060830152898116608083015260a08083018a90528351808403909101815260c08301938490526316f1983160e01b90935293841695506316f198319450610bf1939092169160c401611677565b600060405180830381600087803b158015610c0b57600080fd5b505af1158015610c1f573d6000803e3d6000fd5b505060408051338152602081018690526001600160a01b03808816945085811693508816917fe3abb9ec195b50242582cc6cb7abc990ac26439b9ed7635ed72f7bd720e7477a910160405180910390a46105cb565b6000808080610c85858701876116c7565b6001600160a01b0380851660009081526037602052604090205494985092965090945092501680610cb857610cb8611718565b604051632142170760e11b81526001600160a01b038616906342842e0e90610ce8903090879087906004016116a3565b600060405180830381600087803b158015610d0257600080fd5b505af1158015610d16573d6000803e3d6000fd5b5050604080516001600160a01b03888116825260208201879052808816945085811693508916917f80afffd84aa825dd9b3c1ba262eb55eef58f78616634437a367a263f3c48fe3d91015b60405180910390a450505050505050565b6000808080610d83858701876117b7565b6001600160a01b03808516600090815260376020526040902054949950929750909550935016905080610db857610db8611718565b60005b8251811015610e6357856001600160a01b03166342842e0e30868481518110610de657610de66118a9565b6020026020010151868581518110610e0057610e006118a9565b60200260200101516040518463ffffffff1660e01b8152600401610e26939291906116a3565b600060405180830381600087803b158015610e4057600080fd5b505af1158015610e54573d6000803e3d6000fd5b50505050806001019050610dbb565b50836001600160a01b0316816001600160a01b0316866001600160a01b03167ff64537485d843fa8a1f4354cb9937954c64282de0828be9f096355006222186b8686604051610d619291906118bf565b6001600160a01b03841615801590610ed357506001600160a01b03831615155b8015610ee757506001600160a01b03821615155b8015610efb57506001600160a01b03811615155b610f5f5760405162461bcd60e51b815260206004820152602f60248201527f526f6f744d696e7461626c654552433732315072656469636174653a2042414460448201526e2fa4a724aa24a0a624ad20aa24a7a760891b6064820152608401610460565b603380546001600160a01b039586166001600160a01b0319918216179091556034805494861694821694909417909355603580549285169284169290921790915560368054919093169116179055565b6000610fba86611165565b905060005b8281101561104c57866001600160a01b03166342842e0e3330878786818110610fea57610fea6118a9565b905060200201356040518463ffffffff1660e01b815260040161100f939291906116a3565b600060405180830381600087803b15801561102957600080fd5b505af115801561103d573d6000803e3d6000fd5b50505050806001019050610fbf565b506033546035546040516001600160a01b03928316926316f198319216906110a4907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908b9033908c908c908c908c906020016119be565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016110d0929190611677565b600060405180830381600087803b1580156110ea57600080fd5b505af11580156110fe573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316876001600160a01b03167f0ca5d99e0f7e7651c00c76347baa68a333790cbc9fb039a10843d1bd61f578c5888888886040516111559493929190611a12565b60405180910390a4505050505050565b6001600160a01b0380821660009081526037602052604090205416806111915761118e826105e1565b90505b6001600160a01b0381166111a7576111a7611718565b919050565b6001600160a01b03811681146111c157600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611202576112026111c4565b604052919050565b60006001600160401b03821115611223576112236111c4565b50601f01601f191660200190565b6000806000806080858703121561124757600080fd5b8435611252816111ac565b93506020850135611262816111ac565b92506040850135915060608501356001600160401b0381111561128457600080fd5b8501601f8101871361129557600080fd5b80356112a86112a38261120a565b6111da565b8181528860208385010111156112bd57600080fd5b8160208401602083013760006020838301015280935050505092959194509250565b600080604083850312156112f257600080fd5b82356112fd816111ac565b946020939093013593505050565b60006020828403121561131d57600080fd5b8135611328816111ac565b9392505050565b6000806000806060858703121561134557600080fd5b843593506020850135611357816111ac565b925060408501356001600160401b038082111561137357600080fd5b818701915087601f83011261138757600080fd5b81358181111561139657600080fd5b8860208285010111156113a857600080fd5b95989497505060200194505050565b6000806000606084860312156113cc57600080fd5b83356113d7816111ac565b925060208401356113e7816111ac565b929592945050506040919091013590565b6000806000806080858703121561140e57600080fd5b8435611419816111ac565b93506020850135611429816111ac565b92506040850135611439816111ac565b91506060850135611449816111ac565b939692955090935050565b60008083601f84011261146657600080fd5b5081356001600160401b0381111561147d57600080fd5b6020830191508360208260051b850101111561149857600080fd5b9250929050565b6000806000806000606086880312156114b757600080fd5b85356114c2816111ac565b945060208601356001600160401b03808211156114de57600080fd5b6114ea89838a01611454565b9096509450604088013591508082111561150357600080fd5b5061151088828901611454565b969995985093965092949392505050565b6000808585111561153157600080fd5b8386111561153e57600080fd5b5050820193919092039150565b8035602083101561156457600019602084900360031b1b165b92915050565b60005b8381101561158557818101518382015260200161156d565b50506000910152565b6000602082840312156115a057600080fd5b81516001600160401b038111156115b657600080fd5b8201601f810184136115c757600080fd5b80516115d56112a38261120a565b8181528560208385010111156115ea57600080fd5b6115fb82602083016020860161156a565b95945050505050565b6000815180845261161c81602086016020860161156a565b601f01601f19169290920160200192915050565b8481526001600160a01b038416602082015260806040820181905260009061165a90830185611604565b828103606084015261166c8185611604565b979650505050505050565b6001600160a01b038316815260406020820181905260009061169b90830184611604565b949350505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600080600080608085870312156116dd57600080fd5b84356116e8816111ac565b935060208501356116f8816111ac565b92506040850135611708816111ac565b9396929550929360600135925050565b634e487b7160e01b600052600160045260246000fd5b60006001600160401b03821115611747576117476111c4565b5060051b60200190565b600082601f83011261176257600080fd5b813560206117726112a38361172e565b82815260059290921b8401810191818101908684111561179157600080fd5b8286015b848110156117ac5780358352918301918301611795565b509695505050505050565b600080600080600060a086880312156117cf57600080fd5b853594506020808701356117e2816111ac565b945060408701356117f2816111ac565b935060608701356001600160401b038082111561180e57600080fd5b818901915089601f83011261182257600080fd5b81356118306112a38261172e565b81815260059190911b8301840190848101908c83111561184f57600080fd5b938501935b82851015611876578435611867816111ac565b82529385019390850190611854565b96505050608089013592508083111561188e57600080fd5b505061189c88828901611751565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b604080825283519082018190526000906020906060840190828701845b828110156119015781516001600160a01b0316845292840192908401906001016118dc565b5050508381038285015284518082528583019183019060005b818110156119365783518352928401929184019160010161191a565b5090979650505050505050565b8183526000602080850194508260005b85811015611981578135611966816111ac565b6001600160a01b031687529582019590820190600101611953565b509495945050505050565b81835260006001600160fb1b038311156119a557600080fd5b8260051b80836020870137939093016020019392505050565b8781526001600160a01b0387811660208301528616604082015260a0606082018190526000906119f19083018688611943565b8281036080840152611a0481858761198c565b9a9950505050505050505050565b604081526000611a26604083018688611943565b828103602084015261166c81858761198c56fe526f6f744d696e7461626c654552433732315072656469636174653a20494e56a2646970667358221220d56687504a65fbdf30d6fa40bc1481712f630a02ef2aa4dc136cf9be78742d2364736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RootMintableERC721PredicateACLArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RootMintableERC721PredicateAccessList","sourceName":"contracts/child/RootMintableERC721PredicateAccessList.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"AllowListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"BlockListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"L2MintableERC721Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"L2MintableERC721DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"L2MintableERC721Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"L2MintableERC721WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2MintableTokenMapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"childERC721Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"depositBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newChildERC721Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"},{"internalType":"bool","name":"newUseAllowList","type":"bool"},{"internalType":"bool","name":"newUseBlockList","type":"bool"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newChildERC721Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"}],"name":"mapToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseAllowList","type":"bool"}],"name":"setAllowList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseBlockList","type":"bool"}],"name":"setBlockList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50612231806100206000396000f3fe608060405234801561001057600080fd5b50600436106101e65760003560e01c8063b17680651161010a578063e0563ab1116100ad578063f2fde38b1161007c578063f2fde38b14610466578063f4a120f714610479578063f64512551461048c578063f8c8765e146104b3578063ff6f870c146104c657600080fd5b8063e0563ab114610426578063e30c39781461042f578063eeb4994514610440578063f213159c1461045357600080fd5b8063b17680651461033e578063b68ad1e414610365578063c1225a2014610378578063c5ac2b1c1461038b578063c5e4683a146103b2578063d41f1771146103c5578063d7c9e3ec146103ec578063d8dd17731461041357600080fd5b806351351d531161018d57806379ba50971161015c57806379ba5097146102e85780637efab4f5146102f05780638da5cb5b1461031a578063947287cf1461032b57806397e5230d1461033457600080fd5b806351351d53146102ad57806355b01e4d146102bb5780635ea5df79146102c9578063715018a6146102e057600080fd5b806305dc2e8f146101eb57806307b3e2521461021b578063150b7a02146102295780631bc114ba14610260578063284017f5146102735780633b878c221461027c57806347e7ef24146102855780634fdca69d1461029a575b600080fd5b60fd546101fe906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101fe6004600360981b0181565b61024761023736600461186f565b630a85bd0160e11b949350505050565b6040516001600160e01b03199091168152602001610212565b60fc546101fe906001600160a01b031681565b6101fe61202081565b6101fe61101081565b61029861029336600461191d565b6104d9565b005b60fe546101fe906001600160a01b031681565b6101fe6002600160a01b0381565b6101fe6004600160991b0181565b6102d261138881565b604051908152602001610212565b6102986104e8565b6102986104fc565b6101fe6102fe366004611949565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b03166101fe565b6102d261520881565b6102d2620249f081565b6102d27f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff546101fe906001600160a01b031681565b61029861038636600461197d565b61057b565b6102d27faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6102986103c036600461197d565b6105ca565b6102d27f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102d27f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b610298610421366004611998565b610611565b6101fe61203081565b6065546001600160a01b03166101fe565b61029861044e366004611a2a565b610732565b610298610461366004611ab2565b61090d565b610298610474366004611949565b61091d565b6101fe610487366004611949565b61098e565b6102d27f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b6102986104c1366004611af3565b610ce9565b6102986104d4366004611b9a565b610de2565b6104e4823383610e40565b5050565b6104f0610fc7565b6104fa6000611021565b565b60655433906001600160a01b0316811461056f5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b61057881611021565b50565b610583610fc7565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b6105d2610fc7565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b03146106385760405163973d02cb60e01b815260040161056690611c1c565b600054610100900460ff16158080156106585750600054600160ff909116105b806106725750303b158015610672575060005460ff166001145b61068e5760405162461bcd60e51b815260040161056690611c40565b6000805460ff1916600117905580156106b1576000805461ff0019166101001790555b6106bd8888888861103a565b60c9805461ffff191685151561ff00191617610100851515021790556106e282611021565b8015610728576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146107a55760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c654552433732315072656469636174653a204f4e4c60448201526f2cafa9aa20aa22afa922a1a2a4ab22a960811b6064820152608401610566565b60fe546001600160a01b0384811691161461081c5760405162461bcd60e51b815260206004820152603160248201527f526f6f744d696e7461626c654552433732315072656469636174653a204f4e4c604482015270595f4348494c445f50524544494341544560781b6064820152608401610566565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286961084b602060008486611c8e565b61085491611cb8565b036108735761086e6108698260208186611c8e565b611136565b610907565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed26108a2602060008486611c8e565b6108ab91611cb8565b036108ba5761086e8282611235565b60405162461bcd60e51b815260206004820152602e60248201526000805160206121dc83398151915260448201526d414c49445f5349474e415455524560901b6064820152608401610566565b50505050565b610918838383610e40565b505050565b610925610fc7565b606580546001600160a01b0383166001600160a01b031990911681179091556109566033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60006001600160a01b0382166109e75760405162461bcd60e51b815260206004820152602a60248201526000805160206121dc83398151915260448201526920a624a22faa27a5a2a760b11b6064820152608401610566565b6001600160a01b03828116600090815261010060205260409020541615610a645760405162461bcd60e51b815260206004820152602b60248201527f526f6f744d696e7461626c654552433732315072656469636174653a20414c5260448201526a1150511657d3505414115160aa1b6064820152608401610566565b60fe5460ff546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b0392831692600092610b1292911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b038581166000818152610100602052604080822080546001600160a01b03191686861617905560fc5481516306fdde0360e01b81529151959650909316936316f198319387937f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad938b9391926306fdde0392600480830193928290030181865afa158015610bab573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610bd39190810190611cfb565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610c11573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610c399190810190611cfb565b604051602001610c4c9493929190611d9d565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610c78929190611de4565b600060405180830381600087803b158015610c9257600080fd5b505af1158015610ca6573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a39392505050565b336002600160a01b0314610d105760405163973d02cb60e01b815260040161056690611c1c565b600054610100900460ff1615808015610d305750600054600160ff909116105b80610d4a5750303b158015610d4a575060005460ff166001145b610d665760405162461bcd60e51b815260040161056690611c40565b6000805460ff191660011790558015610d89576000805461ff0019166101001790555b610d958585858561103a565b8015610ddb576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b828114610e335760405162461bcd60e51b815260206004820152602b60248201526000805160206121dc83398151915260448201526a082989288be988a9c8ea8960ab1b6064820152608401610566565b610ddb8585858585611377565b610e48611535565b6000610e538461153d565b604051632142170760e11b81529091506001600160a01b038516906342842e0e90610e8690339030908790600401611e10565b600060405180830381600087803b158015610ea057600080fd5b505af1158015610eb4573d6000803e3d6000fd5b505060fc5460fe54604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038a811682840152336060830152898116608083015260a08083018a90528351808403909101815260c08301938490526316f1983160e01b90935293841695506316f198319450610f44939092169160c401611de4565b600060405180830381600087803b158015610f5e57600080fd5b505af1158015610f72573d6000803e3d6000fd5b505060408051338152602081018690526001600160a01b03808816945085811693508816917fe3abb9ec195b50242582cc6cb7abc990ac26439b9ed7635ed72f7bd720e7477a910160405180910390a4610907565b6033546001600160a01b031633146104fa5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610566565b606580546001600160a01b031916905561057881611585565b6001600160a01b0384161580159061105a57506001600160a01b03831615155b801561106e57506001600160a01b03821615155b801561108257506001600160a01b03811615155b6110e65760405162461bcd60e51b815260206004820152602f60248201527f526f6f744d696e7461626c654552433732315072656469636174653a2042414460448201526e2fa4a724aa24a0a624ad20aa24a7a760891b6064820152608401610566565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b600080808061114785870187611e34565b6001600160a01b03808516600090815261010060205260409020549498509296509094509250168061117b5761117b611e85565b604051632142170760e11b81526001600160a01b038616906342842e0e906111ab90309087908790600401611e10565b600060405180830381600087803b1580156111c557600080fd5b505af11580156111d9573d6000803e3d6000fd5b5050604080516001600160a01b03888116825260208201879052808816945085811693508916917f80afffd84aa825dd9b3c1ba262eb55eef58f78616634437a367a263f3c48fe3d91015b60405180910390a450505050505050565b600080808061124685870187611f24565b6001600160a01b038085166000908152610100602052604090205494995092975090955093501690508061127c5761127c611e85565b60005b825181101561132757856001600160a01b03166342842e0e308684815181106112aa576112aa612016565b60200260200101518685815181106112c4576112c4612016565b60200260200101516040518463ffffffff1660e01b81526004016112ea93929190611e10565b600060405180830381600087803b15801561130457600080fd5b505af1158015611318573d6000803e3d6000fd5b5050505080600101905061127f565b50836001600160a01b0316816001600160a01b0316866001600160a01b03167ff64537485d843fa8a1f4354cb9937954c64282de0828be9f096355006222186b868660405161122492919061202c565b61137f611535565b600061138a8661153d565b905060005b8281101561141c57866001600160a01b03166342842e0e33308787868181106113ba576113ba612016565b905060200201356040518463ffffffff1660e01b81526004016113df93929190611e10565b600060405180830381600087803b1580156113f957600080fd5b505af115801561140d573d6000803e3d6000fd5b5050505080600101905061138f565b5060fc5460fe546040516001600160a01b03928316926316f19831921690611474907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908b9033908c908c908c908c9060200161212b565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016114a0929190611de4565b600060405180830381600087803b1580156114ba57600080fd5b505af11580156114ce573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316876001600160a01b03167f0ca5d99e0f7e7651c00c76347baa68a333790cbc9fb039a10843d1bd61f578c588888888604051611525949392919061217f565b60405180910390a4505050505050565b6104fa6115d7565b6001600160a01b0380821660009081526101006020526040902054168061156a576115678261098e565b90505b6001600160a01b03811661158057611580611e85565b919050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60c95460ff16156116e257604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161163991906121a6565b6000604051808303818686fa925050503d8060008114611675576040519150601f19603f3d011682016040523d82523d6000602084013e61167a565b606091505b509150915081801561169f575060008180602001905181019061169d91906121c2565b115b6116df5760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b6044820152606401610566565b50505b60c954610100900460ff16156104fa57604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b01916113889161174991906121a6565b6000604051808303818686fa925050503d8060008114611785576040519150601f19603f3d011682016040523d82523d6000602084013e61178a565b606091505b50915091508180156117b05750808060200190518101906117ab91906121c2565b600114155b6104e45760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b6044820152606401610566565b6001600160a01b038116811461057857600080fd5b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b038111828210171561184057611840611802565b604052919050565b60006001600160401b0382111561186157611861611802565b50601f01601f191660200190565b6000806000806080858703121561188557600080fd5b8435611890816117ed565b935060208501356118a0816117ed565b92506040850135915060608501356001600160401b038111156118c257600080fd5b8501601f810187136118d357600080fd5b80356118e66118e182611848565b611818565b8181528860208385010111156118fb57600080fd5b8160208401602083013760006020838301015280935050505092959194509250565b6000806040838503121561193057600080fd5b823561193b816117ed565b946020939093013593505050565b60006020828403121561195b57600080fd5b8135611966816117ed565b9392505050565b8035801515811461158057600080fd5b60006020828403121561198f57600080fd5b6119668261196d565b600080600080600080600060e0888a0312156119b357600080fd5b87356119be816117ed565b965060208801356119ce816117ed565b955060408801356119de816117ed565b945060608801356119ee816117ed565b93506119fc6080890161196d565b9250611a0a60a0890161196d565b915060c0880135611a1a816117ed565b8091505092959891949750929550565b60008060008060608587031215611a4057600080fd5b843593506020850135611a52816117ed565b925060408501356001600160401b0380821115611a6e57600080fd5b818701915087601f830112611a8257600080fd5b813581811115611a9157600080fd5b886020828501011115611aa357600080fd5b95989497505060200194505050565b600080600060608486031215611ac757600080fd5b8335611ad2816117ed565b92506020840135611ae2816117ed565b929592945050506040919091013590565b60008060008060808587031215611b0957600080fd5b8435611b14816117ed565b93506020850135611b24816117ed565b92506040850135611b34816117ed565b91506060850135611b44816117ed565b939692955090935050565b60008083601f840112611b6157600080fd5b5081356001600160401b03811115611b7857600080fd5b6020830191508360208260051b8501011115611b9357600080fd5b9250929050565b600080600080600060608688031215611bb257600080fd5b8535611bbd816117ed565b945060208601356001600160401b0380821115611bd957600080fd5b611be589838a01611b4f565b90965094506040880135915080821115611bfe57600080fd5b50611c0b88828901611b4f565b969995985093965092949392505050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008085851115611c9e57600080fd5b83861115611cab57600080fd5b5050820193919092039150565b80356020831015611cd157600019602084900360031b1b165b92915050565b60005b83811015611cf2578181015183820152602001611cda565b50506000910152565b600060208284031215611d0d57600080fd5b81516001600160401b03811115611d2357600080fd5b8201601f81018413611d3457600080fd5b8051611d426118e182611848565b818152856020838501011115611d5757600080fd5b611d68826020830160208601611cd7565b95945050505050565b60008151808452611d89816020860160208601611cd7565b601f01601f19169290920160200192915050565b8481526001600160a01b0384166020820152608060408201819052600090611dc790830185611d71565b8281036060840152611dd98185611d71565b979650505050505050565b6001600160a01b0383168152604060208201819052600090611e0890830184611d71565b949350505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60008060008060808587031215611e4a57600080fd5b8435611e55816117ed565b93506020850135611e65816117ed565b92506040850135611e75816117ed565b9396929550929360600135925050565b634e487b7160e01b600052600160045260246000fd5b60006001600160401b03821115611eb457611eb4611802565b5060051b60200190565b600082601f830112611ecf57600080fd5b81356020611edf6118e183611e9b565b82815260059290921b84018101918181019086841115611efe57600080fd5b8286015b84811015611f195780358352918301918301611f02565b509695505050505050565b600080600080600060a08688031215611f3c57600080fd5b85359450602080870135611f4f816117ed565b94506040870135611f5f816117ed565b935060608701356001600160401b0380821115611f7b57600080fd5b818901915089601f830112611f8f57600080fd5b8135611f9d6118e182611e9b565b81815260059190911b8301840190848101908c831115611fbc57600080fd5b938501935b82851015611fe3578435611fd4816117ed565b82529385019390850190611fc1565b965050506080890135925080831115611ffb57600080fd5b505061200988828901611ebe565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b604080825283519082018190526000906020906060840190828701845b8281101561206e5781516001600160a01b031684529284019290840190600101612049565b5050508381038285015284518082528583019183019060005b818110156120a357835183529284019291840191600101612087565b5090979650505050505050565b8183526000602080850194508260005b858110156120ee5781356120d3816117ed565b6001600160a01b0316875295820195908201906001016120c0565b509495945050505050565b81835260006001600160fb1b0383111561211257600080fd5b8260051b80836020870137939093016020019392505050565b8781526001600160a01b0387811660208301528616604082015260a06060820181905260009061215e90830186886120b0565b82810360808401526121718185876120f9565b9a9950505050505050505050565b6040815260006121936040830186886120b0565b8281036020840152611dd98185876120f9565b600082516121b8818460208701611cd7565b9190910192915050565b6000602082840312156121d457600080fd5b505191905056fe526f6f744d696e7461626c654552433732315072656469636174653a20494e56a26469706673582212204063e808f9818c931a3cdbf2406d8b4f218ddd313bef99b78b2b8455373be0c864736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101e65760003560e01c8063b17680651161010a578063e0563ab1116100ad578063f2fde38b1161007c578063f2fde38b14610466578063f4a120f714610479578063f64512551461048c578063f8c8765e146104b3578063ff6f870c146104c657600080fd5b8063e0563ab114610426578063e30c39781461042f578063eeb4994514610440578063f213159c1461045357600080fd5b8063b17680651461033e578063b68ad1e414610365578063c1225a2014610378578063c5ac2b1c1461038b578063c5e4683a146103b2578063d41f1771146103c5578063d7c9e3ec146103ec578063d8dd17731461041357600080fd5b806351351d531161018d57806379ba50971161015c57806379ba5097146102e85780637efab4f5146102f05780638da5cb5b1461031a578063947287cf1461032b57806397e5230d1461033457600080fd5b806351351d53146102ad57806355b01e4d146102bb5780635ea5df79146102c9578063715018a6146102e057600080fd5b806305dc2e8f146101eb57806307b3e2521461021b578063150b7a02146102295780631bc114ba14610260578063284017f5146102735780633b878c221461027c57806347e7ef24146102855780634fdca69d1461029a575b600080fd5b60fd546101fe906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101fe6004600360981b0181565b61024761023736600461186f565b630a85bd0160e11b949350505050565b6040516001600160e01b03199091168152602001610212565b60fc546101fe906001600160a01b031681565b6101fe61202081565b6101fe61101081565b61029861029336600461191d565b6104d9565b005b60fe546101fe906001600160a01b031681565b6101fe6002600160a01b0381565b6101fe6004600160991b0181565b6102d261138881565b604051908152602001610212565b6102986104e8565b6102986104fc565b6101fe6102fe366004611949565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b03166101fe565b6102d261520881565b6102d2620249f081565b6102d27f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff546101fe906001600160a01b031681565b61029861038636600461197d565b61057b565b6102d27faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6102986103c036600461197d565b6105ca565b6102d27f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102d27f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b610298610421366004611998565b610611565b6101fe61203081565b6065546001600160a01b03166101fe565b61029861044e366004611a2a565b610732565b610298610461366004611ab2565b61090d565b610298610474366004611949565b61091d565b6101fe610487366004611949565b61098e565b6102d27f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b6102986104c1366004611af3565b610ce9565b6102986104d4366004611b9a565b610de2565b6104e4823383610e40565b5050565b6104f0610fc7565b6104fa6000611021565b565b60655433906001600160a01b0316811461056f5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b61057881611021565b50565b610583610fc7565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b6105d2610fc7565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b03146106385760405163973d02cb60e01b815260040161056690611c1c565b600054610100900460ff16158080156106585750600054600160ff909116105b806106725750303b158015610672575060005460ff166001145b61068e5760405162461bcd60e51b815260040161056690611c40565b6000805460ff1916600117905580156106b1576000805461ff0019166101001790555b6106bd8888888861103a565b60c9805461ffff191685151561ff00191617610100851515021790556106e282611021565b8015610728576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146107a55760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c654552433732315072656469636174653a204f4e4c60448201526f2cafa9aa20aa22afa922a1a2a4ab22a960811b6064820152608401610566565b60fe546001600160a01b0384811691161461081c5760405162461bcd60e51b815260206004820152603160248201527f526f6f744d696e7461626c654552433732315072656469636174653a204f4e4c604482015270595f4348494c445f50524544494341544560781b6064820152608401610566565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286961084b602060008486611c8e565b61085491611cb8565b036108735761086e6108698260208186611c8e565b611136565b610907565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed26108a2602060008486611c8e565b6108ab91611cb8565b036108ba5761086e8282611235565b60405162461bcd60e51b815260206004820152602e60248201526000805160206121dc83398151915260448201526d414c49445f5349474e415455524560901b6064820152608401610566565b50505050565b610918838383610e40565b505050565b610925610fc7565b606580546001600160a01b0383166001600160a01b031990911681179091556109566033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60006001600160a01b0382166109e75760405162461bcd60e51b815260206004820152602a60248201526000805160206121dc83398151915260448201526920a624a22faa27a5a2a760b11b6064820152608401610566565b6001600160a01b03828116600090815261010060205260409020541615610a645760405162461bcd60e51b815260206004820152602b60248201527f526f6f744d696e7461626c654552433732315072656469636174653a20414c5260448201526a1150511657d3505414115160aa1b6064820152608401610566565b60fe5460ff546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b0392831692600092610b1292911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b038581166000818152610100602052604080822080546001600160a01b03191686861617905560fc5481516306fdde0360e01b81529151959650909316936316f198319387937f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad938b9391926306fdde0392600480830193928290030181865afa158015610bab573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610bd39190810190611cfb565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610c11573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610c399190810190611cfb565b604051602001610c4c9493929190611d9d565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610c78929190611de4565b600060405180830381600087803b158015610c9257600080fd5b505af1158015610ca6573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a39392505050565b336002600160a01b0314610d105760405163973d02cb60e01b815260040161056690611c1c565b600054610100900460ff1615808015610d305750600054600160ff909116105b80610d4a5750303b158015610d4a575060005460ff166001145b610d665760405162461bcd60e51b815260040161056690611c40565b6000805460ff191660011790558015610d89576000805461ff0019166101001790555b610d958585858561103a565b8015610ddb576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b828114610e335760405162461bcd60e51b815260206004820152602b60248201526000805160206121dc83398151915260448201526a082989288be988a9c8ea8960ab1b6064820152608401610566565b610ddb8585858585611377565b610e48611535565b6000610e538461153d565b604051632142170760e11b81529091506001600160a01b038516906342842e0e90610e8690339030908790600401611e10565b600060405180830381600087803b158015610ea057600080fd5b505af1158015610eb4573d6000803e3d6000fd5b505060fc5460fe54604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038a811682840152336060830152898116608083015260a08083018a90528351808403909101815260c08301938490526316f1983160e01b90935293841695506316f198319450610f44939092169160c401611de4565b600060405180830381600087803b158015610f5e57600080fd5b505af1158015610f72573d6000803e3d6000fd5b505060408051338152602081018690526001600160a01b03808816945085811693508816917fe3abb9ec195b50242582cc6cb7abc990ac26439b9ed7635ed72f7bd720e7477a910160405180910390a4610907565b6033546001600160a01b031633146104fa5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610566565b606580546001600160a01b031916905561057881611585565b6001600160a01b0384161580159061105a57506001600160a01b03831615155b801561106e57506001600160a01b03821615155b801561108257506001600160a01b03811615155b6110e65760405162461bcd60e51b815260206004820152602f60248201527f526f6f744d696e7461626c654552433732315072656469636174653a2042414460448201526e2fa4a724aa24a0a624ad20aa24a7a760891b6064820152608401610566565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b600080808061114785870187611e34565b6001600160a01b03808516600090815261010060205260409020549498509296509094509250168061117b5761117b611e85565b604051632142170760e11b81526001600160a01b038616906342842e0e906111ab90309087908790600401611e10565b600060405180830381600087803b1580156111c557600080fd5b505af11580156111d9573d6000803e3d6000fd5b5050604080516001600160a01b03888116825260208201879052808816945085811693508916917f80afffd84aa825dd9b3c1ba262eb55eef58f78616634437a367a263f3c48fe3d91015b60405180910390a450505050505050565b600080808061124685870187611f24565b6001600160a01b038085166000908152610100602052604090205494995092975090955093501690508061127c5761127c611e85565b60005b825181101561132757856001600160a01b03166342842e0e308684815181106112aa576112aa612016565b60200260200101518685815181106112c4576112c4612016565b60200260200101516040518463ffffffff1660e01b81526004016112ea93929190611e10565b600060405180830381600087803b15801561130457600080fd5b505af1158015611318573d6000803e3d6000fd5b5050505080600101905061127f565b50836001600160a01b0316816001600160a01b0316866001600160a01b03167ff64537485d843fa8a1f4354cb9937954c64282de0828be9f096355006222186b868660405161122492919061202c565b61137f611535565b600061138a8661153d565b905060005b8281101561141c57866001600160a01b03166342842e0e33308787868181106113ba576113ba612016565b905060200201356040518463ffffffff1660e01b81526004016113df93929190611e10565b600060405180830381600087803b1580156113f957600080fd5b505af115801561140d573d6000803e3d6000fd5b5050505080600101905061138f565b5060fc5460fe546040516001600160a01b03928316926316f19831921690611474907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908b9033908c908c908c908c9060200161212b565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016114a0929190611de4565b600060405180830381600087803b1580156114ba57600080fd5b505af11580156114ce573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316876001600160a01b03167f0ca5d99e0f7e7651c00c76347baa68a333790cbc9fb039a10843d1bd61f578c588888888604051611525949392919061217f565b60405180910390a4505050505050565b6104fa6115d7565b6001600160a01b0380821660009081526101006020526040902054168061156a576115678261098e565b90505b6001600160a01b03811661158057611580611e85565b919050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60c95460ff16156116e257604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161163991906121a6565b6000604051808303818686fa925050503d8060008114611675576040519150601f19603f3d011682016040523d82523d6000602084013e61167a565b606091505b509150915081801561169f575060008180602001905181019061169d91906121c2565b115b6116df5760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b6044820152606401610566565b50505b60c954610100900460ff16156104fa57604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b01916113889161174991906121a6565b6000604051808303818686fa925050503d8060008114611785576040519150601f19603f3d011682016040523d82523d6000602084013e61178a565b606091505b50915091508180156117b05750808060200190518101906117ab91906121c2565b600114155b6104e45760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b6044820152606401610566565b6001600160a01b038116811461057857600080fd5b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b038111828210171561184057611840611802565b604052919050565b60006001600160401b0382111561186157611861611802565b50601f01601f191660200190565b6000806000806080858703121561188557600080fd5b8435611890816117ed565b935060208501356118a0816117ed565b92506040850135915060608501356001600160401b038111156118c257600080fd5b8501601f810187136118d357600080fd5b80356118e66118e182611848565b611818565b8181528860208385010111156118fb57600080fd5b8160208401602083013760006020838301015280935050505092959194509250565b6000806040838503121561193057600080fd5b823561193b816117ed565b946020939093013593505050565b60006020828403121561195b57600080fd5b8135611966816117ed565b9392505050565b8035801515811461158057600080fd5b60006020828403121561198f57600080fd5b6119668261196d565b600080600080600080600060e0888a0312156119b357600080fd5b87356119be816117ed565b965060208801356119ce816117ed565b955060408801356119de816117ed565b945060608801356119ee816117ed565b93506119fc6080890161196d565b9250611a0a60a0890161196d565b915060c0880135611a1a816117ed565b8091505092959891949750929550565b60008060008060608587031215611a4057600080fd5b843593506020850135611a52816117ed565b925060408501356001600160401b0380821115611a6e57600080fd5b818701915087601f830112611a8257600080fd5b813581811115611a9157600080fd5b886020828501011115611aa357600080fd5b95989497505060200194505050565b600080600060608486031215611ac757600080fd5b8335611ad2816117ed565b92506020840135611ae2816117ed565b929592945050506040919091013590565b60008060008060808587031215611b0957600080fd5b8435611b14816117ed565b93506020850135611b24816117ed565b92506040850135611b34816117ed565b91506060850135611b44816117ed565b939692955090935050565b60008083601f840112611b6157600080fd5b5081356001600160401b03811115611b7857600080fd5b6020830191508360208260051b8501011115611b9357600080fd5b9250929050565b600080600080600060608688031215611bb257600080fd5b8535611bbd816117ed565b945060208601356001600160401b0380821115611bd957600080fd5b611be589838a01611b4f565b90965094506040880135915080821115611bfe57600080fd5b50611c0b88828901611b4f565b969995985093965092949392505050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008085851115611c9e57600080fd5b83861115611cab57600080fd5b5050820193919092039150565b80356020831015611cd157600019602084900360031b1b165b92915050565b60005b83811015611cf2578181015183820152602001611cda565b50506000910152565b600060208284031215611d0d57600080fd5b81516001600160401b03811115611d2357600080fd5b8201601f81018413611d3457600080fd5b8051611d426118e182611848565b818152856020838501011115611d5757600080fd5b611d68826020830160208601611cd7565b95945050505050565b60008151808452611d89816020860160208601611cd7565b601f01601f19169290920160200192915050565b8481526001600160a01b0384166020820152608060408201819052600090611dc790830185611d71565b8281036060840152611dd98185611d71565b979650505050505050565b6001600160a01b0383168152604060208201819052600090611e0890830184611d71565b949350505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60008060008060808587031215611e4a57600080fd5b8435611e55816117ed565b93506020850135611e65816117ed565b92506040850135611e75816117ed565b9396929550929360600135925050565b634e487b7160e01b600052600160045260246000fd5b60006001600160401b03821115611eb457611eb4611802565b5060051b60200190565b600082601f830112611ecf57600080fd5b81356020611edf6118e183611e9b565b82815260059290921b84018101918181019086841115611efe57600080fd5b8286015b84811015611f195780358352918301918301611f02565b509695505050505050565b600080600080600060a08688031215611f3c57600080fd5b85359450602080870135611f4f816117ed565b94506040870135611f5f816117ed565b935060608701356001600160401b0380821115611f7b57600080fd5b818901915089601f830112611f8f57600080fd5b8135611f9d6118e182611e9b565b81815260059190911b8301840190848101908c831115611fbc57600080fd5b938501935b82851015611fe3578435611fd4816117ed565b82529385019390850190611fc1565b965050506080890135925080831115611ffb57600080fd5b505061200988828901611ebe565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b604080825283519082018190526000906020906060840190828701845b8281101561206e5781516001600160a01b031684529284019290840190600101612049565b5050508381038285015284518082528583019183019060005b818110156120a357835183529284019291840191600101612087565b5090979650505050505050565b8183526000602080850194508260005b858110156120ee5781356120d3816117ed565b6001600160a01b0316875295820195908201906001016120c0565b509495945050505050565b81835260006001600160fb1b0383111561211257600080fd5b8260051b80836020870137939093016020019392505050565b8781526001600160a01b0387811660208301528616604082015260a06060820181905260009061215e90830186886120b0565b82810360808401526121718185876120f9565b9a9950505050505050505050565b6040815260006121936040830186886120b0565b8281036020840152611dd98185876120f9565b600082516121b8818460208701611cd7565b9190910192915050565b6000602082840312156121d457600080fd5b505191905056fe526f6f744d696e7461626c654552433732315072656469636174653a20494e56a26469706673582212204063e808f9818c931a3cdbf2406d8b4f218ddd313bef99b78b2b8455373be0c864736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildERC1155Artifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildERC1155","sourceName":"contracts/child/ChildERC1155.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"TransferSingle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"value","type":"string"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"URI","type":"event"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"accounts","type":"address[]"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"burnBatch","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rootToken_","type":"address"},{"internalType":"string","name":"uri_","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"}],"name":"invalidateNext","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"accounts","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"mintBatch","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rootToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeBatchTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"uri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50612b8b806100206000396000f3fe608060405234801561001057600080fd5b50600436106101155760003560e01c806357128683116100a2578063e619870511610071578063e619870514610279578063e985e9c51461028b578063f242432a146102c7578063f399e22e146102da578063f5298aca146102ed57600080fd5b8063571286831461022d5780636b20c454146102405780639b77ef1114610253578063a22cb4651461026657600080fd5b8063156e29f6116100e9578063156e29f6146101965780631f2d0065146101a95780632d0335ab146101cf5780632eb2c2d6146101f85780634e1273f41461020d57600080fd5b8062fdd58e1461011a57806301ffc9a7146101405780630c53c51c146101635780630e89341c14610183575b600080fd5b61012d610128366004611cd5565b610300565b6040519081526020015b60405180910390f35b61015361014e366004611d15565b61039b565b6040519015158152602001610137565b610176610171366004611d7a565b6103eb565b6040516101379190611e49565b610176610191366004611e5c565b6106c9565b6101536101a4366004611e75565b61075d565b610103546001600160a01b03165b6040516001600160a01b039091168152602001610137565b61012d6101dd366004611ea8565b6001600160a01b031660009081526038602052604090205490565b61020b61020636600461200c565b6107b0565b005b61022061021b3660046120b5565b61080e565b60405161013791906121ba565b61015361023b366004612211565b610937565b61015361024e3660046122aa565b610a53565b61020b610261366004611e5c565b610afb565b61020b61027436600461232a565b610b22565b610102546001600160a01b03166101b7565b610153610299366004612366565b6001600160a01b03918216600090815260d16020908152604080832093909416825291909152205460ff1690565b61020b6102d5366004612399565b610b38565b61020b6102e83660046123fd565b610b8f565b6101536102fb366004611e75565b610da5565b60006001600160a01b0383166103705760405162461bcd60e51b815260206004820152602a60248201527f455243313135353a2061646472657373207a65726f206973206e6f742061207660448201526930b634b21037bbb732b960b11b60648201526084015b60405180910390fd5b50600081815260d0602090815260408083206001600160a01b03861684529091529020545b92915050565b60006001600160e01b03198216636cdb3d1360e11b14806103cc57506001600160e01b031982166303a24d0760e21b145b8061039557506301ffc9a760e01b6001600160e01b0319831614610395565b6060600061042e87878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610dde92505050565b90506001600160e01b03196000358116908216036104b45760405162461bcd60e51b815260206004820152603d60248201527f66756e6374696f6e5369676e61747572652063616e206e6f74206265206f662060448201527f657865637574654d6574615472616e73616374696f6e206d6574686f640000006064820152608401610367565b604080516060810182526001600160a01b038a16600081815260386020908152848220548452808401929092528351601f8b0183900483028101830185528a815290938301918b908b9081908401838280828437600092019190915250505091525090506105258982888888610df9565b61057b5760405162461bcd60e51b815260206004820152602160248201527f5369676e657220616e64207369676e617475726520646f206e6f74206d6174636044820152600d60fb1b6064820152608401610367565b603860008a6001600160a01b03166001600160a01b031681526020019081526020016000206000815460010191905081905550600080306001600160a01b03168a8a8d6040516020016105d09392919061244f565b60408051601f19818403018152908290526105ea91612475565b6000604051808303816000865af19150503d8060008114610627576040519150601f19603f3d011682016040523d82523d6000602084013e61062c565b606091505b50915091508161067e5760405162461bcd60e51b815260206004820152601c60248201527f46756e6374696f6e2063616c6c206e6f74207375636365737366756c000000006044820152606401610367565b7f5845892132946850460bff5a0083f71031bc5bf9aadcd40f1de79423eac9b10b8b338c8c6040516106b39493929190612491565b60405180910390a19a9950505050505050505050565b606060d280546106d8906124dd565b80601f0160208091040260200160405190810160405280929190818152602001828054610704906124dd565b80156107515780601f1061072657610100808354040283529160200191610751565b820191906000526020600020905b81548152906001019060200180831161073457829003601f168201915b50505050509050919050565b610102546000906001600160a01b0316331461078b5760405162461bcd60e51b815260040161036790612517565b6107a684848460405180602001604052806000815250610ed5565b5060019392505050565b6107b8610fe4565b6001600160a01b0316856001600160a01b031614806107de57506107de85610299610fe4565b6107fa5760405162461bcd60e51b81526004016103679061255c565b6108078585858585610ff3565b5050505050565b606081518351146108735760405162461bcd60e51b815260206004820152602960248201527f455243313135353a206163636f756e747320616e6420696473206c656e677468604482015268040dad2e6dac2e8c6d60bb1b6064820152608401610367565b600083516001600160401b0381111561088e5761088e611ec3565b6040519080825280602002602001820160405280156108b7578160200160208202803683370190505b50905060005b845181101561092f576109028582815181106108db576108db6125aa565b60200260200101518583815181106108f5576108f56125aa565b6020026020010151610300565b828281518110610914576109146125aa565b6020908102919091010152610928816125d6565b90506108bd565b509392505050565b610102546000906001600160a01b031633146109655760405162461bcd60e51b815260040161036790612517565b85848114801561097457508083145b6109c05760405162461bcd60e51b815260206004820181905260248201527f4368696c64455243313135353a206172726179206c656e206d69736d617463686044820152606401610367565b60005b81811015610a4457610a3c8989838181106109e0576109e06125aa565b90506020020160208101906109f59190611ea8565b888884818110610a0757610a076125aa565b90506020020135878785818110610a2057610a206125aa565b9050602002013560405180602001604052806000815250610ed5565b6001016109c3565b50600198975050505050505050565b610102546000906001600160a01b03163314610a815760405162461bcd60e51b815260040161036790612517565b610aef8686868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020808a0282810182019093528982529093508992508891829185019084908082843760009201919091525061119d92505050565b50600195945050505050565b3360009081526038602052604081208054839290610b1a9084906125ef565b909155505050565b610b34610b2d610fe4565b8383611334565b5050565b610b40610fe4565b6001600160a01b0316856001600160a01b03161480610b665750610b6685610299610fe4565b610b825760405162461bcd60e51b81526004016103679061255c565b6108078585858585611414565b606b54610100900460ff1615808015610baf5750606b54600160ff909116105b80610bc95750303b158015610bc95750606b5460ff166001145b610c2c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610367565b606b805460ff191660011790558015610c4f57606b805461ff0019166101001790555b6001600160a01b038416610ca55760405162461bcd60e51b815260206004820181905260248201527f4368696c64455243313135353a204241445f494e495449414c495a4154494f4e6044820152606401610367565b61010380546001600160a01b0386166001600160a01b031991821617909155610102805490911633179055604080516020601f8501819004810282018101909252838152610d0d91859085908190840183828082843760009201919091525061153b92505050565b610d59610d22856001600160a01b031661156e565b604051602001610d329190612602565b60408051601f1981840301815282820190915260018252603160f81b602083015290611584565b8015610d9f57606b805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b610102546000906001600160a01b03163314610dd35760405162461bcd60e51b815260040161036790612517565b6107a68484846115f0565b60008151600003610df157506000919050565b506020015190565b6000806001610e0f610e0a886116f0565b61176d565b6040805160008152602081018083529290925260ff861690820152606081018790526080810186905260a0016020604051602081039080840390855afa158015610e5d573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610eb45760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964207369676e617475726560781b6044820152606401610367565b866001600160a01b0316816001600160a01b03161491505095945050505050565b6001600160a01b038416610f355760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b6064820152608401610367565b6000610f3f610fe4565b90506000610f4c8561179a565b90506000610f598561179a565b9050600086815260d0602090815260408083206001600160a01b038b16845290915281208054879290610f8d9084906125ef565b909155505060408051878152602081018790526001600160a01b03808a169260009291871691600080516020612af3833981519152910160405180910390a4610fdb836000898989896117e5565b50505050505050565b6000610fee611940565b905090565b81518351146110145760405162461bcd60e51b815260040161036790612637565b6001600160a01b03841661103a5760405162461bcd60e51b81526004016103679061267f565b6000611044610fe4565b905060005b845181101561112f576000858281518110611066576110666125aa565b602002602001015190506000858381518110611084576110846125aa565b602090810291909101810151600084815260d0835260408082206001600160a01b038e1683529093529190912054909150818110156110d55760405162461bcd60e51b8152600401610367906126c4565b600083815260d0602090815260408083206001600160a01b038e8116855292528083208585039055908b168252812080548492906111149084906125ef565b9250508190555050505080611128906125d6565b9050611049565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb878760405161117f92919061270e565b60405180910390a461119581878787878761199c565b505050505050565b6001600160a01b0383166111c35760405162461bcd60e51b81526004016103679061273c565b80518251146111e45760405162461bcd60e51b815260040161036790612637565b60006111ee610fe4565b604080516020810190915260009052905060005b83518110156112c757600084828151811061121f5761121f6125aa565b60200260200101519050600084838151811061123d5761123d6125aa565b602090810291909101810151600084815260d0835260408082206001600160a01b038c16835290935291909120549091508181101561128e5760405162461bcd60e51b81526004016103679061277f565b600092835260d0602090815260408085206001600160a01b038b16865290915290922091039055806112bf816125d6565b915050611202565b5060006001600160a01b0316846001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb868660405161131892919061270e565b60405180910390a4604080516020810190915260009052610d9f565b816001600160a01b0316836001600160a01b0316036113a75760405162461bcd60e51b815260206004820152602960248201527f455243313135353a2073657474696e6720617070726f76616c20737461747573604482015268103337b91039b2b63360b91b6064820152608401610367565b6001600160a01b03838116600081815260d16020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6001600160a01b03841661143a5760405162461bcd60e51b81526004016103679061267f565b6000611444610fe4565b905060006114518561179a565b9050600061145e8561179a565b9050600086815260d0602090815260408083206001600160a01b038c168452909152902054858110156114a35760405162461bcd60e51b8152600401610367906126c4565b600087815260d0602090815260408083206001600160a01b038d8116855292528083208985039055908a168252812080548892906114e29084906125ef565b909155505060408051888152602081018890526001600160a01b03808b16928c82169291881691600080516020612af3833981519152910160405180910390a4611530848a8a8a8a8a6117e5565b505050505050505050565b606b54610100900460ff166115625760405162461bcd60e51b8152600401610367906127c3565b61156b81611a57565b50565b60606103956001600160a01b0383166014611a87565b815160208084019190912082519183019190912060038290556004819055466001557f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6115d2818484611c29565b600055600280546001600160a01b0319163017905560055550505050565b6001600160a01b0383166116165760405162461bcd60e51b81526004016103679061273c565b6000611620610fe4565b9050600061162d8461179a565b9050600061163a8461179a565b604080516020808201835260009182905288825260d081528282206001600160a01b038b16835290522054909150848110156116885760405162461bcd60e51b81526004016103679061277f565b600086815260d0602090815260408083206001600160a01b038b81168086529184528285208a8703905582518b81529384018a9052909290881691600080516020612af3833981519152910160405180910390a4604080516020810190915260009052610fdb565b6000604051806080016040528060438152602001612b136043913980516020918201208351848301516040808701518051908601209051611750950193845260208401929092526001600160a01b03166040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b600061039561177a611c72565b8360405161190160f01b8152600281019290925260228201526042902090565b604080516001808252818301909252606091600091906020808301908036833701905050905082816000815181106117d4576117d46125aa565b602090810291909101015292915050565b6001600160a01b0384163b156111955760405163f23a6e6160e01b81526001600160a01b0385169063f23a6e6190611829908990899088908890889060040161280e565b6020604051808303816000875af1925050508015611864575060408051601f3d908101601f1916820190925261186191810190612853565b60015b61191057611870612870565b806308c379a0036118a9575061188461288b565b8061188f57506118ab565b8060405162461bcd60e51b81526004016103679190611e49565b505b60405162461bcd60e51b815260206004820152603460248201527f455243313135353a207472616e7366657220746f206e6f6e2d455243313135356044820152732932b1b2b4bb32b91034b6b83632b6b2b73a32b960611b6064820152608401610367565b6001600160e01b0319811663f23a6e6160e01b14610fdb5760405162461bcd60e51b815260040161036790612914565b600030330361199657600080368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050503601516001600160a01b031691506119999050565b50335b90565b6001600160a01b0384163b156111955760405163bc197c8160e01b81526001600160a01b0385169063bc197c81906119e0908990899088908890889060040161295c565b6020604051808303816000875af1925050508015611a1b575060408051601f3d908101601f19168201909252611a1891810190612853565b60015b611a2757611870612870565b6001600160e01b0319811663bc197c8160e01b14610fdb5760405162461bcd60e51b815260040161036790612914565b606b54610100900460ff16611a7e5760405162461bcd60e51b8152600401610367906127c3565b61156b81611cad565b60606000611a968360026129ba565b611aa19060026125ef565b6001600160401b03811115611ab857611ab8611ec3565b6040519080825280601f01601f191660200182016040528015611ae2576020820181803683370190505b509050600360fc1b81600081518110611afd57611afd6125aa565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110611b2c57611b2c6125aa565b60200101906001600160f81b031916908160001a9053506000611b508460026129ba565b611b5b9060016125ef565b90505b6001811115611bd3576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110611b8f57611b8f6125aa565b1a60f81b828281518110611ba557611ba56125aa565b60200101906001600160f81b031916908160001a90535060049490941c93611bcc816129d1565b9050611b5e565b508315611c225760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610367565b9392505050565b6040805160208101859052908101839052606081018290524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090509392505050565b6002546000906001600160a01b031630148015611c90575060015446145b15611c9c575060005490565b610fee600554600354600454611c29565b60d2610b348282612a33565b80356001600160a01b0381168114611cd057600080fd5b919050565b60008060408385031215611ce857600080fd5b611cf183611cb9565b946020939093013593505050565b6001600160e01b03198116811461156b57600080fd5b600060208284031215611d2757600080fd5b8135611c2281611cff565b60008083601f840112611d4457600080fd5b5081356001600160401b03811115611d5b57600080fd5b602083019150836020828501011115611d7357600080fd5b9250929050565b60008060008060008060a08789031215611d9357600080fd5b611d9c87611cb9565b955060208701356001600160401b03811115611db757600080fd5b611dc389828a01611d32565b9096509450506040870135925060608701359150608087013560ff81168114611deb57600080fd5b809150509295509295509295565b60005b83811015611e14578181015183820152602001611dfc565b50506000910152565b60008151808452611e35816020860160208601611df9565b601f01601f19169290920160200192915050565b602081526000611c226020830184611e1d565b600060208284031215611e6e57600080fd5b5035919050565b600080600060608486031215611e8a57600080fd5b611e9384611cb9565b95602085013595506040909401359392505050565b600060208284031215611eba57600080fd5b611c2282611cb9565b634e487b7160e01b600052604160045260246000fd5b601f8201601f191681016001600160401b0381118282101715611efe57611efe611ec3565b6040525050565b60006001600160401b03821115611f1e57611f1e611ec3565b5060051b60200190565b600082601f830112611f3957600080fd5b81356020611f4682611f05565b604051611f538282611ed9565b83815260059390931b8501820192828101915086841115611f7357600080fd5b8286015b84811015611f8e5780358352918301918301611f77565b509695505050505050565b600082601f830112611faa57600080fd5b81356001600160401b03811115611fc357611fc3611ec3565b604051611fda601f8301601f191660200182611ed9565b818152846020838601011115611fef57600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a0868803121561202457600080fd5b61202d86611cb9565b945061203b60208701611cb9565b935060408601356001600160401b038082111561205757600080fd5b61206389838a01611f28565b9450606088013591508082111561207957600080fd5b61208589838a01611f28565b9350608088013591508082111561209b57600080fd5b506120a888828901611f99565b9150509295509295909350565b600080604083850312156120c857600080fd5b82356001600160401b03808211156120df57600080fd5b818501915085601f8301126120f357600080fd5b8135602061210082611f05565b60405161210d8282611ed9565b83815260059390931b850182019282810191508984111561212d57600080fd5b948201945b838610156121525761214386611cb9565b82529482019490820190612132565b9650508601359250508082111561216857600080fd5b5061217585828601611f28565b9150509250929050565b600081518084526020808501945080840160005b838110156121af57815187529582019590820190600101612193565b509495945050505050565b602081526000611c22602083018461217f565b60008083601f8401126121df57600080fd5b5081356001600160401b038111156121f657600080fd5b6020830191508360208260051b8501011115611d7357600080fd5b6000806000806000806060878903121561222a57600080fd5b86356001600160401b038082111561224157600080fd5b61224d8a838b016121cd565b9098509650602089013591508082111561226657600080fd5b6122728a838b016121cd565b9096509450604089013591508082111561228b57600080fd5b5061229889828a016121cd565b979a9699509497509295939492505050565b6000806000806000606086880312156122c257600080fd5b6122cb86611cb9565b945060208601356001600160401b03808211156122e757600080fd5b6122f389838a016121cd565b9096509450604088013591508082111561230c57600080fd5b50612319888289016121cd565b969995985093965092949392505050565b6000806040838503121561233d57600080fd5b61234683611cb9565b91506020830135801515811461235b57600080fd5b809150509250929050565b6000806040838503121561237957600080fd5b61238283611cb9565b915061239060208401611cb9565b90509250929050565b600080600080600060a086880312156123b157600080fd5b6123ba86611cb9565b94506123c860208701611cb9565b9350604086013592506060860135915060808601356001600160401b038111156123f157600080fd5b6120a888828901611f99565b60008060006040848603121561241257600080fd5b61241b84611cb9565b925060208401356001600160401b0381111561243657600080fd5b61244286828701611d32565b9497909650939450505050565b8284823760609190911b6bffffffffffffffffffffffff19169101908152601401919050565b60008251612487818460208701611df9565b9190910192915050565b6001600160a01b0385811682528416602082015260606040820181905281018290526000828460808401376000608084840101526080601f19601f850116830101905095945050505050565b600181811c908216806124f157607f821691505b60208210810361251157634e487b7160e01b600052602260045260246000fd5b50919050565b60208082526025908201527f4368696c64455243313135353a204f6e6c79207072656469636174652063616e6040820152640818d85b1b60da1b606082015260800190565b6020808252602e908201527f455243313135353a2063616c6c6572206973206e6f7420746f6b656e206f776e60408201526d195c881bdc88185c1c1c9bdd995960921b606082015260800190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600182016125e8576125e86125c0565b5060010190565b80820180821115610395576103956125c0565b6c4368696c64455243313135352d60981b81526000825161262a81600d850160208701611df9565b91909101600d0192915050565b60208082526028908201527f455243313135353a2069647320616e6420616d6f756e7473206c656e677468206040820152670dad2e6dac2e8c6d60c31b606082015260800190565b60208082526025908201527f455243313135353a207472616e7366657220746f20746865207a65726f206164604082015264647265737360d81b606082015260800190565b6020808252602a908201527f455243313135353a20696e73756666696369656e742062616c616e636520666f60408201526939103a3930b739b332b960b11b606082015260800190565b604081526000612721604083018561217f565b8281036020840152612733818561217f565b95945050505050565b60208082526023908201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260408201526265737360e81b606082015260800190565b60208082526024908201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604082015263616e636560e01b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b03868116825285166020820152604081018490526060810183905260a06080820181905260009061284890830184611e1d565b979650505050505050565b60006020828403121561286557600080fd5b8151611c2281611cff565b600060033d11156119995760046000803e5060005160e01c90565b600060443d10156128995790565b6040516003193d81016004833e81513d6001600160401b0381602484011181841117156128c857505050505090565b82850191508151818111156128e05750505050505090565b843d87010160208285010111156128fa5750505050505090565b61290960208286010187611ed9565b509095945050505050565b60208082526028908201527f455243313135353a204552433131353552656365697665722072656a656374656040820152676420746f6b656e7360c01b606082015260800190565b6001600160a01b0386811682528516602082015260a0604082018190526000906129889083018661217f565b828103606084015261299a818661217f565b905082810360808401526129ae8185611e1d565b98975050505050505050565b8082028115828204841417610395576103956125c0565b6000816129e0576129e06125c0565b506000190190565b601f821115612a2e57600081815260208120601f850160051c81016020861015612a0f5750805b601f850160051c820191505b8181101561119557828155600101612a1b565b505050565b81516001600160401b03811115612a4c57612a4c611ec3565b612a6081612a5a84546124dd565b846129e8565b602080601f831160018114612a955760008415612a7d5750858301515b600019600386901b1c1916600185901b178555611195565b600085815260208120601f198616915b82811015612ac457888601518255948401946001909101908401612aa5565b5085821015612ae25787850151600019600388901b60f8161c191681555b5050505050600190811b0190555056fec3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f624d6574615472616e73616374696f6e2875696e74323536206e6f6e63652c616464726573732066726f6d2c62797465732066756e6374696f6e5369676e617475726529a2646970667358221220d48b6656ae2af8f39607add0ff4fc12029027806b1a7c756a64f97827803920c64736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101155760003560e01c806357128683116100a2578063e619870511610071578063e619870514610279578063e985e9c51461028b578063f242432a146102c7578063f399e22e146102da578063f5298aca146102ed57600080fd5b8063571286831461022d5780636b20c454146102405780639b77ef1114610253578063a22cb4651461026657600080fd5b8063156e29f6116100e9578063156e29f6146101965780631f2d0065146101a95780632d0335ab146101cf5780632eb2c2d6146101f85780634e1273f41461020d57600080fd5b8062fdd58e1461011a57806301ffc9a7146101405780630c53c51c146101635780630e89341c14610183575b600080fd5b61012d610128366004611cd5565b610300565b6040519081526020015b60405180910390f35b61015361014e366004611d15565b61039b565b6040519015158152602001610137565b610176610171366004611d7a565b6103eb565b6040516101379190611e49565b610176610191366004611e5c565b6106c9565b6101536101a4366004611e75565b61075d565b610103546001600160a01b03165b6040516001600160a01b039091168152602001610137565b61012d6101dd366004611ea8565b6001600160a01b031660009081526038602052604090205490565b61020b61020636600461200c565b6107b0565b005b61022061021b3660046120b5565b61080e565b60405161013791906121ba565b61015361023b366004612211565b610937565b61015361024e3660046122aa565b610a53565b61020b610261366004611e5c565b610afb565b61020b61027436600461232a565b610b22565b610102546001600160a01b03166101b7565b610153610299366004612366565b6001600160a01b03918216600090815260d16020908152604080832093909416825291909152205460ff1690565b61020b6102d5366004612399565b610b38565b61020b6102e83660046123fd565b610b8f565b6101536102fb366004611e75565b610da5565b60006001600160a01b0383166103705760405162461bcd60e51b815260206004820152602a60248201527f455243313135353a2061646472657373207a65726f206973206e6f742061207660448201526930b634b21037bbb732b960b11b60648201526084015b60405180910390fd5b50600081815260d0602090815260408083206001600160a01b03861684529091529020545b92915050565b60006001600160e01b03198216636cdb3d1360e11b14806103cc57506001600160e01b031982166303a24d0760e21b145b8061039557506301ffc9a760e01b6001600160e01b0319831614610395565b6060600061042e87878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610dde92505050565b90506001600160e01b03196000358116908216036104b45760405162461bcd60e51b815260206004820152603d60248201527f66756e6374696f6e5369676e61747572652063616e206e6f74206265206f662060448201527f657865637574654d6574615472616e73616374696f6e206d6574686f640000006064820152608401610367565b604080516060810182526001600160a01b038a16600081815260386020908152848220548452808401929092528351601f8b0183900483028101830185528a815290938301918b908b9081908401838280828437600092019190915250505091525090506105258982888888610df9565b61057b5760405162461bcd60e51b815260206004820152602160248201527f5369676e657220616e64207369676e617475726520646f206e6f74206d6174636044820152600d60fb1b6064820152608401610367565b603860008a6001600160a01b03166001600160a01b031681526020019081526020016000206000815460010191905081905550600080306001600160a01b03168a8a8d6040516020016105d09392919061244f565b60408051601f19818403018152908290526105ea91612475565b6000604051808303816000865af19150503d8060008114610627576040519150601f19603f3d011682016040523d82523d6000602084013e61062c565b606091505b50915091508161067e5760405162461bcd60e51b815260206004820152601c60248201527f46756e6374696f6e2063616c6c206e6f74207375636365737366756c000000006044820152606401610367565b7f5845892132946850460bff5a0083f71031bc5bf9aadcd40f1de79423eac9b10b8b338c8c6040516106b39493929190612491565b60405180910390a19a9950505050505050505050565b606060d280546106d8906124dd565b80601f0160208091040260200160405190810160405280929190818152602001828054610704906124dd565b80156107515780601f1061072657610100808354040283529160200191610751565b820191906000526020600020905b81548152906001019060200180831161073457829003601f168201915b50505050509050919050565b610102546000906001600160a01b0316331461078b5760405162461bcd60e51b815260040161036790612517565b6107a684848460405180602001604052806000815250610ed5565b5060019392505050565b6107b8610fe4565b6001600160a01b0316856001600160a01b031614806107de57506107de85610299610fe4565b6107fa5760405162461bcd60e51b81526004016103679061255c565b6108078585858585610ff3565b5050505050565b606081518351146108735760405162461bcd60e51b815260206004820152602960248201527f455243313135353a206163636f756e747320616e6420696473206c656e677468604482015268040dad2e6dac2e8c6d60bb1b6064820152608401610367565b600083516001600160401b0381111561088e5761088e611ec3565b6040519080825280602002602001820160405280156108b7578160200160208202803683370190505b50905060005b845181101561092f576109028582815181106108db576108db6125aa565b60200260200101518583815181106108f5576108f56125aa565b6020026020010151610300565b828281518110610914576109146125aa565b6020908102919091010152610928816125d6565b90506108bd565b509392505050565b610102546000906001600160a01b031633146109655760405162461bcd60e51b815260040161036790612517565b85848114801561097457508083145b6109c05760405162461bcd60e51b815260206004820181905260248201527f4368696c64455243313135353a206172726179206c656e206d69736d617463686044820152606401610367565b60005b81811015610a4457610a3c8989838181106109e0576109e06125aa565b90506020020160208101906109f59190611ea8565b888884818110610a0757610a076125aa565b90506020020135878785818110610a2057610a206125aa565b9050602002013560405180602001604052806000815250610ed5565b6001016109c3565b50600198975050505050505050565b610102546000906001600160a01b03163314610a815760405162461bcd60e51b815260040161036790612517565b610aef8686868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020808a0282810182019093528982529093508992508891829185019084908082843760009201919091525061119d92505050565b50600195945050505050565b3360009081526038602052604081208054839290610b1a9084906125ef565b909155505050565b610b34610b2d610fe4565b8383611334565b5050565b610b40610fe4565b6001600160a01b0316856001600160a01b03161480610b665750610b6685610299610fe4565b610b825760405162461bcd60e51b81526004016103679061255c565b6108078585858585611414565b606b54610100900460ff1615808015610baf5750606b54600160ff909116105b80610bc95750303b158015610bc95750606b5460ff166001145b610c2c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610367565b606b805460ff191660011790558015610c4f57606b805461ff0019166101001790555b6001600160a01b038416610ca55760405162461bcd60e51b815260206004820181905260248201527f4368696c64455243313135353a204241445f494e495449414c495a4154494f4e6044820152606401610367565b61010380546001600160a01b0386166001600160a01b031991821617909155610102805490911633179055604080516020601f8501819004810282018101909252838152610d0d91859085908190840183828082843760009201919091525061153b92505050565b610d59610d22856001600160a01b031661156e565b604051602001610d329190612602565b60408051601f1981840301815282820190915260018252603160f81b602083015290611584565b8015610d9f57606b805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b610102546000906001600160a01b03163314610dd35760405162461bcd60e51b815260040161036790612517565b6107a68484846115f0565b60008151600003610df157506000919050565b506020015190565b6000806001610e0f610e0a886116f0565b61176d565b6040805160008152602081018083529290925260ff861690820152606081018790526080810186905260a0016020604051602081039080840390855afa158015610e5d573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610eb45760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964207369676e617475726560781b6044820152606401610367565b866001600160a01b0316816001600160a01b03161491505095945050505050565b6001600160a01b038416610f355760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b6064820152608401610367565b6000610f3f610fe4565b90506000610f4c8561179a565b90506000610f598561179a565b9050600086815260d0602090815260408083206001600160a01b038b16845290915281208054879290610f8d9084906125ef565b909155505060408051878152602081018790526001600160a01b03808a169260009291871691600080516020612af3833981519152910160405180910390a4610fdb836000898989896117e5565b50505050505050565b6000610fee611940565b905090565b81518351146110145760405162461bcd60e51b815260040161036790612637565b6001600160a01b03841661103a5760405162461bcd60e51b81526004016103679061267f565b6000611044610fe4565b905060005b845181101561112f576000858281518110611066576110666125aa565b602002602001015190506000858381518110611084576110846125aa565b602090810291909101810151600084815260d0835260408082206001600160a01b038e1683529093529190912054909150818110156110d55760405162461bcd60e51b8152600401610367906126c4565b600083815260d0602090815260408083206001600160a01b038e8116855292528083208585039055908b168252812080548492906111149084906125ef565b9250508190555050505080611128906125d6565b9050611049565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb878760405161117f92919061270e565b60405180910390a461119581878787878761199c565b505050505050565b6001600160a01b0383166111c35760405162461bcd60e51b81526004016103679061273c565b80518251146111e45760405162461bcd60e51b815260040161036790612637565b60006111ee610fe4565b604080516020810190915260009052905060005b83518110156112c757600084828151811061121f5761121f6125aa565b60200260200101519050600084838151811061123d5761123d6125aa565b602090810291909101810151600084815260d0835260408082206001600160a01b038c16835290935291909120549091508181101561128e5760405162461bcd60e51b81526004016103679061277f565b600092835260d0602090815260408085206001600160a01b038b16865290915290922091039055806112bf816125d6565b915050611202565b5060006001600160a01b0316846001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb868660405161131892919061270e565b60405180910390a4604080516020810190915260009052610d9f565b816001600160a01b0316836001600160a01b0316036113a75760405162461bcd60e51b815260206004820152602960248201527f455243313135353a2073657474696e6720617070726f76616c20737461747573604482015268103337b91039b2b63360b91b6064820152608401610367565b6001600160a01b03838116600081815260d16020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6001600160a01b03841661143a5760405162461bcd60e51b81526004016103679061267f565b6000611444610fe4565b905060006114518561179a565b9050600061145e8561179a565b9050600086815260d0602090815260408083206001600160a01b038c168452909152902054858110156114a35760405162461bcd60e51b8152600401610367906126c4565b600087815260d0602090815260408083206001600160a01b038d8116855292528083208985039055908a168252812080548892906114e29084906125ef565b909155505060408051888152602081018890526001600160a01b03808b16928c82169291881691600080516020612af3833981519152910160405180910390a4611530848a8a8a8a8a6117e5565b505050505050505050565b606b54610100900460ff166115625760405162461bcd60e51b8152600401610367906127c3565b61156b81611a57565b50565b60606103956001600160a01b0383166014611a87565b815160208084019190912082519183019190912060038290556004819055466001557f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6115d2818484611c29565b600055600280546001600160a01b0319163017905560055550505050565b6001600160a01b0383166116165760405162461bcd60e51b81526004016103679061273c565b6000611620610fe4565b9050600061162d8461179a565b9050600061163a8461179a565b604080516020808201835260009182905288825260d081528282206001600160a01b038b16835290522054909150848110156116885760405162461bcd60e51b81526004016103679061277f565b600086815260d0602090815260408083206001600160a01b038b81168086529184528285208a8703905582518b81529384018a9052909290881691600080516020612af3833981519152910160405180910390a4604080516020810190915260009052610fdb565b6000604051806080016040528060438152602001612b136043913980516020918201208351848301516040808701518051908601209051611750950193845260208401929092526001600160a01b03166040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b600061039561177a611c72565b8360405161190160f01b8152600281019290925260228201526042902090565b604080516001808252818301909252606091600091906020808301908036833701905050905082816000815181106117d4576117d46125aa565b602090810291909101015292915050565b6001600160a01b0384163b156111955760405163f23a6e6160e01b81526001600160a01b0385169063f23a6e6190611829908990899088908890889060040161280e565b6020604051808303816000875af1925050508015611864575060408051601f3d908101601f1916820190925261186191810190612853565b60015b61191057611870612870565b806308c379a0036118a9575061188461288b565b8061188f57506118ab565b8060405162461bcd60e51b81526004016103679190611e49565b505b60405162461bcd60e51b815260206004820152603460248201527f455243313135353a207472616e7366657220746f206e6f6e2d455243313135356044820152732932b1b2b4bb32b91034b6b83632b6b2b73a32b960611b6064820152608401610367565b6001600160e01b0319811663f23a6e6160e01b14610fdb5760405162461bcd60e51b815260040161036790612914565b600030330361199657600080368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050503601516001600160a01b031691506119999050565b50335b90565b6001600160a01b0384163b156111955760405163bc197c8160e01b81526001600160a01b0385169063bc197c81906119e0908990899088908890889060040161295c565b6020604051808303816000875af1925050508015611a1b575060408051601f3d908101601f19168201909252611a1891810190612853565b60015b611a2757611870612870565b6001600160e01b0319811663bc197c8160e01b14610fdb5760405162461bcd60e51b815260040161036790612914565b606b54610100900460ff16611a7e5760405162461bcd60e51b8152600401610367906127c3565b61156b81611cad565b60606000611a968360026129ba565b611aa19060026125ef565b6001600160401b03811115611ab857611ab8611ec3565b6040519080825280601f01601f191660200182016040528015611ae2576020820181803683370190505b509050600360fc1b81600081518110611afd57611afd6125aa565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110611b2c57611b2c6125aa565b60200101906001600160f81b031916908160001a9053506000611b508460026129ba565b611b5b9060016125ef565b90505b6001811115611bd3576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110611b8f57611b8f6125aa565b1a60f81b828281518110611ba557611ba56125aa565b60200101906001600160f81b031916908160001a90535060049490941c93611bcc816129d1565b9050611b5e565b508315611c225760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610367565b9392505050565b6040805160208101859052908101839052606081018290524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090509392505050565b6002546000906001600160a01b031630148015611c90575060015446145b15611c9c575060005490565b610fee600554600354600454611c29565b60d2610b348282612a33565b80356001600160a01b0381168114611cd057600080fd5b919050565b60008060408385031215611ce857600080fd5b611cf183611cb9565b946020939093013593505050565b6001600160e01b03198116811461156b57600080fd5b600060208284031215611d2757600080fd5b8135611c2281611cff565b60008083601f840112611d4457600080fd5b5081356001600160401b03811115611d5b57600080fd5b602083019150836020828501011115611d7357600080fd5b9250929050565b60008060008060008060a08789031215611d9357600080fd5b611d9c87611cb9565b955060208701356001600160401b03811115611db757600080fd5b611dc389828a01611d32565b9096509450506040870135925060608701359150608087013560ff81168114611deb57600080fd5b809150509295509295509295565b60005b83811015611e14578181015183820152602001611dfc565b50506000910152565b60008151808452611e35816020860160208601611df9565b601f01601f19169290920160200192915050565b602081526000611c226020830184611e1d565b600060208284031215611e6e57600080fd5b5035919050565b600080600060608486031215611e8a57600080fd5b611e9384611cb9565b95602085013595506040909401359392505050565b600060208284031215611eba57600080fd5b611c2282611cb9565b634e487b7160e01b600052604160045260246000fd5b601f8201601f191681016001600160401b0381118282101715611efe57611efe611ec3565b6040525050565b60006001600160401b03821115611f1e57611f1e611ec3565b5060051b60200190565b600082601f830112611f3957600080fd5b81356020611f4682611f05565b604051611f538282611ed9565b83815260059390931b8501820192828101915086841115611f7357600080fd5b8286015b84811015611f8e5780358352918301918301611f77565b509695505050505050565b600082601f830112611faa57600080fd5b81356001600160401b03811115611fc357611fc3611ec3565b604051611fda601f8301601f191660200182611ed9565b818152846020838601011115611fef57600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a0868803121561202457600080fd5b61202d86611cb9565b945061203b60208701611cb9565b935060408601356001600160401b038082111561205757600080fd5b61206389838a01611f28565b9450606088013591508082111561207957600080fd5b61208589838a01611f28565b9350608088013591508082111561209b57600080fd5b506120a888828901611f99565b9150509295509295909350565b600080604083850312156120c857600080fd5b82356001600160401b03808211156120df57600080fd5b818501915085601f8301126120f357600080fd5b8135602061210082611f05565b60405161210d8282611ed9565b83815260059390931b850182019282810191508984111561212d57600080fd5b948201945b838610156121525761214386611cb9565b82529482019490820190612132565b9650508601359250508082111561216857600080fd5b5061217585828601611f28565b9150509250929050565b600081518084526020808501945080840160005b838110156121af57815187529582019590820190600101612193565b509495945050505050565b602081526000611c22602083018461217f565b60008083601f8401126121df57600080fd5b5081356001600160401b038111156121f657600080fd5b6020830191508360208260051b8501011115611d7357600080fd5b6000806000806000806060878903121561222a57600080fd5b86356001600160401b038082111561224157600080fd5b61224d8a838b016121cd565b9098509650602089013591508082111561226657600080fd5b6122728a838b016121cd565b9096509450604089013591508082111561228b57600080fd5b5061229889828a016121cd565b979a9699509497509295939492505050565b6000806000806000606086880312156122c257600080fd5b6122cb86611cb9565b945060208601356001600160401b03808211156122e757600080fd5b6122f389838a016121cd565b9096509450604088013591508082111561230c57600080fd5b50612319888289016121cd565b969995985093965092949392505050565b6000806040838503121561233d57600080fd5b61234683611cb9565b91506020830135801515811461235b57600080fd5b809150509250929050565b6000806040838503121561237957600080fd5b61238283611cb9565b915061239060208401611cb9565b90509250929050565b600080600080600060a086880312156123b157600080fd5b6123ba86611cb9565b94506123c860208701611cb9565b9350604086013592506060860135915060808601356001600160401b038111156123f157600080fd5b6120a888828901611f99565b60008060006040848603121561241257600080fd5b61241b84611cb9565b925060208401356001600160401b0381111561243657600080fd5b61244286828701611d32565b9497909650939450505050565b8284823760609190911b6bffffffffffffffffffffffff19169101908152601401919050565b60008251612487818460208701611df9565b9190910192915050565b6001600160a01b0385811682528416602082015260606040820181905281018290526000828460808401376000608084840101526080601f19601f850116830101905095945050505050565b600181811c908216806124f157607f821691505b60208210810361251157634e487b7160e01b600052602260045260246000fd5b50919050565b60208082526025908201527f4368696c64455243313135353a204f6e6c79207072656469636174652063616e6040820152640818d85b1b60da1b606082015260800190565b6020808252602e908201527f455243313135353a2063616c6c6572206973206e6f7420746f6b656e206f776e60408201526d195c881bdc88185c1c1c9bdd995960921b606082015260800190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600182016125e8576125e86125c0565b5060010190565b80820180821115610395576103956125c0565b6c4368696c64455243313135352d60981b81526000825161262a81600d850160208701611df9565b91909101600d0192915050565b60208082526028908201527f455243313135353a2069647320616e6420616d6f756e7473206c656e677468206040820152670dad2e6dac2e8c6d60c31b606082015260800190565b60208082526025908201527f455243313135353a207472616e7366657220746f20746865207a65726f206164604082015264647265737360d81b606082015260800190565b6020808252602a908201527f455243313135353a20696e73756666696369656e742062616c616e636520666f60408201526939103a3930b739b332b960b11b606082015260800190565b604081526000612721604083018561217f565b8281036020840152612733818561217f565b95945050505050565b60208082526023908201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260408201526265737360e81b606082015260800190565b60208082526024908201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604082015263616e636560e01b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b03868116825285166020820152604081018490526060810183905260a06080820181905260009061284890830184611e1d565b979650505050505050565b60006020828403121561286557600080fd5b8151611c2281611cff565b600060033d11156119995760046000803e5060005160e01c90565b600060443d10156128995790565b6040516003193d81016004833e81513d6001600160401b0381602484011181841117156128c857505050505090565b82850191508151818111156128e05750505050505090565b843d87010160208285010111156128fa5750505050505090565b61290960208286010187611ed9565b509095945050505050565b60208082526028908201527f455243313135353a204552433131353552656365697665722072656a656374656040820152676420746f6b656e7360c01b606082015260800190565b6001600160a01b0386811682528516602082015260a0604082018190526000906129889083018661217f565b828103606084015261299a818661217f565b905082810360808401526129ae8185611e1d565b98975050505050505050565b8082028115828204841417610395576103956125c0565b6000816129e0576129e06125c0565b506000190190565b601f821115612a2e57600081815260208120601f850160051c81016020861015612a0f5750805b601f850160051c820191505b8181101561119557828155600101612a1b565b505050565b81516001600160401b03811115612a4c57612a4c611ec3565b612a6081612a5a84546124dd565b846129e8565b602080601f831160018114612a955760008415612a7d5750858301515b600019600386901b1c1916600185901b178555611195565b600085815260208120601f198616915b82811015612ac457888601518255948401946001909101908401612aa5565b5085821015612ae25787850151600019600388901b60f8161c191681555b5050505050600190811b0190555056fec3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f624d6574615472616e73616374696f6e2875696e74323536206e6f6e63652c616464726573732066726f6d2c62797465732066756e6374696f6e5369676e617475726529a2646970667358221220d48b6656ae2af8f39607add0ff4fc12029027806b1a7c756a64f97827803920c64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildERC1155PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildERC1155Predicate","sourceName":"contracts/child/ChildERC1155Predicate.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2ERC1155Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"L2ERC1155DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2ERC1155Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"L2ERC1155WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2TokenMapped","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootERC1155Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootERC1155Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IChildERC1155","name":"childToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC1155","name":"childToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"withdrawBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC1155","name":"childToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50612040806100206000396000f3fe608060405234801561001057600080fd5b50600436106101585760003560e01c806397e5230d116100c3578063d41f17711161007c578063d41f1771146102de578063d7c9e3ec14610305578063e0563ab11461032c578063eeb4994514610335578063f645125514610348578063f8c8765e1461036f57600080fd5b806397e5230d1461024d578063b176806514610257578063b5c5f6721461027e578063b68ad1e414610291578063b8cd3ec0146102a4578063c5ac2b1c146102b757600080fd5b806351351d531161011557806351351d53146101d357806355b01e4d146101e15780635ea5df79146101ef5780637efab4f51461020657806386937eb41461022f578063947287cf1461024457600080fd5b8063051eb2e21461015d57806305dc2e8f1461018d57806307b3e252146101a05780631bc114ba146101ae578063284017f5146101c15780633b878c22146101ca575b600080fd5b603554610170906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b603454610170906001600160a01b031681565b6101706004600360981b0181565b603354610170906001600160a01b031681565b61017061202081565b61017061101081565b6101706002600160a01b0381565b6101706004600160991b0181565b6101f861138881565b604051908152602001610184565b610170610214366004611648565b6037602052600090815260409020546001600160a01b031681565b61024261023d3660046116b7565b610382565b005b6101f861520881565b6101f8620249f081565b6101f87f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b61024261028c366004611763565b61039a565b603654610170906001600160a01b031681565b6102426102b2366004611798565b6103ab565b6101f87faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6101f87f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101f87f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61017061203081565b6102426103433660046117de565b6103bd565b6101f87f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61024261037d366004611866565b6105dd565b6103918787878787878761073a565b50505050505050565b6103a683338484610ab9565b505050565b6103b784848484610ab9565b50505050565b6034546001600160a01b0316331461042f5760405162461bcd60e51b815260206004820152602a60248201527f4368696c64455243313135355072656469636174653a204f4e4c595f5354415460448201526922afa922a1a2a4ab22a960b11b60648201526084015b60405180910390fd5b6035546001600160a01b0384811691161461049f5760405162461bcd60e51b815260206004820152602a60248201527f4368696c64455243313135355072656469636174653a204f4e4c595f524f4f546044820152695f50524544494341544560b01b6064820152608401610426565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216104ce6020600084866118c2565b6104d7916118ec565b036104f6576104f16104ec82602081866118c2565b610dce565b6103b7565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d186105256020600084866118c2565b61052e916118ec565b0361053d576104f18282611039565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad61056c6020600084866118c2565b610575916118ec565b03610584576104f182826112a6565b60405162461bcd60e51b815260206004820152602860248201527f4368696c64455243313135355072656469636174653a20494e56414c49445f5360448201526749474e415455524560c01b6064820152608401610426565b336002600160a01b03146106215760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610426565b600054610100900460ff16158080156106415750600054600160ff909116105b8061065b5750303b15801561065b575060005460ff166001145b6106be5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610426565b6000805460ff1916600117905580156106e1576000805461ff0019166101001790555b6106ed8585858561140e565b8015610733576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b8661074481611504565b6107605760405162461bcd60e51b81526004016104269061190a565b6000886001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107a0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107c4919061194d565b6001600160a01b038181166000908152603760205260409020549192508a81169116146108035760405162461bcd60e51b81526004016104269061196a565b6001600160a01b038116610819576108196119af565b306001600160a01b0316896001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610861573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610885919061194d565b6001600160a01b03161461089b5761089b6119af565b86851480156108a957508483145b6109035760405162461bcd60e51b815260206004820152602560248201527f4368696c64455243313135355072656469636174653a20494e56414c49445f4c60448201526408a9c8ea8960db1b6064820152608401610426565b604051631ac8311560e21b81526001600160a01b038a1690636b20c454906109379033908a908a908a908a906004016119f7565b6020604051808303816000875af1158015610956573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097a9190611a3b565b6109965760405162461bcd60e51b815260040161042690611a5d565b6033546035546040516001600160a01b03928316926316f198319216906109f1907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908f908f908f908f908f908f90602001611ae8565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610a1d929190611b99565b600060405180830381600087803b158015610a3757600080fd5b505af1158015610a4b573d6000803e3d6000fd5b50505050336001600160a01b0316896001600160a01b0316826001600160a01b03167f7a10660242ca367951ff3777cdb3c2a761e3ccad204bac118501e24693f3683d8b8b8b8b8b8b604051610aa696959493929190611bc5565b60405180910390a4505050505050505050565b83610ac381611504565b610adf5760405162461bcd60e51b81526004016104269061190a565b6000856001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b1f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b43919061194d565b6001600160a01b03818116600090815260376020526040902054919250878116911614610b825760405162461bcd60e51b81526004016104269061196a565b6001600160a01b038116610b9857610b986119af565b306001600160a01b0316866001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610be0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c04919061194d565b6001600160a01b031614610c1a57610c1a6119af565b604051637a94c56560e11b81526001600160a01b0387169063f5298aca90610c4a90339088908890600401611c0e565b6020604051808303816000875af1158015610c69573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c8d9190611a3b565b610ca95760405162461bcd60e51b815260040161042690611a5d565b603354603554604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152898116608083015260a0820189905260c08083018990528351808403909101815260e08301938490526316f1983160e01b909352938416936316f1983193610d3b9391169160e401611b99565b600060405180830381600087803b158015610d5557600080fd5b505af1158015610d69573d6000803e3d6000fd5b50505050846001600160a01b0316866001600160a01b0316826001600160a01b03167f2ca9093e8b5356801039806c6a08003e5b7013fb8ae48f720fc90fc1c1a8bec2338888604051610dbe93929190611c0e565b60405180910390a4505050505050565b600080808080610de086880188611c2f565b6001600160a01b03808616600090815260376020526040902054959a50939850919650945092501680610e255760405162461bcd60e51b81526004016104269061196a565b610e2e81611504565b610e3a57610e3a6119af565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e7a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e9e919061194d565b9050866001600160a01b0316816001600160a01b031614610ec157610ec16119af565b6001600160a01b038116610ed757610ed76119af565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f1f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f43919061194d565b6001600160a01b031614610f5957610f596119af565b604051630ab714fb60e11b81526001600160a01b0383169063156e29f690610f8990889088908890600401611c0e565b6020604051808303816000875af1158015610fa8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fcc9190611a3b565b610fe85760405162461bcd60e51b815260040161042690611c8a565b846001600160a01b0316826001600160a01b0316886001600160a01b03167f2930d932c1cccd6add2e0e2d706ede9015db8a194405f2a3e1783703515e104f898888604051610aa693929190611c0e565b60008080808061104b86880188611da0565b6001600160a01b03808616600090815260376020526040902054959b5093995091975095509350169050806110925760405162461bcd60e51b81526004016104269061196a565b61109b81611504565b6110a7576110a76119af565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061110b919061194d565b9050866001600160a01b0316816001600160a01b03161461112e5761112e6119af565b6001600160a01b038116611144576111446119af565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561118c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111b0919061194d565b6001600160a01b0316146111c6576111c66119af565b604051635712868360e01b81526001600160a01b038316906357128683906111f690889088908890600401611ee5565b6020604051808303816000875af1158015611215573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112399190611a3b565b6112555760405162461bcd60e51b815260040161042690611c8a565b856001600160a01b0316826001600160a01b0316886001600160a01b03167f17304b99f8dfa5a2b8dd5695d82f9947c2abfbc9cb64bab610b9a1a0feadb9a0888888604051610aa693929190611ee5565b6000806112b583850185611f5a565b9093509150506001600160a01b0382166112d1576112d16119af565b6001600160a01b0382811660009081526037602052604090205416156112f9576112f96119af565b6036546040516bffffffffffffffffffffffff19606085901b166020820152600091611349916001600160a01b039091169060340160405160208183030381529060405280519060200120611599565b6001600160a01b038481166000908152603760205260409081902080546001600160a01b0319169284169283179055516379ccf11760e11b81529192509063f399e22e9061139d9086908690600401611b99565b600060405180830381600087803b1580156113b757600080fd5b505af11580156113cb573d6000803e3d6000fd5b50506040516001600160a01b038085169350861691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a35050505050565b6001600160a01b0384161580159061142e57506001600160a01b03831615155b801561144257506001600160a01b03821615155b801561145657506001600160a01b03811615155b6114b45760405162461bcd60e51b815260206004820152602960248201527f4368696c64455243313135355072656469636174653a204241445f494e49544960448201526820a624ad20aa24a7a760b91b6064820152608401610426565b603380546001600160a01b039586166001600160a01b0319918216179091556034805494861694821694909417909355603580549285169284169290921790915560368054919093169116179055565b6000816001600160a01b03163b60000361152057506000919050565b6040516301ffc9a760e01b8152636cdb3d1360e11b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611587575060408051601f3d908101601f1916820190925261158491810190611a3b565b60015b61159357506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b0381166115935760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b6044820152606401610426565b6001600160a01b038116811461164557600080fd5b50565b60006020828403121561165a57600080fd5b813561166581611630565b9392505050565b60008083601f84011261167e57600080fd5b5081356001600160401b0381111561169557600080fd5b6020830191508360208260051b85010111156116b057600080fd5b9250929050565b60008060008060008060006080888a0312156116d257600080fd5b87356116dd81611630565b965060208801356001600160401b03808211156116f957600080fd5b6117058b838c0161166c565b909850965060408a013591508082111561171e57600080fd5b61172a8b838c0161166c565b909650945060608a013591508082111561174357600080fd5b506117508a828b0161166c565b989b979a50959850939692959293505050565b60008060006060848603121561177857600080fd5b833561178381611630565b95602085013595506040909401359392505050565b600080600080608085870312156117ae57600080fd5b84356117b981611630565b935060208501356117c981611630565b93969395505050506040820135916060013590565b600080600080606085870312156117f457600080fd5b84359350602085013561180681611630565b925060408501356001600160401b038082111561182257600080fd5b818701915087601f83011261183657600080fd5b81358181111561184557600080fd5b88602082850101111561185757600080fd5b95989497505060200194505050565b6000806000806080858703121561187c57600080fd5b843561188781611630565b9350602085013561189781611630565b925060408501356118a781611630565b915060608501356118b781611630565b939692955090935050565b600080858511156118d257600080fd5b838611156118df57600080fd5b5050820193919092039150565b8035602083101561159357600019602084900360031b1b1692915050565b60208082526023908201527f4368696c64455243313135355072656469636174653a204e4f545f434f4e54526040820152621050d560ea1b606082015260800190565b60006020828403121561195f57600080fd5b815161166581611630565b60208082526025908201527f4368696c64455243313135355072656469636174653a20554e4d41505045445f6040820152642a27a5a2a760d91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b038311156119de57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b0386168152606060208201819052600090611a1c90830186886119c5565b8281036040840152611a2f8185876119c5565b98975050505050505050565b600060208284031215611a4d57600080fd5b8151801515811461166557600080fd5b60208082526022908201527f4368696c64455243313135355072656469636174653a204255524e5f4641494c604082015261115160f21b606082015260800190565b8183526000602080850194508260005b85811015611add578135611ac281611630565b6001600160a01b031687529582019590820190600101611aaf565b509495945050505050565b8981526001600160a01b0389811660208301528816604082015260c060608201819052600090611b1b908301888a611a9f565b8281036080840152611b2e8187896119c5565b905082810360a0840152611b438185876119c5565b9c9b505050505050505050505050565b6000815180845260005b81811015611b7957602081850181015186830182015201611b5d565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b0383168152604060208201819052600090611bbd90830184611b53565b949350505050565b606081526000611bd960608301888a611a9f565b8281036020840152611bec8187896119c5565b90508281036040840152611c018185876119c5565b9998505050505050505050565b6001600160a01b039390931683526020830191909152604082015260600190565b600080600080600060a08688031215611c4757600080fd5b8535611c5281611630565b94506020860135611c6281611630565b93506040860135611c7281611630565b94979396509394606081013594506080013592915050565b60208082526022908201527f4368696c64455243313135355072656469636174653a204d494e545f4641494c604082015261115160f21b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611d0a57611d0a611ccc565b604052919050565b60006001600160401b03821115611d2b57611d2b611ccc565b5060051b60200190565b600082601f830112611d4657600080fd5b81356020611d5b611d5683611d12565b611ce2565b82815260059290921b84018101918181019086841115611d7a57600080fd5b8286015b84811015611d955780358352918301918301611d7e565b509695505050505050565b60008060008060008060c08789031215611db957600080fd5b86359550602080880135611dcc81611630565b95506040880135611ddc81611630565b945060608801356001600160401b0380821115611df857600080fd5b818a0191508a601f830112611e0c57600080fd5b8135611e1a611d5682611d12565b81815260059190911b8301840190848101908d831115611e3957600080fd5b938501935b82851015611e60578435611e5181611630565b82529385019390850190611e3e565b9750505060808a0135925080831115611e7857600080fd5b611e848b848c01611d35565b945060a08a0135925080831115611e9a57600080fd5b5050611ea889828a01611d35565b9150509295509295509295565b600081518084526020808501945080840160005b83811015611add57815187529582019590820190600101611ec9565b606080825284519082018190526000906020906080840190828801845b82811015611f275781516001600160a01b031684529284019290840190600101611f02565b50505083810382850152611f3b8187611eb5565b9150508281036040840152611f508185611eb5565b9695505050505050565b600080600060608486031215611f6f57600080fd5b83359250602080850135611f8281611630565b925060408501356001600160401b0380821115611f9e57600080fd5b818701915087601f830112611fb257600080fd5b813581811115611fc457611fc4611ccc565b611fd6601f8201601f19168501611ce2565b91508082528884828501011115611fec57600080fd5b8084840185840137600084828401015250809350505050925092509256fea2646970667358221220144e7d3687aae30c76d4672a727fea0faa7f80324ee0ce29478a172a7808516964736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101585760003560e01c806397e5230d116100c3578063d41f17711161007c578063d41f1771146102de578063d7c9e3ec14610305578063e0563ab11461032c578063eeb4994514610335578063f645125514610348578063f8c8765e1461036f57600080fd5b806397e5230d1461024d578063b176806514610257578063b5c5f6721461027e578063b68ad1e414610291578063b8cd3ec0146102a4578063c5ac2b1c146102b757600080fd5b806351351d531161011557806351351d53146101d357806355b01e4d146101e15780635ea5df79146101ef5780637efab4f51461020657806386937eb41461022f578063947287cf1461024457600080fd5b8063051eb2e21461015d57806305dc2e8f1461018d57806307b3e252146101a05780631bc114ba146101ae578063284017f5146101c15780633b878c22146101ca575b600080fd5b603554610170906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b603454610170906001600160a01b031681565b6101706004600360981b0181565b603354610170906001600160a01b031681565b61017061202081565b61017061101081565b6101706002600160a01b0381565b6101706004600160991b0181565b6101f861138881565b604051908152602001610184565b610170610214366004611648565b6037602052600090815260409020546001600160a01b031681565b61024261023d3660046116b7565b610382565b005b6101f861520881565b6101f8620249f081565b6101f87f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b61024261028c366004611763565b61039a565b603654610170906001600160a01b031681565b6102426102b2366004611798565b6103ab565b6101f87faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6101f87f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101f87f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61017061203081565b6102426103433660046117de565b6103bd565b6101f87f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61024261037d366004611866565b6105dd565b6103918787878787878761073a565b50505050505050565b6103a683338484610ab9565b505050565b6103b784848484610ab9565b50505050565b6034546001600160a01b0316331461042f5760405162461bcd60e51b815260206004820152602a60248201527f4368696c64455243313135355072656469636174653a204f4e4c595f5354415460448201526922afa922a1a2a4ab22a960b11b60648201526084015b60405180910390fd5b6035546001600160a01b0384811691161461049f5760405162461bcd60e51b815260206004820152602a60248201527f4368696c64455243313135355072656469636174653a204f4e4c595f524f4f546044820152695f50524544494341544560b01b6064820152608401610426565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216104ce6020600084866118c2565b6104d7916118ec565b036104f6576104f16104ec82602081866118c2565b610dce565b6103b7565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d186105256020600084866118c2565b61052e916118ec565b0361053d576104f18282611039565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad61056c6020600084866118c2565b610575916118ec565b03610584576104f182826112a6565b60405162461bcd60e51b815260206004820152602860248201527f4368696c64455243313135355072656469636174653a20494e56414c49445f5360448201526749474e415455524560c01b6064820152608401610426565b336002600160a01b03146106215760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610426565b600054610100900460ff16158080156106415750600054600160ff909116105b8061065b5750303b15801561065b575060005460ff166001145b6106be5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610426565b6000805460ff1916600117905580156106e1576000805461ff0019166101001790555b6106ed8585858561140e565b8015610733576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b8661074481611504565b6107605760405162461bcd60e51b81526004016104269061190a565b6000886001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107a0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107c4919061194d565b6001600160a01b038181166000908152603760205260409020549192508a81169116146108035760405162461bcd60e51b81526004016104269061196a565b6001600160a01b038116610819576108196119af565b306001600160a01b0316896001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610861573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610885919061194d565b6001600160a01b03161461089b5761089b6119af565b86851480156108a957508483145b6109035760405162461bcd60e51b815260206004820152602560248201527f4368696c64455243313135355072656469636174653a20494e56414c49445f4c60448201526408a9c8ea8960db1b6064820152608401610426565b604051631ac8311560e21b81526001600160a01b038a1690636b20c454906109379033908a908a908a908a906004016119f7565b6020604051808303816000875af1158015610956573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097a9190611a3b565b6109965760405162461bcd60e51b815260040161042690611a5d565b6033546035546040516001600160a01b03928316926316f198319216906109f1907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908f908f908f908f908f908f90602001611ae8565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610a1d929190611b99565b600060405180830381600087803b158015610a3757600080fd5b505af1158015610a4b573d6000803e3d6000fd5b50505050336001600160a01b0316896001600160a01b0316826001600160a01b03167f7a10660242ca367951ff3777cdb3c2a761e3ccad204bac118501e24693f3683d8b8b8b8b8b8b604051610aa696959493929190611bc5565b60405180910390a4505050505050505050565b83610ac381611504565b610adf5760405162461bcd60e51b81526004016104269061190a565b6000856001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b1f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b43919061194d565b6001600160a01b03818116600090815260376020526040902054919250878116911614610b825760405162461bcd60e51b81526004016104269061196a565b6001600160a01b038116610b9857610b986119af565b306001600160a01b0316866001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610be0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c04919061194d565b6001600160a01b031614610c1a57610c1a6119af565b604051637a94c56560e11b81526001600160a01b0387169063f5298aca90610c4a90339088908890600401611c0e565b6020604051808303816000875af1158015610c69573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c8d9190611a3b565b610ca95760405162461bcd60e51b815260040161042690611a5d565b603354603554604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152898116608083015260a0820189905260c08083018990528351808403909101815260e08301938490526316f1983160e01b909352938416936316f1983193610d3b9391169160e401611b99565b600060405180830381600087803b158015610d5557600080fd5b505af1158015610d69573d6000803e3d6000fd5b50505050846001600160a01b0316866001600160a01b0316826001600160a01b03167f2ca9093e8b5356801039806c6a08003e5b7013fb8ae48f720fc90fc1c1a8bec2338888604051610dbe93929190611c0e565b60405180910390a4505050505050565b600080808080610de086880188611c2f565b6001600160a01b03808616600090815260376020526040902054959a50939850919650945092501680610e255760405162461bcd60e51b81526004016104269061196a565b610e2e81611504565b610e3a57610e3a6119af565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e7a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e9e919061194d565b9050866001600160a01b0316816001600160a01b031614610ec157610ec16119af565b6001600160a01b038116610ed757610ed76119af565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f1f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f43919061194d565b6001600160a01b031614610f5957610f596119af565b604051630ab714fb60e11b81526001600160a01b0383169063156e29f690610f8990889088908890600401611c0e565b6020604051808303816000875af1158015610fa8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fcc9190611a3b565b610fe85760405162461bcd60e51b815260040161042690611c8a565b846001600160a01b0316826001600160a01b0316886001600160a01b03167f2930d932c1cccd6add2e0e2d706ede9015db8a194405f2a3e1783703515e104f898888604051610aa693929190611c0e565b60008080808061104b86880188611da0565b6001600160a01b03808616600090815260376020526040902054959b5093995091975095509350169050806110925760405162461bcd60e51b81526004016104269061196a565b61109b81611504565b6110a7576110a76119af565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061110b919061194d565b9050866001600160a01b0316816001600160a01b03161461112e5761112e6119af565b6001600160a01b038116611144576111446119af565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561118c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111b0919061194d565b6001600160a01b0316146111c6576111c66119af565b604051635712868360e01b81526001600160a01b038316906357128683906111f690889088908890600401611ee5565b6020604051808303816000875af1158015611215573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112399190611a3b565b6112555760405162461bcd60e51b815260040161042690611c8a565b856001600160a01b0316826001600160a01b0316886001600160a01b03167f17304b99f8dfa5a2b8dd5695d82f9947c2abfbc9cb64bab610b9a1a0feadb9a0888888604051610aa693929190611ee5565b6000806112b583850185611f5a565b9093509150506001600160a01b0382166112d1576112d16119af565b6001600160a01b0382811660009081526037602052604090205416156112f9576112f96119af565b6036546040516bffffffffffffffffffffffff19606085901b166020820152600091611349916001600160a01b039091169060340160405160208183030381529060405280519060200120611599565b6001600160a01b038481166000908152603760205260409081902080546001600160a01b0319169284169283179055516379ccf11760e11b81529192509063f399e22e9061139d9086908690600401611b99565b600060405180830381600087803b1580156113b757600080fd5b505af11580156113cb573d6000803e3d6000fd5b50506040516001600160a01b038085169350861691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a35050505050565b6001600160a01b0384161580159061142e57506001600160a01b03831615155b801561144257506001600160a01b03821615155b801561145657506001600160a01b03811615155b6114b45760405162461bcd60e51b815260206004820152602960248201527f4368696c64455243313135355072656469636174653a204241445f494e49544960448201526820a624ad20aa24a7a760b91b6064820152608401610426565b603380546001600160a01b039586166001600160a01b0319918216179091556034805494861694821694909417909355603580549285169284169290921790915560368054919093169116179055565b6000816001600160a01b03163b60000361152057506000919050565b6040516301ffc9a760e01b8152636cdb3d1360e11b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611587575060408051601f3d908101601f1916820190925261158491810190611a3b565b60015b61159357506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b0381166115935760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b6044820152606401610426565b6001600160a01b038116811461164557600080fd5b50565b60006020828403121561165a57600080fd5b813561166581611630565b9392505050565b60008083601f84011261167e57600080fd5b5081356001600160401b0381111561169557600080fd5b6020830191508360208260051b85010111156116b057600080fd5b9250929050565b60008060008060008060006080888a0312156116d257600080fd5b87356116dd81611630565b965060208801356001600160401b03808211156116f957600080fd5b6117058b838c0161166c565b909850965060408a013591508082111561171e57600080fd5b61172a8b838c0161166c565b909650945060608a013591508082111561174357600080fd5b506117508a828b0161166c565b989b979a50959850939692959293505050565b60008060006060848603121561177857600080fd5b833561178381611630565b95602085013595506040909401359392505050565b600080600080608085870312156117ae57600080fd5b84356117b981611630565b935060208501356117c981611630565b93969395505050506040820135916060013590565b600080600080606085870312156117f457600080fd5b84359350602085013561180681611630565b925060408501356001600160401b038082111561182257600080fd5b818701915087601f83011261183657600080fd5b81358181111561184557600080fd5b88602082850101111561185757600080fd5b95989497505060200194505050565b6000806000806080858703121561187c57600080fd5b843561188781611630565b9350602085013561189781611630565b925060408501356118a781611630565b915060608501356118b781611630565b939692955090935050565b600080858511156118d257600080fd5b838611156118df57600080fd5b5050820193919092039150565b8035602083101561159357600019602084900360031b1b1692915050565b60208082526023908201527f4368696c64455243313135355072656469636174653a204e4f545f434f4e54526040820152621050d560ea1b606082015260800190565b60006020828403121561195f57600080fd5b815161166581611630565b60208082526025908201527f4368696c64455243313135355072656469636174653a20554e4d41505045445f6040820152642a27a5a2a760d91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b038311156119de57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b0386168152606060208201819052600090611a1c90830186886119c5565b8281036040840152611a2f8185876119c5565b98975050505050505050565b600060208284031215611a4d57600080fd5b8151801515811461166557600080fd5b60208082526022908201527f4368696c64455243313135355072656469636174653a204255524e5f4641494c604082015261115160f21b606082015260800190565b8183526000602080850194508260005b85811015611add578135611ac281611630565b6001600160a01b031687529582019590820190600101611aaf565b509495945050505050565b8981526001600160a01b0389811660208301528816604082015260c060608201819052600090611b1b908301888a611a9f565b8281036080840152611b2e8187896119c5565b905082810360a0840152611b438185876119c5565b9c9b505050505050505050505050565b6000815180845260005b81811015611b7957602081850181015186830182015201611b5d565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b0383168152604060208201819052600090611bbd90830184611b53565b949350505050565b606081526000611bd960608301888a611a9f565b8281036020840152611bec8187896119c5565b90508281036040840152611c018185876119c5565b9998505050505050505050565b6001600160a01b039390931683526020830191909152604082015260600190565b600080600080600060a08688031215611c4757600080fd5b8535611c5281611630565b94506020860135611c6281611630565b93506040860135611c7281611630565b94979396509394606081013594506080013592915050565b60208082526022908201527f4368696c64455243313135355072656469636174653a204d494e545f4641494c604082015261115160f21b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611d0a57611d0a611ccc565b604052919050565b60006001600160401b03821115611d2b57611d2b611ccc565b5060051b60200190565b600082601f830112611d4657600080fd5b81356020611d5b611d5683611d12565b611ce2565b82815260059290921b84018101918181019086841115611d7a57600080fd5b8286015b84811015611d955780358352918301918301611d7e565b509695505050505050565b60008060008060008060c08789031215611db957600080fd5b86359550602080880135611dcc81611630565b95506040880135611ddc81611630565b945060608801356001600160401b0380821115611df857600080fd5b818a0191508a601f830112611e0c57600080fd5b8135611e1a611d5682611d12565b81815260059190911b8301840190848101908d831115611e3957600080fd5b938501935b82851015611e60578435611e5181611630565b82529385019390850190611e3e565b9750505060808a0135925080831115611e7857600080fd5b611e848b848c01611d35565b945060a08a0135925080831115611e9a57600080fd5b5050611ea889828a01611d35565b9150509295509295509295565b600081518084526020808501945080840160005b83811015611add57815187529582019590820190600101611ec9565b606080825284519082018190526000906020906080840190828801845b82811015611f275781516001600160a01b031684529284019290840190600101611f02565b50505083810382850152611f3b8187611eb5565b9150508281036040840152611f508185611eb5565b9695505050505050565b600080600060608486031215611f6f57600080fd5b83359250602080850135611f8281611630565b925060408501356001600160401b0380821115611f9e57600080fd5b818701915087601f830112611fb257600080fd5b813581811115611fc457611fc4611ccc565b611fd6601f8201601f19168501611ce2565b91508082528884828501011115611fec57600080fd5b8084840185840137600084828401015250809350505050925092509256fea2646970667358221220144e7d3687aae30c76d4672a727fea0faa7f80324ee0ce29478a172a7808516964736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildERC1155PredicateACLArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildERC1155PredicateAccessList","sourceName":"contracts/child/ChildERC1155PredicateAccessList.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"AllowListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"BlockListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2ERC1155Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"L2ERC1155DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2ERC1155Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"L2ERC1155WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2TokenMapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootERC1155Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"},{"internalType":"bool","name":"newUseAllowList","type":"bool"},{"internalType":"bool","name":"newUseBlockList","type":"bool"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootERC1155Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootERC1155Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseAllowList","type":"bool"}],"name":"setAllowList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseBlockList","type":"bool"}],"name":"setBlockList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC1155","name":"childToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC1155","name":"childToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"withdrawBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC1155","name":"childToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506127d8806100206000396000f3fe608060405234801561001057600080fd5b50600436106101b05760003560e01c8063b1768065116100ef578063d7c9e3ec11610092578063d7c9e3ec146103a5578063d8dd1773146103cc578063e0563ab1146103df578063e30c3978146103e8578063eeb49945146103f9578063f2fde38b1461040c578063f64512551461041f578063f8c8765e1461044657600080fd5b8063b1768065146102d1578063b5c5f672146102f8578063b68ad1e41461030b578063b8cd3ec01461031e578063c1225a2014610331578063c5ac2b1c14610344578063c5e4683a1461036b578063d41f17711461037e57600080fd5b80635ea5df79116101575780635ea5df7914610247578063715018a61461025e57806379ba5097146102685780637efab4f51461027057806386937eb41461029a5780638da5cb5b146102ad578063947287cf146102be57806397e5230d146102c757600080fd5b8063051eb2e2146101b557806305dc2e8f146101e557806307b3e252146101f85780631bc114ba14610206578063284017f5146102195780633b878c221461022257806351351d531461022b57806355b01e4d14610239575b600080fd5b60fe546101c8906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b60fd546101c8906001600160a01b031681565b6101c86004600360981b0181565b60fc546101c8906001600160a01b031681565b6101c861202081565b6101c861101081565b6101c86002600160a01b0381565b6101c86004600160991b0181565b61025061138881565b6040519081526020016101dc565b610266610459565b005b61026661046d565b6101c861027e366004611c73565b610100602052600090815260409020546001600160a01b031681565b6102666102a8366004611ce2565b6104ec565b6033546001600160a01b03166101c8565b61025061520881565b610250620249f081565b6102507f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b610266610306366004611d8e565b61050c565b60ff546101c8906001600160a01b031681565b61026661032c366004611dc3565b610525565b61026661033f366004611e17565b61053f565b6102507faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b610266610379366004611e17565b61058e565b6102507f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102507f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b6102666103da366004611e34565b6105d5565b6101c861203081565b6065546001600160a01b03166101c8565b610266610407366004611eca565b6106f6565b61026661041a366004611c73565b610911565b6102507f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b610266610454366004611f52565b610982565b610461610a7b565b61046b6000610ad5565b565b60655433906001600160a01b031681146104e05760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b6104e981610ad5565b50565b6104f4610aee565b61050387878787878787610af6565b50505050505050565b610514610aee565b61052083338484610e76565b505050565b61052d610aee565b61053984848484610e76565b50505050565b610547610a7b565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b610596610a7b565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b03146105fc5760405163973d02cb60e01b81526004016104d790611fae565b600054610100900460ff161580801561061c5750600054600160ff909116105b806106365750303b158015610636575060005460ff166001145b6106525760405162461bcd60e51b81526004016104d790611fd2565b6000805460ff191660011790558015610675576000805461ff0019166101001790555b6106818888888861118c565b60c9805461ffff191685151561ff00191617610100851515021790556106a682610ad5565b80156106ec576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146107635760405162461bcd60e51b815260206004820152602a60248201527f4368696c64455243313135355072656469636174653a204f4e4c595f5354415460448201526922afa922a1a2a4ab22a960b11b60648201526084016104d7565b60fe546001600160a01b038481169116146107d35760405162461bcd60e51b815260206004820152602a60248201527f4368696c64455243313135355072656469636174653a204f4e4c595f524f4f546044820152695f50524544494341544560b01b60648201526084016104d7565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f821610802602060008486612020565b61080b9161204a565b0361082a576108256108208260208186612020565b611282565b610539565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18610859602060008486612020565b6108629161204a565b036108715761082582826114ee565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad6108a0602060008486612020565b6108a99161204a565b036108b857610825828261175c565b60405162461bcd60e51b815260206004820152602860248201527f4368696c64455243313135355072656469636174653a20494e56414c49445f5360448201526749474e415455524560c01b60648201526084016104d7565b610919610a7b565b606580546001600160a01b0383166001600160a01b0319909116811790915561094a6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b336002600160a01b03146109a95760405163973d02cb60e01b81526004016104d790611fae565b600054610100900460ff16158080156109c95750600054600160ff909116105b806109e35750303b1580156109e3575060005460ff166001145b6109ff5760405162461bcd60e51b81526004016104d790611fd2565b6000805460ff191660011790558015610a22576000805461ff0019166101001790555b610a2e8585858561118c565b8015610a74576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6033546001600160a01b0316331461046b5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104d7565b606580546001600160a01b03191690556104e9816118c6565b61046b611918565b86610b0081611b32565b610b1c5760405162461bcd60e51b81526004016104d790612068565b6000886001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b5c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8091906120ab565b6001600160a01b03818116600090815261010060205260409020549192508a8116911614610bc05760405162461bcd60e51b81526004016104d7906120c8565b6001600160a01b038116610bd657610bd661210d565b306001600160a01b0316896001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c4291906120ab565b6001600160a01b031614610c5857610c5861210d565b8685148015610c6657508483145b610cc05760405162461bcd60e51b815260206004820152602560248201527f4368696c64455243313135355072656469636174653a20494e56414c49445f4c60448201526408a9c8ea8960db1b60648201526084016104d7565b604051631ac8311560e21b81526001600160a01b038a1690636b20c45490610cf49033908a908a908a908a90600401612155565b6020604051808303816000875af1158015610d13573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d379190612199565b610d535760405162461bcd60e51b81526004016104d7906121b6565b60fc5460fe546040516001600160a01b03928316926316f19831921690610dae907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908f908f908f908f908f908f90602001612241565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610dda9291906122fc565b600060405180830381600087803b158015610df457600080fd5b505af1158015610e08573d6000803e3d6000fd5b50505050336001600160a01b0316896001600160a01b0316826001600160a01b03167f7a10660242ca367951ff3777cdb3c2a761e3ccad204bac118501e24693f3683d8b8b8b8b8b8b604051610e6396959493929190612328565b60405180910390a4505050505050505050565b83610e8081611b32565b610e9c5760405162461bcd60e51b81526004016104d790612068565b6000856001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610edc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f0091906120ab565b6001600160a01b0381811660009081526101006020526040902054919250878116911614610f405760405162461bcd60e51b81526004016104d7906120c8565b6001600160a01b038116610f5657610f5661210d565b306001600160a01b0316866001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f9e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fc291906120ab565b6001600160a01b031614610fd857610fd861210d565b604051637a94c56560e11b81526001600160a01b0387169063f5298aca9061100890339088908890600401612371565b6020604051808303816000875af1158015611027573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061104b9190612199565b6110675760405162461bcd60e51b81526004016104d7906121b6565b60fc5460fe54604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152898116608083015260a0820189905260c08083018990528351808403909101815260e08301938490526316f1983160e01b909352938416936316f19831936110f99391169160e4016122fc565b600060405180830381600087803b15801561111357600080fd5b505af1158015611127573d6000803e3d6000fd5b50505050846001600160a01b0316866001600160a01b0316826001600160a01b03167f2ca9093e8b5356801039806c6a08003e5b7013fb8ae48f720fc90fc1c1a8bec233888860405161117c93929190612371565b60405180910390a4505050505050565b6001600160a01b038416158015906111ac57506001600160a01b03831615155b80156111c057506001600160a01b03821615155b80156111d457506001600160a01b03811615155b6112325760405162461bcd60e51b815260206004820152602960248201527f4368696c64455243313135355072656469636174653a204241445f494e49544960448201526820a624ad20aa24a7a760b91b60648201526084016104d7565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b60008080808061129486880188612392565b6001600160a01b0380861660009081526101006020526040902054959a509398509196509450925016806112da5760405162461bcd60e51b81526004016104d7906120c8565b6112e381611b32565b6112ef576112ef61210d565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561132f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061135391906120ab565b9050866001600160a01b0316816001600160a01b0316146113765761137661210d565b6001600160a01b03811661138c5761138c61210d565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113d4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113f891906120ab565b6001600160a01b03161461140e5761140e61210d565b604051630ab714fb60e11b81526001600160a01b0383169063156e29f69061143e90889088908890600401612371565b6020604051808303816000875af115801561145d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114819190612199565b61149d5760405162461bcd60e51b81526004016104d7906123ed565b846001600160a01b0316826001600160a01b0316886001600160a01b03167f2930d932c1cccd6add2e0e2d706ede9015db8a194405f2a3e1783703515e104f898888604051610e6393929190612371565b60008080808061150086880188612503565b6001600160a01b0380861660009081526101006020526040902054959b5093995091975095509350169050806115485760405162461bcd60e51b81526004016104d7906120c8565b61155181611b32565b61155d5761155d61210d565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561159d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115c191906120ab565b9050866001600160a01b0316816001600160a01b0316146115e4576115e461210d565b6001600160a01b0381166115fa576115fa61210d565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015611642573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061166691906120ab565b6001600160a01b03161461167c5761167c61210d565b604051635712868360e01b81526001600160a01b038316906357128683906116ac90889088908890600401612648565b6020604051808303816000875af11580156116cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116ef9190612199565b61170b5760405162461bcd60e51b81526004016104d7906123ed565b856001600160a01b0316826001600160a01b0316886001600160a01b03167f17304b99f8dfa5a2b8dd5695d82f9947c2abfbc9cb64bab610b9a1a0feadb9a0888888604051610e6393929190612648565b60008061176b838501856126bd565b9093509150506001600160a01b0382166117875761178761210d565b6001600160a01b038281166000908152610100602052604090205416156117b0576117b061210d565b60ff546040516bffffffffffffffffffffffff19606085901b166020820152600091611800916001600160a01b039091169060340160405160208183030381529060405280519060200120611bc7565b6001600160a01b03848116600090815261010060205260409081902080546001600160a01b0319169284169283179055516379ccf11760e11b81529192509063f399e22e9061185590869086906004016122fc565b600060405180830381600087803b15801561186f57600080fd5b505af1158015611883573d6000803e3d6000fd5b50506040516001600160a01b038085169350861691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a35050505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60c95460ff1615611a2357604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161197a919061276d565b6000604051808303818686fa925050503d80600081146119b6576040519150601f19603f3d011682016040523d82523d6000602084013e6119bb565b606091505b50915091508180156119e057506000818060200190518101906119de9190612789565b115b611a205760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b60448201526064016104d7565b50505b60c954610100900460ff161561046b57604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b019161138891611a8a919061276d565b6000604051808303818686fa925050503d8060008114611ac6576040519150601f19603f3d011682016040523d82523d6000602084013e611acb565b606091505b5091509150818015611af1575080806020019051810190611aec9190612789565b600114155b611b2e5760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b60448201526064016104d7565b5050565b6000816001600160a01b03163b600003611b4e57506000919050565b6040516301ffc9a760e01b8152636cdb3d1360e11b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611bb5575060408051601f3d908101601f19168201909252611bb291810190612199565b60015b611bc157506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116611bc15760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b60448201526064016104d7565b6001600160a01b03811681146104e957600080fd5b600060208284031215611c8557600080fd5b8135611c9081611c5e565b9392505050565b60008083601f840112611ca957600080fd5b5081356001600160401b03811115611cc057600080fd5b6020830191508360208260051b8501011115611cdb57600080fd5b9250929050565b60008060008060008060006080888a031215611cfd57600080fd5b8735611d0881611c5e565b965060208801356001600160401b0380821115611d2457600080fd5b611d308b838c01611c97565b909850965060408a0135915080821115611d4957600080fd5b611d558b838c01611c97565b909650945060608a0135915080821115611d6e57600080fd5b50611d7b8a828b01611c97565b989b979a50959850939692959293505050565b600080600060608486031215611da357600080fd5b8335611dae81611c5e565b95602085013595506040909401359392505050565b60008060008060808587031215611dd957600080fd5b8435611de481611c5e565b93506020850135611df481611c5e565b93969395505050506040820135916060013590565b80151581146104e957600080fd5b600060208284031215611e2957600080fd5b8135611c9081611e09565b600080600080600080600060e0888a031215611e4f57600080fd5b8735611e5a81611c5e565b96506020880135611e6a81611c5e565b95506040880135611e7a81611c5e565b94506060880135611e8a81611c5e565b93506080880135611e9a81611e09565b925060a0880135611eaa81611e09565b915060c0880135611eba81611c5e565b8091505092959891949750929550565b60008060008060608587031215611ee057600080fd5b843593506020850135611ef281611c5e565b925060408501356001600160401b0380821115611f0e57600080fd5b818701915087601f830112611f2257600080fd5b813581811115611f3157600080fd5b886020828501011115611f4357600080fd5b95989497505060200194505050565b60008060008060808587031215611f6857600080fd5b8435611f7381611c5e565b93506020850135611f8381611c5e565b92506040850135611f9381611c5e565b91506060850135611fa381611c5e565b939692955090935050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6000808585111561203057600080fd5b8386111561203d57600080fd5b5050820193919092039150565b80356020831015611bc157600019602084900360031b1b1692915050565b60208082526023908201527f4368696c64455243313135355072656469636174653a204e4f545f434f4e54526040820152621050d560ea1b606082015260800190565b6000602082840312156120bd57600080fd5b8151611c9081611c5e565b60208082526025908201527f4368696c64455243313135355072656469636174653a20554e4d41505045445f6040820152642a27a5a2a760d91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b0383111561213c57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b038616815260606020820181905260009061217a9083018688612123565b828103604084015261218d818587612123565b98975050505050505050565b6000602082840312156121ab57600080fd5b8151611c9081611e09565b60208082526022908201527f4368696c64455243313135355072656469636174653a204255524e5f4641494c604082015261115160f21b606082015260800190565b8183526000602080850194508260005b8581101561223657813561221b81611c5e565b6001600160a01b031687529582019590820190600101612208565b509495945050505050565b8981526001600160a01b0389811660208301528816604082015260c060608201819052600090612274908301888a6121f8565b8281036080840152612287818789612123565b905082810360a084015261229c818587612123565b9c9b505050505050505050505050565b60005b838110156122c75781810151838201526020016122af565b50506000910152565b600081518084526122e88160208601602086016122ac565b601f01601f19169290920160200192915050565b6001600160a01b0383168152604060208201819052600090612320908301846122d0565b949350505050565b60608152600061233c60608301888a6121f8565b828103602084015261234f818789612123565b90508281036040840152612364818587612123565b9998505050505050505050565b6001600160a01b039390931683526020830191909152604082015260600190565b600080600080600060a086880312156123aa57600080fd5b85356123b581611c5e565b945060208601356123c581611c5e565b935060408601356123d581611c5e565b94979396509394606081013594506080013592915050565b60208082526022908201527f4368696c64455243313135355072656469636174653a204d494e545f4641494c604082015261115160f21b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b038111828210171561246d5761246d61242f565b604052919050565b60006001600160401b0382111561248e5761248e61242f565b5060051b60200190565b600082601f8301126124a957600080fd5b813560206124be6124b983612475565b612445565b82815260059290921b840181019181810190868411156124dd57600080fd5b8286015b848110156124f857803583529183019183016124e1565b509695505050505050565b60008060008060008060c0878903121561251c57600080fd5b8635955060208088013561252f81611c5e565b9550604088013561253f81611c5e565b945060608801356001600160401b038082111561255b57600080fd5b818a0191508a601f83011261256f57600080fd5b813561257d6124b982612475565b81815260059190911b8301840190848101908d83111561259c57600080fd5b938501935b828510156125c35784356125b481611c5e565b825293850193908501906125a1565b9750505060808a01359250808311156125db57600080fd5b6125e78b848c01612498565b945060a08a01359250808311156125fd57600080fd5b505061260b89828a01612498565b9150509295509295509295565b600081518084526020808501945080840160005b838110156122365781518752958201959082019060010161262c565b606080825284519082018190526000906020906080840190828801845b8281101561268a5781516001600160a01b031684529284019290840190600101612665565b5050508381038285015261269e8187612618565b91505082810360408401526126b38185612618565b9695505050505050565b6000806000606084860312156126d257600080fd5b833592506020808501356126e581611c5e565b925060408501356001600160401b038082111561270157600080fd5b818701915087601f83011261271557600080fd5b8135818111156127275761272761242f565b612739601f8201601f19168501612445565b9150808252888482850101111561274f57600080fd5b80848401858401376000848284010152508093505050509250925092565b6000825161277f8184602087016122ac565b9190910192915050565b60006020828403121561279b57600080fd5b505191905056fea26469706673582212205d35ced486c2210da22d8254dca762acf5687ec4e599a9e2dbab26a8777a105964736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101b05760003560e01c8063b1768065116100ef578063d7c9e3ec11610092578063d7c9e3ec146103a5578063d8dd1773146103cc578063e0563ab1146103df578063e30c3978146103e8578063eeb49945146103f9578063f2fde38b1461040c578063f64512551461041f578063f8c8765e1461044657600080fd5b8063b1768065146102d1578063b5c5f672146102f8578063b68ad1e41461030b578063b8cd3ec01461031e578063c1225a2014610331578063c5ac2b1c14610344578063c5e4683a1461036b578063d41f17711461037e57600080fd5b80635ea5df79116101575780635ea5df7914610247578063715018a61461025e57806379ba5097146102685780637efab4f51461027057806386937eb41461029a5780638da5cb5b146102ad578063947287cf146102be57806397e5230d146102c757600080fd5b8063051eb2e2146101b557806305dc2e8f146101e557806307b3e252146101f85780631bc114ba14610206578063284017f5146102195780633b878c221461022257806351351d531461022b57806355b01e4d14610239575b600080fd5b60fe546101c8906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b60fd546101c8906001600160a01b031681565b6101c86004600360981b0181565b60fc546101c8906001600160a01b031681565b6101c861202081565b6101c861101081565b6101c86002600160a01b0381565b6101c86004600160991b0181565b61025061138881565b6040519081526020016101dc565b610266610459565b005b61026661046d565b6101c861027e366004611c73565b610100602052600090815260409020546001600160a01b031681565b6102666102a8366004611ce2565b6104ec565b6033546001600160a01b03166101c8565b61025061520881565b610250620249f081565b6102507f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b610266610306366004611d8e565b61050c565b60ff546101c8906001600160a01b031681565b61026661032c366004611dc3565b610525565b61026661033f366004611e17565b61053f565b6102507faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b610266610379366004611e17565b61058e565b6102507f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102507f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b6102666103da366004611e34565b6105d5565b6101c861203081565b6065546001600160a01b03166101c8565b610266610407366004611eca565b6106f6565b61026661041a366004611c73565b610911565b6102507f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b610266610454366004611f52565b610982565b610461610a7b565b61046b6000610ad5565b565b60655433906001600160a01b031681146104e05760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084015b60405180910390fd5b6104e981610ad5565b50565b6104f4610aee565b61050387878787878787610af6565b50505050505050565b610514610aee565b61052083338484610e76565b505050565b61052d610aee565b61053984848484610e76565b50505050565b610547610a7b565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b610596610a7b565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b03146105fc5760405163973d02cb60e01b81526004016104d790611fae565b600054610100900460ff161580801561061c5750600054600160ff909116105b806106365750303b158015610636575060005460ff166001145b6106525760405162461bcd60e51b81526004016104d790611fd2565b6000805460ff191660011790558015610675576000805461ff0019166101001790555b6106818888888861118c565b60c9805461ffff191685151561ff00191617610100851515021790556106a682610ad5565b80156106ec576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146107635760405162461bcd60e51b815260206004820152602a60248201527f4368696c64455243313135355072656469636174653a204f4e4c595f5354415460448201526922afa922a1a2a4ab22a960b11b60648201526084016104d7565b60fe546001600160a01b038481169116146107d35760405162461bcd60e51b815260206004820152602a60248201527f4368696c64455243313135355072656469636174653a204f4e4c595f524f4f546044820152695f50524544494341544560b01b60648201526084016104d7565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f821610802602060008486612020565b61080b9161204a565b0361082a576108256108208260208186612020565b611282565b610539565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18610859602060008486612020565b6108629161204a565b036108715761082582826114ee565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad6108a0602060008486612020565b6108a99161204a565b036108b857610825828261175c565b60405162461bcd60e51b815260206004820152602860248201527f4368696c64455243313135355072656469636174653a20494e56414c49445f5360448201526749474e415455524560c01b60648201526084016104d7565b610919610a7b565b606580546001600160a01b0383166001600160a01b0319909116811790915561094a6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b336002600160a01b03146109a95760405163973d02cb60e01b81526004016104d790611fae565b600054610100900460ff16158080156109c95750600054600160ff909116105b806109e35750303b1580156109e3575060005460ff166001145b6109ff5760405162461bcd60e51b81526004016104d790611fd2565b6000805460ff191660011790558015610a22576000805461ff0019166101001790555b610a2e8585858561118c565b8015610a74576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6033546001600160a01b0316331461046b5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104d7565b606580546001600160a01b03191690556104e9816118c6565b61046b611918565b86610b0081611b32565b610b1c5760405162461bcd60e51b81526004016104d790612068565b6000886001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b5c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8091906120ab565b6001600160a01b03818116600090815261010060205260409020549192508a8116911614610bc05760405162461bcd60e51b81526004016104d7906120c8565b6001600160a01b038116610bd657610bd661210d565b306001600160a01b0316896001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c4291906120ab565b6001600160a01b031614610c5857610c5861210d565b8685148015610c6657508483145b610cc05760405162461bcd60e51b815260206004820152602560248201527f4368696c64455243313135355072656469636174653a20494e56414c49445f4c60448201526408a9c8ea8960db1b60648201526084016104d7565b604051631ac8311560e21b81526001600160a01b038a1690636b20c45490610cf49033908a908a908a908a90600401612155565b6020604051808303816000875af1158015610d13573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d379190612199565b610d535760405162461bcd60e51b81526004016104d7906121b6565b60fc5460fe546040516001600160a01b03928316926316f19831921690610dae907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908f908f908f908f908f908f90602001612241565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610dda9291906122fc565b600060405180830381600087803b158015610df457600080fd5b505af1158015610e08573d6000803e3d6000fd5b50505050336001600160a01b0316896001600160a01b0316826001600160a01b03167f7a10660242ca367951ff3777cdb3c2a761e3ccad204bac118501e24693f3683d8b8b8b8b8b8b604051610e6396959493929190612328565b60405180910390a4505050505050505050565b83610e8081611b32565b610e9c5760405162461bcd60e51b81526004016104d790612068565b6000856001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610edc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f0091906120ab565b6001600160a01b0381811660009081526101006020526040902054919250878116911614610f405760405162461bcd60e51b81526004016104d7906120c8565b6001600160a01b038116610f5657610f5661210d565b306001600160a01b0316866001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f9e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fc291906120ab565b6001600160a01b031614610fd857610fd861210d565b604051637a94c56560e11b81526001600160a01b0387169063f5298aca9061100890339088908890600401612371565b6020604051808303816000875af1158015611027573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061104b9190612199565b6110675760405162461bcd60e51b81526004016104d7906121b6565b60fc5460fe54604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152898116608083015260a0820189905260c08083018990528351808403909101815260e08301938490526316f1983160e01b909352938416936316f19831936110f99391169160e4016122fc565b600060405180830381600087803b15801561111357600080fd5b505af1158015611127573d6000803e3d6000fd5b50505050846001600160a01b0316866001600160a01b0316826001600160a01b03167f2ca9093e8b5356801039806c6a08003e5b7013fb8ae48f720fc90fc1c1a8bec233888860405161117c93929190612371565b60405180910390a4505050505050565b6001600160a01b038416158015906111ac57506001600160a01b03831615155b80156111c057506001600160a01b03821615155b80156111d457506001600160a01b03811615155b6112325760405162461bcd60e51b815260206004820152602960248201527f4368696c64455243313135355072656469636174653a204241445f494e49544960448201526820a624ad20aa24a7a760b91b60648201526084016104d7565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b60008080808061129486880188612392565b6001600160a01b0380861660009081526101006020526040902054959a509398509196509450925016806112da5760405162461bcd60e51b81526004016104d7906120c8565b6112e381611b32565b6112ef576112ef61210d565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561132f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061135391906120ab565b9050866001600160a01b0316816001600160a01b0316146113765761137661210d565b6001600160a01b03811661138c5761138c61210d565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113d4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113f891906120ab565b6001600160a01b03161461140e5761140e61210d565b604051630ab714fb60e11b81526001600160a01b0383169063156e29f69061143e90889088908890600401612371565b6020604051808303816000875af115801561145d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114819190612199565b61149d5760405162461bcd60e51b81526004016104d7906123ed565b846001600160a01b0316826001600160a01b0316886001600160a01b03167f2930d932c1cccd6add2e0e2d706ede9015db8a194405f2a3e1783703515e104f898888604051610e6393929190612371565b60008080808061150086880188612503565b6001600160a01b0380861660009081526101006020526040902054959b5093995091975095509350169050806115485760405162461bcd60e51b81526004016104d7906120c8565b61155181611b32565b61155d5761155d61210d565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561159d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115c191906120ab565b9050866001600160a01b0316816001600160a01b0316146115e4576115e461210d565b6001600160a01b0381166115fa576115fa61210d565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015611642573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061166691906120ab565b6001600160a01b03161461167c5761167c61210d565b604051635712868360e01b81526001600160a01b038316906357128683906116ac90889088908890600401612648565b6020604051808303816000875af11580156116cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116ef9190612199565b61170b5760405162461bcd60e51b81526004016104d7906123ed565b856001600160a01b0316826001600160a01b0316886001600160a01b03167f17304b99f8dfa5a2b8dd5695d82f9947c2abfbc9cb64bab610b9a1a0feadb9a0888888604051610e6393929190612648565b60008061176b838501856126bd565b9093509150506001600160a01b0382166117875761178761210d565b6001600160a01b038281166000908152610100602052604090205416156117b0576117b061210d565b60ff546040516bffffffffffffffffffffffff19606085901b166020820152600091611800916001600160a01b039091169060340160405160208183030381529060405280519060200120611bc7565b6001600160a01b03848116600090815261010060205260409081902080546001600160a01b0319169284169283179055516379ccf11760e11b81529192509063f399e22e9061185590869086906004016122fc565b600060405180830381600087803b15801561186f57600080fd5b505af1158015611883573d6000803e3d6000fd5b50506040516001600160a01b038085169350861691507f46bd56f98e1b14fd35691959270a6e1edf7cb8fcd489e0f9dda89e46c0d1ff0d90600090a35050505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60c95460ff1615611a2357604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161197a919061276d565b6000604051808303818686fa925050503d80600081146119b6576040519150601f19603f3d011682016040523d82523d6000602084013e6119bb565b606091505b50915091508180156119e057506000818060200190518101906119de9190612789565b115b611a205760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b60448201526064016104d7565b50505b60c954610100900460ff161561046b57604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b019161138891611a8a919061276d565b6000604051808303818686fa925050503d8060008114611ac6576040519150601f19603f3d011682016040523d82523d6000602084013e611acb565b606091505b5091509150818015611af1575080806020019051810190611aec9190612789565b600114155b611b2e5760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b60448201526064016104d7565b5050565b6000816001600160a01b03163b600003611b4e57506000919050565b6040516301ffc9a760e01b8152636cdb3d1360e11b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611bb5575060408051601f3d908101601f19168201909252611bb291810190612199565b60015b611bc157506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116611bc15760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b60448201526064016104d7565b6001600160a01b03811681146104e957600080fd5b600060208284031215611c8557600080fd5b8135611c9081611c5e565b9392505050565b60008083601f840112611ca957600080fd5b5081356001600160401b03811115611cc057600080fd5b6020830191508360208260051b8501011115611cdb57600080fd5b9250929050565b60008060008060008060006080888a031215611cfd57600080fd5b8735611d0881611c5e565b965060208801356001600160401b0380821115611d2457600080fd5b611d308b838c01611c97565b909850965060408a0135915080821115611d4957600080fd5b611d558b838c01611c97565b909650945060608a0135915080821115611d6e57600080fd5b50611d7b8a828b01611c97565b989b979a50959850939692959293505050565b600080600060608486031215611da357600080fd5b8335611dae81611c5e565b95602085013595506040909401359392505050565b60008060008060808587031215611dd957600080fd5b8435611de481611c5e565b93506020850135611df481611c5e565b93969395505050506040820135916060013590565b80151581146104e957600080fd5b600060208284031215611e2957600080fd5b8135611c9081611e09565b600080600080600080600060e0888a031215611e4f57600080fd5b8735611e5a81611c5e565b96506020880135611e6a81611c5e565b95506040880135611e7a81611c5e565b94506060880135611e8a81611c5e565b93506080880135611e9a81611e09565b925060a0880135611eaa81611e09565b915060c0880135611eba81611c5e565b8091505092959891949750929550565b60008060008060608587031215611ee057600080fd5b843593506020850135611ef281611c5e565b925060408501356001600160401b0380821115611f0e57600080fd5b818701915087601f830112611f2257600080fd5b813581811115611f3157600080fd5b886020828501011115611f4357600080fd5b95989497505060200194505050565b60008060008060808587031215611f6857600080fd5b8435611f7381611c5e565b93506020850135611f8381611c5e565b92506040850135611f9381611c5e565b91506060850135611fa381611c5e565b939692955090935050565b6020808252600a908201526914d654d5115350d0531360b21b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6000808585111561203057600080fd5b8386111561203d57600080fd5b5050820193919092039150565b80356020831015611bc157600019602084900360031b1b1692915050565b60208082526023908201527f4368696c64455243313135355072656469636174653a204e4f545f434f4e54526040820152621050d560ea1b606082015260800190565b6000602082840312156120bd57600080fd5b8151611c9081611c5e565b60208082526025908201527f4368696c64455243313135355072656469636174653a20554e4d41505045445f6040820152642a27a5a2a760d91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b0383111561213c57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b038616815260606020820181905260009061217a9083018688612123565b828103604084015261218d818587612123565b98975050505050505050565b6000602082840312156121ab57600080fd5b8151611c9081611e09565b60208082526022908201527f4368696c64455243313135355072656469636174653a204255524e5f4641494c604082015261115160f21b606082015260800190565b8183526000602080850194508260005b8581101561223657813561221b81611c5e565b6001600160a01b031687529582019590820190600101612208565b509495945050505050565b8981526001600160a01b0389811660208301528816604082015260c060608201819052600090612274908301888a6121f8565b8281036080840152612287818789612123565b905082810360a084015261229c818587612123565b9c9b505050505050505050505050565b60005b838110156122c75781810151838201526020016122af565b50506000910152565b600081518084526122e88160208601602086016122ac565b601f01601f19169290920160200192915050565b6001600160a01b0383168152604060208201819052600090612320908301846122d0565b949350505050565b60608152600061233c60608301888a6121f8565b828103602084015261234f818789612123565b90508281036040840152612364818587612123565b9998505050505050505050565b6001600160a01b039390931683526020830191909152604082015260600190565b600080600080600060a086880312156123aa57600080fd5b85356123b581611c5e565b945060208601356123c581611c5e565b935060408601356123d581611c5e565b94979396509394606081013594506080013592915050565b60208082526022908201527f4368696c64455243313135355072656469636174653a204d494e545f4641494c604082015261115160f21b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b038111828210171561246d5761246d61242f565b604052919050565b60006001600160401b0382111561248e5761248e61242f565b5060051b60200190565b600082601f8301126124a957600080fd5b813560206124be6124b983612475565b612445565b82815260059290921b840181019181810190868411156124dd57600080fd5b8286015b848110156124f857803583529183019183016124e1565b509695505050505050565b60008060008060008060c0878903121561251c57600080fd5b8635955060208088013561252f81611c5e565b9550604088013561253f81611c5e565b945060608801356001600160401b038082111561255b57600080fd5b818a0191508a601f83011261256f57600080fd5b813561257d6124b982612475565b81815260059190911b8301840190848101908d83111561259c57600080fd5b938501935b828510156125c35784356125b481611c5e565b825293850193908501906125a1565b9750505060808a01359250808311156125db57600080fd5b6125e78b848c01612498565b945060a08a01359250808311156125fd57600080fd5b505061260b89828a01612498565b9150509295509295509295565b600081518084526020808501945080840160005b838110156122365781518752958201959082019060010161262c565b606080825284519082018190526000906020906080840190828801845b8281101561268a5781516001600160a01b031684529284019290840190600101612665565b5050508381038285015261269e8187612618565b91505082810360408401526126b38185612618565b9695505050505050565b6000806000606084860312156126d257600080fd5b833592506020808501356126e581611c5e565b925060408501356001600160401b038082111561270157600080fd5b818701915087601f83011261271557600080fd5b8135818111156127275761272761242f565b612739601f8201601f19168501612445565b9150808252888482850101111561274f57600080fd5b80848401858401376000848284010152508093505050509250925092565b6000825161277f8184602087016122ac565b9190910192915050565b60006020828403121561279b57600080fd5b505191905056fea26469706673582212205d35ced486c2210da22d8254dca762acf5687ec4e599a9e2dbab26a8777a105964736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RootMintableERC1155PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RootMintableERC1155Predicate","sourceName":"contracts/child/RootMintableERC1155Predicate.sol","abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2MintableERC1155Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"L2MintableERC1155DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2MintableERC1155Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"L2MintableERC1155WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2MintableTokenMapped","type":"event"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childERC1155Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"depositBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newChildERC1155Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"}],"name":"mapToken","outputs":[{"internalType":"address","name":"childToken","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611c01806100206000396000f3fe608060405234801561001057600080fd5b50600436106101215760003560e01c8063b68ad1e4116100ad578063eeb4994511610071578063eeb49945146102fe578063f23a6e6114610311578063f4a120f714610330578063f645125514610343578063f8c8765e1461036a57600080fd5b8063b68ad1e41461023e578063bc197c8114610251578063c5ac2b1c14610289578063d41f1771146102b0578063d7c9e3ec146102d757600080fd5b80634c4c45de116100f45780634c4c45de146101a7578063654715e6146101ba5780637efab4f5146101cd578063a78e111c146101f6578063b17680651461020957600080fd5b806301ffc9a71461012657806305dc2e8f1461014e5780630efe6a8b146101795780631bc114ba1461018e575b600080fd5b610139610134366004611182565b61037d565b60405190151581526020015b60405180910390f35b600154610161906001600160a01b031681565b6040516001600160a01b039091168152602001610145565b61018c6101873660046111cb565b6103b4565b005b600054610161906201000090046001600160a01b031681565b61018c6101b536600461124b565b6103c5565b600254610161906001600160a01b031681565b6101616101db3660046112f7565b6004602052600090815260409020546001600160a01b031681565b61018c610204366004611314565b61043f565b6102307f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b604051908152602001610145565b600354610161906001600160a01b031681565b61027061025f3660046114a6565b63bc197c8160e01b95945050505050565b6040516001600160e01b03199091168152602001610145565b6102307faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6102307f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102307f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61018c61030c366004611553565b610451565b61027061031f3660046115db565b63f23a6e6160e01b95945050505050565b61016161033e3660046112f7565b610629565b6102307f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61018c610378366004611643565b610944565b60006001600160e01b03198216630271189760e51b14806103ae57506301ffc9a760e01b6001600160e01b03198316145b92915050565b6103c083338484610a5d565b505050565b84831480156103d357508481145b6104275760405162461bcd60e51b815260206004820152602c6024820152600080516020611bac83398151915260448201526b0ac82989288be988a9c8ea8960a31b60648201526084015b60405180910390fd5b61043687878787878787610bf3565b50505050505050565b61044b84848484610a5d565b50505050565b6001546001600160a01b031633146104c55760405162461bcd60e51b815260206004820152603160248201527f526f6f744d696e7461626c65455243313135355072656469636174653a204f4e604482015270262cafa9aa20aa22afa922a1a2a4ab22a960791b606482015260840161041e565b6002546001600160a01b0384811691161461053d5760405162461bcd60e51b815260206004820152603260248201527f526f6f744d696e7461626c65455243313135355072656469636174653a204f4e6044820152714c595f4348494c445f50524544494341544560701b606482015260840161041e565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286961056c60206000848661169f565b610575916116c9565b036105945761058f61058a826020818661169f565b610dd3565b61044b565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed26105c360206000848661169f565b6105cc916116c9565b036105db5761058f8282610ed2565b60405162461bcd60e51b815260206004820152602f6024820152600080516020611bac83398151915260448201526e56414c49445f5349474e415455524560881b606482015260840161041e565b60006001600160a01b0382166106835760405162461bcd60e51b815260206004820152602b6024820152600080516020611bac83398151915260448201526a2b20a624a22faa27a5a2a760a91b606482015260840161041e565b6001600160a01b0382811660009081526004602052604090205416156107005760405162461bcd60e51b815260206004820152602c60248201527f526f6f744d696e7461626c65455243313135355072656469636174653a20414c60448201526b149150511657d3505414115160a21b606482015260840161041e565b6002546003546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b03928316926107aa921690603401604051602081830303815290604052805190602001208360405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b03848116600081815260046020818152604080842080546001600160a01b031916968816969096179095558451908101855282815293516303a24d0760e21b8152908101919091529294509091630e89341c90602401600060405180830381865afa92505050801561084557506040513d6000823e601f3d908101601f19168201604052610842919081019061170b565b60015b1561084d5790505b600060029054906101000a90046001600160a01b03166001600160a01b03166316f19831837f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad87856040516020016108a7939291906117ad565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016108d39291906117d7565b600060405180830381600087803b1580156108ed57600080fd5b505af1158015610901573d6000803e3d6000fd5b50506040516001600160a01b038087169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a35050919050565b600054610100900460ff16158080156109645750600054600160ff909116105b8061097e5750303b15801561097e575060005460ff166001145b6109e15760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161041e565b6000805460ff191660011790558015610a04576000805461ff0019166101001790555b610a1085858585611033565b8015610a56576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6000610a688561113b565b604051637921219560e11b81529091506001600160a01b0386169063f242432a90610a9d903390309088908890600401611803565b600060405180830381600087803b158015610ab757600080fd5b505af1158015610acb573d6000803e3d6000fd5b5050600054600254604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038b8116828401523360608301528a8116608083015260a082018a905260c08083018a90528351808403909101815260e08301938490526316f1983160e01b90935262010000909404841695506316f198319450610b68939092169160e4016117d7565b600060405180830381600087803b158015610b8257600080fd5b505af1158015610b96573d6000803e3d6000fd5b505060408051338152602081018790529081018590526001600160a01b03808816935084811692508816907fe6f1fb6bb1930a5d4b997d56e0a4a8cb6f3f941f0d5391f63c1f6914ee01e0b89060600160405180910390a4610a56565b6000610bfe8861113b565b905060005b84811015610caa57886001600160a01b031663f242432a3330898986818110610c2e57610c2e61183b565b90506020020135888887818110610c4757610c4761183b565b905060200201356040518563ffffffff1660e01b8152600401610c6d9493929190611803565b600060405180830381600087803b158015610c8757600080fd5b505af1158015610c9b573d6000803e3d6000fd5b50505050806001019050610c03565b506000546002546040516001600160a01b03620100009093048316926316f19831921690610d0c907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908d9033908e908e908e908e908e908e906020016118cc565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610d389291906117d7565b600060405180830381600087803b158015610d5257600080fd5b505af1158015610d66573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316896001600160a01b03167fa7d9cd27143f817fe006011faf3c04351f70f75cd30e7b21405817f2f670041e8a8a8a8a8a8a604051610dc196959493929190611937565b60405180910390a45050505050505050565b600080808080610de586880188611980565b6001600160a01b03808616600090815260046020526040902054959a50939850919650945092501680610e1a57610e1a6119db565b604051637921219560e11b81526001600160a01b0387169063f242432a90610e4c903090889088908890600401611803565b600060405180830381600087803b158015610e6657600080fd5b505af1158015610e7a573d6000803e3d6000fd5b5050604080516001600160a01b0389811682526020820188905291810186905281881693508482169250908916907f52cdfbe452878311d3237a4c20a951600d8e3a4fe6691d967d33788dc12d19c690606001610dc1565b600080808080610ee4868801886119f1565b6001600160a01b03808616600090815260046020526040902054959b509399509197509550935016905080610f1b57610f1b6119db565b60005b8351811015610fe157866001600160a01b031663f242432a30878481518110610f4957610f4961183b565b6020026020010151878581518110610f6357610f6361183b565b6020026020010151878681518110610f7d57610f7d61183b565b60200260200101516040518563ffffffff1660e01b8152600401610fa49493929190611803565b600060405180830381600087803b158015610fbe57600080fd5b505af1158015610fd2573d6000803e3d6000fd5b50505050806001019050610f1e565b50846001600160a01b0316816001600160a01b0316876001600160a01b03167f52733a40a0466155541c2fc4044165dd37a3db116bc834bab2f020665e90fd20878787604051610dc193929190611b36565b6001600160a01b0384161580159061105357506001600160a01b03831615155b801561106757506001600160a01b03821615155b801561107b57506001600160a01b03811615155b6110e05760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c65455243313135355072656469636174653a20424160448201526f222fa4a724aa24a0a624ad20aa24a7a760811b606482015260840161041e565b600080546001600160a01b03958616620100000262010000600160b01b0319909116179055600180549385166001600160a01b0319948516179055600280549285169284169290921790915560038054919093169116179055565b6001600160a01b0380821660009081526004602052604090205416806111675761116482610629565b90505b6001600160a01b03811661117d5761117d6119db565b919050565b60006020828403121561119457600080fd5b81356001600160e01b0319811681146111ac57600080fd5b9392505050565b6001600160a01b03811681146111c857600080fd5b50565b6000806000606084860312156111e057600080fd5b83356111eb816111b3565b95602085013595506040909401359392505050565b60008083601f84011261121257600080fd5b5081356001600160401b0381111561122957600080fd5b6020830191508360208260051b850101111561124457600080fd5b9250929050565b60008060008060008060006080888a03121561126657600080fd5b8735611271816111b3565b965060208801356001600160401b038082111561128d57600080fd5b6112998b838c01611200565b909850965060408a01359150808211156112b257600080fd5b6112be8b838c01611200565b909650945060608a01359150808211156112d757600080fd5b506112e48a828b01611200565b989b979a50959850939692959293505050565b60006020828403121561130957600080fd5b81356111ac816111b3565b6000806000806080858703121561132a57600080fd5b8435611335816111b3565b93506020850135611345816111b3565b93969395505050506040820135916060013590565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156113985761139861135a565b604052919050565b60006001600160401b038211156113b9576113b961135a565b5060051b60200190565b600082601f8301126113d457600080fd5b813560206113e96113e4836113a0565b611370565b82815260059290921b8401810191818101908684111561140857600080fd5b8286015b84811015611423578035835291830191830161140c565b509695505050505050565b60006001600160401b038211156114475761144761135a565b50601f01601f191660200190565b600082601f83011261146657600080fd5b81356114746113e48261142e565b81815284602083860101111561148957600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a086880312156114be57600080fd5b85356114c9816111b3565b945060208601356114d9816111b3565b935060408601356001600160401b03808211156114f557600080fd5b61150189838a016113c3565b9450606088013591508082111561151757600080fd5b61152389838a016113c3565b9350608088013591508082111561153957600080fd5b5061154688828901611455565b9150509295509295909350565b6000806000806060858703121561156957600080fd5b84359350602085013561157b816111b3565b925060408501356001600160401b038082111561159757600080fd5b818701915087601f8301126115ab57600080fd5b8135818111156115ba57600080fd5b8860208285010111156115cc57600080fd5b95989497505060200194505050565b600080600080600060a086880312156115f357600080fd5b85356115fe816111b3565b9450602086013561160e816111b3565b9350604086013592506060860135915060808601356001600160401b0381111561163757600080fd5b61154688828901611455565b6000806000806080858703121561165957600080fd5b8435611664816111b3565b93506020850135611674816111b3565b92506040850135611684816111b3565b91506060850135611694816111b3565b939692955090935050565b600080858511156116af57600080fd5b838611156116bc57600080fd5b5050820193919092039150565b803560208310156103ae57600019602084900360031b1b1692915050565b60005b838110156117025781810151838201526020016116ea565b50506000910152565b60006020828403121561171d57600080fd5b81516001600160401b0381111561173357600080fd5b8201601f8101841361174457600080fd5b80516117526113e48261142e565b81815285602083850101111561176757600080fd5b6117788260208301602086016116e7565b95945050505050565b600081518084526117998160208601602086016116e7565b601f01601f19169290920160200192915050565b8381526001600160a01b038316602082015260606040820181905260009061177890830184611781565b6001600160a01b03831681526040602082018190526000906117fb90830184611781565b949350505050565b6001600160a01b0394851681529290931660208301526040820152606081019190915260a06080820181905260009082015260c00190565b634e487b7160e01b600052603260045260246000fd5b8183526000602080850194508260005b8581101561188f578135611874816111b3565b6001600160a01b031687529582019590820190600101611861565b509495945050505050565b81835260006001600160fb1b038311156118b357600080fd5b8260051b80836020870137939093016020019392505050565b8981526001600160a01b0389811660208301528816604082015260c0606082018190526000906118ff908301888a611851565b828103608084015261191281878961189a565b905082810360a084015261192781858761189a565b9c9b505050505050505050505050565b60608152600061194b60608301888a611851565b828103602084015261195e81878961189a565b9050828103604084015261197381858761189a565b9998505050505050505050565b600080600080600060a0868803121561199857600080fd5b85356119a3816111b3565b945060208601356119b3816111b3565b935060408601356119c3816111b3565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052600160045260246000fd5b60008060008060008060c08789031215611a0a57600080fd5b86359550602080880135611a1d816111b3565b95506040880135611a2d816111b3565b945060608801356001600160401b0380821115611a4957600080fd5b818a0191508a601f830112611a5d57600080fd5b8135611a6b6113e4826113a0565b81815260059190911b8301840190848101908d831115611a8a57600080fd5b938501935b82851015611ab1578435611aa2816111b3565b82529385019390850190611a8f565b9750505060808a0135925080831115611ac957600080fd5b611ad58b848c016113c3565b945060a08a0135925080831115611aeb57600080fd5b5050611af989828a016113c3565b9150509295509295509295565b600081518084526020808501945080840160005b8381101561188f57815187529582019590820190600101611b1a565b606080825284519082018190526000906020906080840190828801845b82811015611b785781516001600160a01b031684529284019290840190600101611b53565b50505083810382850152611b8c8187611b06565b9150508281036040840152611ba18185611b06565b969550505050505056fe526f6f744d696e7461626c65455243313135355072656469636174653a20494ea2646970667358221220bac4157c867ef25ba423441a6f4177226bff4063bb665be9fb147719e7b08cc664736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101215760003560e01c8063b68ad1e4116100ad578063eeb4994511610071578063eeb49945146102fe578063f23a6e6114610311578063f4a120f714610330578063f645125514610343578063f8c8765e1461036a57600080fd5b8063b68ad1e41461023e578063bc197c8114610251578063c5ac2b1c14610289578063d41f1771146102b0578063d7c9e3ec146102d757600080fd5b80634c4c45de116100f45780634c4c45de146101a7578063654715e6146101ba5780637efab4f5146101cd578063a78e111c146101f6578063b17680651461020957600080fd5b806301ffc9a71461012657806305dc2e8f1461014e5780630efe6a8b146101795780631bc114ba1461018e575b600080fd5b610139610134366004611182565b61037d565b60405190151581526020015b60405180910390f35b600154610161906001600160a01b031681565b6040516001600160a01b039091168152602001610145565b61018c6101873660046111cb565b6103b4565b005b600054610161906201000090046001600160a01b031681565b61018c6101b536600461124b565b6103c5565b600254610161906001600160a01b031681565b6101616101db3660046112f7565b6004602052600090815260409020546001600160a01b031681565b61018c610204366004611314565b61043f565b6102307f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b604051908152602001610145565b600354610161906001600160a01b031681565b61027061025f3660046114a6565b63bc197c8160e01b95945050505050565b6040516001600160e01b03199091168152602001610145565b6102307faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6102307f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102307f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61018c61030c366004611553565b610451565b61027061031f3660046115db565b63f23a6e6160e01b95945050505050565b61016161033e3660046112f7565b610629565b6102307f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61018c610378366004611643565b610944565b60006001600160e01b03198216630271189760e51b14806103ae57506301ffc9a760e01b6001600160e01b03198316145b92915050565b6103c083338484610a5d565b505050565b84831480156103d357508481145b6104275760405162461bcd60e51b815260206004820152602c6024820152600080516020611bac83398151915260448201526b0ac82989288be988a9c8ea8960a31b60648201526084015b60405180910390fd5b61043687878787878787610bf3565b50505050505050565b61044b84848484610a5d565b50505050565b6001546001600160a01b031633146104c55760405162461bcd60e51b815260206004820152603160248201527f526f6f744d696e7461626c65455243313135355072656469636174653a204f4e604482015270262cafa9aa20aa22afa922a1a2a4ab22a960791b606482015260840161041e565b6002546001600160a01b0384811691161461053d5760405162461bcd60e51b815260206004820152603260248201527f526f6f744d696e7461626c65455243313135355072656469636174653a204f4e6044820152714c595f4348494c445f50524544494341544560701b606482015260840161041e565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286961056c60206000848661169f565b610575916116c9565b036105945761058f61058a826020818661169f565b610dd3565b61044b565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed26105c360206000848661169f565b6105cc916116c9565b036105db5761058f8282610ed2565b60405162461bcd60e51b815260206004820152602f6024820152600080516020611bac83398151915260448201526e56414c49445f5349474e415455524560881b606482015260840161041e565b60006001600160a01b0382166106835760405162461bcd60e51b815260206004820152602b6024820152600080516020611bac83398151915260448201526a2b20a624a22faa27a5a2a760a91b606482015260840161041e565b6001600160a01b0382811660009081526004602052604090205416156107005760405162461bcd60e51b815260206004820152602c60248201527f526f6f744d696e7461626c65455243313135355072656469636174653a20414c60448201526b149150511657d3505414115160a21b606482015260840161041e565b6002546003546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b03928316926107aa921690603401604051602081830303815290604052805190602001208360405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b03848116600081815260046020818152604080842080546001600160a01b031916968816969096179095558451908101855282815293516303a24d0760e21b8152908101919091529294509091630e89341c90602401600060405180830381865afa92505050801561084557506040513d6000823e601f3d908101601f19168201604052610842919081019061170b565b60015b1561084d5790505b600060029054906101000a90046001600160a01b03166001600160a01b03166316f19831837f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad87856040516020016108a7939291906117ad565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016108d39291906117d7565b600060405180830381600087803b1580156108ed57600080fd5b505af1158015610901573d6000803e3d6000fd5b50506040516001600160a01b038087169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a35050919050565b600054610100900460ff16158080156109645750600054600160ff909116105b8061097e5750303b15801561097e575060005460ff166001145b6109e15760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161041e565b6000805460ff191660011790558015610a04576000805461ff0019166101001790555b610a1085858585611033565b8015610a56576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6000610a688561113b565b604051637921219560e11b81529091506001600160a01b0386169063f242432a90610a9d903390309088908890600401611803565b600060405180830381600087803b158015610ab757600080fd5b505af1158015610acb573d6000803e3d6000fd5b5050600054600254604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038b8116828401523360608301528a8116608083015260a082018a905260c08083018a90528351808403909101815260e08301938490526316f1983160e01b90935262010000909404841695506316f198319450610b68939092169160e4016117d7565b600060405180830381600087803b158015610b8257600080fd5b505af1158015610b96573d6000803e3d6000fd5b505060408051338152602081018790529081018590526001600160a01b03808816935084811692508816907fe6f1fb6bb1930a5d4b997d56e0a4a8cb6f3f941f0d5391f63c1f6914ee01e0b89060600160405180910390a4610a56565b6000610bfe8861113b565b905060005b84811015610caa57886001600160a01b031663f242432a3330898986818110610c2e57610c2e61183b565b90506020020135888887818110610c4757610c4761183b565b905060200201356040518563ffffffff1660e01b8152600401610c6d9493929190611803565b600060405180830381600087803b158015610c8757600080fd5b505af1158015610c9b573d6000803e3d6000fd5b50505050806001019050610c03565b506000546002546040516001600160a01b03620100009093048316926316f19831921690610d0c907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908d9033908e908e908e908e908e908e906020016118cc565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610d389291906117d7565b600060405180830381600087803b158015610d5257600080fd5b505af1158015610d66573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316896001600160a01b03167fa7d9cd27143f817fe006011faf3c04351f70f75cd30e7b21405817f2f670041e8a8a8a8a8a8a604051610dc196959493929190611937565b60405180910390a45050505050505050565b600080808080610de586880188611980565b6001600160a01b03808616600090815260046020526040902054959a50939850919650945092501680610e1a57610e1a6119db565b604051637921219560e11b81526001600160a01b0387169063f242432a90610e4c903090889088908890600401611803565b600060405180830381600087803b158015610e6657600080fd5b505af1158015610e7a573d6000803e3d6000fd5b5050604080516001600160a01b0389811682526020820188905291810186905281881693508482169250908916907f52cdfbe452878311d3237a4c20a951600d8e3a4fe6691d967d33788dc12d19c690606001610dc1565b600080808080610ee4868801886119f1565b6001600160a01b03808616600090815260046020526040902054959b509399509197509550935016905080610f1b57610f1b6119db565b60005b8351811015610fe157866001600160a01b031663f242432a30878481518110610f4957610f4961183b565b6020026020010151878581518110610f6357610f6361183b565b6020026020010151878681518110610f7d57610f7d61183b565b60200260200101516040518563ffffffff1660e01b8152600401610fa49493929190611803565b600060405180830381600087803b158015610fbe57600080fd5b505af1158015610fd2573d6000803e3d6000fd5b50505050806001019050610f1e565b50846001600160a01b0316816001600160a01b0316876001600160a01b03167f52733a40a0466155541c2fc4044165dd37a3db116bc834bab2f020665e90fd20878787604051610dc193929190611b36565b6001600160a01b0384161580159061105357506001600160a01b03831615155b801561106757506001600160a01b03821615155b801561107b57506001600160a01b03811615155b6110e05760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c65455243313135355072656469636174653a20424160448201526f222fa4a724aa24a0a624ad20aa24a7a760811b606482015260840161041e565b600080546001600160a01b03958616620100000262010000600160b01b0319909116179055600180549385166001600160a01b0319948516179055600280549285169284169290921790915560038054919093169116179055565b6001600160a01b0380821660009081526004602052604090205416806111675761116482610629565b90505b6001600160a01b03811661117d5761117d6119db565b919050565b60006020828403121561119457600080fd5b81356001600160e01b0319811681146111ac57600080fd5b9392505050565b6001600160a01b03811681146111c857600080fd5b50565b6000806000606084860312156111e057600080fd5b83356111eb816111b3565b95602085013595506040909401359392505050565b60008083601f84011261121257600080fd5b5081356001600160401b0381111561122957600080fd5b6020830191508360208260051b850101111561124457600080fd5b9250929050565b60008060008060008060006080888a03121561126657600080fd5b8735611271816111b3565b965060208801356001600160401b038082111561128d57600080fd5b6112998b838c01611200565b909850965060408a01359150808211156112b257600080fd5b6112be8b838c01611200565b909650945060608a01359150808211156112d757600080fd5b506112e48a828b01611200565b989b979a50959850939692959293505050565b60006020828403121561130957600080fd5b81356111ac816111b3565b6000806000806080858703121561132a57600080fd5b8435611335816111b3565b93506020850135611345816111b3565b93969395505050506040820135916060013590565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156113985761139861135a565b604052919050565b60006001600160401b038211156113b9576113b961135a565b5060051b60200190565b600082601f8301126113d457600080fd5b813560206113e96113e4836113a0565b611370565b82815260059290921b8401810191818101908684111561140857600080fd5b8286015b84811015611423578035835291830191830161140c565b509695505050505050565b60006001600160401b038211156114475761144761135a565b50601f01601f191660200190565b600082601f83011261146657600080fd5b81356114746113e48261142e565b81815284602083860101111561148957600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a086880312156114be57600080fd5b85356114c9816111b3565b945060208601356114d9816111b3565b935060408601356001600160401b03808211156114f557600080fd5b61150189838a016113c3565b9450606088013591508082111561151757600080fd5b61152389838a016113c3565b9350608088013591508082111561153957600080fd5b5061154688828901611455565b9150509295509295909350565b6000806000806060858703121561156957600080fd5b84359350602085013561157b816111b3565b925060408501356001600160401b038082111561159757600080fd5b818701915087601f8301126115ab57600080fd5b8135818111156115ba57600080fd5b8860208285010111156115cc57600080fd5b95989497505060200194505050565b600080600080600060a086880312156115f357600080fd5b85356115fe816111b3565b9450602086013561160e816111b3565b9350604086013592506060860135915060808601356001600160401b0381111561163757600080fd5b61154688828901611455565b6000806000806080858703121561165957600080fd5b8435611664816111b3565b93506020850135611674816111b3565b92506040850135611684816111b3565b91506060850135611694816111b3565b939692955090935050565b600080858511156116af57600080fd5b838611156116bc57600080fd5b5050820193919092039150565b803560208310156103ae57600019602084900360031b1b1692915050565b60005b838110156117025781810151838201526020016116ea565b50506000910152565b60006020828403121561171d57600080fd5b81516001600160401b0381111561173357600080fd5b8201601f8101841361174457600080fd5b80516117526113e48261142e565b81815285602083850101111561176757600080fd5b6117788260208301602086016116e7565b95945050505050565b600081518084526117998160208601602086016116e7565b601f01601f19169290920160200192915050565b8381526001600160a01b038316602082015260606040820181905260009061177890830184611781565b6001600160a01b03831681526040602082018190526000906117fb90830184611781565b949350505050565b6001600160a01b0394851681529290931660208301526040820152606081019190915260a06080820181905260009082015260c00190565b634e487b7160e01b600052603260045260246000fd5b8183526000602080850194508260005b8581101561188f578135611874816111b3565b6001600160a01b031687529582019590820190600101611861565b509495945050505050565b81835260006001600160fb1b038311156118b357600080fd5b8260051b80836020870137939093016020019392505050565b8981526001600160a01b0389811660208301528816604082015260c0606082018190526000906118ff908301888a611851565b828103608084015261191281878961189a565b905082810360a084015261192781858761189a565b9c9b505050505050505050505050565b60608152600061194b60608301888a611851565b828103602084015261195e81878961189a565b9050828103604084015261197381858761189a565b9998505050505050505050565b600080600080600060a0868803121561199857600080fd5b85356119a3816111b3565b945060208601356119b3816111b3565b935060408601356119c3816111b3565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052600160045260246000fd5b60008060008060008060c08789031215611a0a57600080fd5b86359550602080880135611a1d816111b3565b95506040880135611a2d816111b3565b945060608801356001600160401b0380821115611a4957600080fd5b818a0191508a601f830112611a5d57600080fd5b8135611a6b6113e4826113a0565b81815260059190911b8301840190848101908d831115611a8a57600080fd5b938501935b82851015611ab1578435611aa2816111b3565b82529385019390850190611a8f565b9750505060808a0135925080831115611ac957600080fd5b611ad58b848c016113c3565b945060a08a0135925080831115611aeb57600080fd5b5050611af989828a016113c3565b9150509295509295509295565b600081518084526020808501945080840160005b8381101561188f57815187529582019590820190600101611b1a565b606080825284519082018190526000906020906080840190828801845b82811015611b785781516001600160a01b031684529284019290840190600101611b53565b50505083810382850152611b8c8187611b06565b9150508281036040840152611ba18185611b06565b969550505050505056fe526f6f744d696e7461626c65455243313135355072656469636174653a20494ea2646970667358221220bac4157c867ef25ba423441a6f4177226bff4063bb665be9fb147719e7b08cc664736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RootMintableERC1155PredicateACLArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RootMintableERC1155PredicateAccessList","sourceName":"contracts/child/RootMintableERC1155PredicateAccessList.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"AllowListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"block","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"BlockListUsageSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2MintableERC1155Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"L2MintableERC1155DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"L2MintableERC1155Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"L2MintableERC1155WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"L2MintableTokenMapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"childERC1155Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"depositBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newChildERC1155Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"},{"internalType":"bool","name":"newUseAllowList","type":"bool"},{"internalType":"bool","name":"newUseBlockList","type":"bool"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newL2StateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newChildERC1155Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2StateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"}],"name":"mapToken","outputs":[{"internalType":"address","name":"childToken","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseAllowList","type":"bool"}],"name":"setAllowList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"newUseBlockList","type":"bool"}],"name":"setBlockList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stateReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50612480806100206000396000f3fe608060405234801561001057600080fd5b506004361061021c5760003560e01c8063a78e111c11610125578063d8dd1773116100ad578063f23a6e611161007c578063f23a6e61146104d3578063f2fde38b146104f2578063f4a120f714610505578063f645125514610518578063f8c8765e1461053f57600080fd5b8063d8dd177314610493578063e0563ab1146104a6578063e30c3978146104af578063eeb49945146104c057600080fd5b8063c1225a20116100f4578063c1225a20146103f8578063c5ac2b1c1461040b578063c5e4683a14610432578063d41f177114610445578063d7c9e3ec1461046c57600080fd5b8063a78e111c14610373578063b176806514610386578063b68ad1e4146103ad578063bc197c81146103c057600080fd5b806355b01e4d116101a857806379ba50971161017757806379ba50971461031d5780637efab4f5146103255780638da5cb5b1461034f578063947287cf1461036057806397e5230d1461036957600080fd5b806355b01e4d146102dd5780635ea5df79146102eb578063654715e614610302578063715018a61461031557600080fd5b80631bc114ba116101ef5780631bc114ba14610297578063284017f5146102aa5780633b878c22146102b35780634c4c45de146102bc57806351351d53146102cf57600080fd5b806301ffc9a71461022157806305dc2e8f1461024957806307b3e252146102745780630efe6a8b14610282575b600080fd5b61023461022f3660046118c4565b610552565b60405190151581526020015b60405180910390f35b60fd5461025c906001600160a01b031681565b6040516001600160a01b039091168152602001610240565b61025c6004600360981b0181565b61029561029036600461190a565b610589565b005b60fc5461025c906001600160a01b031681565b61025c61202081565b61025c61101081565b6102956102ca36600461198a565b61059a565b61025c6002600160a01b0381565b61025c6004600160991b0181565b6102f461138881565b604051908152602001610240565b60fe5461025c906001600160a01b031681565b610295610614565b610295610628565b61025c610333366004611a36565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b031661025c565b6102f461520881565b6102f4620249f081565b610295610381366004611a53565b6106a2565b6102f47f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff5461025c906001600160a01b031681565b6103df6103ce366004611be5565b63bc197c8160e01b95945050505050565b6040516001600160e01b03199091168152602001610240565b610295610406366004611ca2565b6106b4565b6102f47faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b610295610440366004611ca2565b610703565b6102f47f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102f47f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b6102956104a1366004611cbd565b61074a565b61025c61203081565b6065546001600160a01b031661025c565b6102956104ce366004611d4f565b610888565b6103df6104e1366004611dd7565b63f23a6e6160e01b95945050505050565b610295610500366004611a36565b610a60565b61025c610513366004611a36565b610ad1565b6102f47f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61029561054d366004611e3f565b610de1565b60006001600160e01b03198216630271189760e51b148061058357506301ffc9a760e01b6001600160e01b03198316145b92915050565b61059583338484610eb3565b505050565b84831480156105a857508481145b6105fc5760405162461bcd60e51b815260206004820152602c602482015260008051602061242b83398151915260448201526b0ac82989288be988a9c8ea8960a31b60648201526084015b60405180910390fd5b61060b8787878787878761104b565b50505050505050565b61061c611228565b6106266000611282565b565b60655433906001600160a01b031681146106965760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016105f3565b61069f81611282565b50565b6106ae84848484610eb3565b50505050565b6106bc611228565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b61070b611228565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b031461078e5760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064016105f3565b600054610100900460ff16158080156107ae5750600054600160ff909116105b806107c85750303b1580156107c8575060005460ff166001145b6107e45760405162461bcd60e51b81526004016105f390611e9b565b6000805460ff191660011790558015610807576000805461ff0019166101001790555b6108138888888861129b565b60c9805461ffff191685151561ff001916176101008515150217905561083882611282565b801561087e576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146108fc5760405162461bcd60e51b815260206004820152603160248201527f526f6f744d696e7461626c65455243313135355072656469636174653a204f4e604482015270262cafa9aa20aa22afa922a1a2a4ab22a960791b60648201526084016105f3565b60fe546001600160a01b038481169116146109745760405162461bcd60e51b815260206004820152603260248201527f526f6f744d696e7461626c65455243313135355072656469636174653a204f4e6044820152714c595f4348494c445f50524544494341544560701b60648201526084016105f3565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e9828696109a3602060008486611ee9565b6109ac91611f13565b036109cb576109c66109c18260208186611ee9565b611398565b6106ae565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed26109fa602060008486611ee9565b610a0391611f13565b03610a12576109c682826114a6565b60405162461bcd60e51b815260206004820152602f602482015260008051602061242b83398151915260448201526e56414c49445f5349474e415455524560881b60648201526084016105f3565b610a68611228565b606580546001600160a01b0383166001600160a01b03199091168117909155610a996033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60006001600160a01b038216610b2b5760405162461bcd60e51b815260206004820152602b602482015260008051602061242b83398151915260448201526a2b20a624a22faa27a5a2a760a91b60648201526084016105f3565b6001600160a01b03828116600090815261010060205260409020541615610ba95760405162461bcd60e51b815260206004820152602c60248201527f526f6f744d696e7461626c65455243313135355072656469636174653a20414c60448201526b149150511657d3505414115160a21b60648201526084016105f3565b60fe5460ff546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b0392831692610c53921690603401604051602081830303815290604052805190602001208360405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b0384811660008181526101006020908152604080832080546001600160a01b031916958716959095179094558351908101845281815292516303a24d0760e21b815260048101919091529294509091630e89341c90602401600060405180830381865afa925050508015610cf057506040513d6000823e601f3d908101601f19168201604052610ced9190810190611f55565b60015b15610cf85790505b60fc546040516001600160a01b03909116906316f19831908490610d44907f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad9089908790602001611ff7565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610d70929190612021565b600060405180830381600087803b158015610d8a57600080fd5b505af1158015610d9e573d6000803e3d6000fd5b50506040516001600160a01b038087169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a35050919050565b600054610100900460ff1615808015610e015750600054600160ff909116105b80610e1b5750303b158015610e1b575060005460ff166001145b610e375760405162461bcd60e51b81526004016105f390611e9b565b6000805460ff191660011790558015610e5a576000805461ff0019166101001790555b610e668585858561129b565b8015610eac576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b610ebb611608565b6000610ec685611610565b604051637921219560e11b81529091506001600160a01b0386169063f242432a90610efb90339030908890889060040161204d565b600060405180830381600087803b158015610f1557600080fd5b505af1158015610f29573d6000803e3d6000fd5b505060fc5460fe54604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038b8116828401523360608301528a8116608083015260a082018a905260c08083018a90528351808403909101815260e08301938490526316f1983160e01b90935293841695506316f198319450610fc0939092169160e401612021565b600060405180830381600087803b158015610fda57600080fd5b505af1158015610fee573d6000803e3d6000fd5b505060408051338152602081018790529081018590526001600160a01b03808816935084811692508816907fe6f1fb6bb1930a5d4b997d56e0a4a8cb6f3f941f0d5391f63c1f6914ee01e0b89060600160405180910390a4610eac565b611053611608565b600061105e88611610565b905060005b8481101561110a57886001600160a01b031663f242432a333089898681811061108e5761108e612085565b905060200201358888878181106110a7576110a7612085565b905060200201356040518563ffffffff1660e01b81526004016110cd949392919061204d565b600060405180830381600087803b1580156110e757600080fd5b505af11580156110fb573d6000803e3d6000fd5b50505050806001019050611063565b5060fc5460fe546040516001600160a01b03928316926316f19831921690611166907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908d9033908e908e908e908e908e908e90602001612116565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401611192929190612021565b600060405180830381600087803b1580156111ac57600080fd5b505af11580156111c0573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316896001600160a01b03167fa7d9cd27143f817fe006011faf3c04351f70f75cd30e7b21405817f2f670041e8a8a8a8a8a8a60405161121b96959493929190612181565b60405180910390a461087e565b6033546001600160a01b031633146106265760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016105f3565b606580546001600160a01b031916905561069f81611658565b6001600160a01b038416158015906112bb57506001600160a01b03831615155b80156112cf57506001600160a01b03821615155b80156112e357506001600160a01b03811615155b6113485760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c65455243313135355072656469636174653a20424160448201526f222fa4a724aa24a0a624ad20aa24a7a760811b60648201526084016105f3565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b6000808080806113aa868801886121ca565b6001600160a01b0380861660009081526101006020526040902054959a509398509196509450925016806113e0576113e0612225565b604051637921219560e11b81526001600160a01b0387169063f242432a9061141290309088908890889060040161204d565b600060405180830381600087803b15801561142c57600080fd5b505af1158015611440573d6000803e3d6000fd5b5050604080516001600160a01b0389811682526020820188905291810186905281881693508482169250908916907f52cdfbe452878311d3237a4c20a951600d8e3a4fe6691d967d33788dc12d19c6906060015b60405180910390a45050505050505050565b6000808080806114b88688018861223b565b6001600160a01b0380861660009081526101006020526040902054959b5093995091975095509350169050806114f0576114f0612225565b60005b83518110156115b657866001600160a01b031663f242432a3087848151811061151e5761151e612085565b602002602001015187858151811061153857611538612085565b602002602001015187868151811061155257611552612085565b60200260200101516040518563ffffffff1660e01b8152600401611579949392919061204d565b600060405180830381600087803b15801561159357600080fd5b505af11580156115a7573d6000803e3d6000fd5b505050508060010190506114f3565b50846001600160a01b0316816001600160a01b0316876001600160a01b03167f52733a40a0466155541c2fc4044165dd37a3db116bc834bab2f020665e90fd2087878760405161149493929190612380565b6106266116aa565b6001600160a01b0380821660009081526101006020526040902054168061163d5761163a82610ad1565b90505b6001600160a01b03811661165357611653612225565b919050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60c95460ff16156117b557604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161170c91906123f5565b6000604051808303818686fa925050503d8060008114611748576040519150601f19603f3d011682016040523d82523d6000602084013e61174d565b606091505b509150915081801561177257506000818060200190518101906117709190612411565b115b6117b25760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b60448201526064016105f3565b50505b60c954610100900460ff161561062657604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b01916113889161181c91906123f5565b6000604051808303818686fa925050503d8060008114611858576040519150601f19603f3d011682016040523d82523d6000602084013e61185d565b606091505b509150915081801561188357508080602001905181019061187e9190612411565b600114155b6118c05760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b60448201526064016105f3565b5050565b6000602082840312156118d657600080fd5b81356001600160e01b0319811681146118ee57600080fd5b9392505050565b6001600160a01b038116811461069f57600080fd5b60008060006060848603121561191f57600080fd5b833561192a816118f5565b95602085013595506040909401359392505050565b60008083601f84011261195157600080fd5b5081356001600160401b0381111561196857600080fd5b6020830191508360208260051b850101111561198357600080fd5b9250929050565b60008060008060008060006080888a0312156119a557600080fd5b87356119b0816118f5565b965060208801356001600160401b03808211156119cc57600080fd5b6119d88b838c0161193f565b909850965060408a01359150808211156119f157600080fd5b6119fd8b838c0161193f565b909650945060608a0135915080821115611a1657600080fd5b50611a238a828b0161193f565b989b979a50959850939692959293505050565b600060208284031215611a4857600080fd5b81356118ee816118f5565b60008060008060808587031215611a6957600080fd5b8435611a74816118f5565b93506020850135611a84816118f5565b93969395505050506040820135916060013590565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611ad757611ad7611a99565b604052919050565b60006001600160401b03821115611af857611af8611a99565b5060051b60200190565b600082601f830112611b1357600080fd5b81356020611b28611b2383611adf565b611aaf565b82815260059290921b84018101918181019086841115611b4757600080fd5b8286015b84811015611b625780358352918301918301611b4b565b509695505050505050565b60006001600160401b03821115611b8657611b86611a99565b50601f01601f191660200190565b600082601f830112611ba557600080fd5b8135611bb3611b2382611b6d565b818152846020838601011115611bc857600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a08688031215611bfd57600080fd5b8535611c08816118f5565b94506020860135611c18816118f5565b935060408601356001600160401b0380821115611c3457600080fd5b611c4089838a01611b02565b94506060880135915080821115611c5657600080fd5b611c6289838a01611b02565b93506080880135915080821115611c7857600080fd5b50611c8588828901611b94565b9150509295509295909350565b8035801515811461165357600080fd5b600060208284031215611cb457600080fd5b6118ee82611c92565b600080600080600080600060e0888a031215611cd857600080fd5b8735611ce3816118f5565b96506020880135611cf3816118f5565b95506040880135611d03816118f5565b94506060880135611d13816118f5565b9350611d2160808901611c92565b9250611d2f60a08901611c92565b915060c0880135611d3f816118f5565b8091505092959891949750929550565b60008060008060608587031215611d6557600080fd5b843593506020850135611d77816118f5565b925060408501356001600160401b0380821115611d9357600080fd5b818701915087601f830112611da757600080fd5b813581811115611db657600080fd5b886020828501011115611dc857600080fd5b95989497505060200194505050565b600080600080600060a08688031215611def57600080fd5b8535611dfa816118f5565b94506020860135611e0a816118f5565b9350604086013592506060860135915060808601356001600160401b03811115611e3357600080fd5b611c8588828901611b94565b60008060008060808587031215611e5557600080fd5b8435611e60816118f5565b93506020850135611e70816118f5565b92506040850135611e80816118f5565b91506060850135611e90816118f5565b939692955090935050565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008085851115611ef957600080fd5b83861115611f0657600080fd5b5050820193919092039150565b8035602083101561058357600019602084900360031b1b1692915050565b60005b83811015611f4c578181015183820152602001611f34565b50506000910152565b600060208284031215611f6757600080fd5b81516001600160401b03811115611f7d57600080fd5b8201601f81018413611f8e57600080fd5b8051611f9c611b2382611b6d565b818152856020838501011115611fb157600080fd5b611fc2826020830160208601611f31565b95945050505050565b60008151808452611fe3816020860160208601611f31565b601f01601f19169290920160200192915050565b8381526001600160a01b0383166020820152606060408201819052600090611fc290830184611fcb565b6001600160a01b038316815260406020820181905260009061204590830184611fcb565b949350505050565b6001600160a01b0394851681529290931660208301526040820152606081019190915260a06080820181905260009082015260c00190565b634e487b7160e01b600052603260045260246000fd5b8183526000602080850194508260005b858110156120d95781356120be816118f5565b6001600160a01b0316875295820195908201906001016120ab565b509495945050505050565b81835260006001600160fb1b038311156120fd57600080fd5b8260051b80836020870137939093016020019392505050565b8981526001600160a01b0389811660208301528816604082015260c060608201819052600090612149908301888a61209b565b828103608084015261215c8187896120e4565b905082810360a08401526121718185876120e4565b9c9b505050505050505050505050565b60608152600061219560608301888a61209b565b82810360208401526121a88187896120e4565b905082810360408401526121bd8185876120e4565b9998505050505050505050565b600080600080600060a086880312156121e257600080fd5b85356121ed816118f5565b945060208601356121fd816118f5565b9350604086013561220d816118f5565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052600160045260246000fd5b60008060008060008060c0878903121561225457600080fd5b86359550602080880135612267816118f5565b95506040880135612277816118f5565b945060608801356001600160401b038082111561229357600080fd5b818a0191508a601f8301126122a757600080fd5b81356122b5611b2382611adf565b81815260059190911b8301840190848101908d8311156122d457600080fd5b938501935b828510156122fb5784356122ec816118f5565b825293850193908501906122d9565b9750505060808a013592508083111561231357600080fd5b61231f8b848c01611b02565b945060a08a013592508083111561233557600080fd5b505061234389828a01611b02565b9150509295509295509295565b600081518084526020808501945080840160005b838110156120d957815187529582019590820190600101612364565b606080825284519082018190526000906020906080840190828801845b828110156123c25781516001600160a01b03168452928401929084019060010161239d565b505050838103828501526123d68187612350565b91505082810360408401526123eb8185612350565b9695505050505050565b60008251612407818460208701611f31565b9190910192915050565b60006020828403121561242357600080fd5b505191905056fe526f6f744d696e7461626c65455243313135355072656469636174653a20494ea26469706673582212207cc4d2e1fa811483449e50a585f2819503c945760fb6c93dc493fe7ad5264ad364736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061021c5760003560e01c8063a78e111c11610125578063d8dd1773116100ad578063f23a6e611161007c578063f23a6e61146104d3578063f2fde38b146104f2578063f4a120f714610505578063f645125514610518578063f8c8765e1461053f57600080fd5b8063d8dd177314610493578063e0563ab1146104a6578063e30c3978146104af578063eeb49945146104c057600080fd5b8063c1225a20116100f4578063c1225a20146103f8578063c5ac2b1c1461040b578063c5e4683a14610432578063d41f177114610445578063d7c9e3ec1461046c57600080fd5b8063a78e111c14610373578063b176806514610386578063b68ad1e4146103ad578063bc197c81146103c057600080fd5b806355b01e4d116101a857806379ba50971161017757806379ba50971461031d5780637efab4f5146103255780638da5cb5b1461034f578063947287cf1461036057806397e5230d1461036957600080fd5b806355b01e4d146102dd5780635ea5df79146102eb578063654715e614610302578063715018a61461031557600080fd5b80631bc114ba116101ef5780631bc114ba14610297578063284017f5146102aa5780633b878c22146102b35780634c4c45de146102bc57806351351d53146102cf57600080fd5b806301ffc9a71461022157806305dc2e8f1461024957806307b3e252146102745780630efe6a8b14610282575b600080fd5b61023461022f3660046118c4565b610552565b60405190151581526020015b60405180910390f35b60fd5461025c906001600160a01b031681565b6040516001600160a01b039091168152602001610240565b61025c6004600360981b0181565b61029561029036600461190a565b610589565b005b60fc5461025c906001600160a01b031681565b61025c61202081565b61025c61101081565b6102956102ca36600461198a565b61059a565b61025c6002600160a01b0381565b61025c6004600160991b0181565b6102f461138881565b604051908152602001610240565b60fe5461025c906001600160a01b031681565b610295610614565b610295610628565b61025c610333366004611a36565b610100602052600090815260409020546001600160a01b031681565b6033546001600160a01b031661025c565b6102f461520881565b6102f4620249f081565b610295610381366004611a53565b6106a2565b6102f47f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60ff5461025c906001600160a01b031681565b6103df6103ce366004611be5565b63bc197c8160e01b95945050505050565b6040516001600160e01b03199091168152602001610240565b610295610406366004611ca2565b6106b4565b6102f47faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b610295610440366004611ca2565b610703565b6102f47f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102f47f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b6102956104a1366004611cbd565b61074a565b61025c61203081565b6065546001600160a01b031661025c565b6102956104ce366004611d4f565b610888565b6103df6104e1366004611dd7565b63f23a6e6160e01b95945050505050565b610295610500366004611a36565b610a60565b61025c610513366004611a36565b610ad1565b6102f47f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61029561054d366004611e3f565b610de1565b60006001600160e01b03198216630271189760e51b148061058357506301ffc9a760e01b6001600160e01b03198316145b92915050565b61059583338484610eb3565b505050565b84831480156105a857508481145b6105fc5760405162461bcd60e51b815260206004820152602c602482015260008051602061242b83398151915260448201526b0ac82989288be988a9c8ea8960a31b60648201526084015b60405180910390fd5b61060b8787878787878761104b565b50505050505050565b61061c611228565b6106266000611282565b565b60655433906001600160a01b031681146106965760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016105f3565b61069f81611282565b50565b6106ae84848484610eb3565b50505050565b6106bc611228565b60c9805461ff0019166101008315159081029190911790915560405143907f61d574757cde41a357d030b39b2705796ccd699578037ab9a1cfd8117d82bf8690600090a350565b61070b611228565b60c9805460ff191682151590811790915560405143907fe0a0f0fa52db091cb71c202d80420311430ce1ae2e7794149877b6720ce8bf0b90600090a350565b336002600160a01b031461078e5760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064016105f3565b600054610100900460ff16158080156107ae5750600054600160ff909116105b806107c85750303b1580156107c8575060005460ff166001145b6107e45760405162461bcd60e51b81526004016105f390611e9b565b6000805460ff191660011790558015610807576000805461ff0019166101001790555b6108138888888861129b565b60c9805461ffff191685151561ff001916176101008515150217905561083882611282565b801561087e576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b60fd546001600160a01b031633146108fc5760405162461bcd60e51b815260206004820152603160248201527f526f6f744d696e7461626c65455243313135355072656469636174653a204f4e604482015270262cafa9aa20aa22afa922a1a2a4ab22a960791b60648201526084016105f3565b60fe546001600160a01b038481169116146109745760405162461bcd60e51b815260206004820152603260248201527f526f6f744d696e7461626c65455243313135355072656469636174653a204f4e6044820152714c595f4348494c445f50524544494341544560701b60648201526084016105f3565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e9828696109a3602060008486611ee9565b6109ac91611f13565b036109cb576109c66109c18260208186611ee9565b611398565b6106ae565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed26109fa602060008486611ee9565b610a0391611f13565b03610a12576109c682826114a6565b60405162461bcd60e51b815260206004820152602f602482015260008051602061242b83398151915260448201526e56414c49445f5349474e415455524560881b60648201526084016105f3565b610a68611228565b606580546001600160a01b0383166001600160a01b03199091168117909155610a996033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60006001600160a01b038216610b2b5760405162461bcd60e51b815260206004820152602b602482015260008051602061242b83398151915260448201526a2b20a624a22faa27a5a2a760a91b60648201526084016105f3565b6001600160a01b03828116600090815261010060205260409020541615610ba95760405162461bcd60e51b815260206004820152602c60248201527f526f6f744d696e7461626c65455243313135355072656469636174653a20414c60448201526b149150511657d3505414115160a21b60648201526084016105f3565b60fe5460ff546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b0392831692610c53921690603401604051602081830303815290604052805190602001208360405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b0384811660008181526101006020908152604080832080546001600160a01b031916958716959095179094558351908101845281815292516303a24d0760e21b815260048101919091529294509091630e89341c90602401600060405180830381865afa925050508015610cf057506040513d6000823e601f3d908101601f19168201604052610ced9190810190611f55565b60015b15610cf85790505b60fc546040516001600160a01b03909116906316f19831908490610d44907f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad9089908790602001611ff7565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610d70929190612021565b600060405180830381600087803b158015610d8a57600080fd5b505af1158015610d9e573d6000803e3d6000fd5b50506040516001600160a01b038087169350871691507fb96a191bae4e25ffdff7f4136994eb0dec75d263750a07c035202c348c9515f090600090a35050919050565b600054610100900460ff1615808015610e015750600054600160ff909116105b80610e1b5750303b158015610e1b575060005460ff166001145b610e375760405162461bcd60e51b81526004016105f390611e9b565b6000805460ff191660011790558015610e5a576000805461ff0019166101001790555b610e668585858561129b565b8015610eac576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b610ebb611608565b6000610ec685611610565b604051637921219560e11b81529091506001600160a01b0386169063f242432a90610efb90339030908890889060040161204d565b600060405180830381600087803b158015610f1557600080fd5b505af1158015610f29573d6000803e3d6000fd5b505060fc5460fe54604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038b8116828401523360608301528a8116608083015260a082018a905260c08083018a90528351808403909101815260e08301938490526316f1983160e01b90935293841695506316f198319450610fc0939092169160e401612021565b600060405180830381600087803b158015610fda57600080fd5b505af1158015610fee573d6000803e3d6000fd5b505060408051338152602081018790529081018590526001600160a01b03808816935084811692508816907fe6f1fb6bb1930a5d4b997d56e0a4a8cb6f3f941f0d5391f63c1f6914ee01e0b89060600160405180910390a4610eac565b611053611608565b600061105e88611610565b905060005b8481101561110a57886001600160a01b031663f242432a333089898681811061108e5761108e612085565b905060200201358888878181106110a7576110a7612085565b905060200201356040518563ffffffff1660e01b81526004016110cd949392919061204d565b600060405180830381600087803b1580156110e757600080fd5b505af11580156110fb573d6000803e3d6000fd5b50505050806001019050611063565b5060fc5460fe546040516001600160a01b03928316926316f19831921690611166907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908d9033908e908e908e908e908e908e90602001612116565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401611192929190612021565b600060405180830381600087803b1580156111ac57600080fd5b505af11580156111c0573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316896001600160a01b03167fa7d9cd27143f817fe006011faf3c04351f70f75cd30e7b21405817f2f670041e8a8a8a8a8a8a60405161121b96959493929190612181565b60405180910390a461087e565b6033546001600160a01b031633146106265760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016105f3565b606580546001600160a01b031916905561069f81611658565b6001600160a01b038416158015906112bb57506001600160a01b03831615155b80156112cf57506001600160a01b03821615155b80156112e357506001600160a01b03811615155b6113485760405162461bcd60e51b815260206004820152603060248201527f526f6f744d696e7461626c65455243313135355072656469636174653a20424160448201526f222fa4a724aa24a0a624ad20aa24a7a760811b60648201526084016105f3565b60fc80546001600160a01b039586166001600160a01b03199182161790915560fd80549486169482169490941790935560fe80549285169284169290921790915560ff8054919093169116179055565b6000808080806113aa868801886121ca565b6001600160a01b0380861660009081526101006020526040902054959a509398509196509450925016806113e0576113e0612225565b604051637921219560e11b81526001600160a01b0387169063f242432a9061141290309088908890889060040161204d565b600060405180830381600087803b15801561142c57600080fd5b505af1158015611440573d6000803e3d6000fd5b5050604080516001600160a01b0389811682526020820188905291810186905281881693508482169250908916907f52cdfbe452878311d3237a4c20a951600d8e3a4fe6691d967d33788dc12d19c6906060015b60405180910390a45050505050505050565b6000808080806114b88688018861223b565b6001600160a01b0380861660009081526101006020526040902054959b5093995091975095509350169050806114f0576114f0612225565b60005b83518110156115b657866001600160a01b031663f242432a3087848151811061151e5761151e612085565b602002602001015187858151811061153857611538612085565b602002602001015187868151811061155257611552612085565b60200260200101516040518563ffffffff1660e01b8152600401611579949392919061204d565b600060405180830381600087803b15801561159357600080fd5b505af11580156115a7573d6000803e3d6000fd5b505050508060010190506114f3565b50846001600160a01b0316816001600160a01b0316876001600160a01b03167f52733a40a0466155541c2fc4044165dd37a3db116bc834bab2f020665e90fd2087878760405161149493929190612380565b6106266116aa565b6001600160a01b0380821660009081526101006020526040902054168061163d5761163a82610ad1565b90505b6001600160a01b03811661165357611653612225565b919050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60c95460ff16156117b557604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600160991b01916113889161170c91906123f5565b6000604051808303818686fa925050503d8060008114611748576040519150601f19603f3d011682016040523d82523d6000602084013e61174d565b606091505b509150915081801561177257506000818060200190518101906117709190612411565b115b6117b25760405162461bcd60e51b81526020600482015260116024820152702224a9a0a62627aba2a22fa9a2a72222a960791b60448201526064016105f3565b50505b60c954610100900460ff161561062657604080513360248083019190915282518083039091018152604490910182526020810180516001600160e01b031663d78bca6960e01b179052905160009182916004600360981b01916113889161181c91906123f5565b6000604051808303818686fa925050503d8060008114611858576040519150601f19603f3d011682016040523d82523d6000602084013e61185d565b606091505b509150915081801561188357508080602001905181019061187e9190612411565b600114155b6118c05760405162461bcd60e51b815260206004820152600e60248201526d212627a1a5a2a22fa9a2a72222a960911b60448201526064016105f3565b5050565b6000602082840312156118d657600080fd5b81356001600160e01b0319811681146118ee57600080fd5b9392505050565b6001600160a01b038116811461069f57600080fd5b60008060006060848603121561191f57600080fd5b833561192a816118f5565b95602085013595506040909401359392505050565b60008083601f84011261195157600080fd5b5081356001600160401b0381111561196857600080fd5b6020830191508360208260051b850101111561198357600080fd5b9250929050565b60008060008060008060006080888a0312156119a557600080fd5b87356119b0816118f5565b965060208801356001600160401b03808211156119cc57600080fd5b6119d88b838c0161193f565b909850965060408a01359150808211156119f157600080fd5b6119fd8b838c0161193f565b909650945060608a0135915080821115611a1657600080fd5b50611a238a828b0161193f565b989b979a50959850939692959293505050565b600060208284031215611a4857600080fd5b81356118ee816118f5565b60008060008060808587031215611a6957600080fd5b8435611a74816118f5565b93506020850135611a84816118f5565b93969395505050506040820135916060013590565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611ad757611ad7611a99565b604052919050565b60006001600160401b03821115611af857611af8611a99565b5060051b60200190565b600082601f830112611b1357600080fd5b81356020611b28611b2383611adf565b611aaf565b82815260059290921b84018101918181019086841115611b4757600080fd5b8286015b84811015611b625780358352918301918301611b4b565b509695505050505050565b60006001600160401b03821115611b8657611b86611a99565b50601f01601f191660200190565b600082601f830112611ba557600080fd5b8135611bb3611b2382611b6d565b818152846020838601011115611bc857600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a08688031215611bfd57600080fd5b8535611c08816118f5565b94506020860135611c18816118f5565b935060408601356001600160401b0380821115611c3457600080fd5b611c4089838a01611b02565b94506060880135915080821115611c5657600080fd5b611c6289838a01611b02565b93506080880135915080821115611c7857600080fd5b50611c8588828901611b94565b9150509295509295909350565b8035801515811461165357600080fd5b600060208284031215611cb457600080fd5b6118ee82611c92565b600080600080600080600060e0888a031215611cd857600080fd5b8735611ce3816118f5565b96506020880135611cf3816118f5565b95506040880135611d03816118f5565b94506060880135611d13816118f5565b9350611d2160808901611c92565b9250611d2f60a08901611c92565b915060c0880135611d3f816118f5565b8091505092959891949750929550565b60008060008060608587031215611d6557600080fd5b843593506020850135611d77816118f5565b925060408501356001600160401b0380821115611d9357600080fd5b818701915087601f830112611da757600080fd5b813581811115611db657600080fd5b886020828501011115611dc857600080fd5b95989497505060200194505050565b600080600080600060a08688031215611def57600080fd5b8535611dfa816118f5565b94506020860135611e0a816118f5565b9350604086013592506060860135915060808601356001600160401b03811115611e3357600080fd5b611c8588828901611b94565b60008060008060808587031215611e5557600080fd5b8435611e60816118f5565b93506020850135611e70816118f5565b92506040850135611e80816118f5565b91506060850135611e90816118f5565b939692955090935050565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008085851115611ef957600080fd5b83861115611f0657600080fd5b5050820193919092039150565b8035602083101561058357600019602084900360031b1b1692915050565b60005b83811015611f4c578181015183820152602001611f34565b50506000910152565b600060208284031215611f6757600080fd5b81516001600160401b03811115611f7d57600080fd5b8201601f81018413611f8e57600080fd5b8051611f9c611b2382611b6d565b818152856020838501011115611fb157600080fd5b611fc2826020830160208601611f31565b95945050505050565b60008151808452611fe3816020860160208601611f31565b601f01601f19169290920160200192915050565b8381526001600160a01b0383166020820152606060408201819052600090611fc290830184611fcb565b6001600160a01b038316815260406020820181905260009061204590830184611fcb565b949350505050565b6001600160a01b0394851681529290931660208301526040820152606081019190915260a06080820181905260009082015260c00190565b634e487b7160e01b600052603260045260246000fd5b8183526000602080850194508260005b858110156120d95781356120be816118f5565b6001600160a01b0316875295820195908201906001016120ab565b509495945050505050565b81835260006001600160fb1b038311156120fd57600080fd5b8260051b80836020870137939093016020019392505050565b8981526001600160a01b0389811660208301528816604082015260c060608201819052600090612149908301888a61209b565b828103608084015261215c8187896120e4565b905082810360a08401526121718185876120e4565b9c9b505050505050505050505050565b60608152600061219560608301888a61209b565b82810360208401526121a88187896120e4565b905082810360408401526121bd8185876120e4565b9998505050505050505050565b600080600080600060a086880312156121e257600080fd5b85356121ed816118f5565b945060208601356121fd816118f5565b9350604086013561220d816118f5565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052600160045260246000fd5b60008060008060008060c0878903121561225457600080fd5b86359550602080880135612267816118f5565b95506040880135612277816118f5565b945060608801356001600160401b038082111561229357600080fd5b818a0191508a601f8301126122a757600080fd5b81356122b5611b2382611adf565b81815260059190911b8301840190848101908d8311156122d457600080fd5b938501935b828510156122fb5784356122ec816118f5565b825293850193908501906122d9565b9750505060808a013592508083111561231357600080fd5b61231f8b848c01611b02565b945060a08a013592508083111561233557600080fd5b505061234389828a01611b02565b9150509295509295509295565b600081518084526020808501945080840160005b838110156120d957815187529582019590820190600101612364565b606080825284519082018190526000906020906080840190828801845b828110156123c25781516001600160a01b03168452928401929084019060010161239d565b505050838103828501526123d68187612350565b91505082810360408401526123eb8185612350565b9695505050505050565b60008251612407818460208701611f31565b9190910192915050565b60006020828403121561242357600080fd5b505191905056fe526f6f744d696e7461626c65455243313135355072656469636174653a20494ea26469706673582212207cc4d2e1fa811483449e50a585f2819503c945760fb6c93dc493fe7ad5264ad364736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var SystemArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"System","sourceName":"contracts/child/System.sol","abi":[{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b5061015a806100206000396000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c806355b01e4d1161006657806355b01e4d146100e35780635ea5df79146100f1578063947287cf1461010857806397e5230d14610111578063e0563ab11461011b57600080fd5b806307b3e25214610098578063284017f5146100c35780633b878c22146100cc57806351351d53146100d5575b600080fd5b6100a66004600360981b0181565b6040516001600160a01b0390911681526020015b60405180910390f35b6100a661202081565b6100a661101081565b6100a66002600160a01b0381565b6100a66004600160991b0181565b6100fa61138881565b6040519081526020016100ba565b6100fa61520881565b6100fa620249f081565b6100a66120308156fea2646970667358221220cdc81f82626224fb8a04246ca1d1898f8b03c160e9b2f63cd0bc4b5d0b55c3f964736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100935760003560e01c806355b01e4d1161006657806355b01e4d146100e35780635ea5df79146100f1578063947287cf1461010857806397e5230d14610111578063e0563ab11461011b57600080fd5b806307b3e25214610098578063284017f5146100c35780633b878c22146100cc57806351351d53146100d5575b600080fd5b6100a66004600360981b0181565b6040516001600160a01b0390911681526020015b60405180910390f35b6100a661202081565b6100a661101081565b6100a66002600160a01b0381565b6100a66004600160991b0181565b6100fa61138881565b6040519081526020016100ba565b6100fa61520881565b6100fa620249f081565b6100a66120308156fea2646970667358221220cdc81f82626224fb8a04246ca1d1898f8b03c160e9b2f63cd0bc4b5d0b55c3f964736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var BLSArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"BLS","sourceName":"contracts/common/BLS.sol","abi":[{"inputs":[{"internalType":"bytes32","name":"domain","type":"bytes32"},{"internalType":"bytes","name":"message","type":"bytes"}],"name":"expandMsgTo96","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"domain","type":"bytes32"},{"internalType":"bytes","name":"messages","type":"bytes"}],"name":"hashToField","outputs":[{"internalType":"uint256[2]","name":"","type":"uint256[2]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"domain","type":"bytes32"},{"internalType":"bytes","name":"message","type":"bytes"}],"name":"hashToPoint","outputs":[{"internalType":"uint256[2]","name":"","type":"uint256[2]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[2]","name":"point","type":"uint256[2]"}],"name":"isOnCurveG1","outputs":[{"internalType":"bool","name":"_isOnCurve","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[4]","name":"point","type":"uint256[4]"}],"name":"isOnCurveG2","outputs":[{"internalType":"bool","name":"_isOnCurve","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[2]","name":"signature","type":"uint256[2]"}],"name":"isValidSignature","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_x","type":"uint256"}],"name":"mapToPoint","outputs":[{"internalType":"uint256[2]","name":"p","type":"uint256[2]"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[2]","name":"signature","type":"uint256[2]"},{"internalType":"uint256[4][]","name":"pubkeys","type":"uint256[4][]"},{"internalType":"uint256[2][]","name":"messages","type":"uint256[2][]"}],"name":"verifyMultiple","outputs":[{"internalType":"bool","name":"checkResult","type":"bool"},{"internalType":"bool","name":"callSuccess","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[2]","name":"signature","type":"uint256[2]"},{"internalType":"uint256[4][]","name":"pubkeys","type":"uint256[4][]"},{"internalType":"uint256[2]","name":"message","type":"uint256[2]"}],"name":"verifyMultipleSameMsg","outputs":[{"internalType":"bool","name":"checkResult","type":"bool"},{"internalType":"bool","name":"callSuccess","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[2]","name":"signature","type":"uint256[2]"},{"internalType":"uint256[4]","name":"pubkey","type":"uint256[4]"},{"internalType":"uint256[2]","name":"message","type":"uint256[2]"}],"name":"verifySingle","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50612df8806100206000396000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c806391ec2d2b1161006657806391ec2d2b1461013b578063a850a9091461015b578063d58e77331461016e578063e242cce914610181578063ebbdac911461019457600080fd5b8063115000fe146100a3578063247dd9fb146100cb5780633e5476ce146100de5780638669026f146101085780639141376314610128575b600080fd5b6100b66100b1366004612708565b6101a7565b60405190151581526020015b60405180910390f35b6100b66100d9366004612785565b61030e565b6100f16100ec366004612841565b6103b8565b6040805192151583529015156020830152016100c2565b61011b6101163660046128ce565b61079f565b6040516100c29190612957565b6100f1610136366004612988565b6108bb565b61014e6101493660046128ce565b610d5c565b6040516100c29190612a86565b61011b6101693660046128ce565b610ff8565b61011b61017c366004612aa0565b6111d4565b6100b661018f366004612785565b6115aa565b6100f16101a2366004612ab9565b611609565b600081516020830151600080516020612d63833981519152828309600080516020612d638339815191528283098182830101600080516020612d638339815191528283840108600080516020612d638339815191528682600080516020612d6383398151915203860109935050600080516020612d638339815191528483600080516020612d63833981519152038301099150600080516020612d638339815191527f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e584089450600080516020612d638339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2830893506040870151925060608701519150600080516020612d638339815191528083600080516020612d63833981519152038508600080516020612d63833981519152848608099050600080516020612d63833981519152828460011b0994149290931491909116949350505050565b8051600090600080516020612d6383398151915211158061034157506020820151600080516020612d6383398151915211155b1561034e57506000919050565b60405163e242cce960e01b8152309063e242cce990610371908590600401612957565b602060405180830381865afa15801561038e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103b29190612b1c565b92915050565b60008083806103e25760405162461bcd60e51b81526004016103d990612b3e565b60405180910390fd5b60006103ef826001612b95565b6103fa906006612ba8565b90506000816001600160401b038111156104165761041661269a565b60405190808252806020026020018201604052801561043f578160200160208202803683370190505b50905088600060200201358160008151811061045d5761045d612b06565b602090810291909101015288600160200201358160018151811061048357610483612b06565b602002602001018181525050600080516020612d43833981519152816002815181106104b1576104b1612b06565b602002602001018181525050600080516020612d23833981519152816003815181106104df576104df612b06565b602002602001018181525050600080516020612d838339815191528160048151811061050d5761050d612b06565b602002602001018181525050600080516020612da38339815191528160058151811061053b5761053b612b06565b60200260200101818152505060005b8381101561075a57863582610560836006612ba8565b61056b906006612b95565b8151811061057b5761057b612b06565b602090810291909101015286600160200201358261059a836006612ba8565b6105a5906007612b95565b815181106105b5576105b5612b06565b6020026020010181815250508888828181106105d3576105d3612b06565b9050608002016001600481106105eb576105eb612b06565b6020020135826105fc836006612ba8565b610607906008612b95565b8151811061061757610617612b06565b60200260200101818152505088888281811061063557610635612b06565b90506080020160006004811061064d5761064d612b06565b60200201358261065e836006612ba8565b610669906009612b95565b8151811061067957610679612b06565b60200260200101818152505088888281811061069757610697612b06565b9050608002016003600481106106af576106af612b06565b6020020135826106c0836006612ba8565b6106cb90600a612b95565b815181106106db576106db612b06565b6020026020010181815250508888828181106106f9576106f9612b06565b90506080020160026004811061071157610711612b06565b602002013582610722836006612ba8565b61072d90600b612b95565b8151811061073d5761073d612b06565b60209081029190910101528061075281612bbf565b91505061054a565b50610763612640565b602081602085026020850160085afa945084610789576000809550955050505050610796565b5115159450600193505050505b94509492505050565b6107a761265e565b6040516391ec2d2b60e01b815260009030906391ec2d2b906107cf9087908790600401612bd8565b600060405180830381865afa1580156107ec573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526108149190810190612bf9565b9050600080600080601885016001600160c01b0381511693506030860190506001600160c01b038151169450600080516020612d6383398151915285600080516020612d63833981519152600160c01b870908604887015160608801516001600160c01b0390811697501694509250600080516020612d6383398151915290508481600160c01b860908604080518082019091529283526020830152509695505050505050565b60008084806108dc5760405162461bcd60e51b81526004016103d990612b3e565b8084146109495760405162461bcd60e51b815260206004820152603560248201527f424c533a206e756d626572206f66207075626c6963206b65797320616e64206d604482015274195cdcd859d95cc81b5d5cdd08189948195c5d585b605a1b60648201526084016103d9565b6000610956826001612b95565b610961906006612ba8565b90506000816001600160401b0381111561097d5761097d61269a565b6040519080825280602002602001820160405280156109a6578160200160208202803683370190505b5090508960006020020135816000815181106109c4576109c4612b06565b60209081029190910101528960016020020135816001815181106109ea576109ea612b06565b602002602001018181525050600080516020612d4383398151915281600281518110610a1857610a18612b06565b602002602001018181525050600080516020612d2383398151915281600381518110610a4657610a46612b06565b602002602001018181525050600080516020612d8383398151915281600481518110610a7457610a74612b06565b602002602001018181525050600080516020612da383398151915281600581518110610aa257610aa2612b06565b60200260200101818152505060005b83811015610d1657878782818110610acb57610acb612b06565b905060400201600060028110610ae357610ae3612b06565b602002013582610af4836006612ba8565b610aff906006612b95565b81518110610b0f57610b0f612b06565b602002602001018181525050878782818110610b2d57610b2d612b06565b905060400201600160028110610b4557610b45612b06565b602002013582610b56836006612ba8565b610b61906007612b95565b81518110610b7157610b71612b06565b602002602001018181525050898982818110610b8f57610b8f612b06565b905060800201600160048110610ba757610ba7612b06565b602002013582610bb8836006612ba8565b610bc3906008612b95565b81518110610bd357610bd3612b06565b602002602001018181525050898982818110610bf157610bf1612b06565b905060800201600060048110610c0957610c09612b06565b602002013582610c1a836006612ba8565b610c25906009612b95565b81518110610c3557610c35612b06565b602002602001018181525050898982818110610c5357610c53612b06565b905060800201600360048110610c6b57610c6b612b06565b602002013582610c7c836006612ba8565b610c8790600a612b95565b81518110610c9757610c97612b06565b602002602001018181525050898982818110610cb557610cb5612b06565b905060800201600260048110610ccd57610ccd612b06565b602002013582610cde836006612ba8565b610ce990600b612b95565b81518110610cf957610cf9612b06565b602090810291909101015280610d0e81612bbf565b915050610ab1565b50610d1f612640565b602081602085026020850160085afa945084610d45576000809550955050505050610d52565b5115159450600193505050505b9550959350505050565b80516060906000610d6e826020612b95565b610d79906040612b95565b610d84906004612b95565b6001600160401b03811115610d9b57610d9b61269a565b6040519080825280601f01601f191660200182016040528015610dc5576020820181803683370190505b5060408051606080825260808201909252919250600091906020820181803683370190505090506060820160005b84811015610e0d5760208188018101518383015201610df3565b5083016000815360010160608153600101600081536001810187905260210160208153506000600283604051610e439190612c6f565b602060405180830381855afa158015610e60573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610e839190612c8b565b9050600060429450848452816020850152600160408501536041840188905260206061850153600284604051610eb99190612c6f565b602060405180830381855afa158015610ed6573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610ef99190612c8b565b905080602084015280821880602086015250600260408501536041840188905260206061850153600284604051610f309190612c6f565b602060405180830381855afa158015610f4d573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610f709190612c8b565b905080604084015280821880602086015250600360408501536041840188905260206061850153600284604051610fa79190612c6f565b602060405180830381855afa158015610fc4573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610fe79190612c8b565b606084015250909695505050505050565b61100061265e565b604051638669026f60e01b81526000903090638669026f906110289087908790600401612bd8565b6040805180830381865afa158015611044573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110689190612ca4565b805160405163d58e773360e01b81526004810191909152909150600090309063d58e7733906024016040805180830381865afa1580156110ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110d09190612ca4565b602083015160405163d58e773360e01b81526004810191909152909150600090309063d58e7733906024016040805180830381865afa158015611117573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061113b9190612ca4565b905061114561267c565b825181526020808401518282015282516040808401919091529083015160608301526000908460808460066107d05a03fa9050808061118057fe5b50806111c85760405162461bcd60e51b8152602060048201526017602482015276109314ce88189b881859190818d85b1b0819985a5b1959604a1b60448201526064016103d9565b50919695505050505050565b6111dc61265e565b600080516020612d6383398151915282106112455760405162461bcd60e51b815260206004820152602360248201527f6d6170546f506f696e7446543a20696e76616c6964206669656c6420656c656d604482015262195b9d60ea1b60648201526084016103d9565b81600061125182611790565b9150506000600080516020612d638339815191528061127257611272612cf9565b8384099050600080516020612d638339815191526004820890506000600080516020612d6383398151915277b3c4d79d41a91759a9e4c7e359b6b89eaec68e62effffffd850990506000600080516020612d6383398151915283830990506112d9816117b9565b9050600080516020612d638339815191528283099150600080516020612d638339815191528183099150600080516020612d638339815191528286099150600080516020612d6383398151915261133e83600080516020612d63833981519152612d0f565b7759e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe089450600080516020612d638339815191528586099150600080516020612d638339815191528583099150600080516020612d6383398151915260038308915060006113a283611790565b909350905080156113ea57846113cd576113ca83600080516020612d63833981519152612d0f565b92505b505060408051808201909152938452602084015250909392505050565b600080516020612d638339815191526001870861141590600080516020612d63833981519152612d0f565b9550600080516020612d638339815191528687099250600080516020612d638339815191528684099250600080516020612d6383398151915260038408925061145d83611790565b9093509050801561148557846113cd576113ca83600080516020612d63833981519152612d0f565b600080516020612d638339815191528485099550600080516020612d638339815191528687099550600080516020612d638339815191528287099550600080516020612d638339815191528287099550600080516020612d63833981519152600187089550600080516020612d638339815191528687099250600080516020612d638339815191528684099250600080516020612d6383398151915260038408925061153083611790565b90935090508061158d5760405162461bcd60e51b815260206004820152602260248201527f424c533a20626164206674206d617070696e6720696d706c656d656e7461746960448201526137b760f11b60648201526084016103d9565b846113cd576113ca83600080516020612d63833981519152612d0f565b600081516020830151600080516020612d63833981519152828309600080516020612d638339815191528382099050600080516020612d63833981519152600382089050600080516020612d6383398151915282830914949350505050565b60008060006040518061018001604052808760006002811061162d5761162d612b06565b602002013581526020018760016002811061164a5761164a612b06565b60200201358152602001600080516020612d438339815191528152602001600080516020612d238339815191528152602001600080516020612d838339815191528152602001600080516020612da38339815191528152602001856000600281106116b7576116b7612b06565b60200201358152602001856001600281106116d4576116d4612b06565b60200201358152602001866001600481106116f1576116f1612b06565b602002013581526020018660006004811061170e5761170e612b06565b602002013581526020018660036004811061172b5761172b612b06565b602002013581526020018660026004811061174857611748612b06565b602002013590529050611759612640565b60006020826101808560085afa90508061177c5760008094509450505050611788565b50511515925060019150505b935093915050565b60008061179c836117c4565b915082600080516020612d63833981519152838409149050915091565b60006103b282611ef9565b6000600080516020612d638339815191528083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508584880996508587880996508587880996508587880996508587880996508587880996508581880996505050505050808283099392505050565b6000600080516020612d638339815191528083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996505050838586099450838586099450838586099450838586099450838186099450508284850993508284850993508284850993508281850993508284850993508284850993508285850993508284850993508284850993508284850993508284850993508284850993508284850993508281850995945050505050565b60405180602001604052806001906020820280368337509192915050565b60405180604001604052806002906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b03811182821017156126d2576126d261269a565b60405290565b604051601f8201601f191681016001600160401b03811182821017156127005761270061269a565b604052919050565b60006080828403121561271a57600080fd5b82601f83011261272957600080fd5b604051608081018181106001600160401b038211171561274b5761274b61269a565b60405280608084018581111561276057600080fd5b845b8181101561277a578035835260209283019201612762565b509195945050505050565b60006040828403121561279757600080fd5b82601f8301126127a657600080fd5b6127ae6126b0565b8060408401858111156127c057600080fd5b845b818110156127da5780358452602093840193016127c2565b509095945050505050565b80604081018310156103b257600080fd5b60008083601f84011261280857600080fd5b5081356001600160401b0381111561281f57600080fd5b6020830191508360208260071b850101111561283a57600080fd5b9250929050565b60008060008060a0858703121561285757600080fd5b61286186866127e5565b935060408501356001600160401b0381111561287c57600080fd5b612888878288016127f6565b909450925061289c905086606087016127e5565b905092959194509250565b60006001600160401b038211156128c0576128c061269a565b50601f01601f191660200190565b600080604083850312156128e157600080fd5b8235915060208301356001600160401b038111156128fe57600080fd5b8301601f8101851361290f57600080fd5b803561292261291d826128a7565b6126d8565b81815286602083850101111561293757600080fd5b816020840160208301376000602083830101528093505050509250929050565b60408101818360005b600281101561297f578151835260209283019290910190600101612960565b50505092915050565b6000806000806000608086880312156129a057600080fd5b6129aa87876127e5565b945060408601356001600160401b03808211156129c657600080fd5b6129d289838a016127f6565b909650945060608801359150808211156129eb57600080fd5b818801915088601f8301126129ff57600080fd5b813581811115612a0e57600080fd5b8960208260061b8501011115612a2357600080fd5b9699959850939650602001949392505050565b60005b83811015612a51578181015183820152602001612a39565b50506000910152565b60008151808452612a72816020860160208601612a36565b601f01601f19169290920160200192915050565b602081526000612a996020830184612a5a565b9392505050565b600060208284031215612ab257600080fd5b5035919050565b60008060006101008486031215612acf57600080fd5b612ad985856127e5565b925060c0840185811115612aec57600080fd5b604085019250612afc86826127e5565b9150509250925092565b634e487b7160e01b600052603260045260246000fd5b600060208284031215612b2e57600080fd5b81518015158114612a9957600080fd5b60208082526021908201527f424c533a206e756d626572206f66207075626c6963206b6579206973207a65726040820152606f60f81b606082015260800190565b634e487b7160e01b600052601160045260246000fd5b808201808211156103b2576103b2612b7f565b80820281158282048414176103b2576103b2612b7f565b600060018201612bd157612bd1612b7f565b5060010190565b828152604060208201526000612bf16040830184612a5a565b949350505050565b600060208284031215612c0b57600080fd5b81516001600160401b03811115612c2157600080fd5b8201601f81018413612c3257600080fd5b8051612c4061291d826128a7565b818152856020838501011115612c5557600080fd5b612c66826020830160208601612a36565b95945050505050565b60008251612c81818460208701612a36565b9190910192915050565b600060208284031215612c9d57600080fd5b5051919050565b600060408284031215612cb657600080fd5b82601f830112612cc557600080fd5b612ccd6126b0565b806040840185811115612cdf57600080fd5b845b818110156127da578051845260209384019301612ce1565b634e487b7160e01b600052601260045260246000fd5b818103818111156103b2576103b2612b7f56fe1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c230644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9da26469706673582212204742dc9bfec89e0e4850b0f02ca1922cc3c73b8db4ec85457e11b7fd8d117c1264736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061009e5760003560e01c806391ec2d2b1161006657806391ec2d2b1461013b578063a850a9091461015b578063d58e77331461016e578063e242cce914610181578063ebbdac911461019457600080fd5b8063115000fe146100a3578063247dd9fb146100cb5780633e5476ce146100de5780638669026f146101085780639141376314610128575b600080fd5b6100b66100b1366004612708565b6101a7565b60405190151581526020015b60405180910390f35b6100b66100d9366004612785565b61030e565b6100f16100ec366004612841565b6103b8565b6040805192151583529015156020830152016100c2565b61011b6101163660046128ce565b61079f565b6040516100c29190612957565b6100f1610136366004612988565b6108bb565b61014e6101493660046128ce565b610d5c565b6040516100c29190612a86565b61011b6101693660046128ce565b610ff8565b61011b61017c366004612aa0565b6111d4565b6100b661018f366004612785565b6115aa565b6100f16101a2366004612ab9565b611609565b600081516020830151600080516020612d63833981519152828309600080516020612d638339815191528283098182830101600080516020612d638339815191528283840108600080516020612d638339815191528682600080516020612d6383398151915203860109935050600080516020612d638339815191528483600080516020612d63833981519152038301099150600080516020612d638339815191527f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e584089450600080516020612d638339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2830893506040870151925060608701519150600080516020612d638339815191528083600080516020612d63833981519152038508600080516020612d63833981519152848608099050600080516020612d63833981519152828460011b0994149290931491909116949350505050565b8051600090600080516020612d6383398151915211158061034157506020820151600080516020612d6383398151915211155b1561034e57506000919050565b60405163e242cce960e01b8152309063e242cce990610371908590600401612957565b602060405180830381865afa15801561038e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103b29190612b1c565b92915050565b60008083806103e25760405162461bcd60e51b81526004016103d990612b3e565b60405180910390fd5b60006103ef826001612b95565b6103fa906006612ba8565b90506000816001600160401b038111156104165761041661269a565b60405190808252806020026020018201604052801561043f578160200160208202803683370190505b50905088600060200201358160008151811061045d5761045d612b06565b602090810291909101015288600160200201358160018151811061048357610483612b06565b602002602001018181525050600080516020612d43833981519152816002815181106104b1576104b1612b06565b602002602001018181525050600080516020612d23833981519152816003815181106104df576104df612b06565b602002602001018181525050600080516020612d838339815191528160048151811061050d5761050d612b06565b602002602001018181525050600080516020612da38339815191528160058151811061053b5761053b612b06565b60200260200101818152505060005b8381101561075a57863582610560836006612ba8565b61056b906006612b95565b8151811061057b5761057b612b06565b602090810291909101015286600160200201358261059a836006612ba8565b6105a5906007612b95565b815181106105b5576105b5612b06565b6020026020010181815250508888828181106105d3576105d3612b06565b9050608002016001600481106105eb576105eb612b06565b6020020135826105fc836006612ba8565b610607906008612b95565b8151811061061757610617612b06565b60200260200101818152505088888281811061063557610635612b06565b90506080020160006004811061064d5761064d612b06565b60200201358261065e836006612ba8565b610669906009612b95565b8151811061067957610679612b06565b60200260200101818152505088888281811061069757610697612b06565b9050608002016003600481106106af576106af612b06565b6020020135826106c0836006612ba8565b6106cb90600a612b95565b815181106106db576106db612b06565b6020026020010181815250508888828181106106f9576106f9612b06565b90506080020160026004811061071157610711612b06565b602002013582610722836006612ba8565b61072d90600b612b95565b8151811061073d5761073d612b06565b60209081029190910101528061075281612bbf565b91505061054a565b50610763612640565b602081602085026020850160085afa945084610789576000809550955050505050610796565b5115159450600193505050505b94509492505050565b6107a761265e565b6040516391ec2d2b60e01b815260009030906391ec2d2b906107cf9087908790600401612bd8565b600060405180830381865afa1580156107ec573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526108149190810190612bf9565b9050600080600080601885016001600160c01b0381511693506030860190506001600160c01b038151169450600080516020612d6383398151915285600080516020612d63833981519152600160c01b870908604887015160608801516001600160c01b0390811697501694509250600080516020612d6383398151915290508481600160c01b860908604080518082019091529283526020830152509695505050505050565b60008084806108dc5760405162461bcd60e51b81526004016103d990612b3e565b8084146109495760405162461bcd60e51b815260206004820152603560248201527f424c533a206e756d626572206f66207075626c6963206b65797320616e64206d604482015274195cdcd859d95cc81b5d5cdd08189948195c5d585b605a1b60648201526084016103d9565b6000610956826001612b95565b610961906006612ba8565b90506000816001600160401b0381111561097d5761097d61269a565b6040519080825280602002602001820160405280156109a6578160200160208202803683370190505b5090508960006020020135816000815181106109c4576109c4612b06565b60209081029190910101528960016020020135816001815181106109ea576109ea612b06565b602002602001018181525050600080516020612d4383398151915281600281518110610a1857610a18612b06565b602002602001018181525050600080516020612d2383398151915281600381518110610a4657610a46612b06565b602002602001018181525050600080516020612d8383398151915281600481518110610a7457610a74612b06565b602002602001018181525050600080516020612da383398151915281600581518110610aa257610aa2612b06565b60200260200101818152505060005b83811015610d1657878782818110610acb57610acb612b06565b905060400201600060028110610ae357610ae3612b06565b602002013582610af4836006612ba8565b610aff906006612b95565b81518110610b0f57610b0f612b06565b602002602001018181525050878782818110610b2d57610b2d612b06565b905060400201600160028110610b4557610b45612b06565b602002013582610b56836006612ba8565b610b61906007612b95565b81518110610b7157610b71612b06565b602002602001018181525050898982818110610b8f57610b8f612b06565b905060800201600160048110610ba757610ba7612b06565b602002013582610bb8836006612ba8565b610bc3906008612b95565b81518110610bd357610bd3612b06565b602002602001018181525050898982818110610bf157610bf1612b06565b905060800201600060048110610c0957610c09612b06565b602002013582610c1a836006612ba8565b610c25906009612b95565b81518110610c3557610c35612b06565b602002602001018181525050898982818110610c5357610c53612b06565b905060800201600360048110610c6b57610c6b612b06565b602002013582610c7c836006612ba8565b610c8790600a612b95565b81518110610c9757610c97612b06565b602002602001018181525050898982818110610cb557610cb5612b06565b905060800201600260048110610ccd57610ccd612b06565b602002013582610cde836006612ba8565b610ce990600b612b95565b81518110610cf957610cf9612b06565b602090810291909101015280610d0e81612bbf565b915050610ab1565b50610d1f612640565b602081602085026020850160085afa945084610d45576000809550955050505050610d52565b5115159450600193505050505b9550959350505050565b80516060906000610d6e826020612b95565b610d79906040612b95565b610d84906004612b95565b6001600160401b03811115610d9b57610d9b61269a565b6040519080825280601f01601f191660200182016040528015610dc5576020820181803683370190505b5060408051606080825260808201909252919250600091906020820181803683370190505090506060820160005b84811015610e0d5760208188018101518383015201610df3565b5083016000815360010160608153600101600081536001810187905260210160208153506000600283604051610e439190612c6f565b602060405180830381855afa158015610e60573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610e839190612c8b565b9050600060429450848452816020850152600160408501536041840188905260206061850153600284604051610eb99190612c6f565b602060405180830381855afa158015610ed6573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610ef99190612c8b565b905080602084015280821880602086015250600260408501536041840188905260206061850153600284604051610f309190612c6f565b602060405180830381855afa158015610f4d573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610f709190612c8b565b905080604084015280821880602086015250600360408501536041840188905260206061850153600284604051610fa79190612c6f565b602060405180830381855afa158015610fc4573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610fe79190612c8b565b606084015250909695505050505050565b61100061265e565b604051638669026f60e01b81526000903090638669026f906110289087908790600401612bd8565b6040805180830381865afa158015611044573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110689190612ca4565b805160405163d58e773360e01b81526004810191909152909150600090309063d58e7733906024016040805180830381865afa1580156110ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110d09190612ca4565b602083015160405163d58e773360e01b81526004810191909152909150600090309063d58e7733906024016040805180830381865afa158015611117573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061113b9190612ca4565b905061114561267c565b825181526020808401518282015282516040808401919091529083015160608301526000908460808460066107d05a03fa9050808061118057fe5b50806111c85760405162461bcd60e51b8152602060048201526017602482015276109314ce88189b881859190818d85b1b0819985a5b1959604a1b60448201526064016103d9565b50919695505050505050565b6111dc61265e565b600080516020612d6383398151915282106112455760405162461bcd60e51b815260206004820152602360248201527f6d6170546f506f696e7446543a20696e76616c6964206669656c6420656c656d604482015262195b9d60ea1b60648201526084016103d9565b81600061125182611790565b9150506000600080516020612d638339815191528061127257611272612cf9565b8384099050600080516020612d638339815191526004820890506000600080516020612d6383398151915277b3c4d79d41a91759a9e4c7e359b6b89eaec68e62effffffd850990506000600080516020612d6383398151915283830990506112d9816117b9565b9050600080516020612d638339815191528283099150600080516020612d638339815191528183099150600080516020612d638339815191528286099150600080516020612d6383398151915261133e83600080516020612d63833981519152612d0f565b7759e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe089450600080516020612d638339815191528586099150600080516020612d638339815191528583099150600080516020612d6383398151915260038308915060006113a283611790565b909350905080156113ea57846113cd576113ca83600080516020612d63833981519152612d0f565b92505b505060408051808201909152938452602084015250909392505050565b600080516020612d638339815191526001870861141590600080516020612d63833981519152612d0f565b9550600080516020612d638339815191528687099250600080516020612d638339815191528684099250600080516020612d6383398151915260038408925061145d83611790565b9093509050801561148557846113cd576113ca83600080516020612d63833981519152612d0f565b600080516020612d638339815191528485099550600080516020612d638339815191528687099550600080516020612d638339815191528287099550600080516020612d638339815191528287099550600080516020612d63833981519152600187089550600080516020612d638339815191528687099250600080516020612d638339815191528684099250600080516020612d6383398151915260038408925061153083611790565b90935090508061158d5760405162461bcd60e51b815260206004820152602260248201527f424c533a20626164206674206d617070696e6720696d706c656d656e7461746960448201526137b760f11b60648201526084016103d9565b846113cd576113ca83600080516020612d63833981519152612d0f565b600081516020830151600080516020612d63833981519152828309600080516020612d638339815191528382099050600080516020612d63833981519152600382089050600080516020612d6383398151915282830914949350505050565b60008060006040518061018001604052808760006002811061162d5761162d612b06565b602002013581526020018760016002811061164a5761164a612b06565b60200201358152602001600080516020612d438339815191528152602001600080516020612d238339815191528152602001600080516020612d838339815191528152602001600080516020612da38339815191528152602001856000600281106116b7576116b7612b06565b60200201358152602001856001600281106116d4576116d4612b06565b60200201358152602001866001600481106116f1576116f1612b06565b602002013581526020018660006004811061170e5761170e612b06565b602002013581526020018660036004811061172b5761172b612b06565b602002013581526020018660026004811061174857611748612b06565b602002013590529050611759612640565b60006020826101808560085afa90508061177c5760008094509450505050611788565b50511515925060019150505b935093915050565b60008061179c836117c4565b915082600080516020612d63833981519152838409149050915091565b60006103b282611ef9565b6000600080516020612d638339815191528083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508584880996508587880996508587880996508587880996508587880996508587880996508581880996505050505050808283099392505050565b6000600080516020612d638339815191528083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996505050838586099450838586099450838586099450838586099450838186099450508284850993508284850993508284850993508281850993508284850993508284850993508285850993508284850993508284850993508284850993508284850993508284850993508284850993508281850995945050505050565b60405180602001604052806001906020820280368337509192915050565b60405180604001604052806002906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b03811182821017156126d2576126d261269a565b60405290565b604051601f8201601f191681016001600160401b03811182821017156127005761270061269a565b604052919050565b60006080828403121561271a57600080fd5b82601f83011261272957600080fd5b604051608081018181106001600160401b038211171561274b5761274b61269a565b60405280608084018581111561276057600080fd5b845b8181101561277a578035835260209283019201612762565b509195945050505050565b60006040828403121561279757600080fd5b82601f8301126127a657600080fd5b6127ae6126b0565b8060408401858111156127c057600080fd5b845b818110156127da5780358452602093840193016127c2565b509095945050505050565b80604081018310156103b257600080fd5b60008083601f84011261280857600080fd5b5081356001600160401b0381111561281f57600080fd5b6020830191508360208260071b850101111561283a57600080fd5b9250929050565b60008060008060a0858703121561285757600080fd5b61286186866127e5565b935060408501356001600160401b0381111561287c57600080fd5b612888878288016127f6565b909450925061289c905086606087016127e5565b905092959194509250565b60006001600160401b038211156128c0576128c061269a565b50601f01601f191660200190565b600080604083850312156128e157600080fd5b8235915060208301356001600160401b038111156128fe57600080fd5b8301601f8101851361290f57600080fd5b803561292261291d826128a7565b6126d8565b81815286602083850101111561293757600080fd5b816020840160208301376000602083830101528093505050509250929050565b60408101818360005b600281101561297f578151835260209283019290910190600101612960565b50505092915050565b6000806000806000608086880312156129a057600080fd5b6129aa87876127e5565b945060408601356001600160401b03808211156129c657600080fd5b6129d289838a016127f6565b909650945060608801359150808211156129eb57600080fd5b818801915088601f8301126129ff57600080fd5b813581811115612a0e57600080fd5b8960208260061b8501011115612a2357600080fd5b9699959850939650602001949392505050565b60005b83811015612a51578181015183820152602001612a39565b50506000910152565b60008151808452612a72816020860160208601612a36565b601f01601f19169290920160200192915050565b602081526000612a996020830184612a5a565b9392505050565b600060208284031215612ab257600080fd5b5035919050565b60008060006101008486031215612acf57600080fd5b612ad985856127e5565b925060c0840185811115612aec57600080fd5b604085019250612afc86826127e5565b9150509250925092565b634e487b7160e01b600052603260045260246000fd5b600060208284031215612b2e57600080fd5b81518015158114612a9957600080fd5b60208082526021908201527f424c533a206e756d626572206f66207075626c6963206b6579206973207a65726040820152606f60f81b606082015260800190565b634e487b7160e01b600052601160045260246000fd5b808201808211156103b2576103b2612b7f565b80820281158282048414176103b2576103b2612b7f565b600060018201612bd157612bd1612b7f565b5060010190565b828152604060208201526000612bf16040830184612a5a565b949350505050565b600060208284031215612c0b57600080fd5b81516001600160401b03811115612c2157600080fd5b8201601f81018413612c3257600080fd5b8051612c4061291d826128a7565b818152856020838501011115612c5557600080fd5b612c66826020830160208601612a36565b95945050505050565b60008251612c81818460208701612a36565b9190910192915050565b600060208284031215612c9d57600080fd5b5051919050565b600060408284031215612cb657600080fd5b82601f830112612cc557600080fd5b612ccd6126b0565b806040840185811115612cdf57600080fd5b845b818110156127da578051845260209384019301612ce1565b634e487b7160e01b600052601260045260246000fd5b818103818111156103b2576103b2612b7f56fe1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c230644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9da26469706673582212204742dc9bfec89e0e4850b0f02ca1922cc3c73b8db4ec85457e11b7fd8d117c1264736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var BN256G2Artifact string = `{"_format":"hh-sol-artifact-1","contractName":"BN256G2","sourceName":"contracts/common/BN256G2.sol","abi":[{"inputs":[],"name":"G2_NEG_X_IM","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"G2_NEG_X_RE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"G2_NEG_Y_IM","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"G2_NEG_Y_RE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"pt1xx","type":"uint256"},{"internalType":"uint256","name":"pt1xy","type":"uint256"},{"internalType":"uint256","name":"pt1yx","type":"uint256"},{"internalType":"uint256","name":"pt1yy","type":"uint256"},{"internalType":"uint256","name":"pt2xx","type":"uint256"},{"internalType":"uint256","name":"pt2xy","type":"uint256"},{"internalType":"uint256","name":"pt2yx","type":"uint256"},{"internalType":"uint256","name":"pt2yy","type":"uint256"}],"name":"ecTwistAdd","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"s","type":"uint256"},{"internalType":"uint256","name":"pt1xx","type":"uint256"},{"internalType":"uint256","name":"pt1xy","type":"uint256"},{"internalType":"uint256","name":"pt1yx","type":"uint256"},{"internalType":"uint256","name":"pt1yy","type":"uint256"}],"name":"ecTwistMul","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFieldModulus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610dfd806100206000396000f3fe608060405234801561001057600080fd5b506004361061006d5760003560e01c80635120675214610072578063779d890d146100ac578063783bde80146100c05780639b0c399a146100e7578063ad50f9c11461010e578063cbe96a5014610135578063defbcdee14610168575b600080fd5b6100997f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d81565b6040519081526020015b60405180910390f35b600080516020610da8833981519152610099565b6100997f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c281565b6100997f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed81565b6100997f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec81565b610148610143366004610c8c565b61017b565b6040805194855260208501939093529183015260608201526080016100a3565b610148610176366004610ce1565b61031a565b60008080808b15801561018c57508a155b8015610196575089155b80156101a0575088155b1561020a57871580156101b1575086155b80156101bb575085155b80156101c5575084155b6101fa576101d5888888886103b1565b6101fa5760405162461bcd60e51b81526004016101f190610d1c565b60405180910390fd5b508692508591508490508361030b565b87158015610216575086155b8015610220575085155b801561022a575084155b156102675761023b8c8c8c8c6103b1565b6102575760405162461bcd60e51b81526004016101f190610d1c565b508a92508991508890508761030b565b6102738c8c8c8c6103b1565b61028f5760405162461bcd60e51b81526004016101f190610d1c565b61029b888888886103b1565b6102b75760405162461bcd60e51b81526004016101f190610d1c565b60006102d18d8d8d8d600160008f8f8f8f60016000610466565b90506103018160005b602090810291909101519083015160408401516060850151608086015160a08701516106f1565b9450945094509450505b98509850985098945050505050565b600080808060018815801561032d575087155b8015610337575086155b8015610341575085155b15610355575060019750879550600061037d565b610361898989896103b1565b61037d5760405162461bcd60e51b81526004016101f190610d1c565b600061038f8b8b8b8b8b87600061075c565b905061039c8160006102da565b929e919d509b50909950975050505050505050565b60008060008060006103c5878789896107df565b90945092506103d6898981816107df565b90925090506103e782828b8b6107df565b90925090506103f884848484610850565b909450925061044884847f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e57e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2610850565b909450925083158015610459575082155b9998505050505050505050565b61046e610c50565b8815801561047a575087155b156104bc578686868686868660005b60a089019290925260808801929092526060870192909252604086019290925260208581019390935290910201526106e1565b821580156104c8575081155b156104db578c8c8c8c8c8c866000610489565b6104e785858b8b6107df565b90955093506104f88b8b85856107df565b6060830152604082015261050e87878b8b6107df565b909750955061051f8d8d85856107df565b60a0830152608082018190528714801561053c575060a081015186145b15610581576040810151851480156105575750606081015184145b156105725761056a8d8d8d8d8d8d610892565b866000610489565b60016000818180808681610489565b61058d898985856107df565b90935091506105ad858583600260200201518460035b6020020151610850565b909d509b506105c887878360045b60200201518460056105a3565b909b5099506105d98b8b81816107df565b90995097506105fa89898360045b60200201518460055b60200201516107df565b909550935061060b89898d8d6107df565b909950975061061c898985856107df565b60a083015260808201526106328d8d81816107df565b9097509550610643878785856107df565b909750955061065487878b8b610850565b909750955061066585856002610ada565b909350915061067687878585610850565b90975095506106878b8b89896107df565b6020830152815261069a85858989610850565b909b5099506106ab8d8d8d8d6107df565b909b5099506106c5898983600260200201518460036105f0565b909d509b506106d68b8b8f8f610850565b606083015260408201525b9c9b505050505050505050505050565b600080600080600080610702610c6e565b61070c8989610b0d565b909350915061071d8d8d85856107df565b602083015281526107308b8b85856107df565b60608301819052604083018290528251602090930151929f929e50909c509a5098505050505050505050565b610764610c50565b87156107d45760018816156107a5578051602082015160408301516060840151608085015160a08601516107a29594939291908d8d8d8d8d8d610466565b90505b6107b3878787878787610892565b949b509299509097509550935091506107cd600289610d5e565b9750610764565b979650505050505050565b60008061081d600080516020610da8833981519152858809600080516020610da8833981519152858809600080516020610da8833981519152610b98565b600080516020610da883398151915280868809600080516020610da8833981519152868a09089150915094509492505050565b60008061086c8685600080516020610da8833981519152610b98565b6108858685600080516020610da8833981519152610b98565b9150915094509492505050565b6000806000806000806108a3610c50565b6108af8d8d6003610ada565b602083018190528183526108c591908f8f6107df565b602083015281526108d88b8b8b8b6107df565b90995097506108e98d8d8d8d6107df565b606083015260408201819052610909908260035b60200201518b8b6107df565b60608301526040820152805161092c908260015b602002015183518460016105f0565b6040830151919e509c5061094a908260035b60200201516008610ada565b60a083015260808201526109618d8d8360046105bb565b909d509b50610972898981816107df565b60a083015260808201526040810151606082015161099291906004610ada565b60608301819052604083018290526109ac91908f8f610850565b6060830152604082018190526109c49082600361091d565b606083015260408201526109da8b8b6008610ada565b602083018190528183526109f091908d8d6107df565b60208301819052818352610a0791908360046105e7565b602083015280825260408201516060830151610a25928460016105a3565b60608301526040820152610a3b8d8d6002610ada565b6020830152808252610a4f908260016108fd565b60208301528152610a6389898360046105e7565b60a083015260808201819052610a7b9082600561093e565b826004602002018360056020020191909152528060006020020151816001602002015182600260200201518360036020020151846004602002015185600560200201519650965096509650965096505096509650965096509650969050565b600080600080516020610da8833981519152838609600080516020610da883398151915284860991509150935093915050565b60008080610b4e600080516020610da883398151915280878809600080516020610da883398151915287880908600080516020610da8833981519152610bbc565b9050600080516020610da8833981519152818609600080516020610da8833981519152828609610b8c90600080516020610da8833981519152610d80565b92509250509250929050565b60008180610ba857610ba8610d48565b610bb28484610d80565b8508949350505050565b60008060405160208152602080820152602060408201528460608201526002840360808201528360a082015260208160c08360056107d05a03fa90519250905080610c495760405162461bcd60e51b815260206004820152601a60248201527f6572726f722077697468206d6f64756c617220696e766572736500000000000060448201526064016101f1565b5092915050565b6040518060c001604052806006906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b600080600080600080600080610100898b031215610ca957600080fd5b505086359860208801359850604088013597606081013597506080810135965060a0810135955060c0810135945060e0013592509050565b600080600080600060a08688031215610cf957600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b602080825260129082015271706f696e74206e6f7420696e20637572766560701b604082015260600190565b634e487b7160e01b600052601260045260246000fd5b600082610d7b57634e487b7160e01b600052601260045260246000fd5b500490565b81810381811115610da157634e487b7160e01b600052601160045260246000fd5b9291505056fe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47a2646970667358221220bbba255b0b3e97590748f6a15256dba836d9ad9b81608a97cbf4954595465a8664736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061006d5760003560e01c80635120675214610072578063779d890d146100ac578063783bde80146100c05780639b0c399a146100e7578063ad50f9c11461010e578063cbe96a5014610135578063defbcdee14610168575b600080fd5b6100997f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d81565b6040519081526020015b60405180910390f35b600080516020610da8833981519152610099565b6100997f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c281565b6100997f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed81565b6100997f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec81565b610148610143366004610c8c565b61017b565b6040805194855260208501939093529183015260608201526080016100a3565b610148610176366004610ce1565b61031a565b60008080808b15801561018c57508a155b8015610196575089155b80156101a0575088155b1561020a57871580156101b1575086155b80156101bb575085155b80156101c5575084155b6101fa576101d5888888886103b1565b6101fa5760405162461bcd60e51b81526004016101f190610d1c565b60405180910390fd5b508692508591508490508361030b565b87158015610216575086155b8015610220575085155b801561022a575084155b156102675761023b8c8c8c8c6103b1565b6102575760405162461bcd60e51b81526004016101f190610d1c565b508a92508991508890508761030b565b6102738c8c8c8c6103b1565b61028f5760405162461bcd60e51b81526004016101f190610d1c565b61029b888888886103b1565b6102b75760405162461bcd60e51b81526004016101f190610d1c565b60006102d18d8d8d8d600160008f8f8f8f60016000610466565b90506103018160005b602090810291909101519083015160408401516060850151608086015160a08701516106f1565b9450945094509450505b98509850985098945050505050565b600080808060018815801561032d575087155b8015610337575086155b8015610341575085155b15610355575060019750879550600061037d565b610361898989896103b1565b61037d5760405162461bcd60e51b81526004016101f190610d1c565b600061038f8b8b8b8b8b87600061075c565b905061039c8160006102da565b929e919d509b50909950975050505050505050565b60008060008060006103c5878789896107df565b90945092506103d6898981816107df565b90925090506103e782828b8b6107df565b90925090506103f884848484610850565b909450925061044884847f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e57e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2610850565b909450925083158015610459575082155b9998505050505050505050565b61046e610c50565b8815801561047a575087155b156104bc578686868686868660005b60a089019290925260808801929092526060870192909252604086019290925260208581019390935290910201526106e1565b821580156104c8575081155b156104db578c8c8c8c8c8c866000610489565b6104e785858b8b6107df565b90955093506104f88b8b85856107df565b6060830152604082015261050e87878b8b6107df565b909750955061051f8d8d85856107df565b60a0830152608082018190528714801561053c575060a081015186145b15610581576040810151851480156105575750606081015184145b156105725761056a8d8d8d8d8d8d610892565b866000610489565b60016000818180808681610489565b61058d898985856107df565b90935091506105ad858583600260200201518460035b6020020151610850565b909d509b506105c887878360045b60200201518460056105a3565b909b5099506105d98b8b81816107df565b90995097506105fa89898360045b60200201518460055b60200201516107df565b909550935061060b89898d8d6107df565b909950975061061c898985856107df565b60a083015260808201526106328d8d81816107df565b9097509550610643878785856107df565b909750955061065487878b8b610850565b909750955061066585856002610ada565b909350915061067687878585610850565b90975095506106878b8b89896107df565b6020830152815261069a85858989610850565b909b5099506106ab8d8d8d8d6107df565b909b5099506106c5898983600260200201518460036105f0565b909d509b506106d68b8b8f8f610850565b606083015260408201525b9c9b505050505050505050505050565b600080600080600080610702610c6e565b61070c8989610b0d565b909350915061071d8d8d85856107df565b602083015281526107308b8b85856107df565b60608301819052604083018290528251602090930151929f929e50909c509a5098505050505050505050565b610764610c50565b87156107d45760018816156107a5578051602082015160408301516060840151608085015160a08601516107a29594939291908d8d8d8d8d8d610466565b90505b6107b3878787878787610892565b949b509299509097509550935091506107cd600289610d5e565b9750610764565b979650505050505050565b60008061081d600080516020610da8833981519152858809600080516020610da8833981519152858809600080516020610da8833981519152610b98565b600080516020610da883398151915280868809600080516020610da8833981519152868a09089150915094509492505050565b60008061086c8685600080516020610da8833981519152610b98565b6108858685600080516020610da8833981519152610b98565b9150915094509492505050565b6000806000806000806108a3610c50565b6108af8d8d6003610ada565b602083018190528183526108c591908f8f6107df565b602083015281526108d88b8b8b8b6107df565b90995097506108e98d8d8d8d6107df565b606083015260408201819052610909908260035b60200201518b8b6107df565b60608301526040820152805161092c908260015b602002015183518460016105f0565b6040830151919e509c5061094a908260035b60200201516008610ada565b60a083015260808201526109618d8d8360046105bb565b909d509b50610972898981816107df565b60a083015260808201526040810151606082015161099291906004610ada565b60608301819052604083018290526109ac91908f8f610850565b6060830152604082018190526109c49082600361091d565b606083015260408201526109da8b8b6008610ada565b602083018190528183526109f091908d8d6107df565b60208301819052818352610a0791908360046105e7565b602083015280825260408201516060830151610a25928460016105a3565b60608301526040820152610a3b8d8d6002610ada565b6020830152808252610a4f908260016108fd565b60208301528152610a6389898360046105e7565b60a083015260808201819052610a7b9082600561093e565b826004602002018360056020020191909152528060006020020151816001602002015182600260200201518360036020020151846004602002015185600560200201519650965096509650965096505096509650965096509650969050565b600080600080516020610da8833981519152838609600080516020610da883398151915284860991509150935093915050565b60008080610b4e600080516020610da883398151915280878809600080516020610da883398151915287880908600080516020610da8833981519152610bbc565b9050600080516020610da8833981519152818609600080516020610da8833981519152828609610b8c90600080516020610da8833981519152610d80565b92509250509250929050565b60008180610ba857610ba8610d48565b610bb28484610d80565b8508949350505050565b60008060405160208152602080820152602060408201528460608201526002840360808201528360a082015260208160c08360056107d05a03fa90519250905080610c495760405162461bcd60e51b815260206004820152601a60248201527f6572726f722077697468206d6f64756c617220696e766572736500000000000060448201526064016101f1565b5092915050565b6040518060c001604052806006906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b600080600080600080600080610100898b031215610ca957600080fd5b505086359860208801359850604088013597606081013597506080810135965060a0810135955060c0810135945060e0013592509050565b600080600080600060a08688031215610cf957600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b602080825260129082015271706f696e74206e6f7420696e20637572766560701b604082015260600190565b634e487b7160e01b600052601260045260246000fd5b600082610d7b57634e487b7160e01b600052601260045260246000fd5b500490565b81810381811115610da157634e487b7160e01b600052601160045260246000fd5b9291505056fe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47a2646970667358221220bbba255b0b3e97590748f6a15256dba836d9ad9b81608a97cbf4954595465a8664736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var MerkleArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"Merkle","sourceName":"contracts/common/Merkle.sol","abi":[],"bytecode":"0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212202db2509119c8628b728a484ebc6ba9b1e5898ea19639476f14228eb88863575864736f6c63430008130033","deployedBytecode":"0x73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212202db2509119c8628b728a484ebc6ba9b1e5898ea19639476f14228eb88863575864736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var CheckpointManagerArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"CheckpointManager","sourceName":"contracts/root/CheckpointManager.sol","abi":[{"inputs":[{"internalType":"address","name":"initiator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"inputs":[],"name":"DOMAIN","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bls","outputs":[{"internalType":"contract IBLS","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bn256G2","outputs":[{"internalType":"contract IBN256G2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"chainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"checkpointBlockNumbers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"checkpoints","outputs":[{"internalType":"uint256","name":"epoch","type":"uint256"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes32","name":"eventRoot","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentCheckpointBlockNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"currentValidatorSet","outputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"uint256","name":"votingPower","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentValidatorSetHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentValidatorSetLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getCheckpointBlock","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes32","name":"leaf","type":"bytes32"},{"internalType":"uint256","name":"leafIndex","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"getEventMembershipByBlockNumber","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"epoch","type":"uint256"},{"internalType":"bytes32","name":"leaf","type":"bytes32"},{"internalType":"uint256","name":"leafIndex","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"getEventMembershipByEpoch","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getEventRootByBlock","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IBLS","name":"newBls","type":"address"},{"internalType":"contract IBN256G2","name":"newBn256G2","type":"address"},{"internalType":"uint256","name":"chainId_","type":"uint256"},{"components":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"uint256[4]","name":"blsKey","type":"uint256[4]"},{"internalType":"uint256","name":"votingPower","type":"uint256"}],"internalType":"struct ICheckpointManager.Validator[]","name":"newValidatorSet","type":"tuple[]"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"internalType":"uint256","name":"blockRound","type":"uint256"},{"internalType":"bytes32","name":"currentValidatorSetHash","type":"bytes32"}],"internalType":"struct ICheckpointManager.CheckpointMetadata","name":"checkpointMetadata","type":"tuple"},{"components":[{"internalType":"uint256","name":"epoch","type":"uint256"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes32","name":"eventRoot","type":"bytes32"}],"internalType":"struct ICheckpointManager.Checkpoint","name":"checkpoint","type":"tuple"},{"internalType":"uint256[2]","name":"signature","type":"uint256[2]"},{"components":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"uint256[4]","name":"blsKey","type":"uint256[4]"},{"internalType":"uint256","name":"votingPower","type":"uint256"}],"internalType":"struct ICheckpointManager.Validator[]","name":"newValidatorSet","type":"tuple[]"},{"internalType":"bytes","name":"bitmap","type":"bytes"}],"name":"submit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalVotingPower","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"bytecode":"0x60a060405234801561001057600080fd5b506040516115ed3803806115ed83398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161155b6100926000396000818161052b0152610563015261155b6000f3fe608060405234801561001057600080fd5b50600436106101165760003560e01c806376671808116100a2578063babd4ee411610071578063babd4ee4146102c8578063d4c8e3e8146102db578063e416d677146102ee578063e9193d2b146102f7578063f896f1a51461030a57600080fd5b8063766718081461024157806395b0b0271461024a5780639a8a059214610275578063b8a242521461027e57600080fd5b806361a02208116100e957806361a022081461019b578063671b3793146101be5780636969a25c146101c7578063729e7c6e1461021957806373cb1a111461022c57600080fd5b80631d1d4f261461011b57806322fd1818146101375780633569ed931461016157806352a9674b14610174575b600080fd5b61012460035481565b6040519081526020015b60405180910390f35b61014a610145366004610f34565b610313565b60408051921515835260208301919091520161012e565b61012461016f366004610f34565b610364565b6101247fbee7fa562a38908559e4a988e62d6c08b84ef05f2ebd5d2bb2b855dc19d19fe281565b6101ae6101a9366004610f4d565b610398565b604051901515815260200161012e565b61012460055481565b6101fa6101d5366004610f34565b600960205260009081526040902080546005909101546001600160a01b039091169082565b604080516001600160a01b03909316835260208301919091520161012e565b6101ae610227366004610f4d565b610410565b61023f61023a366004611041565b610469565b005b61012460025481565b60065461025d906001600160a01b031681565b6040516001600160a01b03909116815260200161012e565b61012460015481565b6102ad61028c366004610f34565b60086020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161012e565b61023f6102d63660046110cc565b61061f565b60075461025d906001600160a01b031681565b61012460045481565b610124610305366004610f34565b610884565b610124600b5481565b60008080610322600a856108a5565b600a54909150810361033a5750600093849350915050565b6001600a828154811061034f5761034f6111a5565b90600052602060002001549250925050915091565b6000600881610374600a856108a5565b61037f9060016111d1565b8152602001908152602001600020600201549050919050565b6000806103a487610364565b9050806103f85760405162461bcd60e51b815260206004820152601e60248201527f4e4f5f4556454e545f524f4f545f464f525f424c4f434b5f4e554d424552000060448201526064015b60405180910390fd5b6104058686838787610954565b979650505050505050565b600085815260086020526040812060020154806103f85760405162461bcd60e51b815260206004820152601760248201527609c9ebe8aac8a9ca8bea49e9ea8be8c9ea4be8aa09e869604b1b60448201526064016103ef565b600054610100900460ff16158080156104895750600054600160ff909116105b806104a35750303b1580156104a3575060005460ff166001145b6105065760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016103ef565b6000805460ff191660011790558015610529576000805461ff0019166101001790555b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161561058d57336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461058d57600080fd5b6001849055600680546001600160a01b038089166001600160a01b031992831617909255600780549288169290911691909117905560038290556105d1838361099e565b8015610617576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b8660400135600b54146106745760405162461bcd60e51b815260206004820152601a60248201527f494e56414c49445f56414c494441544f525f5345545f4841534800000000000060448201526064016103ef565b6000600154876020013589600001358a602001358a600001358b604001358d604001358b8b6040516020016106aa9291906111e4565b604051602081830303815290604052805190602001206040516020016106d7989796959493929190611247565b60408051601f198184030181528282528051602091820120908301520160408051601f198184030181529082905260065463a850a90960e01b83529092506107a3916001600160a01b039091169063a850a9099061075b907fbee7fa562a38908559e4a988e62d6c08b84ef05f2ebd5d2bb2b855dc19d19fe290869060040161127d565b6040805180830381865afa158015610777573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061079b91906112d3565b878585610aa4565b6002546107b08189610dae565b8735600081815260086020908152604091829020838155908b01356001820155908a013560029091015581101561083357600a8054600181018255600091825260208a01357fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8909101556002805490919061082a90611361565b90915550610867565b600a805460208a0135919061084a9060019061137a565b8154811061085a5761085a6111a5565b6000918252602090912001555b6020880135600455610879868661099e565b505050505050505050565b600a818154811061089457600080fd5b600091825260209091200154905081565b815460009081036108b85750600061094e565b82546000905b808210156109055760006108d28383610e8b565b600087815260209020909150859082015411156108f1578091506108ff565b6108fc8160016111d1565b92505b506108be565b60008211801561093157508361092e8661092060018661137a565b600091825260209091200190565b54145b1561094a5761094160018361137a565b9250505061094e565b5090505b92915050565b60008115610993578160051b8301835b8660011660051b8881528135602082185250604060002097508660011c96506020810190508181106109645750505b505050901591141690565b600381905560405181906109b890849083906020016111e4565b60408051601f198184030181529190528051602090910120600b556000805b82811015610a9b5760008585838181106109f3576109f36111a5565b905060c0020160a00135905060008111610a435760405162461bcd60e51b8152602060048201526011602482015270564f54494e475f504f5745525f5a45524f60781b60448201526064016103ef565b610a4d81846111d1565b9250858583818110610a6157610a616111a5565b905060c00201600960008481526020019081526020016000208181610a8691906113a4565b9050505080610a9490611361565b90506109d7565b50600555505050565b600354610aaf610f16565b6000805b83811015610c3357610ac6868683610ead565b15610c2b5781600003610b1a576000818152600960205260409081902081516080810190925260010160048282826020028201915b815481526020019060010190808311610afb5750505050509250610c0c565b60008181526009602052604080822081516080810190925260010160048282826020028201915b815481526020019060010190808311610b4157505060075488516020808b01516040808d01516060808f01518b51958c0151848d0151928d01519451630cbe96a560e41b81529c9d506001600160a01b039098169b63cbe96a509b50610bb89a509698509396919593949391929091600401611247565b608060405180830381865afa158015610bd5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bf99190611403565b6060880152604087015260208601528452505b600081815260096020526040902060050154610c2890836111d1565b91505b600101610ab3565b5080600003610c765760405162461bcd60e51b815260206004820152600f60248201526e4249544d41505f49535f454d50545960881b60448201526064016103ef565b60036005546002610c87919061138d565b610c91919061144f565b8111610cdb5760405162461bcd60e51b815260206004820152601960248201527824a729aaa32324a1a4a2a72a2fab27aa24a723afa827aba2a960391b60448201526064016103ef565b60065460405163ebbdac9160e01b815260009182916001600160a01b039091169063ebbdac9190610d14908b9088908e90600401611463565b6040805180830381865afa158015610d30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d5491906114de565b91509150818015610d625750805b6108795760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c454400000060448201526064016103ef565b600082815260086020908152604091829020825160608101845281548082526001830154938201939093526002909101549281019290925282351480610e0057508051610dfc9060016111d1565b8235145b610e3c5760405162461bcd60e51b815260206004820152600d60248201526c0929cac82989288be8aa09e869609b1b60448201526064016103ef565b8060200151826020013511610e865760405162461bcd60e51b815260206004820152601060248201526f115354151657d0d21150d2d413d2539560821b60448201526064016103ef565b505050565b6000610e9a600284841861144f565b610ea6908484166111d1565b9392505050565b600080610ebb60088461144f565b90506000610eca600885611511565b9050848210610ede57600092505050610ea6565b6000600160ff83161b878785818110610ef957610ef96111a5565b9050013560f81c60f81b60f81c60ff161611925050509392505050565b60405180608001604052806004906020820280368337509192915050565b600060208284031215610f4657600080fd5b5035919050565b600080600080600060808688031215610f6557600080fd5b853594506020860135935060408601359250606086013567ffffffffffffffff80821115610f9257600080fd5b818801915088601f830112610fa657600080fd5b813581811115610fb557600080fd5b8960208260051b8501011115610fca57600080fd5b9699959850939650602001949392505050565b6001600160a01b0381168114610ff257600080fd5b50565b60008083601f84011261100757600080fd5b50813567ffffffffffffffff81111561101f57600080fd5b60208301915083602060c08302850101111561103a57600080fd5b9250929050565b60008060008060006080868803121561105957600080fd5b853561106481610fdd565b9450602086013561107481610fdd565b935060408601359250606086013567ffffffffffffffff81111561109757600080fd5b6110a388828901610ff5565b969995985093965092949392505050565b6000606082840312156110c657600080fd5b50919050565b6000806000806000806000610140888a0312156110e857600080fd5b6110f289896110b4565b96506111018960608a016110b4565b955061010088018981111561111557600080fd5b60c0890195503567ffffffffffffffff8082111561113257600080fd5b61113e8b838c01610ff5565b90965094506101208a013591508082111561115857600080fd5b818a0191508a601f83011261116c57600080fd5b81358181111561117b57600080fd5b8b602082850101111561118d57600080fd5b60208301945080935050505092959891949750929550565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8082018082111561094e5761094e6111bb565b60208082528181018390526000908460408401835b8681101561123c57823561120c81610fdd565b6001600160a01b0316825260808385018584013760a0838101359083015260c092830192909101906001016111f9565b509695505050505050565b978852602088019690965260408701949094526060860192909252608085015260a084015260c083015260e08201526101000190565b82815260006020604081840152835180604085015260005b818110156112b157858101830151858201606001528201611295565b506000606082860101526060601f19601f830116850101925050509392505050565b6000604082840312156112e557600080fd5b82601f8301126112f457600080fd5b6040516040810181811067ffffffffffffffff8211171561132557634e487b7160e01b600052604160045260246000fd5b806040525080604084018581111561133c57600080fd5b845b8181101561135657805183526020928301920161133e565b509195945050505050565b600060018201611373576113736111bb565b5060010190565b8181038181111561094e5761094e6111bb565b808202811582820484141761094e5761094e6111bb565b81356113af81610fdd565b81546001600160a01b0319166001600160a01b0391909116178155602082810160005b60048110156113f2578135600182860181019190915591830191016113d2565b50505060a082013560058201555050565b6000806000806080858703121561141957600080fd5b505082516020840151604085015160609095015191969095509092509050565b634e487b7160e01b600052601260045260246000fd5b60008261145e5761145e611439565b500490565b61010081016040858337604082018460005b6004811015611494578151835260209283019290910190600101611475565b50505060c082018360005b60028110156114be57815183526020928301929091019060010161149f565b505050949350505050565b805180151581146114d957600080fd5b919050565b600080604083850312156114f157600080fd5b6114fa836114c9565b9150611508602084016114c9565b90509250929050565b60008261152057611520611439565b50069056fea2646970667358221220333371ed350c4bed32fb9f706b6c8919ed167439ab6905122b177e698bce48e964736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101165760003560e01c806376671808116100a2578063babd4ee411610071578063babd4ee4146102c8578063d4c8e3e8146102db578063e416d677146102ee578063e9193d2b146102f7578063f896f1a51461030a57600080fd5b8063766718081461024157806395b0b0271461024a5780639a8a059214610275578063b8a242521461027e57600080fd5b806361a02208116100e957806361a022081461019b578063671b3793146101be5780636969a25c146101c7578063729e7c6e1461021957806373cb1a111461022c57600080fd5b80631d1d4f261461011b57806322fd1818146101375780633569ed931461016157806352a9674b14610174575b600080fd5b61012460035481565b6040519081526020015b60405180910390f35b61014a610145366004610f34565b610313565b60408051921515835260208301919091520161012e565b61012461016f366004610f34565b610364565b6101247fbee7fa562a38908559e4a988e62d6c08b84ef05f2ebd5d2bb2b855dc19d19fe281565b6101ae6101a9366004610f4d565b610398565b604051901515815260200161012e565b61012460055481565b6101fa6101d5366004610f34565b600960205260009081526040902080546005909101546001600160a01b039091169082565b604080516001600160a01b03909316835260208301919091520161012e565b6101ae610227366004610f4d565b610410565b61023f61023a366004611041565b610469565b005b61012460025481565b60065461025d906001600160a01b031681565b6040516001600160a01b03909116815260200161012e565b61012460015481565b6102ad61028c366004610f34565b60086020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161012e565b61023f6102d63660046110cc565b61061f565b60075461025d906001600160a01b031681565b61012460045481565b610124610305366004610f34565b610884565b610124600b5481565b60008080610322600a856108a5565b600a54909150810361033a5750600093849350915050565b6001600a828154811061034f5761034f6111a5565b90600052602060002001549250925050915091565b6000600881610374600a856108a5565b61037f9060016111d1565b8152602001908152602001600020600201549050919050565b6000806103a487610364565b9050806103f85760405162461bcd60e51b815260206004820152601e60248201527f4e4f5f4556454e545f524f4f545f464f525f424c4f434b5f4e554d424552000060448201526064015b60405180910390fd5b6104058686838787610954565b979650505050505050565b600085815260086020526040812060020154806103f85760405162461bcd60e51b815260206004820152601760248201527609c9ebe8aac8a9ca8bea49e9ea8be8c9ea4be8aa09e869604b1b60448201526064016103ef565b600054610100900460ff16158080156104895750600054600160ff909116105b806104a35750303b1580156104a3575060005460ff166001145b6105065760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016103ef565b6000805460ff191660011790558015610529576000805461ff0019166101001790555b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161561058d57336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461058d57600080fd5b6001849055600680546001600160a01b038089166001600160a01b031992831617909255600780549288169290911691909117905560038290556105d1838361099e565b8015610617576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b8660400135600b54146106745760405162461bcd60e51b815260206004820152601a60248201527f494e56414c49445f56414c494441544f525f5345545f4841534800000000000060448201526064016103ef565b6000600154876020013589600001358a602001358a600001358b604001358d604001358b8b6040516020016106aa9291906111e4565b604051602081830303815290604052805190602001206040516020016106d7989796959493929190611247565b60408051601f198184030181528282528051602091820120908301520160408051601f198184030181529082905260065463a850a90960e01b83529092506107a3916001600160a01b039091169063a850a9099061075b907fbee7fa562a38908559e4a988e62d6c08b84ef05f2ebd5d2bb2b855dc19d19fe290869060040161127d565b6040805180830381865afa158015610777573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061079b91906112d3565b878585610aa4565b6002546107b08189610dae565b8735600081815260086020908152604091829020838155908b01356001820155908a013560029091015581101561083357600a8054600181018255600091825260208a01357fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8909101556002805490919061082a90611361565b90915550610867565b600a805460208a0135919061084a9060019061137a565b8154811061085a5761085a6111a5565b6000918252602090912001555b6020880135600455610879868661099e565b505050505050505050565b600a818154811061089457600080fd5b600091825260209091200154905081565b815460009081036108b85750600061094e565b82546000905b808210156109055760006108d28383610e8b565b600087815260209020909150859082015411156108f1578091506108ff565b6108fc8160016111d1565b92505b506108be565b60008211801561093157508361092e8661092060018661137a565b600091825260209091200190565b54145b1561094a5761094160018361137a565b9250505061094e565b5090505b92915050565b60008115610993578160051b8301835b8660011660051b8881528135602082185250604060002097508660011c96506020810190508181106109645750505b505050901591141690565b600381905560405181906109b890849083906020016111e4565b60408051601f198184030181529190528051602090910120600b556000805b82811015610a9b5760008585838181106109f3576109f36111a5565b905060c0020160a00135905060008111610a435760405162461bcd60e51b8152602060048201526011602482015270564f54494e475f504f5745525f5a45524f60781b60448201526064016103ef565b610a4d81846111d1565b9250858583818110610a6157610a616111a5565b905060c00201600960008481526020019081526020016000208181610a8691906113a4565b9050505080610a9490611361565b90506109d7565b50600555505050565b600354610aaf610f16565b6000805b83811015610c3357610ac6868683610ead565b15610c2b5781600003610b1a576000818152600960205260409081902081516080810190925260010160048282826020028201915b815481526020019060010190808311610afb5750505050509250610c0c565b60008181526009602052604080822081516080810190925260010160048282826020028201915b815481526020019060010190808311610b4157505060075488516020808b01516040808d01516060808f01518b51958c0151848d0151928d01519451630cbe96a560e41b81529c9d506001600160a01b039098169b63cbe96a509b50610bb89a509698509396919593949391929091600401611247565b608060405180830381865afa158015610bd5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bf99190611403565b6060880152604087015260208601528452505b600081815260096020526040902060050154610c2890836111d1565b91505b600101610ab3565b5080600003610c765760405162461bcd60e51b815260206004820152600f60248201526e4249544d41505f49535f454d50545960881b60448201526064016103ef565b60036005546002610c87919061138d565b610c91919061144f565b8111610cdb5760405162461bcd60e51b815260206004820152601960248201527824a729aaa32324a1a4a2a72a2fab27aa24a723afa827aba2a960391b60448201526064016103ef565b60065460405163ebbdac9160e01b815260009182916001600160a01b039091169063ebbdac9190610d14908b9088908e90600401611463565b6040805180830381865afa158015610d30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d5491906114de565b91509150818015610d625750805b6108795760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c454400000060448201526064016103ef565b600082815260086020908152604091829020825160608101845281548082526001830154938201939093526002909101549281019290925282351480610e0057508051610dfc9060016111d1565b8235145b610e3c5760405162461bcd60e51b815260206004820152600d60248201526c0929cac82989288be8aa09e869609b1b60448201526064016103ef565b8060200151826020013511610e865760405162461bcd60e51b815260206004820152601060248201526f115354151657d0d21150d2d413d2539560821b60448201526064016103ef565b505050565b6000610e9a600284841861144f565b610ea6908484166111d1565b9392505050565b600080610ebb60088461144f565b90506000610eca600885611511565b9050848210610ede57600092505050610ea6565b6000600160ff83161b878785818110610ef957610ef96111a5565b9050013560f81c60f81b60f81c60ff161611925050509392505050565b60405180608001604052806004906020820280368337509192915050565b600060208284031215610f4657600080fd5b5035919050565b600080600080600060808688031215610f6557600080fd5b853594506020860135935060408601359250606086013567ffffffffffffffff80821115610f9257600080fd5b818801915088601f830112610fa657600080fd5b813581811115610fb557600080fd5b8960208260051b8501011115610fca57600080fd5b9699959850939650602001949392505050565b6001600160a01b0381168114610ff257600080fd5b50565b60008083601f84011261100757600080fd5b50813567ffffffffffffffff81111561101f57600080fd5b60208301915083602060c08302850101111561103a57600080fd5b9250929050565b60008060008060006080868803121561105957600080fd5b853561106481610fdd565b9450602086013561107481610fdd565b935060408601359250606086013567ffffffffffffffff81111561109757600080fd5b6110a388828901610ff5565b969995985093965092949392505050565b6000606082840312156110c657600080fd5b50919050565b6000806000806000806000610140888a0312156110e857600080fd5b6110f289896110b4565b96506111018960608a016110b4565b955061010088018981111561111557600080fd5b60c0890195503567ffffffffffffffff8082111561113257600080fd5b61113e8b838c01610ff5565b90965094506101208a013591508082111561115857600080fd5b818a0191508a601f83011261116c57600080fd5b81358181111561117b57600080fd5b8b602082850101111561118d57600080fd5b60208301945080935050505092959891949750929550565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8082018082111561094e5761094e6111bb565b60208082528181018390526000908460408401835b8681101561123c57823561120c81610fdd565b6001600160a01b0316825260808385018584013760a0838101359083015260c092830192909101906001016111f9565b509695505050505050565b978852602088019690965260408701949094526060860192909252608085015260a084015260c083015260e08201526101000190565b82815260006020604081840152835180604085015260005b818110156112b157858101830151858201606001528201611295565b506000606082860101526060601f19601f830116850101925050509392505050565b6000604082840312156112e557600080fd5b82601f8301126112f457600080fd5b6040516040810181811067ffffffffffffffff8211171561132557634e487b7160e01b600052604160045260246000fd5b806040525080604084018581111561133c57600080fd5b845b8181101561135657805183526020928301920161133e565b509195945050505050565b600060018201611373576113736111bb565b5060010190565b8181038181111561094e5761094e6111bb565b808202811582820484141761094e5761094e6111bb565b81356113af81610fdd565b81546001600160a01b0319166001600160a01b0391909116178155602082810160005b60048110156113f2578135600182860181019190915591830191016113d2565b50505060a082013560058201555050565b6000806000806080858703121561141957600080fd5b505082516020840151604085015160609095015191969095509092509050565b634e487b7160e01b600052601260045260246000fd5b60008261145e5761145e611439565b500490565b61010081016040858337604082018460005b6004811015611494578151835260209283019290910190600101611475565b50505060c082018360005b60028110156114be57815183526020928301929091019060010161149f565b505050949350505050565b805180151581146114d957600080fd5b919050565b600080604083850312156114f157600080fd5b6114fa836114c9565b9150611508602084016114c9565b90509250929050565b60008261152057611520611439565b50069056fea2646970667358221220333371ed350c4bed32fb9f706b6c8919ed167439ab6905122b177e698bce48e964736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ExitHelperArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ExitHelper","sourceName":"contracts/root/ExitHelper.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"bool","name":"success","type":"bool"},{"indexed":false,"internalType":"bytes","name":"returnData","type":"bytes"}],"name":"ExitProcessed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"inputs":[{"components":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"uint256","name":"leafIndex","type":"uint256"},{"internalType":"bytes","name":"unhashedLeaf","type":"bytes"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"internalType":"struct IExitHelper.BatchExitInput[]","name":"inputs","type":"tuple[]"}],"name":"batchExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"checkpointManager","outputs":[{"internalType":"contract ICheckpointManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"uint256","name":"leafIndex","type":"uint256"},{"internalType":"bytes","name":"unhashedLeaf","type":"bytes"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"exit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ICheckpointManager","name":"newCheckpointManager","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"processedExits","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610b62806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c806350607b351461005c578063aa209cc314610071578063bd88ea7914610084578063c0857ba0146100bc578063c4d66de8146100e7575b600080fd5b61006f61006a3660046106c9565b6100fa565b005b61006f61007f36600461070b565b610200565b6100a76100923660046107bc565b60016020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6002546100cf906001600160a01b031681565b6040516001600160a01b0390911681526020016100b3565b61006f6100f53660046107ed565b610240565b6002546001600160a01b031661012b5760405162461bcd60e51b815260040161012290610811565b60405180910390fd5b8060005b818110156101fa576101f284848381811061014c5761014c610848565b905060200281019061015e919061085e565b3585858481811061017157610171610848565b9050602002810190610183919061085e565b6020013586868581811061019957610199610848565b90506020028101906101ab919061085e565b6101b990604081019061087e565b8888878181106101cb576101cb610848565b90506020028101906101dd919061085e565b6101eb9060608101906108c5565b60016103d2565b60010161012f565b50505050565b6002546001600160a01b03166102285760405162461bcd60e51b815260040161012290610811565b61023886868686868660006103d2565b505050505050565b600054610100900460ff16158080156102605750600054600160ff909116105b8061027a5750303b15801561027a575060005460ff166001145b6102dd5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610122565b6000805460ff191660011790558015610300576000805461ff0019166101001790555b6001600160a01b0382161580159061032157506001600160a01b0382163b15155b61036d5760405162461bcd60e51b815260206004820152601b60248201527f4578697448656c7065723a20494e56414c49445f4144445245535300000000006044820152606401610122565b600280546001600160a01b0319166001600160a01b03841617905580156103ce576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b60008080806103e3888a018a610925565b935093509350935084156104165760008481526001602052604090205460ff16156104115750505050610674565b610480565b60008481526001602052604090205460ff16156104805760405162461bcd60e51b815260206004820152602260248201527f4578697448656c7065723a20455849545f414c52454144595f50524f43455353604482015261115160f21b6064820152608401610122565b6002546040516001600160a01b03909116906361a02208908d906104a7908d908d90610a05565b6040519081900381206001600160e01b031960e085901b1682526104d492918f908d908d90600401610a15565b602060405180830381865afa1580156104f1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105159190610a62565b61055d5760405162461bcd60e51b815260206004820152601960248201527822bc34ba2432b63832b91d1024a72b20a624a22fa82927a7a360391b6044820152606401610122565b6000848152600160208190526040808320805460ff19169092179091555181906001600160a01b0385169061059a90889088908790602401610ad4565b60408051601f198184030181529181526020820180516001600160e01b031663f43cda8b60e01b179052516105cf9190610b07565b6000604051808303816000865af19150503d806000811461060c576040519150601f19603f3d011682016040523d82523d6000602084013e610611565b606091505b509150915081610632576000868152600160205260409020805460ff191690555b811515867f8bbfa0c9bee3785c03700d2a909592286efb83fc7e7002be5764424b9842f7ec836040516106659190610b19565b60405180910390a35050505050505b50505050505050565b60008083601f84011261068f57600080fd5b50813567ffffffffffffffff8111156106a757600080fd5b6020830191508360208260051b85010111156106c257600080fd5b9250929050565b600080602083850312156106dc57600080fd5b823567ffffffffffffffff8111156106f357600080fd5b6106ff8582860161067d565b90969095509350505050565b6000806000806000806080878903121561072457600080fd5b8635955060208701359450604087013567ffffffffffffffff8082111561074a57600080fd5b818901915089601f83011261075e57600080fd5b81358181111561076d57600080fd5b8a602082850101111561077f57600080fd5b60208301965080955050606089013591508082111561079d57600080fd5b506107aa89828a0161067d565b979a9699509497509295939492505050565b6000602082840312156107ce57600080fd5b5035919050565b6001600160a01b03811681146107ea57600080fd5b50565b6000602082840312156107ff57600080fd5b813561080a816107d5565b9392505050565b6020808252601b908201527f4578697448656c7065723a204e4f545f494e495449414c495a45440000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b60008235607e1983360301811261087457600080fd5b9190910192915050565b6000808335601e1984360301811261089557600080fd5b83018035915067ffffffffffffffff8211156108b057600080fd5b6020019150368190038213156106c257600080fd5b6000808335601e198436030181126108dc57600080fd5b83018035915067ffffffffffffffff8211156108f757600080fd5b6020019150600581901b36038213156106c257600080fd5b634e487b7160e01b600052604160045260246000fd5b6000806000806080858703121561093b57600080fd5b84359350602085013561094d816107d5565b9250604085013561095d816107d5565b9150606085013567ffffffffffffffff8082111561097a57600080fd5b818701915087601f83011261098e57600080fd5b8135818111156109a0576109a061090f565b604051601f8201601f19908116603f011681019083821181831017156109c8576109c861090f565b816040528281528a60208487010111156109e157600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b8183823760009101908152919050565b85815284602082015283604082015260806060820152816080820152600060018060fb1b03831115610a4657600080fd5b8260051b808560a08501379190910160a0019695505050505050565b600060208284031215610a7457600080fd5b8151801515811461080a57600080fd5b60005b83811015610a9f578181015183820152602001610a87565b50506000910152565b60008151808452610ac0816020860160208601610a84565b601f01601f19169290920160200192915050565b8381526001600160a01b0383166020820152606060408201819052600090610afe90830184610aa8565b95945050505050565b60008251610874818460208701610a84565b60208152600061080a6020830184610aa856fea264697066735822122062ac4b08903d6279500a6ee30957a69454dadfc7f8af07bc8702efa271a1f6f364736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100575760003560e01c806350607b351461005c578063aa209cc314610071578063bd88ea7914610084578063c0857ba0146100bc578063c4d66de8146100e7575b600080fd5b61006f61006a3660046106c9565b6100fa565b005b61006f61007f36600461070b565b610200565b6100a76100923660046107bc565b60016020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6002546100cf906001600160a01b031681565b6040516001600160a01b0390911681526020016100b3565b61006f6100f53660046107ed565b610240565b6002546001600160a01b031661012b5760405162461bcd60e51b815260040161012290610811565b60405180910390fd5b8060005b818110156101fa576101f284848381811061014c5761014c610848565b905060200281019061015e919061085e565b3585858481811061017157610171610848565b9050602002810190610183919061085e565b6020013586868581811061019957610199610848565b90506020028101906101ab919061085e565b6101b990604081019061087e565b8888878181106101cb576101cb610848565b90506020028101906101dd919061085e565b6101eb9060608101906108c5565b60016103d2565b60010161012f565b50505050565b6002546001600160a01b03166102285760405162461bcd60e51b815260040161012290610811565b61023886868686868660006103d2565b505050505050565b600054610100900460ff16158080156102605750600054600160ff909116105b8061027a5750303b15801561027a575060005460ff166001145b6102dd5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610122565b6000805460ff191660011790558015610300576000805461ff0019166101001790555b6001600160a01b0382161580159061032157506001600160a01b0382163b15155b61036d5760405162461bcd60e51b815260206004820152601b60248201527f4578697448656c7065723a20494e56414c49445f4144445245535300000000006044820152606401610122565b600280546001600160a01b0319166001600160a01b03841617905580156103ce576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b60008080806103e3888a018a610925565b935093509350935084156104165760008481526001602052604090205460ff16156104115750505050610674565b610480565b60008481526001602052604090205460ff16156104805760405162461bcd60e51b815260206004820152602260248201527f4578697448656c7065723a20455849545f414c52454144595f50524f43455353604482015261115160f21b6064820152608401610122565b6002546040516001600160a01b03909116906361a02208908d906104a7908d908d90610a05565b6040519081900381206001600160e01b031960e085901b1682526104d492918f908d908d90600401610a15565b602060405180830381865afa1580156104f1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105159190610a62565b61055d5760405162461bcd60e51b815260206004820152601960248201527822bc34ba2432b63832b91d1024a72b20a624a22fa82927a7a360391b6044820152606401610122565b6000848152600160208190526040808320805460ff19169092179091555181906001600160a01b0385169061059a90889088908790602401610ad4565b60408051601f198184030181529181526020820180516001600160e01b031663f43cda8b60e01b179052516105cf9190610b07565b6000604051808303816000865af19150503d806000811461060c576040519150601f19603f3d011682016040523d82523d6000602084013e610611565b606091505b509150915081610632576000868152600160205260409020805460ff191690555b811515867f8bbfa0c9bee3785c03700d2a909592286efb83fc7e7002be5764424b9842f7ec836040516106659190610b19565b60405180910390a35050505050505b50505050505050565b60008083601f84011261068f57600080fd5b50813567ffffffffffffffff8111156106a757600080fd5b6020830191508360208260051b85010111156106c257600080fd5b9250929050565b600080602083850312156106dc57600080fd5b823567ffffffffffffffff8111156106f357600080fd5b6106ff8582860161067d565b90969095509350505050565b6000806000806000806080878903121561072457600080fd5b8635955060208701359450604087013567ffffffffffffffff8082111561074a57600080fd5b818901915089601f83011261075e57600080fd5b81358181111561076d57600080fd5b8a602082850101111561077f57600080fd5b60208301965080955050606089013591508082111561079d57600080fd5b506107aa89828a0161067d565b979a9699509497509295939492505050565b6000602082840312156107ce57600080fd5b5035919050565b6001600160a01b03811681146107ea57600080fd5b50565b6000602082840312156107ff57600080fd5b813561080a816107d5565b9392505050565b6020808252601b908201527f4578697448656c7065723a204e4f545f494e495449414c495a45440000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b60008235607e1983360301811261087457600080fd5b9190910192915050565b6000808335601e1984360301811261089557600080fd5b83018035915067ffffffffffffffff8211156108b057600080fd5b6020019150368190038213156106c257600080fd5b6000808335601e198436030181126108dc57600080fd5b83018035915067ffffffffffffffff8211156108f757600080fd5b6020019150600581901b36038213156106c257600080fd5b634e487b7160e01b600052604160045260246000fd5b6000806000806080858703121561093b57600080fd5b84359350602085013561094d816107d5565b9250604085013561095d816107d5565b9150606085013567ffffffffffffffff8082111561097a57600080fd5b818701915087601f83011261098e57600080fd5b8135818111156109a0576109a061090f565b604051601f8201601f19908116603f011681019083821181831017156109c8576109c861090f565b816040528281528a60208487010111156109e157600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b8183823760009101908152919050565b85815284602082015283604082015260806060820152816080820152600060018060fb1b03831115610a4657600080fd5b8260051b808560a08501379190910160a0019695505050505050565b600060208284031215610a7457600080fd5b8151801515811461080a57600080fd5b60005b83811015610a9f578181015183820152602001610a87565b50506000910152565b60008151808452610ac0816020860160208601610a84565b601f01601f19169290920160200192915050565b8381526001600160a01b0383166020820152606060408201819052600090610afe90830184610aa8565b95945050505050565b60008251610874818460208701610a84565b60208152600061080a6020830184610aa856fea264697066735822122062ac4b08903d6279500a6ee30957a69454dadfc7f8af07bc8702efa271a1f6f364736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var StateSenderArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"StateSender","sourceName":"contracts/root/StateSender.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"StateSynced","type":"event"},{"inputs":[],"name":"MAX_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"counter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"syncState","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610297806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806316f198311461004657806361bc221a1461005b578063a6f9885c14610076575b600080fd5b61005961005436600461017a565b61007f565b005b61006460005481565b60405190815260200160405180910390f35b61006461080081565b6001600160a01b0383166100cd5760405162461bcd60e51b815260206004820152601060248201526f24a72b20a624a22fa922a1a2a4ab22a960811b60448201526064015b60405180910390fd5b6108008111156101145760405162461bcd60e51b815260206004820152601260248201527108ab0868a8a88a6be9a82b0be988a9c8ea8960731b60448201526064016100c4565b826001600160a01b0316336001600160a01b031660008081546101369061020b565b9190508190557fd1d7f6609674cc5871fdb4b0bcd4f0a214118411de9e38983866514f22659165858560405161016d929190610232565b60405180910390a4505050565b60008060006040848603121561018f57600080fd5b83356001600160a01b03811681146101a657600080fd5b9250602084013567ffffffffffffffff808211156101c357600080fd5b818601915086601f8301126101d757600080fd5b8135818111156101e657600080fd5b8760208285010111156101f857600080fd5b6020830194508093505050509250925092565b60006001820161022b57634e487b7160e01b600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f1916010191905056fea264697066735822122031b7ec2bc87f0234fe89e0f741bc18f51baa2be5cc844033ea10f35e4b86094c64736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100415760003560e01c806316f198311461004657806361bc221a1461005b578063a6f9885c14610076575b600080fd5b61005961005436600461017a565b61007f565b005b61006460005481565b60405190815260200160405180910390f35b61006461080081565b6001600160a01b0383166100cd5760405162461bcd60e51b815260206004820152601060248201526f24a72b20a624a22fa922a1a2a4ab22a960811b60448201526064015b60405180910390fd5b6108008111156101145760405162461bcd60e51b815260206004820152601260248201527108ab0868a8a88a6be9a82b0be988a9c8ea8960731b60448201526064016100c4565b826001600160a01b0316336001600160a01b031660008081546101369061020b565b9190508190557fd1d7f6609674cc5871fdb4b0bcd4f0a214118411de9e38983866514f22659165858560405161016d929190610232565b60405180910390a4505050565b60008060006040848603121561018f57600080fd5b83356001600160a01b03811681146101a657600080fd5b9250602084013567ffffffffffffffff808211156101c357600080fd5b818601915086601f8301126101d757600080fd5b8135818111156101e657600080fd5b8760208285010111156101f857600080fd5b6020830194508093505050509250925092565b60006001820161022b57634e487b7160e01b600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f1916010191905056fea264697066735822122031b7ec2bc87f0234fe89e0f741bc18f51baa2be5cc844033ea10f35e4b86094c64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var MockERC20Artifact string = `{"_format":"hh-sol-artifact-1","contractName":"MockERC20","sourceName":"contracts/mocks/MockERC20.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burnFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x60806040523480156200001157600080fd5b50604080518082018252600480825263151154d560e21b60208084018290528451808601909552918452908301529081816005620000508382620002ca565b5060066200005f8282620002ca565b50506007805460ff191690555062000079600033620000d9565b620000a57f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633620000d9565b620000d17f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a33620000d9565b505062000396565b620000e58282620000e9565b5050565b620000f5828262000114565b60008281526001602052604090206200010f9082620001b4565b505050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff16620000e5576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055620001703390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000620001cb836001600160a01b038416620001d4565b90505b92915050565b60008181526001830160205260408120546200021d57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155620001ce565b506000620001ce565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806200025157607f821691505b6020821081036200027257634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200010f57600081815260208120601f850160051c81016020861015620002a15750805b601f850160051c820191505b81811015620002c257828155600101620002ad565b505050505050565b81516001600160401b03811115620002e657620002e662000226565b620002fe81620002f784546200023c565b8462000278565b602080601f8311600181146200033657600084156200031d5750858301515b600019600386901b1c1916600185901b178555620002c2565b600085815260208120601f198616915b82811015620003675788860151825594840194600190910190840162000346565b5085821015620003865787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6117fa80620003a66000396000f3fe608060405234801561001057600080fd5b50600436106101845760003560e01c806370a08231116100d9578063a457c2d711610087578063a457c2d714610336578063a9059cbb14610349578063ca15c8731461035c578063d53913931461036f578063d547741f14610396578063dd62ed3e146103a9578063e63ab1e9146103bc57600080fd5b806370a08231146102a457806379cc6790146102cd5780638456cb59146102e05780639010d07c146102e857806391d148541461031357806395d89b4114610326578063a217fddf1461032e57600080fd5b8063313ce56711610136578063313ce5671461023657806336568abe1461024557806339509351146102585780633f4ba83a1461026b57806340c10f191461027357806342966c68146102865780635c975abb1461029957600080fd5b806301ffc9a71461018957806306fdde03146101b1578063095ea7b3146101c657806318160ddd146101d957806323b872dd146101eb578063248a9ca3146101fe5780632f2ff15d14610221575b600080fd5b61019c610197366004611460565b6103d1565b60405190151581526020015b60405180910390f35b6101b96103fc565b6040516101a891906114ae565b61019c6101d43660046114fd565b61048e565b6004545b6040519081526020016101a8565b61019c6101f9366004611527565b6104a6565b6101dd61020c366004611563565b60009081526020819052604090206001015490565b61023461022f36600461157c565b6104ca565b005b604051601281526020016101a8565b61023461025336600461157c565b6104f4565b61019c6102663660046114fd565b610577565b610234610599565b6102346102813660046114fd565b610617565b610234610294366004611563565b6106a4565b60075460ff1661019c565b6101dd6102b23660046115a8565b6001600160a01b031660009081526002602052604090205490565b6102346102db3660046114fd565b6106b1565b6102346106c6565b6102fb6102f63660046115c3565b610740565b6040516001600160a01b0390911681526020016101a8565b61019c61032136600461157c565b61075f565b6101b9610788565b6101dd600081565b61019c6103443660046114fd565b610797565b61019c6103573660046114fd565b610812565b6101dd61036a366004611563565b610820565b6101dd7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6102346103a436600461157c565b610837565b6101dd6103b73660046115e5565b61085c565b6101dd60008051602061178583398151915281565b60006001600160e01b03198216635a05180f60e01b14806103f657506103f682610887565b92915050565b60606005805461040b9061160f565b80601f01602080910402602001604051908101604052809291908181526020018280546104379061160f565b80156104845780601f1061045957610100808354040283529160200191610484565b820191906000526020600020905b81548152906001019060200180831161046757829003601f168201915b5050505050905090565b60003361049c8185856108bc565b5060019392505050565b6000336104b48582856109e0565b6104bf858585610a5a565b506001949350505050565b6000828152602081905260409020600101546104e581610bfe565b6104ef8383610c08565b505050565b6001600160a01b03811633146105695760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b60648201526084015b60405180910390fd5b6105738282610c2a565b5050565b60003361049c81858561058a838361085c565b610594919061165f565b6108bc565b6105b16000805160206117858339815191523361075f565b61060d5760405162461bcd60e51b8152602060048201526039602482015260008051602061176583398151915260448201527876652070617573657220726f6c6520746f20756e706175736560381b6064820152608401610560565b610615610c4c565b565b6106417f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a63361075f565b61069a5760405162461bcd60e51b815260206004820152603660248201526000805160206117658339815191526044820152751d99481b5a5b9d195c881c9bdb19481d1bc81b5a5b9d60521b6064820152608401610560565b6105738282610c9e565b6106ae3382610d59565b50565b6106bc8233836109e0565b6105738282610d59565b6106de6000805160206117858339815191523361075f565b6107385760405162461bcd60e51b8152602060048201526037602482015260008051602061176583398151915260448201527676652070617573657220726f6c6520746f20706175736560481b6064820152608401610560565b610615610e87565b60008281526001602052604081206107589083610ec4565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b60606006805461040b9061160f565b600033816107a5828661085c565b9050838110156108055760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610560565b6104bf82868684036108bc565b60003361049c818585610a5a565b60008181526001602052604081206103f690610ed0565b60008281526020819052604090206001015461085281610bfe565b6104ef8383610c2a565b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205490565b60006001600160e01b03198216637965db0b60e01b14806103f657506301ffc9a760e01b6001600160e01b03198316146103f6565b6001600160a01b03831661091e5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610560565b6001600160a01b03821661097f5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610560565b6001600160a01b0383811660008181526003602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006109ec848461085c565b90506000198114610a545781811015610a475760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610560565b610a5484848484036108bc565b50505050565b6001600160a01b038316610abe5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610560565b6001600160a01b038216610b205760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610560565b610b2b838383610eda565b6001600160a01b03831660009081526002602052604090205481811015610ba35760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610560565b6001600160a01b0380851660008181526002602052604080822086860390559286168082529083902080548601905591516000805160206117a583398151915290610bf19086815260200190565b60405180910390a3610a54565b6106ae8133610ee5565b610c128282610f3e565b60008281526001602052604090206104ef9082610fc2565b610c348282610fd7565b60008281526001602052604090206104ef908261103c565b610c54611051565b6007805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6001600160a01b038216610cf45760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610560565b610d0060008383610eda565b8060046000828254610d12919061165f565b90915550506001600160a01b0382166000818152600260209081526040808320805486019055518481526000805160206117a5833981519152910160405180910390a35050565b6001600160a01b038216610db95760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610560565b610dc582600083610eda565b6001600160a01b03821660009081526002602052604090205481811015610e395760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610560565b6001600160a01b03831660008181526002602090815260408083208686039055600480548790039055518581529192916000805160206117a5833981519152910160405180910390a3505050565b610e8f61109a565b6007805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258610c813390565b600061075883836110e0565b60006103f6825490565b6104ef83838361110a565b610eef828261075f565b61057357610efc81611170565b610f07836020611182565b604051602001610f18929190611672565b60408051601f198184030181529082905262461bcd60e51b8252610560916004016114ae565b610f48828261075f565b610573576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055610f7e3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610758836001600160a01b03841661131e565b610fe1828261075f565b15610573576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000610758836001600160a01b03841661136d565b60075460ff166106155760405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606401610560565b60075460ff16156106155760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610560565b60008260000182815481106110f7576110f76116e1565b9060005260206000200154905092915050565b60075460ff16156104ef5760405162461bcd60e51b815260206004820152602a60248201527f45524332305061757361626c653a20746f6b656e207472616e736665722077686044820152691a5b19481c185d5cd95960b21b6064820152608401610560565b60606103f66001600160a01b03831660145b606060006111918360026116f7565b61119c90600261165f565b67ffffffffffffffff8111156111b4576111b461170e565b6040519080825280601f01601f1916602001820160405280156111de576020820181803683370190505b509050600360fc1b816000815181106111f9576111f96116e1565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110611228576112286116e1565b60200101906001600160f81b031916908160001a905350600061124c8460026116f7565b61125790600161165f565b90505b60018111156112cf576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811061128b5761128b6116e1565b1a60f81b8282815181106112a1576112a16116e1565b60200101906001600160f81b031916908160001a90535060049490941c936112c881611724565b905061125a565b5083156107585760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610560565b6000818152600183016020526040812054611365575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556103f6565b5060006103f6565b6000818152600183016020526040812054801561145657600061139160018361173b565b85549091506000906113a59060019061173b565b905081811461140a5760008660000182815481106113c5576113c56116e1565b90600052602060002001549050808760000184815481106113e8576113e86116e1565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061141b5761141b61174e565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506103f6565b60009150506103f6565b60006020828403121561147257600080fd5b81356001600160e01b03198116811461075857600080fd5b60005b838110156114a557818101518382015260200161148d565b50506000910152565b60208152600082518060208401526114cd81604085016020870161148a565b601f01601f19169190910160400192915050565b80356001600160a01b03811681146114f857600080fd5b919050565b6000806040838503121561151057600080fd5b611519836114e1565b946020939093013593505050565b60008060006060848603121561153c57600080fd5b611545846114e1565b9250611553602085016114e1565b9150604084013590509250925092565b60006020828403121561157557600080fd5b5035919050565b6000806040838503121561158f57600080fd5b8235915061159f602084016114e1565b90509250929050565b6000602082840312156115ba57600080fd5b610758826114e1565b600080604083850312156115d657600080fd5b50508035926020909101359150565b600080604083850312156115f857600080fd5b611601836114e1565b915061159f602084016114e1565b600181811c9082168061162357607f821691505b60208210810361164357634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b808201808211156103f6576103f6611649565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8152600083516116a481601785016020880161148a565b7001034b99036b4b9b9b4b733903937b6329607d1b60179184019182015283516116d581602884016020880161148a565b01602801949350505050565b634e487b7160e01b600052603260045260246000fd5b80820281158282048414176103f6576103f6611649565b634e487b7160e01b600052604160045260246000fd5b60008161173357611733611649565b506000190190565b818103818111156103f6576103f6611649565b634e487b7160e01b600052603160045260246000fdfe45524332305072657365744d696e7465725061757365723a206d75737420686165d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862addf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220d5a22b9391b8f37e5b49e43cc1eabfcea8be6d7b5aa0a84dc5daa1b7a05730f364736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101845760003560e01c806370a08231116100d9578063a457c2d711610087578063a457c2d714610336578063a9059cbb14610349578063ca15c8731461035c578063d53913931461036f578063d547741f14610396578063dd62ed3e146103a9578063e63ab1e9146103bc57600080fd5b806370a08231146102a457806379cc6790146102cd5780638456cb59146102e05780639010d07c146102e857806391d148541461031357806395d89b4114610326578063a217fddf1461032e57600080fd5b8063313ce56711610136578063313ce5671461023657806336568abe1461024557806339509351146102585780633f4ba83a1461026b57806340c10f191461027357806342966c68146102865780635c975abb1461029957600080fd5b806301ffc9a71461018957806306fdde03146101b1578063095ea7b3146101c657806318160ddd146101d957806323b872dd146101eb578063248a9ca3146101fe5780632f2ff15d14610221575b600080fd5b61019c610197366004611460565b6103d1565b60405190151581526020015b60405180910390f35b6101b96103fc565b6040516101a891906114ae565b61019c6101d43660046114fd565b61048e565b6004545b6040519081526020016101a8565b61019c6101f9366004611527565b6104a6565b6101dd61020c366004611563565b60009081526020819052604090206001015490565b61023461022f36600461157c565b6104ca565b005b604051601281526020016101a8565b61023461025336600461157c565b6104f4565b61019c6102663660046114fd565b610577565b610234610599565b6102346102813660046114fd565b610617565b610234610294366004611563565b6106a4565b60075460ff1661019c565b6101dd6102b23660046115a8565b6001600160a01b031660009081526002602052604090205490565b6102346102db3660046114fd565b6106b1565b6102346106c6565b6102fb6102f63660046115c3565b610740565b6040516001600160a01b0390911681526020016101a8565b61019c61032136600461157c565b61075f565b6101b9610788565b6101dd600081565b61019c6103443660046114fd565b610797565b61019c6103573660046114fd565b610812565b6101dd61036a366004611563565b610820565b6101dd7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6102346103a436600461157c565b610837565b6101dd6103b73660046115e5565b61085c565b6101dd60008051602061178583398151915281565b60006001600160e01b03198216635a05180f60e01b14806103f657506103f682610887565b92915050565b60606005805461040b9061160f565b80601f01602080910402602001604051908101604052809291908181526020018280546104379061160f565b80156104845780601f1061045957610100808354040283529160200191610484565b820191906000526020600020905b81548152906001019060200180831161046757829003601f168201915b5050505050905090565b60003361049c8185856108bc565b5060019392505050565b6000336104b48582856109e0565b6104bf858585610a5a565b506001949350505050565b6000828152602081905260409020600101546104e581610bfe565b6104ef8383610c08565b505050565b6001600160a01b03811633146105695760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b60648201526084015b60405180910390fd5b6105738282610c2a565b5050565b60003361049c81858561058a838361085c565b610594919061165f565b6108bc565b6105b16000805160206117858339815191523361075f565b61060d5760405162461bcd60e51b8152602060048201526039602482015260008051602061176583398151915260448201527876652070617573657220726f6c6520746f20756e706175736560381b6064820152608401610560565b610615610c4c565b565b6106417f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a63361075f565b61069a5760405162461bcd60e51b815260206004820152603660248201526000805160206117658339815191526044820152751d99481b5a5b9d195c881c9bdb19481d1bc81b5a5b9d60521b6064820152608401610560565b6105738282610c9e565b6106ae3382610d59565b50565b6106bc8233836109e0565b6105738282610d59565b6106de6000805160206117858339815191523361075f565b6107385760405162461bcd60e51b8152602060048201526037602482015260008051602061176583398151915260448201527676652070617573657220726f6c6520746f20706175736560481b6064820152608401610560565b610615610e87565b60008281526001602052604081206107589083610ec4565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b60606006805461040b9061160f565b600033816107a5828661085c565b9050838110156108055760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610560565b6104bf82868684036108bc565b60003361049c818585610a5a565b60008181526001602052604081206103f690610ed0565b60008281526020819052604090206001015461085281610bfe565b6104ef8383610c2a565b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205490565b60006001600160e01b03198216637965db0b60e01b14806103f657506301ffc9a760e01b6001600160e01b03198316146103f6565b6001600160a01b03831661091e5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610560565b6001600160a01b03821661097f5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610560565b6001600160a01b0383811660008181526003602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006109ec848461085c565b90506000198114610a545781811015610a475760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610560565b610a5484848484036108bc565b50505050565b6001600160a01b038316610abe5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610560565b6001600160a01b038216610b205760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610560565b610b2b838383610eda565b6001600160a01b03831660009081526002602052604090205481811015610ba35760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610560565b6001600160a01b0380851660008181526002602052604080822086860390559286168082529083902080548601905591516000805160206117a583398151915290610bf19086815260200190565b60405180910390a3610a54565b6106ae8133610ee5565b610c128282610f3e565b60008281526001602052604090206104ef9082610fc2565b610c348282610fd7565b60008281526001602052604090206104ef908261103c565b610c54611051565b6007805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6001600160a01b038216610cf45760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610560565b610d0060008383610eda565b8060046000828254610d12919061165f565b90915550506001600160a01b0382166000818152600260209081526040808320805486019055518481526000805160206117a5833981519152910160405180910390a35050565b6001600160a01b038216610db95760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610560565b610dc582600083610eda565b6001600160a01b03821660009081526002602052604090205481811015610e395760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610560565b6001600160a01b03831660008181526002602090815260408083208686039055600480548790039055518581529192916000805160206117a5833981519152910160405180910390a3505050565b610e8f61109a565b6007805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258610c813390565b600061075883836110e0565b60006103f6825490565b6104ef83838361110a565b610eef828261075f565b61057357610efc81611170565b610f07836020611182565b604051602001610f18929190611672565b60408051601f198184030181529082905262461bcd60e51b8252610560916004016114ae565b610f48828261075f565b610573576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055610f7e3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610758836001600160a01b03841661131e565b610fe1828261075f565b15610573576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000610758836001600160a01b03841661136d565b60075460ff166106155760405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606401610560565b60075460ff16156106155760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610560565b60008260000182815481106110f7576110f76116e1565b9060005260206000200154905092915050565b60075460ff16156104ef5760405162461bcd60e51b815260206004820152602a60248201527f45524332305061757361626c653a20746f6b656e207472616e736665722077686044820152691a5b19481c185d5cd95960b21b6064820152608401610560565b60606103f66001600160a01b03831660145b606060006111918360026116f7565b61119c90600261165f565b67ffffffffffffffff8111156111b4576111b461170e565b6040519080825280601f01601f1916602001820160405280156111de576020820181803683370190505b509050600360fc1b816000815181106111f9576111f96116e1565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110611228576112286116e1565b60200101906001600160f81b031916908160001a905350600061124c8460026116f7565b61125790600161165f565b90505b60018111156112cf576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811061128b5761128b6116e1565b1a60f81b8282815181106112a1576112a16116e1565b60200101906001600160f81b031916908160001a90535060049490941c936112c881611724565b905061125a565b5083156107585760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610560565b6000818152600183016020526040812054611365575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556103f6565b5060006103f6565b6000818152600183016020526040812054801561145657600061139160018361173b565b85549091506000906113a59060019061173b565b905081811461140a5760008660000182815481106113c5576113c56116e1565b90600052602060002001549050808760000184815481106113e8576113e86116e1565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061141b5761141b61174e565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506103f6565b60009150506103f6565b60006020828403121561147257600080fd5b81356001600160e01b03198116811461075857600080fd5b60005b838110156114a557818101518382015260200161148d565b50506000910152565b60208152600082518060208401526114cd81604085016020870161148a565b601f01601f19169190910160400192915050565b80356001600160a01b03811681146114f857600080fd5b919050565b6000806040838503121561151057600080fd5b611519836114e1565b946020939093013593505050565b60008060006060848603121561153c57600080fd5b611545846114e1565b9250611553602085016114e1565b9150604084013590509250925092565b60006020828403121561157557600080fd5b5035919050565b6000806040838503121561158f57600080fd5b8235915061159f602084016114e1565b90509250929050565b6000602082840312156115ba57600080fd5b610758826114e1565b600080604083850312156115d657600080fd5b50508035926020909101359150565b600080604083850312156115f857600080fd5b611601836114e1565b915061159f602084016114e1565b600181811c9082168061162357607f821691505b60208210810361164357634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b808201808211156103f6576103f6611649565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8152600083516116a481601785016020880161148a565b7001034b99036b4b9b9b4b733903937b6329607d1b60179184019182015283516116d581602884016020880161148a565b01602801949350505050565b634e487b7160e01b600052603260045260246000fd5b80820281158282048414176103f6576103f6611649565b634e487b7160e01b600052604160045260246000fd5b60008161173357611733611649565b506000190190565b818103818111156103f6576103f6611649565b634e487b7160e01b600052603160045260246000fdfe45524332305072657365744d696e7465725061757365723a206d75737420686165d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862addf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220d5a22b9391b8f37e5b49e43cc1eabfcea8be6d7b5aa0a84dc5daa1b7a05730f364736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RootERC20PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RootERC20Predicate","sourceName":"contracts/root/RootERC20Predicate.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"TokenMapped","type":"event"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childERC20Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"rootToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"rootToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitHelper","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newStateSender","type":"address"},{"internalType":"address","name":"newExitHelper","type":"address"},{"internalType":"address","name":"newChildERC20Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"},{"internalType":"address","name":"newNativeTokenRoot","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"rootToken","type":"address"}],"name":"mapToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nativeTokenRoot","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onL2StateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506113f5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100ca5760003560e01c8063cb10f94c1161007c578063cb10f94c146101ab578063d41f1771146101c4578063d57184e4146101eb578063f213159c146101fe578063f43cda8b14610211578063f4a120f714610224578063f64512551461023757600080fd5b80631459457a146100cf57806347e7ef24146100e45780636a00c1d7146100f75780637efab4f51461012757806395c7041c14610150578063b176806514610163578063b68ad1e414610198575b600080fd5b6100e26100dd366004610f79565b61025e565b005b6100e26100f2366004610fea565b6104dc565b60055461010a906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61010a610135366004611016565b6004602052600090815260409020546001600160a01b031681565b60015461010a906001600160a01b031681565b61018a7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60405190815260200161011e565b60035461010a906001600160a01b031681565b60005461010a906201000090046001600160a01b031681565b61018a7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b60025461010a906001600160a01b031681565b6100e261020c36600461103a565b6104eb565b6100e261021f36600461107b565b6104fb565b61010a610232366004611016565b610682565b61018a7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b600054610100900460ff161580801561027e5750600054600160ff909116105b806102985750303b158015610298575060005460ff166001145b6103005760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b6000805460ff191660011790558015610323576000805461ff0019166101001790555b6001600160a01b0386161580159061034357506001600160a01b03851615155b801561035757506001600160a01b03841615155b801561036b57506001600160a01b03831615155b6103c65760405162461bcd60e51b815260206004820152602660248201527f526f6f7445524332305072656469636174653a204241445f494e495449414c496044820152652d20aa24a7a760d11b60648201526084016102f7565b6000805462010000600160b01b031916620100006001600160a01b038981169190910291909117909155600180546001600160a01b0319908116888416179091556002805482168784161790556003805490911685831617905582161561048e57600580546001600160a01b03199081166001600160a01b038581169182178455600091825260046020526040808320805461101095168517905593549351929316917f85920d35e6c72f6b2affffa04298b0cecfeba86e4a9f407df661f1cb8ab5e6179190a35b80156104d4576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b6104e7823383610a43565b5050565b6104f6838383610a43565b505050565b6001546001600160a01b031633146105615760405162461bcd60e51b8152602060048201526024808201527f526f6f7445524332305072656469636174653a204f4e4c595f455849545f4845604482015263262822a960e11b60648201526084016102f7565b6002546001600160a01b038481169116146105cf5760405162461bcd60e51b815260206004820152602860248201527f526f6f7445524332305072656469636174653a204f4e4c595f4348494c445f50604482015267524544494341544560c01b60648201526084016102f7565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e9828696105fe602060008486611104565b6106079161112e565b036106265761062161061c8260208186611104565b610bba565b61067c565b60405162461bcd60e51b815260206004820152602560248201527f526f6f7445524332305072656469636174653a20494e56414c49445f5349474e604482015264415455524560d81b60648201526084016102f7565b50505050565b60006001600160a01b0382166106e45760405162461bcd60e51b815260206004820152602160248201527f526f6f7445524332305072656469636174653a20494e56414c49445f544f4b456044820152602760f91b60648201526084016102f7565b6001600160a01b0382811660009081526004602052604090205416156107575760405162461bcd60e51b815260206004820152602260248201527f526f6f7445524332305072656469636174653a20414c52454144595f4d415050604482015261115160f21b60648201526084016102f7565b6002546003546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b039283169260009261080592911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b03858116600081815260046020819052604080832080546001600160a01b031916878716179055825481516306fdde0360e01b81529151969750620100009004909416946316f198319488947f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad948c9491936306fdde039380840193908290030181865afa1580156108a2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526108ca9190810190611187565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610908573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526109309190810190611187565b8a6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109929190611229565b6040516020016109a6959493929190611278565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016109d29291906112ca565b600060405180830381600087803b1580156109ec57600080fd5b505af1158015610a00573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f85920d35e6c72f6b2affffa04298b0cecfeba86e4a9f407df661f1cb8ab5e61790600090a39392505050565b6001600160a01b038084166000908152600460205260409020541680610a6f57610a6c84610682565b90505b6001600160a01b038116610a8557610a856112ee565b610a9a6001600160a01b038516333085610c72565b600054600254604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b0388811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352620100009094048416936316f1983193610b2b9391169160c4016112ca565b600060405180830381600087803b158015610b4557600080fd5b505af1158015610b59573d6000803e3d6000fd5b50505050826001600160a01b0316816001600160a01b0316856001600160a01b03167f8be9001bb612c7123a1861dc0d9d94e683261f6cbbd7c7438b708975bc4908a33386604051610bac929190611304565b60405180910390a450505050565b6000808080610bcb8587018761131d565b6001600160a01b0380851660009081526004602052604090205494985092965090945092501680610bfe57610bfe6112ee565b610c126001600160a01b0386168484610cdd565b826001600160a01b0316816001600160a01b0316866001600160a01b03167f9c4f744b2e971d7058a9d8f43977e0e17bf7d57a48659f0e18541b7ee3d022e48786604051610c61929190611304565b60405180910390a450505050505050565b6040516001600160a01b038085166024830152831660448201526064810182905261067c9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610cfc565b6104f68363a9059cbb60e01b8484604051602401610ca6929190611304565b6000610d51826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610dd19092919063ffffffff16565b9050805160001480610d72575080806020019051810190610d72919061136e565b6104f65760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016102f7565b6060610de08484600085610de8565b949350505050565b606082471015610e495760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016102f7565b600080866001600160a01b03168587604051610e659190611390565b60006040518083038185875af1925050503d8060008114610ea2576040519150601f19603f3d011682016040523d82523d6000602084013e610ea7565b606091505b5091509150610eb887838387610ec3565b979650505050505050565b60608315610f32578251600003610f2b576001600160a01b0385163b610f2b5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016102f7565b5081610de0565b610de08383815115610f475781518083602001fd5b8060405162461bcd60e51b81526004016102f791906113ac565b6001600160a01b0381168114610f7657600080fd5b50565b600080600080600060a08688031215610f9157600080fd5b8535610f9c81610f61565b94506020860135610fac81610f61565b93506040860135610fbc81610f61565b92506060860135610fcc81610f61565b91506080860135610fdc81610f61565b809150509295509295909350565b60008060408385031215610ffd57600080fd5b823561100881610f61565b946020939093013593505050565b60006020828403121561102857600080fd5b813561103381610f61565b9392505050565b60008060006060848603121561104f57600080fd5b833561105a81610f61565b9250602084013561106a81610f61565b929592945050506040919091013590565b6000806000806060858703121561109157600080fd5b8435935060208501356110a381610f61565b9250604085013567ffffffffffffffff808211156110c057600080fd5b818701915087601f8301126110d457600080fd5b8135818111156110e357600080fd5b8860208285010111156110f557600080fd5b95989497505060200194505050565b6000808585111561111457600080fd5b8386111561112157600080fd5b5050820193919092039150565b8035602083101561114757600019602084900360031b1b165b92915050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561117e578181015183820152602001611166565b50506000910152565b60006020828403121561119957600080fd5b815167ffffffffffffffff808211156111b157600080fd5b818401915084601f8301126111c557600080fd5b8151818111156111d7576111d761114d565b604051601f8201601f19908116603f011681019083821181831017156111ff576111ff61114d565b8160405282815287602084870101111561121857600080fd5b610eb8836020830160208801611163565b60006020828403121561123b57600080fd5b815160ff8116811461103357600080fd5b60008151808452611264816020860160208601611163565b601f01601f19169290920160200192915050565b8581526001600160a01b038516602082015260a0604082018190526000906112a29083018661124c565b82810360608401526112b4818661124c565b91505060ff831660808301529695505050505050565b6001600160a01b0383168152604060208201819052600090610de09083018461124c565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000806000806080858703121561133357600080fd5b843561133e81610f61565b9350602085013561134e81610f61565b9250604085013561135e81610f61565b9396929550929360600135925050565b60006020828403121561138057600080fd5b8151801515811461103357600080fd5b600082516113a2818460208701611163565b9190910192915050565b602081526000611033602083018461124c56fea2646970667358221220b638ab862c775779ca46f64f41fce11a25a259733849faf11ead45e4637ff9b964736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100ca5760003560e01c8063cb10f94c1161007c578063cb10f94c146101ab578063d41f1771146101c4578063d57184e4146101eb578063f213159c146101fe578063f43cda8b14610211578063f4a120f714610224578063f64512551461023757600080fd5b80631459457a146100cf57806347e7ef24146100e45780636a00c1d7146100f75780637efab4f51461012757806395c7041c14610150578063b176806514610163578063b68ad1e414610198575b600080fd5b6100e26100dd366004610f79565b61025e565b005b6100e26100f2366004610fea565b6104dc565b60055461010a906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61010a610135366004611016565b6004602052600090815260409020546001600160a01b031681565b60015461010a906001600160a01b031681565b61018a7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60405190815260200161011e565b60035461010a906001600160a01b031681565b60005461010a906201000090046001600160a01b031681565b61018a7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b60025461010a906001600160a01b031681565b6100e261020c36600461103a565b6104eb565b6100e261021f36600461107b565b6104fb565b61010a610232366004611016565b610682565b61018a7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b600054610100900460ff161580801561027e5750600054600160ff909116105b806102985750303b158015610298575060005460ff166001145b6103005760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b6000805460ff191660011790558015610323576000805461ff0019166101001790555b6001600160a01b0386161580159061034357506001600160a01b03851615155b801561035757506001600160a01b03841615155b801561036b57506001600160a01b03831615155b6103c65760405162461bcd60e51b815260206004820152602660248201527f526f6f7445524332305072656469636174653a204241445f494e495449414c496044820152652d20aa24a7a760d11b60648201526084016102f7565b6000805462010000600160b01b031916620100006001600160a01b038981169190910291909117909155600180546001600160a01b0319908116888416179091556002805482168784161790556003805490911685831617905582161561048e57600580546001600160a01b03199081166001600160a01b038581169182178455600091825260046020526040808320805461101095168517905593549351929316917f85920d35e6c72f6b2affffa04298b0cecfeba86e4a9f407df661f1cb8ab5e6179190a35b80156104d4576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b6104e7823383610a43565b5050565b6104f6838383610a43565b505050565b6001546001600160a01b031633146105615760405162461bcd60e51b8152602060048201526024808201527f526f6f7445524332305072656469636174653a204f4e4c595f455849545f4845604482015263262822a960e11b60648201526084016102f7565b6002546001600160a01b038481169116146105cf5760405162461bcd60e51b815260206004820152602860248201527f526f6f7445524332305072656469636174653a204f4e4c595f4348494c445f50604482015267524544494341544560c01b60648201526084016102f7565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e9828696105fe602060008486611104565b6106079161112e565b036106265761062161061c8260208186611104565b610bba565b61067c565b60405162461bcd60e51b815260206004820152602560248201527f526f6f7445524332305072656469636174653a20494e56414c49445f5349474e604482015264415455524560d81b60648201526084016102f7565b50505050565b60006001600160a01b0382166106e45760405162461bcd60e51b815260206004820152602160248201527f526f6f7445524332305072656469636174653a20494e56414c49445f544f4b456044820152602760f91b60648201526084016102f7565b6001600160a01b0382811660009081526004602052604090205416156107575760405162461bcd60e51b815260206004820152602260248201527f526f6f7445524332305072656469636174653a20414c52454144595f4d415050604482015261115160f21b60648201526084016102f7565b6002546003546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b039283169260009261080592911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b03858116600081815260046020819052604080832080546001600160a01b031916878716179055825481516306fdde0360e01b81529151969750620100009004909416946316f198319488947f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad948c9491936306fdde039380840193908290030181865afa1580156108a2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526108ca9190810190611187565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610908573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526109309190810190611187565b8a6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109929190611229565b6040516020016109a6959493929190611278565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016109d29291906112ca565b600060405180830381600087803b1580156109ec57600080fd5b505af1158015610a00573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f85920d35e6c72f6b2affffa04298b0cecfeba86e4a9f407df661f1cb8ab5e61790600090a39392505050565b6001600160a01b038084166000908152600460205260409020541680610a6f57610a6c84610682565b90505b6001600160a01b038116610a8557610a856112ee565b610a9a6001600160a01b038516333085610c72565b600054600254604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b0388811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352620100009094048416936316f1983193610b2b9391169160c4016112ca565b600060405180830381600087803b158015610b4557600080fd5b505af1158015610b59573d6000803e3d6000fd5b50505050826001600160a01b0316816001600160a01b0316856001600160a01b03167f8be9001bb612c7123a1861dc0d9d94e683261f6cbbd7c7438b708975bc4908a33386604051610bac929190611304565b60405180910390a450505050565b6000808080610bcb8587018761131d565b6001600160a01b0380851660009081526004602052604090205494985092965090945092501680610bfe57610bfe6112ee565b610c126001600160a01b0386168484610cdd565b826001600160a01b0316816001600160a01b0316866001600160a01b03167f9c4f744b2e971d7058a9d8f43977e0e17bf7d57a48659f0e18541b7ee3d022e48786604051610c61929190611304565b60405180910390a450505050505050565b6040516001600160a01b038085166024830152831660448201526064810182905261067c9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610cfc565b6104f68363a9059cbb60e01b8484604051602401610ca6929190611304565b6000610d51826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610dd19092919063ffffffff16565b9050805160001480610d72575080806020019051810190610d72919061136e565b6104f65760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016102f7565b6060610de08484600085610de8565b949350505050565b606082471015610e495760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016102f7565b600080866001600160a01b03168587604051610e659190611390565b60006040518083038185875af1925050503d8060008114610ea2576040519150601f19603f3d011682016040523d82523d6000602084013e610ea7565b606091505b5091509150610eb887838387610ec3565b979650505050505050565b60608315610f32578251600003610f2b576001600160a01b0385163b610f2b5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016102f7565b5081610de0565b610de08383815115610f475781518083602001fd5b8060405162461bcd60e51b81526004016102f791906113ac565b6001600160a01b0381168114610f7657600080fd5b50565b600080600080600060a08688031215610f9157600080fd5b8535610f9c81610f61565b94506020860135610fac81610f61565b93506040860135610fbc81610f61565b92506060860135610fcc81610f61565b91506080860135610fdc81610f61565b809150509295509295909350565b60008060408385031215610ffd57600080fd5b823561100881610f61565b946020939093013593505050565b60006020828403121561102857600080fd5b813561103381610f61565b9392505050565b60008060006060848603121561104f57600080fd5b833561105a81610f61565b9250602084013561106a81610f61565b929592945050506040919091013590565b6000806000806060858703121561109157600080fd5b8435935060208501356110a381610f61565b9250604085013567ffffffffffffffff808211156110c057600080fd5b818701915087601f8301126110d457600080fd5b8135818111156110e357600080fd5b8860208285010111156110f557600080fd5b95989497505060200194505050565b6000808585111561111457600080fd5b8386111561112157600080fd5b5050820193919092039150565b8035602083101561114757600019602084900360031b1b165b92915050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561117e578181015183820152602001611166565b50506000910152565b60006020828403121561119957600080fd5b815167ffffffffffffffff808211156111b157600080fd5b818401915084601f8301126111c557600080fd5b8151818111156111d7576111d761114d565b604051601f8201601f19908116603f011681019083821181831017156111ff576111ff61114d565b8160405282815287602084870101111561121857600080fd5b610eb8836020830160208801611163565b60006020828403121561123b57600080fd5b815160ff8116811461103357600080fd5b60008151808452611264816020860160208601611163565b601f01601f19169290920160200192915050565b8581526001600160a01b038516602082015260a0604082018190526000906112a29083018661124c565b82810360608401526112b4818661124c565b91505060ff831660808301529695505050505050565b6001600160a01b0383168152604060208201819052600090610de09083018461124c565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000806000806080858703121561133357600080fd5b843561133e81610f61565b9350602085013561134e81610f61565b9250604085013561135e81610f61565b9396929550929360600135925050565b60006020828403121561138057600080fd5b8151801515811461103357600080fd5b600082516113a2818460208701611163565b9190910192915050565b602081526000611033602083018461124c56fea2646970667358221220b638ab862c775779ca46f64f41fce11a25a259733849faf11ead45e4637ff9b964736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildMintableERC20PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildMintableERC20Predicate","sourceName":"contracts/root/ChildMintableERC20Predicate.sol","abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"MintableERC20Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"MintableERC20Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"MintableTokenMapped","type":"event"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exitHelper","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newStateSender","type":"address"},{"internalType":"address","name":"newExitHelper","type":"address"},{"internalType":"address","name":"newRootERC20Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onL2StateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootERC20Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IChildERC20","name":"childToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC20","name":"childToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506113c8806100206000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c8063cb10f94c11610071578063cb10f94c14610182578063d41f17711461019b578063f3fef3a3146101c2578063f43cda8b146101d5578063f6451255146101e8578063f8c8765e1461020f57600080fd5b806371cf93b7146100b95780637efab4f5146100e957806395c7041c14610112578063b176806514610125578063b68ad1e41461015a578063c3b35a7e1461016d575b600080fd5b6002546100cc906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100cc6100f7366004610ecf565b6004602052600090815260409020546001600160a01b031681565b6001546100cc906001600160a01b031681565b61014c7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b6040519081526020016100e0565b6003546100cc906001600160a01b031681565b61018061017b366004610ef3565b610222565b005b6000546100cc906201000090046001600160a01b031681565b61014c7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101806101d0366004610f34565b610232565b6101806101e3366004610f60565b610241565b61014c7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61018061021d366004610fe9565b610432565b61022d83838361054b565b505050565b61023d82338361054b565b5050565b6001546001600160a01b031633146102b95760405162461bcd60e51b815260206004820152603060248201527f4368696c644d696e7461626c6545524332305072656469636174653a204f4e4c60448201526f2cafa9aa20aa22afa922a1a2a4ab22a960811b60648201526084015b60405180910390fd5b6002546001600160a01b0384811691161461032f5760405162461bcd60e51b815260206004820152603060248201527f4368696c644d696e7461626c6545524332305072656469636174653a204f4e4c60448201526f595f524f4f545f50524544494341544560801b60648201526084016102b0565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82161035e602060008486611045565b6103679161106f565b036103865761038161037c8260208186611045565b6108e0565b61042c565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad6103b5602060008486611045565b6103be9161106f565b036103cd576103818282610b9c565b60405162461bcd60e51b815260206004820152602e60248201527f4368696c644d696e7461626c6545524332305072656469636174653a20494e5660448201526d414c49445f5349474e415455524560901b60648201526084016102b0565b50505050565b600054610100900460ff16158080156104525750600054600160ff909116105b8061046c5750303b15801561046c575060005460ff166001145b6104cf5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016102b0565b6000805460ff1916600117905580156104f2576000805461ff0019166101001790555b6104fe85858585610d13565b8015610544576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b826001600160a01b03163b6000036105b75760405162461bcd60e51b815260206004820152602960248201527f4368696c644d696e7461626c6545524332305072656469636174653a204e4f5460448201526817d0d3d395149050d560ba1b60648201526084016102b0565b6000836001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061b919061108d565b6001600160a01b0381811660009081526004602052604090205491925085811691161461065a5760405162461bcd60e51b81526004016102b0906110aa565b6001600160a01b038116610670576106706110f5565b306001600160a01b0316846001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106dc919061108d565b6001600160a01b0316146106f2576106f26110f5565b604051632770a7eb60e21b81526001600160a01b03851690639dc29fac90610720903390869060040161110b565b6020604051808303816000875af115801561073f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107639190611124565b6107c05760405162461bcd60e51b815260206004820152602860248201527f4368696c644d696e7461626c6545524332305072656469636174653a204255526044820152671397d1905253115160c21b60648201526084016102b0565b600054600254604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352620100009094048416936316f19831936108519391169160c40161118c565b600060405180830381600087803b15801561086b57600080fd5b505af115801561087f573d6000803e3d6000fd5b50505050826001600160a01b0316846001600160a01b0316826001600160a01b03167f1da92ba31d15f9af529a2efe7acebc6c0a4ac8e9cd86a84f199a474e505d77a633866040516108d292919061110b565b60405180910390a450505050565b60008080806108f1858701876111b8565b6001600160a01b03808516600090815260046020526040902054949850929650909450925016806109345760405162461bcd60e51b81526004016102b0906110aa565b806001600160a01b03163b60000361094e5761094e6110f5565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561098e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b2919061108d565b9050856001600160a01b0316816001600160a01b0316146109d5576109d56110f5565b6001600160a01b0381166109eb576109eb6110f5565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a33573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a57919061108d565b6001600160a01b031614610a6d57610a6d6110f5565b6040516340c10f1960e01b81526001600160a01b038316906340c10f1990610a9b908790879060040161110b565b6020604051808303816000875af1158015610aba573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ade9190611124565b610b3b5760405162461bcd60e51b815260206004820152602860248201527f4368696c644d696e7461626c6545524332305072656469636174653a204d494e6044820152671517d1905253115160c21b60648201526084016102b0565b836001600160a01b0316826001600160a01b0316876001600160a01b03167fcf47ccd68b2113d6309d8d504a67c10963dc0bfbf2416e4726a316511cca27b58887604051610b8a92919061110b565b60405180910390a45050505050505050565b6000808080610bad858701876112ac565b92975090955093509150506001600160a01b038416610bce57610bce6110f5565b6001600160a01b038481166000908152600460205260409020541615610bf657610bf66110f5565b6003546040516bffffffffffffffffffffffff19606087901b166020820152600091610c46916001600160a01b039091169060340160405160208183030381529060405280519060200120610e1a565b6001600160a01b0386811660009081526004602081905260409182902080546001600160a01b03191693851693841790559051637b69774360e11b8152929350909163f6d2ee8691610ca091899189918991899101611347565b600060405180830381600087803b158015610cba57600080fd5b505af1158015610cce573d6000803e3d6000fd5b50506040516001600160a01b038085169350881691507f0a1eaf9aa124c3f84c9dd77f7016af0f16f67639abb913af1697387db01f5ca590600090a350505050505050565b6001600160a01b03841615801590610d3357506001600160a01b03831615155b8015610d4757506001600160a01b03821615155b8015610d5b57506001600160a01b03811615155b610dbf5760405162461bcd60e51b815260206004820152602f60248201527f4368696c644d696e7461626c6545524332305072656469636174653a2042414460448201526e2fa4a724aa24a0a624ad20aa24a7a760891b60648201526084016102b0565b600080546001600160a01b03958616620100000262010000600160b01b0319909116179055600180549385166001600160a01b0319948516179055600280549285169284169290921790915560038054919093169116179055565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116610eb15760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b60448201526064016102b0565b92915050565b6001600160a01b0381168114610ecc57600080fd5b50565b600060208284031215610ee157600080fd5b8135610eec81610eb7565b9392505050565b600080600060608486031215610f0857600080fd5b8335610f1381610eb7565b92506020840135610f2381610eb7565b929592945050506040919091013590565b60008060408385031215610f4757600080fd5b8235610f5281610eb7565b946020939093013593505050565b60008060008060608587031215610f7657600080fd5b843593506020850135610f8881610eb7565b9250604085013567ffffffffffffffff80821115610fa557600080fd5b818701915087601f830112610fb957600080fd5b813581811115610fc857600080fd5b886020828501011115610fda57600080fd5b95989497505060200194505050565b60008060008060808587031215610fff57600080fd5b843561100a81610eb7565b9350602085013561101a81610eb7565b9250604085013561102a81610eb7565b9150606085013561103a81610eb7565b939692955090935050565b6000808585111561105557600080fd5b8386111561106257600080fd5b5050820193919092039150565b80356020831015610eb157600019602084900360031b1b1692915050565b60006020828403121561109f57600080fd5b8151610eec81610eb7565b6020808252602b908201527f4368696c644d696e7461626c6545524332305072656469636174653a20554e4d60408201526a20a82822a22faa27a5a2a760a91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b60006020828403121561113657600080fd5b81518015158114610eec57600080fd5b6000815180845260005b8181101561116c57602081850181015186830182015201611150565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190526000906111b090830184611146565b949350505050565b600080600080608085870312156111ce57600080fd5b84356111d981610eb7565b935060208501356111e981610eb7565b925060408501356111f981610eb7565b9396929550929360600135925050565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261123057600080fd5b813567ffffffffffffffff8082111561124b5761124b611209565b604051601f8301601f19908116603f0116810190828211818310171561127357611273611209565b8160405283815286602085880101111561128c57600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600080600060a086880312156112c457600080fd5b8535945060208601356112d681610eb7565b9350604086013567ffffffffffffffff808211156112f357600080fd5b6112ff89838a0161121f565b9450606088013591508082111561131557600080fd5b506113228882890161121f565b925050608086013560ff8116811461133957600080fd5b809150509295509295909350565b6001600160a01b038516815260806020820181905260009061136b90830186611146565b828103604084015261137d8186611146565b91505060ff831660608301529594505050505056fea264697066735822122087775fa0c83774cca5f812d4bf60fa4ef351e7b816104e4edbe586babc84517664736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100b45760003560e01c8063cb10f94c11610071578063cb10f94c14610182578063d41f17711461019b578063f3fef3a3146101c2578063f43cda8b146101d5578063f6451255146101e8578063f8c8765e1461020f57600080fd5b806371cf93b7146100b95780637efab4f5146100e957806395c7041c14610112578063b176806514610125578063b68ad1e41461015a578063c3b35a7e1461016d575b600080fd5b6002546100cc906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100cc6100f7366004610ecf565b6004602052600090815260409020546001600160a01b031681565b6001546100cc906001600160a01b031681565b61014c7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b6040519081526020016100e0565b6003546100cc906001600160a01b031681565b61018061017b366004610ef3565b610222565b005b6000546100cc906201000090046001600160a01b031681565b61014c7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101806101d0366004610f34565b610232565b6101806101e3366004610f60565b610241565b61014c7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61018061021d366004610fe9565b610432565b61022d83838361054b565b505050565b61023d82338361054b565b5050565b6001546001600160a01b031633146102b95760405162461bcd60e51b815260206004820152603060248201527f4368696c644d696e7461626c6545524332305072656469636174653a204f4e4c60448201526f2cafa9aa20aa22afa922a1a2a4ab22a960811b60648201526084015b60405180910390fd5b6002546001600160a01b0384811691161461032f5760405162461bcd60e51b815260206004820152603060248201527f4368696c644d696e7461626c6545524332305072656469636174653a204f4e4c60448201526f595f524f4f545f50524544494341544560801b60648201526084016102b0565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82161035e602060008486611045565b6103679161106f565b036103865761038161037c8260208186611045565b6108e0565b61042c565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad6103b5602060008486611045565b6103be9161106f565b036103cd576103818282610b9c565b60405162461bcd60e51b815260206004820152602e60248201527f4368696c644d696e7461626c6545524332305072656469636174653a20494e5660448201526d414c49445f5349474e415455524560901b60648201526084016102b0565b50505050565b600054610100900460ff16158080156104525750600054600160ff909116105b8061046c5750303b15801561046c575060005460ff166001145b6104cf5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016102b0565b6000805460ff1916600117905580156104f2576000805461ff0019166101001790555b6104fe85858585610d13565b8015610544576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b826001600160a01b03163b6000036105b75760405162461bcd60e51b815260206004820152602960248201527f4368696c644d696e7461626c6545524332305072656469636174653a204e4f5460448201526817d0d3d395149050d560ba1b60648201526084016102b0565b6000836001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061b919061108d565b6001600160a01b0381811660009081526004602052604090205491925085811691161461065a5760405162461bcd60e51b81526004016102b0906110aa565b6001600160a01b038116610670576106706110f5565b306001600160a01b0316846001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106dc919061108d565b6001600160a01b0316146106f2576106f26110f5565b604051632770a7eb60e21b81526001600160a01b03851690639dc29fac90610720903390869060040161110b565b6020604051808303816000875af115801561073f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107639190611124565b6107c05760405162461bcd60e51b815260206004820152602860248201527f4368696c644d696e7461626c6545524332305072656469636174653a204255526044820152671397d1905253115160c21b60648201526084016102b0565b600054600254604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152878116608083015260a08083018890528351808403909101815260c08301938490526316f1983160e01b909352620100009094048416936316f19831936108519391169160c40161118c565b600060405180830381600087803b15801561086b57600080fd5b505af115801561087f573d6000803e3d6000fd5b50505050826001600160a01b0316846001600160a01b0316826001600160a01b03167f1da92ba31d15f9af529a2efe7acebc6c0a4ac8e9cd86a84f199a474e505d77a633866040516108d292919061110b565b60405180910390a450505050565b60008080806108f1858701876111b8565b6001600160a01b03808516600090815260046020526040902054949850929650909450925016806109345760405162461bcd60e51b81526004016102b0906110aa565b806001600160a01b03163b60000361094e5761094e6110f5565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561098e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b2919061108d565b9050856001600160a01b0316816001600160a01b0316146109d5576109d56110f5565b6001600160a01b0381166109eb576109eb6110f5565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a33573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a57919061108d565b6001600160a01b031614610a6d57610a6d6110f5565b6040516340c10f1960e01b81526001600160a01b038316906340c10f1990610a9b908790879060040161110b565b6020604051808303816000875af1158015610aba573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ade9190611124565b610b3b5760405162461bcd60e51b815260206004820152602860248201527f4368696c644d696e7461626c6545524332305072656469636174653a204d494e6044820152671517d1905253115160c21b60648201526084016102b0565b836001600160a01b0316826001600160a01b0316876001600160a01b03167fcf47ccd68b2113d6309d8d504a67c10963dc0bfbf2416e4726a316511cca27b58887604051610b8a92919061110b565b60405180910390a45050505050505050565b6000808080610bad858701876112ac565b92975090955093509150506001600160a01b038416610bce57610bce6110f5565b6001600160a01b038481166000908152600460205260409020541615610bf657610bf66110f5565b6003546040516bffffffffffffffffffffffff19606087901b166020820152600091610c46916001600160a01b039091169060340160405160208183030381529060405280519060200120610e1a565b6001600160a01b0386811660009081526004602081905260409182902080546001600160a01b03191693851693841790559051637b69774360e11b8152929350909163f6d2ee8691610ca091899189918991899101611347565b600060405180830381600087803b158015610cba57600080fd5b505af1158015610cce573d6000803e3d6000fd5b50506040516001600160a01b038085169350881691507f0a1eaf9aa124c3f84c9dd77f7016af0f16f67639abb913af1697387db01f5ca590600090a350505050505050565b6001600160a01b03841615801590610d3357506001600160a01b03831615155b8015610d4757506001600160a01b03821615155b8015610d5b57506001600160a01b03811615155b610dbf5760405162461bcd60e51b815260206004820152602f60248201527f4368696c644d696e7461626c6545524332305072656469636174653a2042414460448201526e2fa4a724aa24a0a624ad20aa24a7a760891b60648201526084016102b0565b600080546001600160a01b03958616620100000262010000600160b01b0319909116179055600180549385166001600160a01b0319948516179055600280549285169284169290921790915560038054919093169116179055565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b038116610eb15760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b60448201526064016102b0565b92915050565b6001600160a01b0381168114610ecc57600080fd5b50565b600060208284031215610ee157600080fd5b8135610eec81610eb7565b9392505050565b600080600060608486031215610f0857600080fd5b8335610f1381610eb7565b92506020840135610f2381610eb7565b929592945050506040919091013590565b60008060408385031215610f4757600080fd5b8235610f5281610eb7565b946020939093013593505050565b60008060008060608587031215610f7657600080fd5b843593506020850135610f8881610eb7565b9250604085013567ffffffffffffffff80821115610fa557600080fd5b818701915087601f830112610fb957600080fd5b813581811115610fc857600080fd5b886020828501011115610fda57600080fd5b95989497505060200194505050565b60008060008060808587031215610fff57600080fd5b843561100a81610eb7565b9350602085013561101a81610eb7565b9250604085013561102a81610eb7565b9150606085013561103a81610eb7565b939692955090935050565b6000808585111561105557600080fd5b8386111561106257600080fd5b5050820193919092039150565b80356020831015610eb157600019602084900360031b1b1692915050565b60006020828403121561109f57600080fd5b8151610eec81610eb7565b6020808252602b908201527f4368696c644d696e7461626c6545524332305072656469636174653a20554e4d60408201526a20a82822a22faa27a5a2a760a91b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b6001600160a01b03929092168252602082015260400190565b60006020828403121561113657600080fd5b81518015158114610eec57600080fd5b6000815180845260005b8181101561116c57602081850181015186830182015201611150565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190526000906111b090830184611146565b949350505050565b600080600080608085870312156111ce57600080fd5b84356111d981610eb7565b935060208501356111e981610eb7565b925060408501356111f981610eb7565b9396929550929360600135925050565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261123057600080fd5b813567ffffffffffffffff8082111561124b5761124b611209565b604051601f8301601f19908116603f0116810190828211818310171561127357611273611209565b8160405283815286602085880101111561128c57600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600080600060a086880312156112c457600080fd5b8535945060208601356112d681610eb7565b9350604086013567ffffffffffffffff808211156112f357600080fd5b6112ff89838a0161121f565b9450606088013591508082111561131557600080fd5b506113228882890161121f565b925050608086013560ff8116811461133957600080fd5b809150509295509295909350565b6001600160a01b038516815260806020820181905260009061136b90830186611146565b828103604084015261137d8186611146565b91505060ff831660608301529594505050505056fea264697066735822122087775fa0c83774cca5f812d4bf60fa4ef351e7b816104e4edbe586babc84517664736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var MockERC721Artifact string = `{"_format":"hh-sol-artifact-1","contractName":"MockERC721","sourceName":"contracts/mocks/MockERC721.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x60806040523480156200001157600080fd5b5060405180604001604052806004815260200163151154d560e21b81525060405180604001604052806004815260200163151154d560e21b815250604051806040016040528060058152602001646c6f72656d60d81b815250828281600290816200007d919062000307565b5060036200008c828262000307565b5050600c805460ff1916905550600e620000a7828262000307565b50620000b560003362000116565b620000e17f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a63362000116565b6200010d7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a3362000116565b505050620003d3565b62000122828262000126565b5050565b62000132828262000151565b60008281526001602052604090206200014c9082620001f1565b505050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff1662000122576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055620001ad3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b600062000208836001600160a01b03841662000211565b90505b92915050565b60008181526001830160205260408120546200025a575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556200020b565b5060006200020b565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806200028e57607f821691505b602082108103620002af57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200014c57600081815260208120601f850160051c81016020861015620002de5750805b601f850160051c820191505b81811015620002ff57828155600101620002ea565b505050505050565b81516001600160401b0381111562000323576200032362000263565b6200033b8162000334845462000279565b84620002b5565b602080601f8311600181146200037357600084156200035a5750858301515b600019600386901b1c1916600185901b178555620002ff565b600085815260208120601f198616915b82811015620003a45788860151825594840194600190910190840162000383565b5085821015620003c35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b61244180620003e36000396000f3fe608060405234801561001057600080fd5b50600436106101a55760003560e01c80636352211e116100ef578063a22cb46511610092578063a22cb4651461036b578063b88d4fde1461037e578063c87b56dd14610391578063ca15c873146103a4578063d5391393146103b7578063d547741f146103de578063e63ab1e9146103f1578063e985e9c51461040657600080fd5b80636352211e146102f45780636a6278421461030757806370a082311461031a5780638456cb591461032d5780639010d07c1461033557806391d148541461034857806395d89b411461035b578063a217fddf1461036357600080fd5b80632f2ff15d116101575780632f2ff15d1461026f5780632f745c591461028257806336568abe146102955780633f4ba83a146102a857806342842e0e146102b057806342966c68146102c35780634f6ccce7146102d65780635c975abb146102e957600080fd5b806301ffc9a7146101aa57806306fdde03146101d2578063081812fc146101e7578063095ea7b31461021257806318160ddd1461022757806323b872dd14610239578063248a9ca31461024c575b600080fd5b6101bd6101b8366004611dba565b610419565b60405190151581526020015b60405180910390f35b6101da61042a565b6040516101c99190611e27565b6101fa6101f5366004611e3a565b6104bc565b6040516001600160a01b0390911681526020016101c9565b610225610220366004611e6f565b6104e3565b005b600a545b6040519081526020016101c9565b610225610247366004611e99565b6105fd565b61022b61025a366004611e3a565b60009081526020819052604090206001015490565b61022561027d366004611ed5565b61062f565b61022b610290366004611e6f565b610654565b6102256102a3366004611ed5565b6106ea565b610225610768565b6102256102be366004611e99565b6107ec565b6102256102d1366004611e3a565b610807565b61022b6102e4366004611e3a565b610838565b600c5460ff166101bd565b6101fa610302366004611e3a565b6108cb565b610225610315366004611f01565b610900565b61022b610328366004611f01565b6109aa565b610225610a30565b6101fa610343366004611f1c565b610ab0565b6101bd610356366004611ed5565b610acf565b6101da610af8565b61022b600081565b610225610379366004611f3e565b610b07565b61022561038c366004611f90565b610b12565b6101da61039f366004611e3a565b610b4a565b61022b6103b2366004611e3a565b610bb0565b61022b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6102256103ec366004611ed5565b610bc7565b61022b6000805160206123ac83398151915281565b6101bd61041436600461206c565b610bec565b600061042482610c1a565b92915050565b60606002805461043990612096565b80601f016020809104026020016040519081016040528092919081815260200182805461046590612096565b80156104b25780601f10610487576101008083540402835291602001916104b2565b820191906000526020600020905b81548152906001019060200180831161049557829003601f168201915b5050505050905090565b60006104c782610c3f565b506000908152600660205260409020546001600160a01b031690565b60006104ee826108cb565b9050806001600160a01b0316836001600160a01b0316036105605760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e656044820152603960f91b60648201526084015b60405180910390fd5b336001600160a01b038216148061057c575061057c8133610bec565b6105ee5760405162461bcd60e51b815260206004820152603d60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c0000006064820152608401610557565b6105f88383610c64565b505050565b610608335b82610cd2565b6106245760405162461bcd60e51b8152600401610557906120d0565b6105f8838383610d31565b60008281526020819052604090206001015461064a81610e90565b6105f88383610e9a565b600061065f836109aa565b82106106c15760405162461bcd60e51b815260206004820152602b60248201527f455243373231456e756d657261626c653a206f776e657220696e646578206f7560448201526a74206f6620626f756e647360a81b6064820152608401610557565b506001600160a01b03919091166000908152600860209081526040808320938352929052205490565b6001600160a01b038116331461075a5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608401610557565b6107648282610ebc565b5050565b6107806000805160206123ac83398151915233610acf565b6107e2576040805162461bcd60e51b81526020600482015260248101919091526000805160206123ec83398151915260448201527f6d75737420686176652070617573657220726f6c6520746f20756e70617573656064820152608401610557565b6107ea610ede565b565b6105f883838360405180602001604052806000815250610b12565b61081033610602565b61082c5760405162461bcd60e51b8152600401610557906120d0565b61083581610f30565b50565b6000610843600a5490565b82106108a65760405162461bcd60e51b815260206004820152602c60248201527f455243373231456e756d657261626c653a20676c6f62616c20696e646578206f60448201526b7574206f6620626f756e647360a01b6064820152608401610557565b600a82815481106108b9576108b961211d565b90600052602060002001549050919050565b6000818152600460205260408120546001600160a01b0316806104245760405162461bcd60e51b815260040161055790612133565b61092a7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633610acf565b61098a5760405162461bcd60e51b815260206004820152603d60248201526000805160206123ec83398151915260448201527f6d7573742068617665206d696e74657220726f6c6520746f206d696e740000006064820152608401610557565b61099c81610997600d5490565b610fc1565b610835600d80546001019055565b60006001600160a01b038216610a145760405162461bcd60e51b815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f7420612076616044820152683634b21037bbb732b960b91b6064820152608401610557565b506001600160a01b031660009081526005602052604090205490565b610a486000805160206123ac83398151915233610acf565b610aa85760405162461bcd60e51b815260206004820152603e60248201526000805160206123ec83398151915260448201527f6d75737420686176652070617573657220726f6c6520746f20706175736500006064820152608401610557565b6107ea6110ca565b6000828152600160205260408120610ac89083611107565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b60606003805461043990612096565b610764338383611113565b610b1c3383610cd2565b610b385760405162461bcd60e51b8152600401610557906120d0565b610b44848484846111dd565b50505050565b6060610b5582610c3f565b6000610b5f611210565b90506000815111610b7f5760405180602001604052806000815250610ac8565b80610b898461121f565b604051602001610b9a929190612165565b6040516020818303038152906040529392505050565b6000818152600160205260408120610424906112b2565b600082815260208190526040902060010154610be281610e90565b6105f88383610ebc565b6001600160a01b03918216600090815260076020908152604080832093909416825291909152205460ff1690565b60006001600160e01b0319821663780e9d6360e01b14806104245750610424826112bc565b610c48816112fc565b6108355760405162461bcd60e51b815260040161055790612133565b600081815260066020526040902080546001600160a01b0319166001600160a01b0384169081179091558190610c99826108cb565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b600080610cde836108cb565b9050806001600160a01b0316846001600160a01b03161480610d055750610d058185610bec565b80610d295750836001600160a01b0316610d1e846104bc565b6001600160a01b0316145b949350505050565b826001600160a01b0316610d44826108cb565b6001600160a01b031614610d6a5760405162461bcd60e51b815260040161055790612194565b6001600160a01b038216610dcc5760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f206164646044820152637265737360e01b6064820152608401610557565b610dd98383836001611319565b826001600160a01b0316610dec826108cb565b6001600160a01b031614610e125760405162461bcd60e51b815260040161055790612194565b600081815260066020908152604080832080546001600160a01b03199081169091556001600160a01b0387811680865260058552838620805460001901905590871680865283862080546001019055868652600490945282852080549092168417909155905184936000805160206123cc83398151915291a4505050565b6108358133611325565b610ea4828261137e565b60008281526001602052604090206105f89082611402565b610ec68282611417565b60008281526001602052604090206105f8908261147c565b610ee6611491565b600c805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6000610f3b826108cb565b9050610f4b816000846001611319565b610f54826108cb565b600083815260066020908152604080832080546001600160a01b03199081169091556001600160a01b0385168085526005845282852080546000190190558785526004909352818420805490911690555192935084926000805160206123cc833981519152908390a45050565b6001600160a01b0382166110175760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f20616464726573736044820152606401610557565b611020816112fc565b1561103d5760405162461bcd60e51b8152600401610557906121d9565b61104b600083836001611319565b611054816112fc565b156110715760405162461bcd60e51b8152600401610557906121d9565b6001600160a01b038216600081815260056020908152604080832080546001019055848352600490915280822080546001600160a01b0319168417905551839291906000805160206123cc833981519152908290a45050565b6110d26114da565b600c805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258610f133390565b6000610ac88383611520565b816001600160a01b0316836001600160a01b0316036111705760405162461bcd60e51b815260206004820152601960248201527822a9219b99189d1030b8383937bb32903a379031b0b63632b960391b6044820152606401610557565b6001600160a01b03838116600081815260076020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6111e8848484610d31565b6111f48484848461154a565b610b445760405162461bcd60e51b815260040161055790612210565b6060600e805461043990612096565b6060600061122c8361164b565b600101905060008167ffffffffffffffff81111561124c5761124c611f7a565b6040519080825280601f01601f191660200182016040528015611276576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461128057509392505050565b6000610424825490565b60006001600160e01b031982166380ac58cd60e01b14806112ed57506001600160e01b03198216635b5e139f60e01b145b80610424575061042482611723565b6000908152600460205260409020546001600160a01b0316151590565b610b4484848484611748565b61132f8282610acf565b6107645761133c816117bb565b6113478360206117cd565b604051602001611358929190612262565b60408051601f198184030181529082905262461bcd60e51b825261055791600401611e27565b6113888282610acf565b610764576000828152602081815260408083206001600160a01b03851684529091529020805460ff191660011790556113be3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610ac8836001600160a01b038416611969565b6114218282610acf565b15610764576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000610ac8836001600160a01b0384166119b8565b600c5460ff166107ea5760405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606401610557565b600c5460ff16156107ea5760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610557565b60008260000182815481106115375761153761211d565b9060005260206000200154905092915050565b60006001600160a01b0384163b1561164057604051630a85bd0160e11b81526001600160a01b0385169063150b7a029061158e9033908990889088906004016122d1565b6020604051808303816000875af19250505080156115c9575060408051601f3d908101601f191682019092526115c69181019061230e565b60015b611626573d8080156115f7576040519150601f19603f3d011682016040523d82523d6000602084013e6115fc565b606091505b50805160000361161e5760405162461bcd60e51b815260040161055790612210565b805181602001fd5b6001600160e01b031916630a85bd0160e11b149050610d29565b506001949350505050565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b831061168a5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef810000000083106116b6576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106116d457662386f26fc10000830492506010015b6305f5e10083106116ec576305f5e100830492506008015b612710831061170057612710830492506004015b60648310611712576064830492506002015b600a83106104245760010192915050565b60006001600160e01b03198216635a05180f60e01b1480610424575061042482611aab565b61175484848484611ae0565b600c5460ff1615610b445760405162461bcd60e51b815260206004820152602b60248201527f4552433732315061757361626c653a20746f6b656e207472616e73666572207760448201526a1a1a5b19481c185d5cd95960aa1b6064820152608401610557565b60606104246001600160a01b03831660145b606060006117dc836002612341565b6117e7906002612358565b67ffffffffffffffff8111156117ff576117ff611f7a565b6040519080825280601f01601f191660200182016040528015611829576020820181803683370190505b509050600360fc1b816000815181106118445761184461211d565b60200101906001600160f81b031916908160001a905350600f60fb1b816001815181106118735761187361211d565b60200101906001600160f81b031916908160001a9053506000611897846002612341565b6118a2906001612358565b90505b600181111561191a576f181899199a1a9b1b9c1cb0b131b232b360811b85600f16601081106118d6576118d661211d565b1a60f81b8282815181106118ec576118ec61211d565b60200101906001600160f81b031916908160001a90535060049490941c936119138161236b565b90506118a5565b508315610ac85760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610557565b60008181526001830160205260408120546119b057508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610424565b506000610424565b60008181526001830160205260408120548015611aa15760006119dc600183612382565b85549091506000906119f090600190612382565b9050818114611a55576000866000018281548110611a1057611a1061211d565b9060005260206000200154905080876000018481548110611a3357611a3361211d565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611a6657611a66612395565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610424565b6000915050610424565b60006001600160e01b03198216637965db0b60e01b148061042457506301ffc9a760e01b6001600160e01b0319831614610424565b6001811115611b4f5760405162461bcd60e51b815260206004820152603560248201527f455243373231456e756d657261626c653a20636f6e7365637574697665207472604482015274185b9cd9995c9cc81b9bdd081cdd5c1c1bdc9d1959605a1b6064820152608401610557565b816001600160a01b038516611bab57611ba681600a80546000838152600b60205260408120829055600182018355919091527fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80155565b611bce565b836001600160a01b0316856001600160a01b031614611bce57611bce8582611c14565b6001600160a01b038416611bea57611be581611cb1565b611c0d565b846001600160a01b0316846001600160a01b031614611c0d57611c0d8482611d60565b5050505050565b60006001611c21846109aa565b611c2b9190612382565b600083815260096020526040902054909150808214611c7e576001600160a01b03841660009081526008602090815260408083208584528252808320548484528184208190558352600990915290208190555b5060009182526009602090815260408084208490556001600160a01b039094168352600881528383209183525290812055565b600a54600090611cc390600190612382565b6000838152600b6020526040812054600a8054939450909284908110611ceb57611ceb61211d565b9060005260206000200154905080600a8381548110611d0c57611d0c61211d565b6000918252602080832090910192909255828152600b9091526040808220849055858252812055600a805480611d4457611d44612395565b6001900381819060005260206000200160009055905550505050565b6000611d6b836109aa565b6001600160a01b039093166000908152600860209081526040808320868452825280832085905593825260099052919091209190915550565b6001600160e01b03198116811461083557600080fd5b600060208284031215611dcc57600080fd5b8135610ac881611da4565b60005b83811015611df2578181015183820152602001611dda565b50506000910152565b60008151808452611e13816020860160208601611dd7565b601f01601f19169290920160200192915050565b602081526000610ac86020830184611dfb565b600060208284031215611e4c57600080fd5b5035919050565b80356001600160a01b0381168114611e6a57600080fd5b919050565b60008060408385031215611e8257600080fd5b611e8b83611e53565b946020939093013593505050565b600080600060608486031215611eae57600080fd5b611eb784611e53565b9250611ec560208501611e53565b9150604084013590509250925092565b60008060408385031215611ee857600080fd5b82359150611ef860208401611e53565b90509250929050565b600060208284031215611f1357600080fd5b610ac882611e53565b60008060408385031215611f2f57600080fd5b50508035926020909101359150565b60008060408385031215611f5157600080fd5b611f5a83611e53565b915060208301358015158114611f6f57600080fd5b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b60008060008060808587031215611fa657600080fd5b611faf85611e53565b9350611fbd60208601611e53565b925060408501359150606085013567ffffffffffffffff80821115611fe157600080fd5b818701915087601f830112611ff557600080fd5b81358181111561200757612007611f7a565b604051601f8201601f19908116603f0116810190838211818310171561202f5761202f611f7a565b816040528281528a602084870101111561204857600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b6000806040838503121561207f57600080fd5b61208883611e53565b9150611ef860208401611e53565b600181811c908216806120aa57607f821691505b6020821081036120ca57634e487b7160e01b600052602260045260246000fd5b50919050565b6020808252602d908201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560408201526c1c881bdc88185c1c1c9bdd9959609a1b606082015260800190565b634e487b7160e01b600052603260045260246000fd5b602080825260189082015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b604082015260600190565b60008351612177818460208801611dd7565b83519083019061218b818360208801611dd7565b01949350505050565b60208082526025908201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060408201526437bbb732b960d91b606082015260800190565b6020808252601c908201527f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000604082015260600190565b60208082526032908201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560408201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b606082015260800190565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b815260008351612294816017850160208801611dd7565b7001034b99036b4b9b9b4b733903937b6329607d1b60179184019182015283516122c5816028840160208801611dd7565b01602801949350505050565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061230490830184611dfb565b9695505050505050565b60006020828403121561232057600080fd5b8151610ac881611da4565b634e487b7160e01b600052601160045260246000fd5b80820281158282048414176104245761042461232b565b808201808211156104245761042461232b565b60008161237a5761237a61232b565b506000190190565b818103818111156104245761042461232b565b634e487b7160e01b600052603160045260246000fdfe65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862addf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef4552433732315072657365744d696e7465725061757365724175746f49643a20a2646970667358221220f586c29d0cbd3e2cee9bd99a648a34864226f46f9a05471902f3728b4f230c2864736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101a55760003560e01c80636352211e116100ef578063a22cb46511610092578063a22cb4651461036b578063b88d4fde1461037e578063c87b56dd14610391578063ca15c873146103a4578063d5391393146103b7578063d547741f146103de578063e63ab1e9146103f1578063e985e9c51461040657600080fd5b80636352211e146102f45780636a6278421461030757806370a082311461031a5780638456cb591461032d5780639010d07c1461033557806391d148541461034857806395d89b411461035b578063a217fddf1461036357600080fd5b80632f2ff15d116101575780632f2ff15d1461026f5780632f745c591461028257806336568abe146102955780633f4ba83a146102a857806342842e0e146102b057806342966c68146102c35780634f6ccce7146102d65780635c975abb146102e957600080fd5b806301ffc9a7146101aa57806306fdde03146101d2578063081812fc146101e7578063095ea7b31461021257806318160ddd1461022757806323b872dd14610239578063248a9ca31461024c575b600080fd5b6101bd6101b8366004611dba565b610419565b60405190151581526020015b60405180910390f35b6101da61042a565b6040516101c99190611e27565b6101fa6101f5366004611e3a565b6104bc565b6040516001600160a01b0390911681526020016101c9565b610225610220366004611e6f565b6104e3565b005b600a545b6040519081526020016101c9565b610225610247366004611e99565b6105fd565b61022b61025a366004611e3a565b60009081526020819052604090206001015490565b61022561027d366004611ed5565b61062f565b61022b610290366004611e6f565b610654565b6102256102a3366004611ed5565b6106ea565b610225610768565b6102256102be366004611e99565b6107ec565b6102256102d1366004611e3a565b610807565b61022b6102e4366004611e3a565b610838565b600c5460ff166101bd565b6101fa610302366004611e3a565b6108cb565b610225610315366004611f01565b610900565b61022b610328366004611f01565b6109aa565b610225610a30565b6101fa610343366004611f1c565b610ab0565b6101bd610356366004611ed5565b610acf565b6101da610af8565b61022b600081565b610225610379366004611f3e565b610b07565b61022561038c366004611f90565b610b12565b6101da61039f366004611e3a565b610b4a565b61022b6103b2366004611e3a565b610bb0565b61022b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6102256103ec366004611ed5565b610bc7565b61022b6000805160206123ac83398151915281565b6101bd61041436600461206c565b610bec565b600061042482610c1a565b92915050565b60606002805461043990612096565b80601f016020809104026020016040519081016040528092919081815260200182805461046590612096565b80156104b25780601f10610487576101008083540402835291602001916104b2565b820191906000526020600020905b81548152906001019060200180831161049557829003601f168201915b5050505050905090565b60006104c782610c3f565b506000908152600660205260409020546001600160a01b031690565b60006104ee826108cb565b9050806001600160a01b0316836001600160a01b0316036105605760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e656044820152603960f91b60648201526084015b60405180910390fd5b336001600160a01b038216148061057c575061057c8133610bec565b6105ee5760405162461bcd60e51b815260206004820152603d60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c0000006064820152608401610557565b6105f88383610c64565b505050565b610608335b82610cd2565b6106245760405162461bcd60e51b8152600401610557906120d0565b6105f8838383610d31565b60008281526020819052604090206001015461064a81610e90565b6105f88383610e9a565b600061065f836109aa565b82106106c15760405162461bcd60e51b815260206004820152602b60248201527f455243373231456e756d657261626c653a206f776e657220696e646578206f7560448201526a74206f6620626f756e647360a81b6064820152608401610557565b506001600160a01b03919091166000908152600860209081526040808320938352929052205490565b6001600160a01b038116331461075a5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608401610557565b6107648282610ebc565b5050565b6107806000805160206123ac83398151915233610acf565b6107e2576040805162461bcd60e51b81526020600482015260248101919091526000805160206123ec83398151915260448201527f6d75737420686176652070617573657220726f6c6520746f20756e70617573656064820152608401610557565b6107ea610ede565b565b6105f883838360405180602001604052806000815250610b12565b61081033610602565b61082c5760405162461bcd60e51b8152600401610557906120d0565b61083581610f30565b50565b6000610843600a5490565b82106108a65760405162461bcd60e51b815260206004820152602c60248201527f455243373231456e756d657261626c653a20676c6f62616c20696e646578206f60448201526b7574206f6620626f756e647360a01b6064820152608401610557565b600a82815481106108b9576108b961211d565b90600052602060002001549050919050565b6000818152600460205260408120546001600160a01b0316806104245760405162461bcd60e51b815260040161055790612133565b61092a7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633610acf565b61098a5760405162461bcd60e51b815260206004820152603d60248201526000805160206123ec83398151915260448201527f6d7573742068617665206d696e74657220726f6c6520746f206d696e740000006064820152608401610557565b61099c81610997600d5490565b610fc1565b610835600d80546001019055565b60006001600160a01b038216610a145760405162461bcd60e51b815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f7420612076616044820152683634b21037bbb732b960b91b6064820152608401610557565b506001600160a01b031660009081526005602052604090205490565b610a486000805160206123ac83398151915233610acf565b610aa85760405162461bcd60e51b815260206004820152603e60248201526000805160206123ec83398151915260448201527f6d75737420686176652070617573657220726f6c6520746f20706175736500006064820152608401610557565b6107ea6110ca565b6000828152600160205260408120610ac89083611107565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b60606003805461043990612096565b610764338383611113565b610b1c3383610cd2565b610b385760405162461bcd60e51b8152600401610557906120d0565b610b44848484846111dd565b50505050565b6060610b5582610c3f565b6000610b5f611210565b90506000815111610b7f5760405180602001604052806000815250610ac8565b80610b898461121f565b604051602001610b9a929190612165565b6040516020818303038152906040529392505050565b6000818152600160205260408120610424906112b2565b600082815260208190526040902060010154610be281610e90565b6105f88383610ebc565b6001600160a01b03918216600090815260076020908152604080832093909416825291909152205460ff1690565b60006001600160e01b0319821663780e9d6360e01b14806104245750610424826112bc565b610c48816112fc565b6108355760405162461bcd60e51b815260040161055790612133565b600081815260066020526040902080546001600160a01b0319166001600160a01b0384169081179091558190610c99826108cb565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b600080610cde836108cb565b9050806001600160a01b0316846001600160a01b03161480610d055750610d058185610bec565b80610d295750836001600160a01b0316610d1e846104bc565b6001600160a01b0316145b949350505050565b826001600160a01b0316610d44826108cb565b6001600160a01b031614610d6a5760405162461bcd60e51b815260040161055790612194565b6001600160a01b038216610dcc5760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f206164646044820152637265737360e01b6064820152608401610557565b610dd98383836001611319565b826001600160a01b0316610dec826108cb565b6001600160a01b031614610e125760405162461bcd60e51b815260040161055790612194565b600081815260066020908152604080832080546001600160a01b03199081169091556001600160a01b0387811680865260058552838620805460001901905590871680865283862080546001019055868652600490945282852080549092168417909155905184936000805160206123cc83398151915291a4505050565b6108358133611325565b610ea4828261137e565b60008281526001602052604090206105f89082611402565b610ec68282611417565b60008281526001602052604090206105f8908261147c565b610ee6611491565b600c805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6000610f3b826108cb565b9050610f4b816000846001611319565b610f54826108cb565b600083815260066020908152604080832080546001600160a01b03199081169091556001600160a01b0385168085526005845282852080546000190190558785526004909352818420805490911690555192935084926000805160206123cc833981519152908390a45050565b6001600160a01b0382166110175760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f20616464726573736044820152606401610557565b611020816112fc565b1561103d5760405162461bcd60e51b8152600401610557906121d9565b61104b600083836001611319565b611054816112fc565b156110715760405162461bcd60e51b8152600401610557906121d9565b6001600160a01b038216600081815260056020908152604080832080546001019055848352600490915280822080546001600160a01b0319168417905551839291906000805160206123cc833981519152908290a45050565b6110d26114da565b600c805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258610f133390565b6000610ac88383611520565b816001600160a01b0316836001600160a01b0316036111705760405162461bcd60e51b815260206004820152601960248201527822a9219b99189d1030b8383937bb32903a379031b0b63632b960391b6044820152606401610557565b6001600160a01b03838116600081815260076020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6111e8848484610d31565b6111f48484848461154a565b610b445760405162461bcd60e51b815260040161055790612210565b6060600e805461043990612096565b6060600061122c8361164b565b600101905060008167ffffffffffffffff81111561124c5761124c611f7a565b6040519080825280601f01601f191660200182016040528015611276576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461128057509392505050565b6000610424825490565b60006001600160e01b031982166380ac58cd60e01b14806112ed57506001600160e01b03198216635b5e139f60e01b145b80610424575061042482611723565b6000908152600460205260409020546001600160a01b0316151590565b610b4484848484611748565b61132f8282610acf565b6107645761133c816117bb565b6113478360206117cd565b604051602001611358929190612262565b60408051601f198184030181529082905262461bcd60e51b825261055791600401611e27565b6113888282610acf565b610764576000828152602081815260408083206001600160a01b03851684529091529020805460ff191660011790556113be3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610ac8836001600160a01b038416611969565b6114218282610acf565b15610764576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000610ac8836001600160a01b0384166119b8565b600c5460ff166107ea5760405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606401610557565b600c5460ff16156107ea5760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610557565b60008260000182815481106115375761153761211d565b9060005260206000200154905092915050565b60006001600160a01b0384163b1561164057604051630a85bd0160e11b81526001600160a01b0385169063150b7a029061158e9033908990889088906004016122d1565b6020604051808303816000875af19250505080156115c9575060408051601f3d908101601f191682019092526115c69181019061230e565b60015b611626573d8080156115f7576040519150601f19603f3d011682016040523d82523d6000602084013e6115fc565b606091505b50805160000361161e5760405162461bcd60e51b815260040161055790612210565b805181602001fd5b6001600160e01b031916630a85bd0160e11b149050610d29565b506001949350505050565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b831061168a5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef810000000083106116b6576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106116d457662386f26fc10000830492506010015b6305f5e10083106116ec576305f5e100830492506008015b612710831061170057612710830492506004015b60648310611712576064830492506002015b600a83106104245760010192915050565b60006001600160e01b03198216635a05180f60e01b1480610424575061042482611aab565b61175484848484611ae0565b600c5460ff1615610b445760405162461bcd60e51b815260206004820152602b60248201527f4552433732315061757361626c653a20746f6b656e207472616e73666572207760448201526a1a1a5b19481c185d5cd95960aa1b6064820152608401610557565b60606104246001600160a01b03831660145b606060006117dc836002612341565b6117e7906002612358565b67ffffffffffffffff8111156117ff576117ff611f7a565b6040519080825280601f01601f191660200182016040528015611829576020820181803683370190505b509050600360fc1b816000815181106118445761184461211d565b60200101906001600160f81b031916908160001a905350600f60fb1b816001815181106118735761187361211d565b60200101906001600160f81b031916908160001a9053506000611897846002612341565b6118a2906001612358565b90505b600181111561191a576f181899199a1a9b1b9c1cb0b131b232b360811b85600f16601081106118d6576118d661211d565b1a60f81b8282815181106118ec576118ec61211d565b60200101906001600160f81b031916908160001a90535060049490941c936119138161236b565b90506118a5565b508315610ac85760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610557565b60008181526001830160205260408120546119b057508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610424565b506000610424565b60008181526001830160205260408120548015611aa15760006119dc600183612382565b85549091506000906119f090600190612382565b9050818114611a55576000866000018281548110611a1057611a1061211d565b9060005260206000200154905080876000018481548110611a3357611a3361211d565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611a6657611a66612395565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610424565b6000915050610424565b60006001600160e01b03198216637965db0b60e01b148061042457506301ffc9a760e01b6001600160e01b0319831614610424565b6001811115611b4f5760405162461bcd60e51b815260206004820152603560248201527f455243373231456e756d657261626c653a20636f6e7365637574697665207472604482015274185b9cd9995c9cc81b9bdd081cdd5c1c1bdc9d1959605a1b6064820152608401610557565b816001600160a01b038516611bab57611ba681600a80546000838152600b60205260408120829055600182018355919091527fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80155565b611bce565b836001600160a01b0316856001600160a01b031614611bce57611bce8582611c14565b6001600160a01b038416611bea57611be581611cb1565b611c0d565b846001600160a01b0316846001600160a01b031614611c0d57611c0d8482611d60565b5050505050565b60006001611c21846109aa565b611c2b9190612382565b600083815260096020526040902054909150808214611c7e576001600160a01b03841660009081526008602090815260408083208584528252808320548484528184208190558352600990915290208190555b5060009182526009602090815260408084208490556001600160a01b039094168352600881528383209183525290812055565b600a54600090611cc390600190612382565b6000838152600b6020526040812054600a8054939450909284908110611ceb57611ceb61211d565b9060005260206000200154905080600a8381548110611d0c57611d0c61211d565b6000918252602080832090910192909255828152600b9091526040808220849055858252812055600a805480611d4457611d44612395565b6001900381819060005260206000200160009055905550505050565b6000611d6b836109aa565b6001600160a01b039093166000908152600860209081526040808320868452825280832085905593825260099052919091209190915550565b6001600160e01b03198116811461083557600080fd5b600060208284031215611dcc57600080fd5b8135610ac881611da4565b60005b83811015611df2578181015183820152602001611dda565b50506000910152565b60008151808452611e13816020860160208601611dd7565b601f01601f19169290920160200192915050565b602081526000610ac86020830184611dfb565b600060208284031215611e4c57600080fd5b5035919050565b80356001600160a01b0381168114611e6a57600080fd5b919050565b60008060408385031215611e8257600080fd5b611e8b83611e53565b946020939093013593505050565b600080600060608486031215611eae57600080fd5b611eb784611e53565b9250611ec560208501611e53565b9150604084013590509250925092565b60008060408385031215611ee857600080fd5b82359150611ef860208401611e53565b90509250929050565b600060208284031215611f1357600080fd5b610ac882611e53565b60008060408385031215611f2f57600080fd5b50508035926020909101359150565b60008060408385031215611f5157600080fd5b611f5a83611e53565b915060208301358015158114611f6f57600080fd5b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b60008060008060808587031215611fa657600080fd5b611faf85611e53565b9350611fbd60208601611e53565b925060408501359150606085013567ffffffffffffffff80821115611fe157600080fd5b818701915087601f830112611ff557600080fd5b81358181111561200757612007611f7a565b604051601f8201601f19908116603f0116810190838211818310171561202f5761202f611f7a565b816040528281528a602084870101111561204857600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b6000806040838503121561207f57600080fd5b61208883611e53565b9150611ef860208401611e53565b600181811c908216806120aa57607f821691505b6020821081036120ca57634e487b7160e01b600052602260045260246000fd5b50919050565b6020808252602d908201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560408201526c1c881bdc88185c1c1c9bdd9959609a1b606082015260800190565b634e487b7160e01b600052603260045260246000fd5b602080825260189082015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b604082015260600190565b60008351612177818460208801611dd7565b83519083019061218b818360208801611dd7565b01949350505050565b60208082526025908201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060408201526437bbb732b960d91b606082015260800190565b6020808252601c908201527f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000604082015260600190565b60208082526032908201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560408201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b606082015260800190565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b815260008351612294816017850160208801611dd7565b7001034b99036b4b9b9b4b733903937b6329607d1b60179184019182015283516122c5816028840160208801611dd7565b01602801949350505050565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061230490830184611dfb565b9695505050505050565b60006020828403121561232057600080fd5b8151610ac881611da4565b634e487b7160e01b600052601160045260246000fd5b80820281158282048414176104245761042461232b565b808201808211156104245761042461232b565b60008161237a5761237a61232b565b506000190190565b818103818111156104245761042461232b565b634e487b7160e01b600052603160045260246000fdfe65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862addf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef4552433732315072657365744d696e7465725061757365724175746f49643a20a2646970667358221220f586c29d0cbd3e2cee9bd99a648a34864226f46f9a05471902f3728b4f230c2864736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RootERC721PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RootERC721Predicate","sourceName":"contracts/root/RootERC721Predicate.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"ERC721DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"ERC721WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"TokenMapped","type":"event"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childERC721Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"depositBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitHelper","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newStateSender","type":"address"},{"internalType":"address","name":"newExitHelper","type":"address"},{"internalType":"address","name":"newChildERC721Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721Metadata","name":"rootToken","type":"address"}],"name":"mapToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onL2StateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b5061196b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100fb5760003560e01c8063cb10f94c116100a2578063f43cda8b11610071578063f43cda8b146102a1578063f4a120f7146102b4578063f6451255146102c7578063f8c8765e146102ee578063ff6f870c1461030157600080fd5b8063cb10f94c14610227578063d41f177114610240578063d7c9e3ec14610267578063f213159c1461028e57600080fd5b8063150b7a021461010057806347e7ef241461013c5780634fdca69d146101515780637efab4f51461017c57806395c7041c146101a5578063b1768065146101b8578063b68ad1e4146101ed578063c5ac2b1c14610200575b600080fd5b61011e61010e36600461112d565b630a85bd0160e11b949350505050565b6040516001600160e01b031990911681526020015b60405180910390f35b61014f61014a3660046111db565b610314565b005b600254610164906001600160a01b031681565b6040516001600160a01b039091168152602001610133565b61016461018a366004611207565b6004602052600090815260409020546001600160a01b031681565b600154610164906001600160a01b031681565b6101df7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b604051908152602001610133565b600354610164906001600160a01b031681565b6101df7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b600054610164906201000090046001600160a01b031681565b6101df7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101df7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61014f61029c36600461122b565b610323565b61014f6102af36600461126c565b610333565b6101646102c2366004611207565b61050a565b6101df7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61014f6102fc3660046112f4565b61086a565b61014f61030f36600461139b565b610a78565b61031f823383610ae0565b5050565b61032e838383610ae0565b505050565b6001546001600160a01b031633146103a05760405162461bcd60e51b815260206004820152602560248201527f526f6f744552433732315072656469636174653a204f4e4c595f455849545f4860448201526422a62822a960d91b60648201526084015b60405180910390fd5b6002546001600160a01b0384811691161461040f5760405162461bcd60e51b815260206004820152602960248201527f526f6f744552433732315072656469636174653a204f4e4c595f4348494c445f60448201526850524544494341544560b81b6064820152608401610397565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286961043e60206000848661141d565b61044791611447565b036104665761046161045c826020818661141d565b610c66565b610504565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed261049560206000848661141d565b61049e91611447565b036104ad576104618282610d64565b60405162461bcd60e51b815260206004820152602660248201527f526f6f744552433732315072656469636174653a20494e56414c49445f5349476044820152654e415455524560d01b6064820152608401610397565b50505050565b60006001600160a01b03821661056d5760405162461bcd60e51b815260206004820152602260248201527f526f6f744552433732315072656469636174653a20494e56414c49445f544f4b60448201526122a760f11b6064820152608401610397565b6001600160a01b0382811660009081526004602052604090205416156105e15760405162461bcd60e51b815260206004820152602360248201527f526f6f744552433732315072656469636174653a20414c52454144595f4d415060448201526214115160ea1b6064820152608401610397565b6002546003546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b039283169260009261068f92911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b03858116600081815260046020819052604080832080546001600160a01b031916878716179055825481516306fdde0360e01b81529151969750620100009004909416946316f198319488947f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad948c9491936306fdde039380840193908290030181865afa15801561072c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610754919081019061148a565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610792573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526107ba919081019061148a565b6040516020016107cd949392919061152c565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016107f9929190611573565b600060405180830381600087803b15801561081357600080fd5b505af1158015610827573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f85920d35e6c72f6b2affffa04298b0cecfeba86e4a9f407df661f1cb8ab5e61790600090a39392505050565b600054610100900460ff161580801561088a5750600054600160ff909116105b806108a45750303b1580156108a4575060005460ff166001145b6109075760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610397565b6000805460ff19166001179055801561092a576000805461ff0019166101001790555b6001600160a01b0385161580159061094a57506001600160a01b03841615155b801561095e57506001600160a01b03831615155b801561097257506001600160a01b03821615155b6109ce5760405162461bcd60e51b815260206004820152602760248201527f526f6f744552433732315072656469636174653a204241445f494e495449414c60448201526624ad20aa24a7a760c91b6064820152608401610397565b6000805462010000600160b01b031916620100006001600160a01b038881169190910291909117909155600180546001600160a01b031990811687841617909155600280548216868416179055600380549091169184169190911790558015610a71576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b828114610ad35760405162461bcd60e51b815260206004820152602360248201527f526f6f744552433732315072656469636174653a20494e56414c49445f4c454e60448201526208ea8960eb1b6064820152608401610397565b610a718585858585610ea5565b6000610aeb84611061565b604051632142170760e11b81529091506001600160a01b038516906342842e0e90610b1e9033903090879060040161159f565b600060405180830381600087803b158015610b3857600080fd5b505af1158015610b4c573d6000803e3d6000fd5b5050600054600254604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038a811682840152336060830152898116608083015260a08083018a90528351808403909101815260c08301938490526316f1983160e01b90935262010000909404841695506316f198319450610be2939092169160c401611573565b600060405180830381600087803b158015610bfc57600080fd5b505af1158015610c10573d6000803e3d6000fd5b505060408051338152602081018690526001600160a01b03808816945085811693508816917f40e242710be14b7d61d3b93e9d56b40df89cc4dbad8bb88da38c8e0c23809ba7910160405180910390a450505050565b6000808080610c77858701876115c3565b6001600160a01b0380851660009081526004602052604090205494985092965090945092501680610caa57610caa611614565b604051632142170760e11b81526001600160a01b038616906342842e0e90610cda9030908790879060040161159f565b600060405180830381600087803b158015610cf457600080fd5b505af1158015610d08573d6000803e3d6000fd5b5050604080516001600160a01b03888116825260208201879052808816945085811693508916917f8194f168e6e9e449d05a46936e071a710c0f22634eaadeb5b85a86540123931991015b60405180910390a450505050505050565b6000808080610d75858701876116b3565b6001600160a01b03808516600090815260046020526040902054949950929750909550935016905080610daa57610daa611614565b60005b8251811015610e5557856001600160a01b03166342842e0e30868481518110610dd857610dd86117a5565b6020026020010151868581518110610df257610df26117a5565b60200260200101516040518463ffffffff1660e01b8152600401610e189392919061159f565b600060405180830381600087803b158015610e3257600080fd5b505af1158015610e46573d6000803e3d6000fd5b50505050806001019050610dad565b50836001600160a01b0316816001600160a01b0316866001600160a01b03167f1b7b9713fdef5dfadf92f6d6e998eb1fc4763cee62a194a095d76349f7dce20c8686604051610d539291906117bb565b6000610eb086611061565b905060005b82811015610f4257866001600160a01b03166342842e0e3330878786818110610ee057610ee06117a5565b905060200201356040518463ffffffff1660e01b8152600401610f059392919061159f565b600060405180830381600087803b158015610f1f57600080fd5b505af1158015610f33573d6000803e3d6000fd5b50505050806001019050610eb5565b506000546002546040516001600160a01b03620100009093048316926316f19831921690610fa0907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908b9033908c908c908c908c906020016118ba565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610fcc929190611573565b600060405180830381600087803b158015610fe657600080fd5b505af1158015610ffa573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316876001600160a01b03167f02c575a943105cda454293b7ac636c87c94ff1bfcfac968aa5423e6b9b312ee988888888604051611051949392919061190e565b60405180910390a4505050505050565b6001600160a01b03808216600090815260046020526040902054168061108d5761108a8261050a565b90505b6001600160a01b0381166110a3576110a3611614565b919050565b6001600160a01b03811681146110bd57600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156110fe576110fe6110c0565b604052919050565b60006001600160401b0382111561111f5761111f6110c0565b50601f01601f191660200190565b6000806000806080858703121561114357600080fd5b843561114e816110a8565b9350602085013561115e816110a8565b92506040850135915060608501356001600160401b0381111561118057600080fd5b8501601f8101871361119157600080fd5b80356111a461119f82611106565b6110d6565b8181528860208385010111156111b957600080fd5b8160208401602083013760006020838301015280935050505092959194509250565b600080604083850312156111ee57600080fd5b82356111f9816110a8565b946020939093013593505050565b60006020828403121561121957600080fd5b8135611224816110a8565b9392505050565b60008060006060848603121561124057600080fd5b833561124b816110a8565b9250602084013561125b816110a8565b929592945050506040919091013590565b6000806000806060858703121561128257600080fd5b843593506020850135611294816110a8565b925060408501356001600160401b03808211156112b057600080fd5b818701915087601f8301126112c457600080fd5b8135818111156112d357600080fd5b8860208285010111156112e557600080fd5b95989497505060200194505050565b6000806000806080858703121561130a57600080fd5b8435611315816110a8565b93506020850135611325816110a8565b92506040850135611335816110a8565b91506060850135611345816110a8565b939692955090935050565b60008083601f84011261136257600080fd5b5081356001600160401b0381111561137957600080fd5b6020830191508360208260051b850101111561139457600080fd5b9250929050565b6000806000806000606086880312156113b357600080fd5b85356113be816110a8565b945060208601356001600160401b03808211156113da57600080fd5b6113e689838a01611350565b909650945060408801359150808211156113ff57600080fd5b5061140c88828901611350565b969995985093965092949392505050565b6000808585111561142d57600080fd5b8386111561143a57600080fd5b5050820193919092039150565b8035602083101561146057600019602084900360031b1b165b92915050565b60005b83811015611481578181015183820152602001611469565b50506000910152565b60006020828403121561149c57600080fd5b81516001600160401b038111156114b257600080fd5b8201601f810184136114c357600080fd5b80516114d161119f82611106565b8181528560208385010111156114e657600080fd5b6114f7826020830160208601611466565b95945050505050565b60008151808452611518816020860160208601611466565b601f01601f19169290920160200192915050565b8481526001600160a01b038416602082015260806040820181905260009061155690830185611500565b82810360608401526115688185611500565b979650505050505050565b6001600160a01b038316815260406020820181905260009061159790830184611500565b949350505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600080600080608085870312156115d957600080fd5b84356115e4816110a8565b935060208501356115f4816110a8565b92506040850135611604816110a8565b9396929550929360600135925050565b634e487b7160e01b600052600160045260246000fd5b60006001600160401b03821115611643576116436110c0565b5060051b60200190565b600082601f83011261165e57600080fd5b8135602061166e61119f8361162a565b82815260059290921b8401810191818101908684111561168d57600080fd5b8286015b848110156116a85780358352918301918301611691565b509695505050505050565b600080600080600060a086880312156116cb57600080fd5b853594506020808701356116de816110a8565b945060408701356116ee816110a8565b935060608701356001600160401b038082111561170a57600080fd5b818901915089601f83011261171e57600080fd5b813561172c61119f8261162a565b81815260059190911b8301840190848101908c83111561174b57600080fd5b938501935b82851015611772578435611763816110a8565b82529385019390850190611750565b96505050608089013592508083111561178a57600080fd5b50506117988882890161164d565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b604080825283519082018190526000906020906060840190828701845b828110156117fd5781516001600160a01b0316845292840192908401906001016117d8565b5050508381038285015284518082528583019183019060005b8181101561183257835183529284019291840191600101611816565b5090979650505050505050565b8183526000602080850194508260005b8581101561187d578135611862816110a8565b6001600160a01b03168752958201959082019060010161184f565b509495945050505050565b81835260006001600160fb1b038311156118a157600080fd5b8260051b80836020870137939093016020019392505050565b8781526001600160a01b0387811660208301528616604082015260a0606082018190526000906118ed908301868861183f565b8281036080840152611900818587611888565b9a9950505050505050505050565b60408152600061192260408301868861183f565b828103602084015261156881858761188856fea26469706673582212203621718a3c2b1847a958c6d88f320b8bf9fde1113c6a705bddbeec4f2127ab7364736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100fb5760003560e01c8063cb10f94c116100a2578063f43cda8b11610071578063f43cda8b146102a1578063f4a120f7146102b4578063f6451255146102c7578063f8c8765e146102ee578063ff6f870c1461030157600080fd5b8063cb10f94c14610227578063d41f177114610240578063d7c9e3ec14610267578063f213159c1461028e57600080fd5b8063150b7a021461010057806347e7ef241461013c5780634fdca69d146101515780637efab4f51461017c57806395c7041c146101a5578063b1768065146101b8578063b68ad1e4146101ed578063c5ac2b1c14610200575b600080fd5b61011e61010e36600461112d565b630a85bd0160e11b949350505050565b6040516001600160e01b031990911681526020015b60405180910390f35b61014f61014a3660046111db565b610314565b005b600254610164906001600160a01b031681565b6040516001600160a01b039091168152602001610133565b61016461018a366004611207565b6004602052600090815260409020546001600160a01b031681565b600154610164906001600160a01b031681565b6101df7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b604051908152602001610133565b600354610164906001600160a01b031681565b6101df7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b600054610164906201000090046001600160a01b031681565b6101df7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101df7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61014f61029c36600461122b565b610323565b61014f6102af36600461126c565b610333565b6101646102c2366004611207565b61050a565b6101df7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61014f6102fc3660046112f4565b61086a565b61014f61030f36600461139b565b610a78565b61031f823383610ae0565b5050565b61032e838383610ae0565b505050565b6001546001600160a01b031633146103a05760405162461bcd60e51b815260206004820152602560248201527f526f6f744552433732315072656469636174653a204f4e4c595f455849545f4860448201526422a62822a960d91b60648201526084015b60405180910390fd5b6002546001600160a01b0384811691161461040f5760405162461bcd60e51b815260206004820152602960248201527f526f6f744552433732315072656469636174653a204f4e4c595f4348494c445f60448201526850524544494341544560b81b6064820152608401610397565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286961043e60206000848661141d565b61044791611447565b036104665761046161045c826020818661141d565b610c66565b610504565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed261049560206000848661141d565b61049e91611447565b036104ad576104618282610d64565b60405162461bcd60e51b815260206004820152602660248201527f526f6f744552433732315072656469636174653a20494e56414c49445f5349476044820152654e415455524560d01b6064820152608401610397565b50505050565b60006001600160a01b03821661056d5760405162461bcd60e51b815260206004820152602260248201527f526f6f744552433732315072656469636174653a20494e56414c49445f544f4b60448201526122a760f11b6064820152608401610397565b6001600160a01b0382811660009081526004602052604090205416156105e15760405162461bcd60e51b815260206004820152602360248201527f526f6f744552433732315072656469636174653a20414c52454144595f4d415060448201526214115160ea1b6064820152608401610397565b6002546003546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b039283169260009261068f92911690603401604051602081830303815290604052805190602001208460405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b03858116600081815260046020819052604080832080546001600160a01b031916878716179055825481516306fdde0360e01b81529151969750620100009004909416946316f198319488947f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad948c9491936306fdde039380840193908290030181865afa15801561072c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610754919081019061148a565b896001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610792573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526107ba919081019061148a565b6040516020016107cd949392919061152c565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016107f9929190611573565b600060405180830381600087803b15801561081357600080fd5b505af1158015610827573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f85920d35e6c72f6b2affffa04298b0cecfeba86e4a9f407df661f1cb8ab5e61790600090a39392505050565b600054610100900460ff161580801561088a5750600054600160ff909116105b806108a45750303b1580156108a4575060005460ff166001145b6109075760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610397565b6000805460ff19166001179055801561092a576000805461ff0019166101001790555b6001600160a01b0385161580159061094a57506001600160a01b03841615155b801561095e57506001600160a01b03831615155b801561097257506001600160a01b03821615155b6109ce5760405162461bcd60e51b815260206004820152602760248201527f526f6f744552433732315072656469636174653a204241445f494e495449414c60448201526624ad20aa24a7a760c91b6064820152608401610397565b6000805462010000600160b01b031916620100006001600160a01b038881169190910291909117909155600180546001600160a01b031990811687841617909155600280548216868416179055600380549091169184169190911790558015610a71576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b828114610ad35760405162461bcd60e51b815260206004820152602360248201527f526f6f744552433732315072656469636174653a20494e56414c49445f4c454e60448201526208ea8960eb1b6064820152608401610397565b610a718585858585610ea5565b6000610aeb84611061565b604051632142170760e11b81529091506001600160a01b038516906342842e0e90610b1e9033903090879060040161159f565b600060405180830381600087803b158015610b3857600080fd5b505af1158015610b4c573d6000803e3d6000fd5b5050600054600254604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038a811682840152336060830152898116608083015260a08083018a90528351808403909101815260c08301938490526316f1983160e01b90935262010000909404841695506316f198319450610be2939092169160c401611573565b600060405180830381600087803b158015610bfc57600080fd5b505af1158015610c10573d6000803e3d6000fd5b505060408051338152602081018690526001600160a01b03808816945085811693508816917f40e242710be14b7d61d3b93e9d56b40df89cc4dbad8bb88da38c8e0c23809ba7910160405180910390a450505050565b6000808080610c77858701876115c3565b6001600160a01b0380851660009081526004602052604090205494985092965090945092501680610caa57610caa611614565b604051632142170760e11b81526001600160a01b038616906342842e0e90610cda9030908790879060040161159f565b600060405180830381600087803b158015610cf457600080fd5b505af1158015610d08573d6000803e3d6000fd5b5050604080516001600160a01b03888116825260208201879052808816945085811693508916917f8194f168e6e9e449d05a46936e071a710c0f22634eaadeb5b85a86540123931991015b60405180910390a450505050505050565b6000808080610d75858701876116b3565b6001600160a01b03808516600090815260046020526040902054949950929750909550935016905080610daa57610daa611614565b60005b8251811015610e5557856001600160a01b03166342842e0e30868481518110610dd857610dd86117a5565b6020026020010151868581518110610df257610df26117a5565b60200260200101516040518463ffffffff1660e01b8152600401610e189392919061159f565b600060405180830381600087803b158015610e3257600080fd5b505af1158015610e46573d6000803e3d6000fd5b50505050806001019050610dad565b50836001600160a01b0316816001600160a01b0316866001600160a01b03167f1b7b9713fdef5dfadf92f6d6e998eb1fc4763cee62a194a095d76349f7dce20c8686604051610d539291906117bb565b6000610eb086611061565b905060005b82811015610f4257866001600160a01b03166342842e0e3330878786818110610ee057610ee06117a5565b905060200201356040518463ffffffff1660e01b8152600401610f059392919061159f565b600060405180830381600087803b158015610f1f57600080fd5b505af1158015610f33573d6000803e3d6000fd5b50505050806001019050610eb5565b506000546002546040516001600160a01b03620100009093048316926316f19831921690610fa0907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908b9033908c908c908c908c906020016118ba565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610fcc929190611573565b600060405180830381600087803b158015610fe657600080fd5b505af1158015610ffa573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316876001600160a01b03167f02c575a943105cda454293b7ac636c87c94ff1bfcfac968aa5423e6b9b312ee988888888604051611051949392919061190e565b60405180910390a4505050505050565b6001600160a01b03808216600090815260046020526040902054168061108d5761108a8261050a565b90505b6001600160a01b0381166110a3576110a3611614565b919050565b6001600160a01b03811681146110bd57600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156110fe576110fe6110c0565b604052919050565b60006001600160401b0382111561111f5761111f6110c0565b50601f01601f191660200190565b6000806000806080858703121561114357600080fd5b843561114e816110a8565b9350602085013561115e816110a8565b92506040850135915060608501356001600160401b0381111561118057600080fd5b8501601f8101871361119157600080fd5b80356111a461119f82611106565b6110d6565b8181528860208385010111156111b957600080fd5b8160208401602083013760006020838301015280935050505092959194509250565b600080604083850312156111ee57600080fd5b82356111f9816110a8565b946020939093013593505050565b60006020828403121561121957600080fd5b8135611224816110a8565b9392505050565b60008060006060848603121561124057600080fd5b833561124b816110a8565b9250602084013561125b816110a8565b929592945050506040919091013590565b6000806000806060858703121561128257600080fd5b843593506020850135611294816110a8565b925060408501356001600160401b03808211156112b057600080fd5b818701915087601f8301126112c457600080fd5b8135818111156112d357600080fd5b8860208285010111156112e557600080fd5b95989497505060200194505050565b6000806000806080858703121561130a57600080fd5b8435611315816110a8565b93506020850135611325816110a8565b92506040850135611335816110a8565b91506060850135611345816110a8565b939692955090935050565b60008083601f84011261136257600080fd5b5081356001600160401b0381111561137957600080fd5b6020830191508360208260051b850101111561139457600080fd5b9250929050565b6000806000806000606086880312156113b357600080fd5b85356113be816110a8565b945060208601356001600160401b03808211156113da57600080fd5b6113e689838a01611350565b909650945060408801359150808211156113ff57600080fd5b5061140c88828901611350565b969995985093965092949392505050565b6000808585111561142d57600080fd5b8386111561143a57600080fd5b5050820193919092039150565b8035602083101561146057600019602084900360031b1b165b92915050565b60005b83811015611481578181015183820152602001611469565b50506000910152565b60006020828403121561149c57600080fd5b81516001600160401b038111156114b257600080fd5b8201601f810184136114c357600080fd5b80516114d161119f82611106565b8181528560208385010111156114e657600080fd5b6114f7826020830160208601611466565b95945050505050565b60008151808452611518816020860160208601611466565b601f01601f19169290920160200192915050565b8481526001600160a01b038416602082015260806040820181905260009061155690830185611500565b82810360608401526115688185611500565b979650505050505050565b6001600160a01b038316815260406020820181905260009061159790830184611500565b949350505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600080600080608085870312156115d957600080fd5b84356115e4816110a8565b935060208501356115f4816110a8565b92506040850135611604816110a8565b9396929550929360600135925050565b634e487b7160e01b600052600160045260246000fd5b60006001600160401b03821115611643576116436110c0565b5060051b60200190565b600082601f83011261165e57600080fd5b8135602061166e61119f8361162a565b82815260059290921b8401810191818101908684111561168d57600080fd5b8286015b848110156116a85780358352918301918301611691565b509695505050505050565b600080600080600060a086880312156116cb57600080fd5b853594506020808701356116de816110a8565b945060408701356116ee816110a8565b935060608701356001600160401b038082111561170a57600080fd5b818901915089601f83011261171e57600080fd5b813561172c61119f8261162a565b81815260059190911b8301840190848101908c83111561174b57600080fd5b938501935b82851015611772578435611763816110a8565b82529385019390850190611750565b96505050608089013592508083111561178a57600080fd5b50506117988882890161164d565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b604080825283519082018190526000906020906060840190828701845b828110156117fd5781516001600160a01b0316845292840192908401906001016117d8565b5050508381038285015284518082528583019183019060005b8181101561183257835183529284019291840191600101611816565b5090979650505050505050565b8183526000602080850194508260005b8581101561187d578135611862816110a8565b6001600160a01b03168752958201959082019060010161184f565b509495945050505050565b81835260006001600160fb1b038311156118a157600080fd5b8260051b80836020870137939093016020019392505050565b8781526001600160a01b0387811660208301528616604082015260a0606082018190526000906118ed908301868861183f565b8281036080840152611900818587611888565b9a9950505050505050505050565b60408152600061192260408301868861183f565b828103602084015261156881858761188856fea26469706673582212203621718a3c2b1847a958c6d88f320b8bf9fde1113c6a705bddbeec4f2127ab7364736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildMintableERC721PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildMintableERC721Predicate","sourceName":"contracts/root/ChildMintableERC721Predicate.sol","abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"MintableERC721Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"MintableERC721DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"MintableERC721Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"MintableERC721WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"MintableTokenMapped","type":"event"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exitHelper","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newStateSender","type":"address"},{"internalType":"address","name":"newExitHelper","type":"address"},{"internalType":"address","name":"newRootERC721Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onL2StateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootERC721Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IChildERC721","name":"childToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC721","name":"childToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC721","name":"childToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611f01806100206000396000f3fe608060405234801561001057600080fd5b50600436106100d55760003560e01c8063cb10f94c11610087578063cb10f94c146101ca578063d41f1771146101e3578063d7c9e3ec1461020a578063f3fef3a314610231578063f43cda8b14610244578063f645125514610257578063f691325c1461027e578063f8c8765e1461029157600080fd5b80636f33e695146100da5780637efab4f5146100ef57806395c7041c14610135578063b176806514610148578063b68ad1e41461017d578063c3b35a7e14610190578063c5ac2b1c146101a3575b600080fd5b6100ed6100e836600461158c565b6102a4565b005b6101186100fd36600461160e565b6004602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b600154610118906001600160a01b031681565b61016f7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60405190815260200161012c565b600354610118906001600160a01b031681565b6100ed61019e366004611632565b6102b8565b61016f7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b600054610118906201000090046001600160a01b031681565b61016f7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b61016f7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b6100ed61023f366004611673565b6102c8565b6100ed61025236600461169f565b6102d7565b61016f7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b600254610118906001600160a01b031681565b6100ed61029f366004611727565b61050f565b6102b18585858585610627565b5050505050565b6102c383838361099a565b505050565b6102d382338361099a565b5050565b6001546001600160a01b0316331461034d5760405162461bcd60e51b815260206004820152602e60248201527f4368696c644d696e7461626c654552433732315072656469636174653a204f4e60448201526d262cafa2ac24aa2fa422a62822a960911b60648201526084015b60405180910390fd5b6002546001600160a01b038481169116146103c45760405162461bcd60e51b815260206004820152603160248201527f4368696c644d696e7461626c654552433732315072656469636174653a204f4e6044820152704c595f524f4f545f50524544494341544560781b6064820152608401610344565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216103f3602060008486611783565b6103fc916117ad565b0361041b576104166104118260208186611783565b610ca9565b610509565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1861044a602060008486611783565b610453916117ad565b03610462576104168282610f1f565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad610491602060008486611783565b61049a916117ad565b036104a9576104168282611185565b60405162461bcd60e51b815260206004820152602f60248201527f4368696c644d696e7461626c654552433732315072656469636174653a20494e60448201526e56414c49445f5349474e415455524560881b6064820152608401610344565b50505050565b600054610100900460ff161580801561052f5750600054600160ff909116105b806105495750303b158015610549575060005460ff166001145b6105ac5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610344565b6000805460ff1916600117905580156105cf576000805461ff0019166101001790555b6105db858585856112f5565b80156102b1576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050505050565b84610631816113fd565b61064d5760405162461bcd60e51b8152600401610344906117cb565b6000866001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561068d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106b19190611815565b6001600160a01b038181166000908152600460205260409020549192508881169116146106f05760405162461bcd60e51b815260040161034490611832565b6001600160a01b0381166107065761070661187e565b306001600160a01b0316876001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561074e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107729190611815565b6001600160a01b0316146107885761078861187e565b8483146107ec5760405162461bcd60e51b815260206004820152602c60248201527f4368696c644d696e7461626c654552433732315072656469636174653a20494e60448201526b0ac82989288be988a9c8ea8960a31b6064820152608401610344565b60405163b2dc5dc360e01b81526001600160a01b0388169063b2dc5dc39061081c903390889088906004016118c6565b6020604051808303816000875af115801561083b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061085f91906118f4565b61087b5760405162461bcd60e51b815260040161034490611916565b6000546002546040516001600160a01b03620100009093048316926316f198319216906108d8907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908d908d908d908d906020016119a8565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610904929190611a42565b600060405180830381600087803b15801561091e57600080fd5b505af1158015610932573d6000803e3d6000fd5b50505050336001600160a01b0316876001600160a01b0316826001600160a01b03167f5faed28924bb6c5b0d3eea1cc028b4814f49c39eac622b81ef17035d794ac774898989896040516109899493929190611a6e565b60405180910390a450505050505050565b826109a4816113fd565b6109c05760405162461bcd60e51b8152600401610344906117cb565b6000846001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a00573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a249190611815565b6001600160a01b03818116600090815260046020526040902054919250868116911614610a635760405162461bcd60e51b815260040161034490611832565b6001600160a01b038116610a7957610a7961187e565b306001600160a01b0316856001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ac1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ae59190611815565b6001600160a01b031614610afb57610afb61187e565b604051632770a7eb60e21b81526001600160a01b03861690639dc29fac90610b299033908790600401611aa0565b6020604051808303816000875af1158015610b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b6c91906118f4565b610b885760405162461bcd60e51b815260040161034490611916565b600054600254604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152888116608083015260a08083018990528351808403909101815260c08301938490526316f1983160e01b909352620100009094048416936316f1983193610c199391169160c401611a42565b600060405180830381600087803b158015610c3357600080fd5b505af1158015610c47573d6000803e3d6000fd5b50505050836001600160a01b0316856001600160a01b0316826001600160a01b03167f85b3d9b3b871daf1d10c7ba1016854e371ab931adef5569172f0289515a9a6de3387604051610c9a929190611aa0565b60405180910390a45050505050565b6000808080610cba85870187611ab9565b6001600160a01b0380851660009081526004602052604090205494985092965090945092501680610cfd5760405162461bcd60e51b815260040161034490611832565b610d06816113fd565b610d1257610d1261187e565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d769190611815565b9050856001600160a01b0316816001600160a01b031614610d9957610d9961187e565b6001600160a01b038116610daf57610daf61187e565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610df7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e1b9190611815565b6001600160a01b031614610e3157610e3161187e565b6040516340c10f1960e01b81526001600160a01b038316906340c10f1990610e5f9087908790600401611aa0565b6020604051808303816000875af1158015610e7e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ea291906118f4565b610ebe5760405162461bcd60e51b815260040161034490611b0a565b836001600160a01b0316826001600160a01b0316876001600160a01b03167fb26f02b79335e4b2c506d9e5ae02e042b464a7556419bbc42522fc92833aff528887604051610f0d929190611aa0565b60405180910390a45050505050505050565b6000808080610f3085870187611c27565b6001600160a01b03808516600090815260046020526040902054949950929750909550935016905080610f755760405162461bcd60e51b815260040161034490611832565b610f7e816113fd565b610f8a57610f8a61187e565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fee9190611815565b9050856001600160a01b0316816001600160a01b0316146110115761101161187e565b6001600160a01b0381166110275761102761187e565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561106f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110939190611815565b6001600160a01b0316146110a9576110a961187e565b604051637c88e3d960e01b81526001600160a01b03831690637c88e3d9906110d79087908790600401611d19565b6020604051808303816000875af11580156110f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061111a91906118f4565b6111365760405162461bcd60e51b815260040161034490611b0a565b846001600160a01b0316826001600160a01b0316876001600160a01b03167f7f51e520a281df2deae2754a9232b5a78c8cfeae79568668a0a97a116e1401b88787604051610f0d929190611d19565b6000808061119584860186611e0c565b91955093509150506001600160a01b0383166111b3576111b361187e565b6001600160a01b0383811660009081526004602052604090205416156111db576111db61187e565b6003546040516bffffffffffffffffffffffff19606086901b16602082015260009161122b916001600160a01b039091169060340160405160208183030381529060405280519060200120611492565b6001600160a01b0385811660009081526004602081905260409182902080546001600160a01b03191693851693841790559051639065714760e01b815292935090916390657147916112839188918891889101611e8b565b600060405180830381600087803b15801561129d57600080fd5b505af11580156112b1573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f0a1eaf9aa124c3f84c9dd77f7016af0f16f67639abb913af1697387db01f5ca590600090a3505050505050565b6001600160a01b0384161580159061131557506001600160a01b03831615155b801561132957506001600160a01b03821615155b801561133d57506001600160a01b03811615155b6113a25760405162461bcd60e51b815260206004820152603060248201527f4368696c644d696e7461626c654552433732315072656469636174653a20424160448201526f222fa4a724aa24a0a624ad20aa24a7a760811b6064820152608401610344565b600080546001600160a01b03958616620100000262010000600160b01b0319909116179055600180549385166001600160a01b0319948516179055600280549285169284169290921790915560038054919093169116179055565b6000816001600160a01b03163b60000361141957506000919050565b6040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611480575060408051601f3d908101601f1916820190925261147d918101906118f4565b60015b61148c57506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b03811661148c5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b6044820152606401610344565b6001600160a01b038116811461153e57600080fd5b50565b60008083601f84011261155357600080fd5b5081356001600160401b0381111561156a57600080fd5b6020830191508360208260051b850101111561158557600080fd5b9250929050565b6000806000806000606086880312156115a457600080fd5b85356115af81611529565b945060208601356001600160401b03808211156115cb57600080fd5b6115d789838a01611541565b909650945060408801359150808211156115f057600080fd5b506115fd88828901611541565b969995985093965092949392505050565b60006020828403121561162057600080fd5b813561162b81611529565b9392505050565b60008060006060848603121561164757600080fd5b833561165281611529565b9250602084013561166281611529565b929592945050506040919091013590565b6000806040838503121561168657600080fd5b823561169181611529565b946020939093013593505050565b600080600080606085870312156116b557600080fd5b8435935060208501356116c781611529565b925060408501356001600160401b03808211156116e357600080fd5b818701915087601f8301126116f757600080fd5b81358181111561170657600080fd5b88602082850101111561171857600080fd5b95989497505060200194505050565b6000806000806080858703121561173d57600080fd5b843561174881611529565b9350602085013561175881611529565b9250604085013561176881611529565b9150606085013561177881611529565b939692955090935050565b6000808585111561179357600080fd5b838611156117a057600080fd5b5050820193919092039150565b8035602083101561148c57600019602084900360031b1b1692915050565b6020808252602a908201527f4368696c644d696e7461626c654552433732315072656469636174653a204e4f6040820152691517d0d3d395149050d560b21b606082015260800190565b60006020828403121561182757600080fd5b815161162b81611529565b6020808252602c908201527f4368696c644d696e7461626c654552433732315072656469636174653a20554e60408201526b26a0a82822a22faa27a5a2a760a11b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b038311156118ad57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b03841681526040602082018190526000906118eb9083018486611894565b95945050505050565b60006020828403121561190657600080fd5b8151801515811461162b57600080fd5b60208082526029908201527f4368696c644d696e7461626c654552433732315072656469636174653a204255604082015268149397d1905253115160ba1b606082015260800190565b8183526000602080850194508260005b8581101561199d57813561198281611529565b6001600160a01b03168752958201959082019060010161196f565b509495945050505050565b8781526001600160a01b0387811660208301528616604082015260a0606082018190526000906119db908301868861195f565b82810360808401526119ee818587611894565b9a9950505050505050505050565b6000815180845260005b81811015611a2257602081850181015186830182015201611a06565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b0383168152604060208201819052600090611a66908301846119fc565b949350505050565b604081526000611a8260408301868861195f565b8281036020840152611a95818587611894565b979650505050505050565b6001600160a01b03929092168252602082015260400190565b60008060008060808587031215611acf57600080fd5b8435611ada81611529565b93506020850135611aea81611529565b92506040850135611afa81611529565b9396929550929360600135925050565b60208082526029908201527f4368696c644d696e7461626c654552433732315072656469636174653a204d49604082015268139517d1905253115160ba1b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611b9157611b91611b53565b604052919050565b60006001600160401b03821115611bb257611bb2611b53565b5060051b60200190565b600082601f830112611bcd57600080fd5b81356020611be2611bdd83611b99565b611b69565b82815260059290921b84018101918181019086841115611c0157600080fd5b8286015b84811015611c1c5780358352918301918301611c05565b509695505050505050565b600080600080600060a08688031215611c3f57600080fd5b85359450602080870135611c5281611529565b94506040870135611c6281611529565b935060608701356001600160401b0380821115611c7e57600080fd5b818901915089601f830112611c9257600080fd5b8135611ca0611bdd82611b99565b81815260059190911b8301840190848101908c831115611cbf57600080fd5b938501935b82851015611ce6578435611cd781611529565b82529385019390850190611cc4565b965050506080890135925080831115611cfe57600080fd5b5050611d0c88828901611bbc565b9150509295509295909350565b604080825283519082018190526000906020906060840190828701845b82811015611d5b5781516001600160a01b031684529284019290840190600101611d36565b5050508381038285015284518082528583019183019060005b81811015611d9057835183529284019291840191600101611d74565b5090979650505050505050565b600082601f830112611dae57600080fd5b81356001600160401b03811115611dc757611dc7611b53565b611dda601f8201601f1916602001611b69565b818152846020838601011115611def57600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060808587031215611e2257600080fd5b843593506020850135611e3481611529565b925060408501356001600160401b0380821115611e5057600080fd5b611e5c88838901611d9d565b93506060870135915080821115611e7257600080fd5b50611e7f87828801611d9d565b91505092959194509250565b6001600160a01b0384168152606060208201819052600090611eaf908301856119fc565b8281036040840152611ec181856119fc565b969550505050505056fea264697066735822122033e4a7fc99de8d907b46e0e352c2feaea0b43d47f4a2303e6d491ab60c6afeed64736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100d55760003560e01c8063cb10f94c11610087578063cb10f94c146101ca578063d41f1771146101e3578063d7c9e3ec1461020a578063f3fef3a314610231578063f43cda8b14610244578063f645125514610257578063f691325c1461027e578063f8c8765e1461029157600080fd5b80636f33e695146100da5780637efab4f5146100ef57806395c7041c14610135578063b176806514610148578063b68ad1e41461017d578063c3b35a7e14610190578063c5ac2b1c146101a3575b600080fd5b6100ed6100e836600461158c565b6102a4565b005b6101186100fd36600461160e565b6004602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b600154610118906001600160a01b031681565b61016f7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b60405190815260200161012c565b600354610118906001600160a01b031681565b6100ed61019e366004611632565b6102b8565b61016f7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b600054610118906201000090046001600160a01b031681565b61016f7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b61016f7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b6100ed61023f366004611673565b6102c8565b6100ed61025236600461169f565b6102d7565b61016f7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b600254610118906001600160a01b031681565b6100ed61029f366004611727565b61050f565b6102b18585858585610627565b5050505050565b6102c383838361099a565b505050565b6102d382338361099a565b5050565b6001546001600160a01b0316331461034d5760405162461bcd60e51b815260206004820152602e60248201527f4368696c644d696e7461626c654552433732315072656469636174653a204f4e60448201526d262cafa2ac24aa2fa422a62822a960911b60648201526084015b60405180910390fd5b6002546001600160a01b038481169116146103c45760405162461bcd60e51b815260206004820152603160248201527f4368696c644d696e7461626c654552433732315072656469636174653a204f4e6044820152704c595f524f4f545f50524544494341544560781b6064820152608401610344565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216103f3602060008486611783565b6103fc916117ad565b0361041b576104166104118260208186611783565b610ca9565b610509565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1861044a602060008486611783565b610453916117ad565b03610462576104168282610f1f565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad610491602060008486611783565b61049a916117ad565b036104a9576104168282611185565b60405162461bcd60e51b815260206004820152602f60248201527f4368696c644d696e7461626c654552433732315072656469636174653a20494e60448201526e56414c49445f5349474e415455524560881b6064820152608401610344565b50505050565b600054610100900460ff161580801561052f5750600054600160ff909116105b806105495750303b158015610549575060005460ff166001145b6105ac5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610344565b6000805460ff1916600117905580156105cf576000805461ff0019166101001790555b6105db858585856112f5565b80156102b1576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050505050565b84610631816113fd565b61064d5760405162461bcd60e51b8152600401610344906117cb565b6000866001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa15801561068d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106b19190611815565b6001600160a01b038181166000908152600460205260409020549192508881169116146106f05760405162461bcd60e51b815260040161034490611832565b6001600160a01b0381166107065761070661187e565b306001600160a01b0316876001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561074e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107729190611815565b6001600160a01b0316146107885761078861187e565b8483146107ec5760405162461bcd60e51b815260206004820152602c60248201527f4368696c644d696e7461626c654552433732315072656469636174653a20494e60448201526b0ac82989288be988a9c8ea8960a31b6064820152608401610344565b60405163b2dc5dc360e01b81526001600160a01b0388169063b2dc5dc39061081c903390889088906004016118c6565b6020604051808303816000875af115801561083b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061085f91906118f4565b61087b5760405162461bcd60e51b815260040161034490611916565b6000546002546040516001600160a01b03620100009093048316926316f198319216906108d8907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908d908d908d908d906020016119a8565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610904929190611a42565b600060405180830381600087803b15801561091e57600080fd5b505af1158015610932573d6000803e3d6000fd5b50505050336001600160a01b0316876001600160a01b0316826001600160a01b03167f5faed28924bb6c5b0d3eea1cc028b4814f49c39eac622b81ef17035d794ac774898989896040516109899493929190611a6e565b60405180910390a450505050505050565b826109a4816113fd565b6109c05760405162461bcd60e51b8152600401610344906117cb565b6000846001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a00573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a249190611815565b6001600160a01b03818116600090815260046020526040902054919250868116911614610a635760405162461bcd60e51b815260040161034490611832565b6001600160a01b038116610a7957610a7961187e565b306001600160a01b0316856001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ac1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ae59190611815565b6001600160a01b031614610afb57610afb61187e565b604051632770a7eb60e21b81526001600160a01b03861690639dc29fac90610b299033908790600401611aa0565b6020604051808303816000875af1158015610b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b6c91906118f4565b610b885760405162461bcd60e51b815260040161034490611916565b600054600254604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152888116608083015260a08083018990528351808403909101815260c08301938490526316f1983160e01b909352620100009094048416936316f1983193610c199391169160c401611a42565b600060405180830381600087803b158015610c3357600080fd5b505af1158015610c47573d6000803e3d6000fd5b50505050836001600160a01b0316856001600160a01b0316826001600160a01b03167f85b3d9b3b871daf1d10c7ba1016854e371ab931adef5569172f0289515a9a6de3387604051610c9a929190611aa0565b60405180910390a45050505050565b6000808080610cba85870187611ab9565b6001600160a01b0380851660009081526004602052604090205494985092965090945092501680610cfd5760405162461bcd60e51b815260040161034490611832565b610d06816113fd565b610d1257610d1261187e565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d769190611815565b9050856001600160a01b0316816001600160a01b031614610d9957610d9961187e565b6001600160a01b038116610daf57610daf61187e565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610df7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e1b9190611815565b6001600160a01b031614610e3157610e3161187e565b6040516340c10f1960e01b81526001600160a01b038316906340c10f1990610e5f9087908790600401611aa0565b6020604051808303816000875af1158015610e7e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ea291906118f4565b610ebe5760405162461bcd60e51b815260040161034490611b0a565b836001600160a01b0316826001600160a01b0316876001600160a01b03167fb26f02b79335e4b2c506d9e5ae02e042b464a7556419bbc42522fc92833aff528887604051610f0d929190611aa0565b60405180910390a45050505050505050565b6000808080610f3085870187611c27565b6001600160a01b03808516600090815260046020526040902054949950929750909550935016905080610f755760405162461bcd60e51b815260040161034490611832565b610f7e816113fd565b610f8a57610f8a61187e565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fee9190611815565b9050856001600160a01b0316816001600160a01b0316146110115761101161187e565b6001600160a01b0381166110275761102761187e565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa15801561106f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110939190611815565b6001600160a01b0316146110a9576110a961187e565b604051637c88e3d960e01b81526001600160a01b03831690637c88e3d9906110d79087908790600401611d19565b6020604051808303816000875af11580156110f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061111a91906118f4565b6111365760405162461bcd60e51b815260040161034490611b0a565b846001600160a01b0316826001600160a01b0316876001600160a01b03167f7f51e520a281df2deae2754a9232b5a78c8cfeae79568668a0a97a116e1401b88787604051610f0d929190611d19565b6000808061119584860186611e0c565b91955093509150506001600160a01b0383166111b3576111b361187e565b6001600160a01b0383811660009081526004602052604090205416156111db576111db61187e565b6003546040516bffffffffffffffffffffffff19606086901b16602082015260009161122b916001600160a01b039091169060340160405160208183030381529060405280519060200120611492565b6001600160a01b0385811660009081526004602081905260409182902080546001600160a01b03191693851693841790559051639065714760e01b815292935090916390657147916112839188918891889101611e8b565b600060405180830381600087803b15801561129d57600080fd5b505af11580156112b1573d6000803e3d6000fd5b50506040516001600160a01b038085169350871691507f0a1eaf9aa124c3f84c9dd77f7016af0f16f67639abb913af1697387db01f5ca590600090a3505050505050565b6001600160a01b0384161580159061131557506001600160a01b03831615155b801561132957506001600160a01b03821615155b801561133d57506001600160a01b03811615155b6113a25760405162461bcd60e51b815260206004820152603060248201527f4368696c644d696e7461626c654552433732315072656469636174653a20424160448201526f222fa4a724aa24a0a624ad20aa24a7a760811b6064820152608401610344565b600080546001600160a01b03958616620100000262010000600160b01b0319909116179055600180549385166001600160a01b0319948516179055600280549285169284169290921790915560038054919093169116179055565b6000816001600160a01b03163b60000361141957506000919050565b6040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa925050508015611480575060408051601f3d908101601f1916820190925261147d918101906118f4565b60015b61148c57506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b03811661148c5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b6044820152606401610344565b6001600160a01b038116811461153e57600080fd5b50565b60008083601f84011261155357600080fd5b5081356001600160401b0381111561156a57600080fd5b6020830191508360208260051b850101111561158557600080fd5b9250929050565b6000806000806000606086880312156115a457600080fd5b85356115af81611529565b945060208601356001600160401b03808211156115cb57600080fd5b6115d789838a01611541565b909650945060408801359150808211156115f057600080fd5b506115fd88828901611541565b969995985093965092949392505050565b60006020828403121561162057600080fd5b813561162b81611529565b9392505050565b60008060006060848603121561164757600080fd5b833561165281611529565b9250602084013561166281611529565b929592945050506040919091013590565b6000806040838503121561168657600080fd5b823561169181611529565b946020939093013593505050565b600080600080606085870312156116b557600080fd5b8435935060208501356116c781611529565b925060408501356001600160401b03808211156116e357600080fd5b818701915087601f8301126116f757600080fd5b81358181111561170657600080fd5b88602082850101111561171857600080fd5b95989497505060200194505050565b6000806000806080858703121561173d57600080fd5b843561174881611529565b9350602085013561175881611529565b9250604085013561176881611529565b9150606085013561177881611529565b939692955090935050565b6000808585111561179357600080fd5b838611156117a057600080fd5b5050820193919092039150565b8035602083101561148c57600019602084900360031b1b1692915050565b6020808252602a908201527f4368696c644d696e7461626c654552433732315072656469636174653a204e4f6040820152691517d0d3d395149050d560b21b606082015260800190565b60006020828403121561182757600080fd5b815161162b81611529565b6020808252602c908201527f4368696c644d696e7461626c654552433732315072656469636174653a20554e60408201526b26a0a82822a22faa27a5a2a760a11b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b038311156118ad57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b03841681526040602082018190526000906118eb9083018486611894565b95945050505050565b60006020828403121561190657600080fd5b8151801515811461162b57600080fd5b60208082526029908201527f4368696c644d696e7461626c654552433732315072656469636174653a204255604082015268149397d1905253115160ba1b606082015260800190565b8183526000602080850194508260005b8581101561199d57813561198281611529565b6001600160a01b03168752958201959082019060010161196f565b509495945050505050565b8781526001600160a01b0387811660208301528616604082015260a0606082018190526000906119db908301868861195f565b82810360808401526119ee818587611894565b9a9950505050505050505050565b6000815180845260005b81811015611a2257602081850181015186830182015201611a06565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b0383168152604060208201819052600090611a66908301846119fc565b949350505050565b604081526000611a8260408301868861195f565b8281036020840152611a95818587611894565b979650505050505050565b6001600160a01b03929092168252602082015260400190565b60008060008060808587031215611acf57600080fd5b8435611ada81611529565b93506020850135611aea81611529565b92506040850135611afa81611529565b9396929550929360600135925050565b60208082526029908201527f4368696c644d696e7461626c654552433732315072656469636174653a204d49604082015268139517d1905253115160ba1b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611b9157611b91611b53565b604052919050565b60006001600160401b03821115611bb257611bb2611b53565b5060051b60200190565b600082601f830112611bcd57600080fd5b81356020611be2611bdd83611b99565b611b69565b82815260059290921b84018101918181019086841115611c0157600080fd5b8286015b84811015611c1c5780358352918301918301611c05565b509695505050505050565b600080600080600060a08688031215611c3f57600080fd5b85359450602080870135611c5281611529565b94506040870135611c6281611529565b935060608701356001600160401b0380821115611c7e57600080fd5b818901915089601f830112611c9257600080fd5b8135611ca0611bdd82611b99565b81815260059190911b8301840190848101908c831115611cbf57600080fd5b938501935b82851015611ce6578435611cd781611529565b82529385019390850190611cc4565b965050506080890135925080831115611cfe57600080fd5b5050611d0c88828901611bbc565b9150509295509295909350565b604080825283519082018190526000906020906060840190828701845b82811015611d5b5781516001600160a01b031684529284019290840190600101611d36565b5050508381038285015284518082528583019183019060005b81811015611d9057835183529284019291840191600101611d74565b5090979650505050505050565b600082601f830112611dae57600080fd5b81356001600160401b03811115611dc757611dc7611b53565b611dda601f8201601f1916602001611b69565b818152846020838601011115611def57600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060808587031215611e2257600080fd5b843593506020850135611e3481611529565b925060408501356001600160401b0380821115611e5057600080fd5b611e5c88838901611d9d565b93506060870135915080821115611e7257600080fd5b50611e7f87828801611d9d565b91505092959194509250565b6001600160a01b0384168152606060208201819052600090611eaf908301856119fc565b8281036040840152611ec181856119fc565b969550505050505056fea264697066735822122033e4a7fc99de8d907b46e0e352c2feaea0b43d47f4a2303e6d491ab60c6afeed64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var MockERC1155Artifact string = `{"_format":"hh-sol-artifact-1","contractName":"MockERC1155","sourceName":"contracts/mocks/MockERC1155.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"TransferSingle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"value","type":"string"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"URI","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"accounts","type":"address[]"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"burnBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mintBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeBatchTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"uri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}],"bytecode":"0x60806040523480156200001157600080fd5b506040805180820190915260058152646c6f72656d60d81b6020820152806200003a81620000b1565b506005805460ff1916905562000052600033620000c3565b6200007e7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633620000c3565b620000aa7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a33620000c3565b5062000376565b6004620000bf8282620002aa565b5050565b620000bf8282620000d58282620000f4565b6000828152600160205260409020620000ef908262000194565b505050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff16620000bf576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055620001503390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000620001ab836001600160a01b038416620001b4565b90505b92915050565b6000818152600183016020526040812054620001fd57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155620001ae565b506000620001ae565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806200023157607f821691505b6020821081036200025257634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620000ef57600081815260208120601f850160051c81016020861015620002815750805b601f850160051c820191505b81811015620002a2578281556001016200028d565b505050505050565b81516001600160401b03811115620002c657620002c662000206565b620002de81620002d784546200021c565b8462000258565b602080601f831160018114620003165760008415620002fd5750858301515b600019600386901b1c1916600185901b178555620002a2565b600085815260208120601f198616915b82811015620003475788860151825594840194600190910190840162000326565b5085821015620003665787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6128a080620003866000396000f3fe608060405234801561001057600080fd5b50600436106101625760003560e01c8063731133e9116100ce578063ca15c87311610087578063ca15c873146102fb578063d53913931461030e578063d547741f14610323578063e63ab1e914610336578063e985e9c51461034b578063f242432a14610387578063f5298aca1461039a57600080fd5b8063731133e9146102875780638456cb591461029a5780639010d07c146102a257806391d14854146102cd578063a217fddf146102e0578063a22cb465146102e857600080fd5b80632f2ff15d116101205780632f2ff15d1461021b57806336568abe1461022e5780633f4ba83a146102415780634e1273f4146102495780635c975abb146102695780636b20c4541461027457600080fd5b8062fdd58e1461016757806301ffc9a71461018d5780630e89341c146101b05780631f7fdffa146101d0578063248a9ca3146101e55780632eb2c2d614610208575b600080fd5b61017a610175366004611bd7565b6103ad565b6040519081526020015b60405180910390f35b6101a061019b366004611c17565b610448565b6040519015158152602001610184565b6101c36101be366004611c34565b610453565b6040516101849190611c9d565b6101e36101de366004611df9565b6104e7565b005b61017a6101f3366004611c34565b60009081526020819052604090206001015490565b6101e3610216366004611e91565b61052d565b6101e3610229366004611f3a565b610579565b6101e361023c366004611f3a565b6105a3565b6101e3610621565b61025c610257366004611f66565b6106a3565b604051610184919061206b565b60055460ff166101a0565b6101e361028236600461207e565b6107cc565b6101e36102953660046120f1565b61080f565b6101e361084f565b6102b56102b0366004612145565b6108cb565b6040516001600160a01b039091168152602001610184565b6101a06102db366004611f3a565b6108ea565b61017a600081565b6101e36102f6366004612167565b610913565b61017a610309366004611c34565b61091e565b61017a60008051602061284b83398151915281565b6101e3610331366004611f3a565b610935565b61017a60008051602061282b83398151915281565b6101a06103593660046121a3565b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205460ff1690565b6101e36103953660046121cd565b61095a565b6101e36103a8366004612231565b61099f565b60006001600160a01b03831661041d5760405162461bcd60e51b815260206004820152602a60248201527f455243313135353a2061646472657373207a65726f206973206e6f742061207660448201526930b634b21037bbb732b960b11b60648201526084015b60405180910390fd5b5060008181526002602090815260408083206001600160a01b03861684529091529020545b92915050565b6000610442826109e2565b60606004805461046290612264565b80601f016020809104026020016040519081016040528092919081815260200182805461048e90612264565b80156104db5780601f106104b0576101008083540402835291602001916104db565b820191906000526020600020905b8154815290600101906020018083116104be57829003601f168201915b50505050509050919050565b6104ff60008051602061284b833981519152336108ea565b61051b5760405162461bcd60e51b81526004016104149061229e565b61052784848484610a22565b50505050565b6001600160a01b03851633148061054957506105498533610359565b6105655760405162461bcd60e51b8152600401610414906122e4565b6105728585858585610b6b565b5050505050565b60008281526020819052604090206001015461059481610d06565b61059e8383610d13565b505050565b6001600160a01b03811633146106135760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608401610414565b61061d8282610d35565b5050565b61063960008051602061282b833981519152336108ea565b6106995760405162461bcd60e51b815260206004820152603b60248201526000805160206127eb83398151915260448201527f686176652070617573657220726f6c6520746f20756e706175736500000000006064820152608401610414565b6106a1610d57565b565b606081518351146107085760405162461bcd60e51b815260206004820152602960248201527f455243313135353a206163636f756e747320616e6420696473206c656e677468604482015268040dad2e6dac2e8c6d60bb1b6064820152608401610414565b600083516001600160401b0381111561072357610723611cb0565b60405190808252806020026020018201604052801561074c578160200160208202803683370190505b50905060005b84518110156107c45761079785828151811061077057610770612332565b602002602001015185838151811061078a5761078a612332565b60200260200101516103ad565b8282815181106107a9576107a9612332565b60209081029190910101526107bd8161235e565b9050610752565b509392505050565b6001600160a01b0383163314806107e857506107e88333610359565b6108045760405162461bcd60e51b8152600401610414906122e4565b61059e838383610da9565b61082760008051602061284b833981519152336108ea565b6108435760405162461bcd60e51b81526004016104149061229e565b61052784848484610f36565b61086760008051602061282b833981519152336108ea565b6108c35760405162461bcd60e51b815260206004820152603960248201526000805160206127eb833981519152604482015278686176652070617573657220726f6c6520746f20706175736560381b6064820152608401610414565b6106a161100f565b60008281526001602052604081206108e3908361104c565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b61061d338383611058565b600081815260016020526040812061044290611138565b60008281526020819052604090206001015461095081610d06565b61059e8383610d35565b6001600160a01b03851633148061097657506109768533610359565b6109925760405162461bcd60e51b8152600401610414906122e4565b6105728585858585611142565b6001600160a01b0383163314806109bb57506109bb8333610359565b6109d75760405162461bcd60e51b8152600401610414906122e4565b61059e83838361126c565b60006001600160e01b03198216636cdb3d1360e11b1480610a1357506001600160e01b031982166303a24d0760e21b145b80610442575061044282611376565b6001600160a01b038416610a485760405162461bcd60e51b815260040161041490612377565b8151835114610a695760405162461bcd60e51b8152600401610414906123b8565b33610a798160008787878761139b565b60005b8451811015610b1557838181518110610a9757610a97612332565b602002602001015160026000878481518110610ab557610ab5612332565b602002602001015181526020019081526020016000206000886001600160a01b03166001600160a01b031681526020019081526020016000206000828254610afd9190612400565b90915550819050610b0d8161235e565b915050610a7c565b50846001600160a01b031660006001600160a01b0316826001600160a01b03166000805160206127cb8339815191528787604051610b54929190612413565b60405180910390a4610572816000878787876113a9565b8151835114610b8c5760405162461bcd60e51b8152600401610414906123b8565b6001600160a01b038416610bb25760405162461bcd60e51b815260040161041490612441565b33610bc181878787878761139b565b60005b8451811015610caa576000858281518110610be157610be1612332565b602002602001015190506000858381518110610bff57610bff612332565b60209081029190910181015160008481526002835260408082206001600160a01b038e168352909352919091205490915081811015610c505760405162461bcd60e51b815260040161041490612486565b60008381526002602090815260408083206001600160a01b038e8116855292528083208585039055908b16825281208054849290610c8f908490612400565b9250508190555050505080610ca39061235e565b9050610bc4565b50846001600160a01b0316866001600160a01b0316826001600160a01b03166000805160206127cb8339815191528787604051610ce8929190612413565b60405180910390a4610cfe8187878787876113a9565b505050505050565b610d108133611504565b50565b610d1d828261155d565b600082815260016020526040902061059e90826115e1565b610d3f82826115f6565b600082815260016020526040902061059e908261165b565b610d5f611670565b6005805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6001600160a01b038316610dcf5760405162461bcd60e51b8152600401610414906124d0565b8051825114610df05760405162461bcd60e51b8152600401610414906123b8565b6000339050610e138185600086866040518060200160405280600081525061139b565b60005b8351811015610edb576000848281518110610e3357610e33612332565b602002602001015190506000848381518110610e5157610e51612332565b60209081029190910181015160008481526002835260408082206001600160a01b038c168352909352919091205490915081811015610ea25760405162461bcd60e51b815260040161041490612513565b60009283526002602090815260408085206001600160a01b038b1686529091529092209103905580610ed38161235e565b915050610e16565b5060006001600160a01b0316846001600160a01b0316826001600160a01b03166000805160206127cb8339815191528686604051610f1a929190612413565b60405180910390a4604080516020810190915260009052610527565b6001600160a01b038416610f5c5760405162461bcd60e51b815260040161041490612377565b336000610f68856116b9565b90506000610f75856116b9565b9050610f868360008985858961139b565b60008681526002602090815260408083206001600160a01b038b16845290915281208054879290610fb8908490612400565b909155505060408051878152602081018790526001600160a01b03808a16926000929187169160008051602061280b833981519152910160405180910390a461100683600089898989611704565b50505050505050565b6110176117bf565b6005805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258610d8c3390565b60006108e38383611805565b816001600160a01b0316836001600160a01b0316036110cb5760405162461bcd60e51b815260206004820152602960248201527f455243313135353a2073657474696e6720617070726f76616c20737461747573604482015268103337b91039b2b63360b91b6064820152608401610414565b6001600160a01b03838116600081815260036020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6000610442825490565b6001600160a01b0384166111685760405162461bcd60e51b815260040161041490612441565b336000611174856116b9565b90506000611181856116b9565b905061119183898985858961139b565b60008681526002602090815260408083206001600160a01b038c168452909152902054858110156111d45760405162461bcd60e51b815260040161041490612486565b60008781526002602090815260408083206001600160a01b038d8116855292528083208985039055908a16825281208054889290611213908490612400565b909155505060408051888152602081018890526001600160a01b03808b16928c8216929188169160008051602061280b833981519152910160405180910390a4611261848a8a8a8a8a611704565b505050505050505050565b6001600160a01b0383166112925760405162461bcd60e51b8152600401610414906124d0565b33600061129e846116b9565b905060006112ab846116b9565b90506112cb8387600085856040518060200160405280600081525061139b565b60008581526002602090815260408083206001600160a01b038a1684529091529020548481101561130e5760405162461bcd60e51b815260040161041490612513565b60008681526002602090815260408083206001600160a01b038b81168086529184528285208a8703905582518b81529384018a905290929088169160008051602061280b833981519152910160405180910390a4604080516020810190915260009052611006565b60006001600160e01b03198216635a05180f60e01b148061044257506104428261182f565b610cfe868686868686611864565b6001600160a01b0384163b15610cfe5760405163bc197c8160e01b81526001600160a01b0385169063bc197c81906113ed9089908990889088908890600401612557565b6020604051808303816000875af1925050508015611428575060408051601f3d908101601f19168201909252611425918101906125b5565b60015b6114d4576114346125d2565b806308c379a00361146d57506114486125ee565b80611453575061146f565b8060405162461bcd60e51b81526004016104149190611c9d565b505b60405162461bcd60e51b815260206004820152603460248201527f455243313135353a207472616e7366657220746f206e6f6e2d455243313135356044820152732932b1b2b4bb32b91034b6b83632b6b2b73a32b960611b6064820152608401610414565b6001600160e01b0319811663bc197c8160e01b146110065760405162461bcd60e51b815260040161041490612677565b61150e82826108ea565b61061d5761151b816118cc565b6115268360206118de565b6040516020016115379291906126bf565b60408051601f198184030181529082905262461bcd60e51b825261041491600401611c9d565b61156782826108ea565b61061d576000828152602081815260408083206001600160a01b03851684529091529020805460ff1916600117905561159d3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b60006108e3836001600160a01b038416611a79565b61160082826108ea565b1561061d576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b60006108e3836001600160a01b038416611ac8565b60055460ff166106a15760405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606401610414565b604080516001808252818301909252606091600091906020808301908036833701905050905082816000815181106116f3576116f3612332565b602090810291909101015292915050565b6001600160a01b0384163b15610cfe5760405163f23a6e6160e01b81526001600160a01b0385169063f23a6e6190611748908990899088908890889060040161272e565b6020604051808303816000875af1925050508015611783575060408051601f3d908101601f19168201909252611780918101906125b5565b60015b61178f576114346125d2565b6001600160e01b0319811663f23a6e6160e01b146110065760405162461bcd60e51b815260040161041490612677565b60055460ff16156106a15760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610414565b600082600001828154811061181c5761181c612332565b9060005260206000200154905092915050565b60006001600160e01b03198216637965db0b60e01b148061044257506301ffc9a760e01b6001600160e01b0319831614610442565b60055460ff1615610cfe5760405162461bcd60e51b815260206004820152602c60248201527f455243313135355061757361626c653a20746f6b656e207472616e736665722060448201526b1dda1a5b19481c185d5cd95960a21b6064820152608401610414565b60606104426001600160a01b03831660145b606060006118ed836002612773565b6118f8906002612400565b6001600160401b0381111561190f5761190f611cb0565b6040519080825280601f01601f191660200182016040528015611939576020820181803683370190505b509050600360fc1b8160008151811061195457611954612332565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061198357611983612332565b60200101906001600160f81b031916908160001a90535060006119a7846002612773565b6119b2906001612400565b90505b6001811115611a2a576f181899199a1a9b1b9c1cb0b131b232b360811b85600f16601081106119e6576119e6612332565b1a60f81b8282815181106119fc576119fc612332565b60200101906001600160f81b031916908160001a90535060049490941c93611a238161278a565b90506119b5565b5083156108e35760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610414565b6000818152600183016020526040812054611ac057508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610442565b506000610442565b60008181526001830160205260408120548015611bb1576000611aec6001836127a1565b8554909150600090611b00906001906127a1565b9050818114611b65576000866000018281548110611b2057611b20612332565b9060005260206000200154905080876000018481548110611b4357611b43612332565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611b7657611b766127b4565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610442565b6000915050610442565b80356001600160a01b0381168114611bd257600080fd5b919050565b60008060408385031215611bea57600080fd5b611bf383611bbb565b946020939093013593505050565b6001600160e01b031981168114610d1057600080fd5b600060208284031215611c2957600080fd5b81356108e381611c01565b600060208284031215611c4657600080fd5b5035919050565b60005b83811015611c68578181015183820152602001611c50565b50506000910152565b60008151808452611c89816020860160208601611c4d565b601f01601f19169290920160200192915050565b6020815260006108e36020830184611c71565b634e487b7160e01b600052604160045260246000fd5b601f8201601f191681016001600160401b0381118282101715611ceb57611ceb611cb0565b6040525050565b60006001600160401b03821115611d0b57611d0b611cb0565b5060051b60200190565b600082601f830112611d2657600080fd5b81356020611d3382611cf2565b604051611d408282611cc6565b83815260059390931b8501820192828101915086841115611d6057600080fd5b8286015b84811015611d7b5780358352918301918301611d64565b509695505050505050565b600082601f830112611d9757600080fd5b81356001600160401b03811115611db057611db0611cb0565b604051611dc7601f8301601f191660200182611cc6565b818152846020838601011115611ddc57600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060808587031215611e0f57600080fd5b611e1885611bbb565b935060208501356001600160401b0380821115611e3457600080fd5b611e4088838901611d15565b94506040870135915080821115611e5657600080fd5b611e6288838901611d15565b93506060870135915080821115611e7857600080fd5b50611e8587828801611d86565b91505092959194509250565b600080600080600060a08688031215611ea957600080fd5b611eb286611bbb565b9450611ec060208701611bbb565b935060408601356001600160401b0380821115611edc57600080fd5b611ee889838a01611d15565b94506060880135915080821115611efe57600080fd5b611f0a89838a01611d15565b93506080880135915080821115611f2057600080fd5b50611f2d88828901611d86565b9150509295509295909350565b60008060408385031215611f4d57600080fd5b82359150611f5d60208401611bbb565b90509250929050565b60008060408385031215611f7957600080fd5b82356001600160401b0380821115611f9057600080fd5b818501915085601f830112611fa457600080fd5b81356020611fb182611cf2565b604051611fbe8282611cc6565b83815260059390931b8501820192828101915089841115611fde57600080fd5b948201945b8386101561200357611ff486611bbb565b82529482019490820190611fe3565b9650508601359250508082111561201957600080fd5b5061202685828601611d15565b9150509250929050565b600081518084526020808501945080840160005b8381101561206057815187529582019590820190600101612044565b509495945050505050565b6020815260006108e36020830184612030565b60008060006060848603121561209357600080fd5b61209c84611bbb565b925060208401356001600160401b03808211156120b857600080fd5b6120c487838801611d15565b935060408601359150808211156120da57600080fd5b506120e786828701611d15565b9150509250925092565b6000806000806080858703121561210757600080fd5b61211085611bbb565b9350602085013592506040850135915060608501356001600160401b0381111561213957600080fd5b611e8587828801611d86565b6000806040838503121561215857600080fd5b50508035926020909101359150565b6000806040838503121561217a57600080fd5b61218383611bbb565b91506020830135801515811461219857600080fd5b809150509250929050565b600080604083850312156121b657600080fd5b6121bf83611bbb565b9150611f5d60208401611bbb565b600080600080600060a086880312156121e557600080fd5b6121ee86611bbb565b94506121fc60208701611bbb565b9350604086013592506060860135915060808601356001600160401b0381111561222557600080fd5b611f2d88828901611d86565b60008060006060848603121561224657600080fd5b61224f84611bbb565b95602085013595506040909401359392505050565b600181811c9082168061227857607f821691505b60208210810361229857634e487b7160e01b600052602260045260246000fd5b50919050565b60208082526038908201526000805160206127eb8339815191526040820152771a185d99481b5a5b9d195c881c9bdb19481d1bc81b5a5b9d60421b606082015260800190565b6020808252602e908201527f455243313135353a2063616c6c6572206973206e6f7420746f6b656e206f776e60408201526d195c881bdc88185c1c1c9bdd995960921b606082015260800190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60006001820161237057612370612348565b5060010190565b60208082526021908201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736040820152607360f81b606082015260800190565b60208082526028908201527f455243313135353a2069647320616e6420616d6f756e7473206c656e677468206040820152670dad2e6dac2e8c6d60c31b606082015260800190565b8082018082111561044257610442612348565b6040815260006124266040830185612030565b82810360208401526124388185612030565b95945050505050565b60208082526025908201527f455243313135353a207472616e7366657220746f20746865207a65726f206164604082015264647265737360d81b606082015260800190565b6020808252602a908201527f455243313135353a20696e73756666696369656e742062616c616e636520666f60408201526939103a3930b739b332b960b11b606082015260800190565b60208082526023908201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260408201526265737360e81b606082015260800190565b60208082526024908201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604082015263616e636560e01b606082015260800190565b6001600160a01b0386811682528516602082015260a06040820181905260009061258390830186612030565b82810360608401526125958186612030565b905082810360808401526125a98185611c71565b98975050505050505050565b6000602082840312156125c757600080fd5b81516108e381611c01565b600060033d11156125eb5760046000803e5060005160e01c5b90565b600060443d10156125fc5790565b6040516003193d81016004833e81513d6001600160401b03816024840111818411171561262b57505050505090565b82850191508151818111156126435750505050505090565b843d870101602082850101111561265d5750505050505090565b61266c60208286010187611cc6565b509095945050505050565b60208082526028908201527f455243313135353a204552433131353552656365697665722072656a656374656040820152676420746f6b656e7360c01b606082015260800190565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8152600083516126f1816017850160208801611c4d565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351612722816028840160208801611c4d565b01602801949350505050565b6001600160a01b03868116825285166020820152604081018490526060810183905260a06080820181905260009061276890830184611c71565b979650505050505050565b808202811582820484141761044257610442612348565b60008161279957612799612348565b506000190190565b8181038181111561044257610442612348565b634e487b7160e01b600052603160045260246000fdfe4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb455243313135355072657365744d696e7465725061757365723a206d75737420c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6265d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6a2646970667358221220294efe154e549198ff0c6b7ab7e9846223d9e8fe7ae7de1938f6eb7d0f5e66a664736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101625760003560e01c8063731133e9116100ce578063ca15c87311610087578063ca15c873146102fb578063d53913931461030e578063d547741f14610323578063e63ab1e914610336578063e985e9c51461034b578063f242432a14610387578063f5298aca1461039a57600080fd5b8063731133e9146102875780638456cb591461029a5780639010d07c146102a257806391d14854146102cd578063a217fddf146102e0578063a22cb465146102e857600080fd5b80632f2ff15d116101205780632f2ff15d1461021b57806336568abe1461022e5780633f4ba83a146102415780634e1273f4146102495780635c975abb146102695780636b20c4541461027457600080fd5b8062fdd58e1461016757806301ffc9a71461018d5780630e89341c146101b05780631f7fdffa146101d0578063248a9ca3146101e55780632eb2c2d614610208575b600080fd5b61017a610175366004611bd7565b6103ad565b6040519081526020015b60405180910390f35b6101a061019b366004611c17565b610448565b6040519015158152602001610184565b6101c36101be366004611c34565b610453565b6040516101849190611c9d565b6101e36101de366004611df9565b6104e7565b005b61017a6101f3366004611c34565b60009081526020819052604090206001015490565b6101e3610216366004611e91565b61052d565b6101e3610229366004611f3a565b610579565b6101e361023c366004611f3a565b6105a3565b6101e3610621565b61025c610257366004611f66565b6106a3565b604051610184919061206b565b60055460ff166101a0565b6101e361028236600461207e565b6107cc565b6101e36102953660046120f1565b61080f565b6101e361084f565b6102b56102b0366004612145565b6108cb565b6040516001600160a01b039091168152602001610184565b6101a06102db366004611f3a565b6108ea565b61017a600081565b6101e36102f6366004612167565b610913565b61017a610309366004611c34565b61091e565b61017a60008051602061284b83398151915281565b6101e3610331366004611f3a565b610935565b61017a60008051602061282b83398151915281565b6101a06103593660046121a3565b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205460ff1690565b6101e36103953660046121cd565b61095a565b6101e36103a8366004612231565b61099f565b60006001600160a01b03831661041d5760405162461bcd60e51b815260206004820152602a60248201527f455243313135353a2061646472657373207a65726f206973206e6f742061207660448201526930b634b21037bbb732b960b11b60648201526084015b60405180910390fd5b5060008181526002602090815260408083206001600160a01b03861684529091529020545b92915050565b6000610442826109e2565b60606004805461046290612264565b80601f016020809104026020016040519081016040528092919081815260200182805461048e90612264565b80156104db5780601f106104b0576101008083540402835291602001916104db565b820191906000526020600020905b8154815290600101906020018083116104be57829003601f168201915b50505050509050919050565b6104ff60008051602061284b833981519152336108ea565b61051b5760405162461bcd60e51b81526004016104149061229e565b61052784848484610a22565b50505050565b6001600160a01b03851633148061054957506105498533610359565b6105655760405162461bcd60e51b8152600401610414906122e4565b6105728585858585610b6b565b5050505050565b60008281526020819052604090206001015461059481610d06565b61059e8383610d13565b505050565b6001600160a01b03811633146106135760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608401610414565b61061d8282610d35565b5050565b61063960008051602061282b833981519152336108ea565b6106995760405162461bcd60e51b815260206004820152603b60248201526000805160206127eb83398151915260448201527f686176652070617573657220726f6c6520746f20756e706175736500000000006064820152608401610414565b6106a1610d57565b565b606081518351146107085760405162461bcd60e51b815260206004820152602960248201527f455243313135353a206163636f756e747320616e6420696473206c656e677468604482015268040dad2e6dac2e8c6d60bb1b6064820152608401610414565b600083516001600160401b0381111561072357610723611cb0565b60405190808252806020026020018201604052801561074c578160200160208202803683370190505b50905060005b84518110156107c45761079785828151811061077057610770612332565b602002602001015185838151811061078a5761078a612332565b60200260200101516103ad565b8282815181106107a9576107a9612332565b60209081029190910101526107bd8161235e565b9050610752565b509392505050565b6001600160a01b0383163314806107e857506107e88333610359565b6108045760405162461bcd60e51b8152600401610414906122e4565b61059e838383610da9565b61082760008051602061284b833981519152336108ea565b6108435760405162461bcd60e51b81526004016104149061229e565b61052784848484610f36565b61086760008051602061282b833981519152336108ea565b6108c35760405162461bcd60e51b815260206004820152603960248201526000805160206127eb833981519152604482015278686176652070617573657220726f6c6520746f20706175736560381b6064820152608401610414565b6106a161100f565b60008281526001602052604081206108e3908361104c565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b61061d338383611058565b600081815260016020526040812061044290611138565b60008281526020819052604090206001015461095081610d06565b61059e8383610d35565b6001600160a01b03851633148061097657506109768533610359565b6109925760405162461bcd60e51b8152600401610414906122e4565b6105728585858585611142565b6001600160a01b0383163314806109bb57506109bb8333610359565b6109d75760405162461bcd60e51b8152600401610414906122e4565b61059e83838361126c565b60006001600160e01b03198216636cdb3d1360e11b1480610a1357506001600160e01b031982166303a24d0760e21b145b80610442575061044282611376565b6001600160a01b038416610a485760405162461bcd60e51b815260040161041490612377565b8151835114610a695760405162461bcd60e51b8152600401610414906123b8565b33610a798160008787878761139b565b60005b8451811015610b1557838181518110610a9757610a97612332565b602002602001015160026000878481518110610ab557610ab5612332565b602002602001015181526020019081526020016000206000886001600160a01b03166001600160a01b031681526020019081526020016000206000828254610afd9190612400565b90915550819050610b0d8161235e565b915050610a7c565b50846001600160a01b031660006001600160a01b0316826001600160a01b03166000805160206127cb8339815191528787604051610b54929190612413565b60405180910390a4610572816000878787876113a9565b8151835114610b8c5760405162461bcd60e51b8152600401610414906123b8565b6001600160a01b038416610bb25760405162461bcd60e51b815260040161041490612441565b33610bc181878787878761139b565b60005b8451811015610caa576000858281518110610be157610be1612332565b602002602001015190506000858381518110610bff57610bff612332565b60209081029190910181015160008481526002835260408082206001600160a01b038e168352909352919091205490915081811015610c505760405162461bcd60e51b815260040161041490612486565b60008381526002602090815260408083206001600160a01b038e8116855292528083208585039055908b16825281208054849290610c8f908490612400565b9250508190555050505080610ca39061235e565b9050610bc4565b50846001600160a01b0316866001600160a01b0316826001600160a01b03166000805160206127cb8339815191528787604051610ce8929190612413565b60405180910390a4610cfe8187878787876113a9565b505050505050565b610d108133611504565b50565b610d1d828261155d565b600082815260016020526040902061059e90826115e1565b610d3f82826115f6565b600082815260016020526040902061059e908261165b565b610d5f611670565b6005805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6001600160a01b038316610dcf5760405162461bcd60e51b8152600401610414906124d0565b8051825114610df05760405162461bcd60e51b8152600401610414906123b8565b6000339050610e138185600086866040518060200160405280600081525061139b565b60005b8351811015610edb576000848281518110610e3357610e33612332565b602002602001015190506000848381518110610e5157610e51612332565b60209081029190910181015160008481526002835260408082206001600160a01b038c168352909352919091205490915081811015610ea25760405162461bcd60e51b815260040161041490612513565b60009283526002602090815260408085206001600160a01b038b1686529091529092209103905580610ed38161235e565b915050610e16565b5060006001600160a01b0316846001600160a01b0316826001600160a01b03166000805160206127cb8339815191528686604051610f1a929190612413565b60405180910390a4604080516020810190915260009052610527565b6001600160a01b038416610f5c5760405162461bcd60e51b815260040161041490612377565b336000610f68856116b9565b90506000610f75856116b9565b9050610f868360008985858961139b565b60008681526002602090815260408083206001600160a01b038b16845290915281208054879290610fb8908490612400565b909155505060408051878152602081018790526001600160a01b03808a16926000929187169160008051602061280b833981519152910160405180910390a461100683600089898989611704565b50505050505050565b6110176117bf565b6005805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258610d8c3390565b60006108e38383611805565b816001600160a01b0316836001600160a01b0316036110cb5760405162461bcd60e51b815260206004820152602960248201527f455243313135353a2073657474696e6720617070726f76616c20737461747573604482015268103337b91039b2b63360b91b6064820152608401610414565b6001600160a01b03838116600081815260036020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6000610442825490565b6001600160a01b0384166111685760405162461bcd60e51b815260040161041490612441565b336000611174856116b9565b90506000611181856116b9565b905061119183898985858961139b565b60008681526002602090815260408083206001600160a01b038c168452909152902054858110156111d45760405162461bcd60e51b815260040161041490612486565b60008781526002602090815260408083206001600160a01b038d8116855292528083208985039055908a16825281208054889290611213908490612400565b909155505060408051888152602081018890526001600160a01b03808b16928c8216929188169160008051602061280b833981519152910160405180910390a4611261848a8a8a8a8a611704565b505050505050505050565b6001600160a01b0383166112925760405162461bcd60e51b8152600401610414906124d0565b33600061129e846116b9565b905060006112ab846116b9565b90506112cb8387600085856040518060200160405280600081525061139b565b60008581526002602090815260408083206001600160a01b038a1684529091529020548481101561130e5760405162461bcd60e51b815260040161041490612513565b60008681526002602090815260408083206001600160a01b038b81168086529184528285208a8703905582518b81529384018a905290929088169160008051602061280b833981519152910160405180910390a4604080516020810190915260009052611006565b60006001600160e01b03198216635a05180f60e01b148061044257506104428261182f565b610cfe868686868686611864565b6001600160a01b0384163b15610cfe5760405163bc197c8160e01b81526001600160a01b0385169063bc197c81906113ed9089908990889088908890600401612557565b6020604051808303816000875af1925050508015611428575060408051601f3d908101601f19168201909252611425918101906125b5565b60015b6114d4576114346125d2565b806308c379a00361146d57506114486125ee565b80611453575061146f565b8060405162461bcd60e51b81526004016104149190611c9d565b505b60405162461bcd60e51b815260206004820152603460248201527f455243313135353a207472616e7366657220746f206e6f6e2d455243313135356044820152732932b1b2b4bb32b91034b6b83632b6b2b73a32b960611b6064820152608401610414565b6001600160e01b0319811663bc197c8160e01b146110065760405162461bcd60e51b815260040161041490612677565b61150e82826108ea565b61061d5761151b816118cc565b6115268360206118de565b6040516020016115379291906126bf565b60408051601f198184030181529082905262461bcd60e51b825261041491600401611c9d565b61156782826108ea565b61061d576000828152602081815260408083206001600160a01b03851684529091529020805460ff1916600117905561159d3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b60006108e3836001600160a01b038416611a79565b61160082826108ea565b1561061d576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b60006108e3836001600160a01b038416611ac8565b60055460ff166106a15760405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606401610414565b604080516001808252818301909252606091600091906020808301908036833701905050905082816000815181106116f3576116f3612332565b602090810291909101015292915050565b6001600160a01b0384163b15610cfe5760405163f23a6e6160e01b81526001600160a01b0385169063f23a6e6190611748908990899088908890889060040161272e565b6020604051808303816000875af1925050508015611783575060408051601f3d908101601f19168201909252611780918101906125b5565b60015b61178f576114346125d2565b6001600160e01b0319811663f23a6e6160e01b146110065760405162461bcd60e51b815260040161041490612677565b60055460ff16156106a15760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610414565b600082600001828154811061181c5761181c612332565b9060005260206000200154905092915050565b60006001600160e01b03198216637965db0b60e01b148061044257506301ffc9a760e01b6001600160e01b0319831614610442565b60055460ff1615610cfe5760405162461bcd60e51b815260206004820152602c60248201527f455243313135355061757361626c653a20746f6b656e207472616e736665722060448201526b1dda1a5b19481c185d5cd95960a21b6064820152608401610414565b60606104426001600160a01b03831660145b606060006118ed836002612773565b6118f8906002612400565b6001600160401b0381111561190f5761190f611cb0565b6040519080825280601f01601f191660200182016040528015611939576020820181803683370190505b509050600360fc1b8160008151811061195457611954612332565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061198357611983612332565b60200101906001600160f81b031916908160001a90535060006119a7846002612773565b6119b2906001612400565b90505b6001811115611a2a576f181899199a1a9b1b9c1cb0b131b232b360811b85600f16601081106119e6576119e6612332565b1a60f81b8282815181106119fc576119fc612332565b60200101906001600160f81b031916908160001a90535060049490941c93611a238161278a565b90506119b5565b5083156108e35760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610414565b6000818152600183016020526040812054611ac057508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610442565b506000610442565b60008181526001830160205260408120548015611bb1576000611aec6001836127a1565b8554909150600090611b00906001906127a1565b9050818114611b65576000866000018281548110611b2057611b20612332565b9060005260206000200154905080876000018481548110611b4357611b43612332565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611b7657611b766127b4565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610442565b6000915050610442565b80356001600160a01b0381168114611bd257600080fd5b919050565b60008060408385031215611bea57600080fd5b611bf383611bbb565b946020939093013593505050565b6001600160e01b031981168114610d1057600080fd5b600060208284031215611c2957600080fd5b81356108e381611c01565b600060208284031215611c4657600080fd5b5035919050565b60005b83811015611c68578181015183820152602001611c50565b50506000910152565b60008151808452611c89816020860160208601611c4d565b601f01601f19169290920160200192915050565b6020815260006108e36020830184611c71565b634e487b7160e01b600052604160045260246000fd5b601f8201601f191681016001600160401b0381118282101715611ceb57611ceb611cb0565b6040525050565b60006001600160401b03821115611d0b57611d0b611cb0565b5060051b60200190565b600082601f830112611d2657600080fd5b81356020611d3382611cf2565b604051611d408282611cc6565b83815260059390931b8501820192828101915086841115611d6057600080fd5b8286015b84811015611d7b5780358352918301918301611d64565b509695505050505050565b600082601f830112611d9757600080fd5b81356001600160401b03811115611db057611db0611cb0565b604051611dc7601f8301601f191660200182611cc6565b818152846020838601011115611ddc57600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060808587031215611e0f57600080fd5b611e1885611bbb565b935060208501356001600160401b0380821115611e3457600080fd5b611e4088838901611d15565b94506040870135915080821115611e5657600080fd5b611e6288838901611d15565b93506060870135915080821115611e7857600080fd5b50611e8587828801611d86565b91505092959194509250565b600080600080600060a08688031215611ea957600080fd5b611eb286611bbb565b9450611ec060208701611bbb565b935060408601356001600160401b0380821115611edc57600080fd5b611ee889838a01611d15565b94506060880135915080821115611efe57600080fd5b611f0a89838a01611d15565b93506080880135915080821115611f2057600080fd5b50611f2d88828901611d86565b9150509295509295909350565b60008060408385031215611f4d57600080fd5b82359150611f5d60208401611bbb565b90509250929050565b60008060408385031215611f7957600080fd5b82356001600160401b0380821115611f9057600080fd5b818501915085601f830112611fa457600080fd5b81356020611fb182611cf2565b604051611fbe8282611cc6565b83815260059390931b8501820192828101915089841115611fde57600080fd5b948201945b8386101561200357611ff486611bbb565b82529482019490820190611fe3565b9650508601359250508082111561201957600080fd5b5061202685828601611d15565b9150509250929050565b600081518084526020808501945080840160005b8381101561206057815187529582019590820190600101612044565b509495945050505050565b6020815260006108e36020830184612030565b60008060006060848603121561209357600080fd5b61209c84611bbb565b925060208401356001600160401b03808211156120b857600080fd5b6120c487838801611d15565b935060408601359150808211156120da57600080fd5b506120e786828701611d15565b9150509250925092565b6000806000806080858703121561210757600080fd5b61211085611bbb565b9350602085013592506040850135915060608501356001600160401b0381111561213957600080fd5b611e8587828801611d86565b6000806040838503121561215857600080fd5b50508035926020909101359150565b6000806040838503121561217a57600080fd5b61218383611bbb565b91506020830135801515811461219857600080fd5b809150509250929050565b600080604083850312156121b657600080fd5b6121bf83611bbb565b9150611f5d60208401611bbb565b600080600080600060a086880312156121e557600080fd5b6121ee86611bbb565b94506121fc60208701611bbb565b9350604086013592506060860135915060808601356001600160401b0381111561222557600080fd5b611f2d88828901611d86565b60008060006060848603121561224657600080fd5b61224f84611bbb565b95602085013595506040909401359392505050565b600181811c9082168061227857607f821691505b60208210810361229857634e487b7160e01b600052602260045260246000fd5b50919050565b60208082526038908201526000805160206127eb8339815191526040820152771a185d99481b5a5b9d195c881c9bdb19481d1bc81b5a5b9d60421b606082015260800190565b6020808252602e908201527f455243313135353a2063616c6c6572206973206e6f7420746f6b656e206f776e60408201526d195c881bdc88185c1c1c9bdd995960921b606082015260800190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60006001820161237057612370612348565b5060010190565b60208082526021908201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736040820152607360f81b606082015260800190565b60208082526028908201527f455243313135353a2069647320616e6420616d6f756e7473206c656e677468206040820152670dad2e6dac2e8c6d60c31b606082015260800190565b8082018082111561044257610442612348565b6040815260006124266040830185612030565b82810360208401526124388185612030565b95945050505050565b60208082526025908201527f455243313135353a207472616e7366657220746f20746865207a65726f206164604082015264647265737360d81b606082015260800190565b6020808252602a908201527f455243313135353a20696e73756666696369656e742062616c616e636520666f60408201526939103a3930b739b332b960b11b606082015260800190565b60208082526023908201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260408201526265737360e81b606082015260800190565b60208082526024908201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604082015263616e636560e01b606082015260800190565b6001600160a01b0386811682528516602082015260a06040820181905260009061258390830186612030565b82810360608401526125958186612030565b905082810360808401526125a98185611c71565b98975050505050505050565b6000602082840312156125c757600080fd5b81516108e381611c01565b600060033d11156125eb5760046000803e5060005160e01c5b90565b600060443d10156125fc5790565b6040516003193d81016004833e81513d6001600160401b03816024840111818411171561262b57505050505090565b82850191508151818111156126435750505050505090565b843d870101602082850101111561265d5750505050505090565b61266c60208286010187611cc6565b509095945050505050565b60208082526028908201527f455243313135353a204552433131353552656365697665722072656a656374656040820152676420746f6b656e7360c01b606082015260800190565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8152600083516126f1816017850160208801611c4d565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351612722816028840160208801611c4d565b01602801949350505050565b6001600160a01b03868116825285166020820152604081018490526060810183905260a06080820181905260009061276890830184611c71565b979650505050505050565b808202811582820484141761044257610442612348565b60008161279957612799612348565b506000190190565b8181038181111561044257610442612348565b634e487b7160e01b600052603160045260246000fdfe4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb455243313135355072657365744d696e7465725061757365723a206d75737420c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6265d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6a2646970667358221220294efe154e549198ff0c6b7ab7e9846223d9e8fe7ae7de1938f6eb7d0f5e66a664736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RootERC1155PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RootERC1155Predicate","sourceName":"contracts/root/RootERC1155Predicate.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC1155Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"ERC1155DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC1155Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"ERC1155WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"TokenMapped","type":"event"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childERC1155Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"depositBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitHelper","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newStateSender","type":"address"},{"internalType":"address","name":"newExitHelper","type":"address"},{"internalType":"address","name":"newChildERC1155Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC1155MetadataURI","name":"rootToken","type":"address"}],"name":"mapToken","outputs":[{"internalType":"address","name":"childToken","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onL2StateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611bd2806100206000396000f3fe608060405234801561001057600080fd5b50600436106101215760003560e01c8063bc197c81116100ad578063f23a6e6111610071578063f23a6e61146102fe578063f43cda8b1461031d578063f4a120f714610330578063f645125514610343578063f8c8765e1461036a57600080fd5b8063bc197c8114610238578063c5ac2b1c14610270578063cb10f94c14610297578063d41f1771146102b0578063d7c9e3ec146102d757600080fd5b80637efab4f5116100f45780637efab4f5146101a157806395c7041c146101ca578063a78e111c146101dd578063b1768065146101f0578063b68ad1e41461022557600080fd5b806301ffc9a7146101265780630efe6a8b1461014e5780634c4c45de14610163578063654715e614610176575b600080fd5b610139610134366004611173565b61037d565b60405190151581526020015b60405180910390f35b61016161015c3660046111bc565b6103b4565b005b61016161017136600461123c565b6103c5565b600254610189906001600160a01b031681565b6040516001600160a01b039091168152602001610145565b6101896101af3660046112e8565b6004602052600090815260409020546001600160a01b031681565b600154610189906001600160a01b031681565b6101616101eb366004611305565b610448565b6102177f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b604051908152602001610145565b600354610189906001600160a01b031681565b610257610246366004611497565b63bc197c8160e01b95945050505050565b6040516001600160e01b03199091168152602001610145565b6102177faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b600054610189906201000090046001600160a01b031681565b6102177f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102177f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61025761030c366004611544565b63f23a6e6160e01b95945050505050565b61016161032b3660046115ac565b61045a565b61018961033e3660046112e8565b610629565b6102177f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b610161610378366004611634565b610945565b60006001600160e01b03198216630271189760e51b14806103ae57506301ffc9a760e01b6001600160e01b03198316145b92915050565b6103c083338484610b54565b505050565b84831480156103d357508481145b6104305760405162461bcd60e51b8152602060048201526024808201527f526f6f74455243313135355072656469636174653a20494e56414c49445f4c4560448201526309c8ea8960e31b60648201526084015b60405180910390fd5b61043f87878787878787610cec565b50505050505050565b61045484848484610b54565b50505050565b6001546001600160a01b031633146104c35760405162461bcd60e51b815260206004820152602660248201527f526f6f74455243313135355072656469636174653a204f4e4c595f455849545f6044820152652422a62822a960d11b6064820152608401610427565b6002546001600160a01b038481169116146105335760405162461bcd60e51b815260206004820152602a60248201527f526f6f74455243313135355072656469636174653a204f4e4c595f4348494c446044820152695f50524544494341544560b01b6064820152608401610427565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e982869610562602060008486611690565b61056b916116ba565b0361058a576105856105808260208186611690565b610ecc565b610454565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed26105b9602060008486611690565b6105c2916116ba565b036105d1576105858282610fcb565b60405162461bcd60e51b815260206004820152602760248201527f526f6f74455243313135355072656469636174653a20494e56414c49445f5349604482015266474e415455524560c81b6064820152608401610427565b60006001600160a01b03821661068d5760405162461bcd60e51b815260206004820152602360248201527f526f6f74455243313135355072656469636174653a20494e56414c49445f544f60448201526225a2a760e91b6064820152608401610427565b6001600160a01b0382811660009081526004602052604090205416156107015760405162461bcd60e51b8152602060048201526024808201527f526f6f74455243313135355072656469636174653a20414c52454144595f4d416044820152631414115160e21b6064820152608401610427565b6002546003546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b03928316926107ab921690603401604051602081830303815290604052805190602001208360405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b03848116600081815260046020818152604080842080546001600160a01b031916968816969096179095558451908101855282815293516303a24d0760e21b8152908101919091529294509091630e89341c90602401600060405180830381865afa92505050801561084657506040513d6000823e601f3d908101601f1916820160405261084391908101906116fc565b60015b1561084e5790505b600060029054906101000a90046001600160a01b03166001600160a01b03166316f19831837f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad87856040516020016108a89392919061179e565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016108d49291906117c8565b600060405180830381600087803b1580156108ee57600080fd5b505af1158015610902573d6000803e3d6000fd5b50506040516001600160a01b038087169350871691507f85920d35e6c72f6b2affffa04298b0cecfeba86e4a9f407df661f1cb8ab5e61790600090a35050919050565b600054610100900460ff16158080156109655750600054600160ff909116105b8061097f5750303b15801561097f575060005460ff166001145b6109e25760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610427565b6000805460ff191660011790558015610a05576000805461ff0019166101001790555b6001600160a01b03851615801590610a2557506001600160a01b03841615155b8015610a3957506001600160a01b03831615155b8015610a4d57506001600160a01b03821615155b610aaa5760405162461bcd60e51b815260206004820152602860248201527f526f6f74455243313135355072656469636174653a204241445f494e495449416044820152672624ad20aa24a7a760c11b6064820152608401610427565b6000805462010000600160b01b031916620100006001600160a01b038881169190910291909117909155600180546001600160a01b031990811687841617909155600280548216868416179055600380549091169184169190911790558015610b4d576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6000610b5f8561112c565b604051637921219560e11b81529091506001600160a01b0386169063f242432a90610b949033903090889088906004016117f4565b600060405180830381600087803b158015610bae57600080fd5b505af1158015610bc2573d6000803e3d6000fd5b5050600054600254604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038b8116828401523360608301528a8116608083015260a082018a905260c08083018a90528351808403909101815260e08301938490526316f1983160e01b90935262010000909404841695506316f198319450610c5f939092169160e4016117c8565b600060405180830381600087803b158015610c7957600080fd5b505af1158015610c8d573d6000803e3d6000fd5b505060408051338152602081018790529081018590526001600160a01b03808816935084811692508816907fbcbdf0c69f975f2a4085544938b054f02e77e87cbd055146d388c0be7120e04f9060600160405180910390a45050505050565b6000610cf78861112c565b905060005b84811015610da357886001600160a01b031663f242432a3330898986818110610d2757610d2761182c565b90506020020135888887818110610d4057610d4061182c565b905060200201356040518563ffffffff1660e01b8152600401610d6694939291906117f4565b600060405180830381600087803b158015610d8057600080fd5b505af1158015610d94573d6000803e3d6000fd5b50505050806001019050610cfc565b506000546002546040516001600160a01b03620100009093048316926316f19831921690610e05907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908d9033908e908e908e908e908e908e906020016118bd565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610e319291906117c8565b600060405180830381600087803b158015610e4b57600080fd5b505af1158015610e5f573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316896001600160a01b03167f7e8ab6f25cb177d65db49b7a2197bb2a7df5757434857d411e2523b332a913e68a8a8a8a8a8a604051610eba96959493929190611928565b60405180910390a45050505050505050565b600080808080610ede86880188611971565b6001600160a01b03808616600090815260046020526040902054959a50939850919650945092501680610f1357610f136119cc565b604051637921219560e11b81526001600160a01b0387169063f242432a90610f459030908890889088906004016117f4565b600060405180830381600087803b158015610f5f57600080fd5b505af1158015610f73573d6000803e3d6000fd5b5050604080516001600160a01b0389811682526020820188905291810186905281881693508482169250908916907fc42752949ca07109e6f25adcde4a9407be23e084b1d32ca2bce8aa55bf3dd62590606001610eba565b600080808080610fdd868801886119e2565b6001600160a01b03808616600090815260046020526040902054959b509399509197509550935016905080611014576110146119cc565b60005b83518110156110da57866001600160a01b031663f242432a308784815181106110425761104261182c565b602002602001015187858151811061105c5761105c61182c565b60200260200101518786815181106110765761107661182c565b60200260200101516040518563ffffffff1660e01b815260040161109d94939291906117f4565b600060405180830381600087803b1580156110b757600080fd5b505af11580156110cb573d6000803e3d6000fd5b50505050806001019050611017565b50846001600160a01b0316816001600160a01b0316876001600160a01b03167fce612e98b81c726375e7c2395409e74ee0a29b22f7641ea5a8ce8508616448c0878787604051610eba93929190611b27565b6001600160a01b0380821660009081526004602052604090205416806111585761115582610629565b90505b6001600160a01b03811661116e5761116e6119cc565b919050565b60006020828403121561118557600080fd5b81356001600160e01b03198116811461119d57600080fd5b9392505050565b6001600160a01b03811681146111b957600080fd5b50565b6000806000606084860312156111d157600080fd5b83356111dc816111a4565b95602085013595506040909401359392505050565b60008083601f84011261120357600080fd5b5081356001600160401b0381111561121a57600080fd5b6020830191508360208260051b850101111561123557600080fd5b9250929050565b60008060008060008060006080888a03121561125757600080fd5b8735611262816111a4565b965060208801356001600160401b038082111561127e57600080fd5b61128a8b838c016111f1565b909850965060408a01359150808211156112a357600080fd5b6112af8b838c016111f1565b909650945060608a01359150808211156112c857600080fd5b506112d58a828b016111f1565b989b979a50959850939692959293505050565b6000602082840312156112fa57600080fd5b813561119d816111a4565b6000806000806080858703121561131b57600080fd5b8435611326816111a4565b93506020850135611336816111a4565b93969395505050506040820135916060013590565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156113895761138961134b565b604052919050565b60006001600160401b038211156113aa576113aa61134b565b5060051b60200190565b600082601f8301126113c557600080fd5b813560206113da6113d583611391565b611361565b82815260059290921b840181019181810190868411156113f957600080fd5b8286015b8481101561141457803583529183019183016113fd565b509695505050505050565b60006001600160401b038211156114385761143861134b565b50601f01601f191660200190565b600082601f83011261145757600080fd5b81356114656113d58261141f565b81815284602083860101111561147a57600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a086880312156114af57600080fd5b85356114ba816111a4565b945060208601356114ca816111a4565b935060408601356001600160401b03808211156114e657600080fd5b6114f289838a016113b4565b9450606088013591508082111561150857600080fd5b61151489838a016113b4565b9350608088013591508082111561152a57600080fd5b5061153788828901611446565b9150509295509295909350565b600080600080600060a0868803121561155c57600080fd5b8535611567816111a4565b94506020860135611577816111a4565b9350604086013592506060860135915060808601356001600160401b038111156115a057600080fd5b61153788828901611446565b600080600080606085870312156115c257600080fd5b8435935060208501356115d4816111a4565b925060408501356001600160401b03808211156115f057600080fd5b818701915087601f83011261160457600080fd5b81358181111561161357600080fd5b88602082850101111561162557600080fd5b95989497505060200194505050565b6000806000806080858703121561164a57600080fd5b8435611655816111a4565b93506020850135611665816111a4565b92506040850135611675816111a4565b91506060850135611685816111a4565b939692955090935050565b600080858511156116a057600080fd5b838611156116ad57600080fd5b5050820193919092039150565b803560208310156103ae57600019602084900360031b1b1692915050565b60005b838110156116f35781810151838201526020016116db565b50506000910152565b60006020828403121561170e57600080fd5b81516001600160401b0381111561172457600080fd5b8201601f8101841361173557600080fd5b80516117436113d58261141f565b81815285602083850101111561175857600080fd5b6117698260208301602086016116d8565b95945050505050565b6000815180845261178a8160208601602086016116d8565b601f01601f19169290920160200192915050565b8381526001600160a01b038316602082015260606040820181905260009061176990830184611772565b6001600160a01b03831681526040602082018190526000906117ec90830184611772565b949350505050565b6001600160a01b0394851681529290931660208301526040820152606081019190915260a06080820181905260009082015260c00190565b634e487b7160e01b600052603260045260246000fd5b8183526000602080850194508260005b85811015611880578135611865816111a4565b6001600160a01b031687529582019590820190600101611852565b509495945050505050565b81835260006001600160fb1b038311156118a457600080fd5b8260051b80836020870137939093016020019392505050565b8981526001600160a01b0389811660208301528816604082015260c0606082018190526000906118f0908301888a611842565b828103608084015261190381878961188b565b905082810360a084015261191881858761188b565b9c9b505050505050505050505050565b60608152600061193c60608301888a611842565b828103602084015261194f81878961188b565b9050828103604084015261196481858761188b565b9998505050505050505050565b600080600080600060a0868803121561198957600080fd5b8535611994816111a4565b945060208601356119a4816111a4565b935060408601356119b4816111a4565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052600160045260246000fd5b60008060008060008060c087890312156119fb57600080fd5b86359550602080880135611a0e816111a4565b95506040880135611a1e816111a4565b945060608801356001600160401b0380821115611a3a57600080fd5b818a0191508a601f830112611a4e57600080fd5b8135611a5c6113d582611391565b81815260059190911b8301840190848101908d831115611a7b57600080fd5b938501935b82851015611aa2578435611a93816111a4565b82529385019390850190611a80565b9750505060808a0135925080831115611aba57600080fd5b611ac68b848c016113b4565b945060a08a0135925080831115611adc57600080fd5b5050611aea89828a016113b4565b9150509295509295509295565b600081518084526020808501945080840160005b8381101561188057815187529582019590820190600101611b0b565b606080825284519082018190526000906020906080840190828801845b82811015611b695781516001600160a01b031684529284019290840190600101611b44565b50505083810382850152611b7d8187611af7565b9150508281036040840152611b928185611af7565b969550505050505056fea2646970667358221220fca09c260114445d76ac4a1ec1efcbdba6ac7f99d1bccd257e90ed39dcea400d64736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101215760003560e01c8063bc197c81116100ad578063f23a6e6111610071578063f23a6e61146102fe578063f43cda8b1461031d578063f4a120f714610330578063f645125514610343578063f8c8765e1461036a57600080fd5b8063bc197c8114610238578063c5ac2b1c14610270578063cb10f94c14610297578063d41f1771146102b0578063d7c9e3ec146102d757600080fd5b80637efab4f5116100f45780637efab4f5146101a157806395c7041c146101ca578063a78e111c146101dd578063b1768065146101f0578063b68ad1e41461022557600080fd5b806301ffc9a7146101265780630efe6a8b1461014e5780634c4c45de14610163578063654715e614610176575b600080fd5b610139610134366004611173565b61037d565b60405190151581526020015b60405180910390f35b61016161015c3660046111bc565b6103b4565b005b61016161017136600461123c565b6103c5565b600254610189906001600160a01b031681565b6040516001600160a01b039091168152602001610145565b6101896101af3660046112e8565b6004602052600090815260409020546001600160a01b031681565b600154610189906001600160a01b031681565b6101616101eb366004611305565b610448565b6102177f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b604051908152602001610145565b600354610189906001600160a01b031681565b610257610246366004611497565b63bc197c8160e01b95945050505050565b6040516001600160e01b03199091168152602001610145565b6102177faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b600054610189906201000090046001600160a01b031681565b6102177f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6102177f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b61025761030c366004611544565b63f23a6e6160e01b95945050505050565b61016161032b3660046115ac565b61045a565b61018961033e3660046112e8565b610629565b6102177f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b610161610378366004611634565b610945565b60006001600160e01b03198216630271189760e51b14806103ae57506301ffc9a760e01b6001600160e01b03198316145b92915050565b6103c083338484610b54565b505050565b84831480156103d357508481145b6104305760405162461bcd60e51b8152602060048201526024808201527f526f6f74455243313135355072656469636174653a20494e56414c49445f4c4560448201526309c8ea8960e31b60648201526084015b60405180910390fd5b61043f87878787878787610cec565b50505050505050565b61045484848484610b54565b50505050565b6001546001600160a01b031633146104c35760405162461bcd60e51b815260206004820152602660248201527f526f6f74455243313135355072656469636174653a204f4e4c595f455849545f6044820152652422a62822a960d11b6064820152608401610427565b6002546001600160a01b038481169116146105335760405162461bcd60e51b815260206004820152602a60248201527f526f6f74455243313135355072656469636174653a204f4e4c595f4348494c446044820152695f50524544494341544560b01b6064820152608401610427565b7f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e982869610562602060008486611690565b61056b916116ba565b0361058a576105856105808260208186611690565b610ecc565b610454565b7f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed26105b9602060008486611690565b6105c2916116ba565b036105d1576105858282610fcb565b60405162461bcd60e51b815260206004820152602760248201527f526f6f74455243313135355072656469636174653a20494e56414c49445f5349604482015266474e415455524560c81b6064820152608401610427565b60006001600160a01b03821661068d5760405162461bcd60e51b815260206004820152602360248201527f526f6f74455243313135355072656469636174653a20494e56414c49445f544f60448201526225a2a760e91b6064820152608401610427565b6001600160a01b0382811660009081526004602052604090205416156107015760405162461bcd60e51b8152602060048201526024808201527f526f6f74455243313135355072656469636174653a20414c52454144595f4d416044820152631414115160e21b6064820152608401610427565b6002546003546040516bffffffffffffffffffffffff19606086901b1660208201526001600160a01b03928316926107ab921690603401604051602081830303815290604052805190602001208360405160388101919091526f5af43d82803e903d91602b57fd5bf3ff60248201526014810192909252733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152605560439091012090565b6001600160a01b03848116600081815260046020818152604080842080546001600160a01b031916968816969096179095558451908101855282815293516303a24d0760e21b8152908101919091529294509091630e89341c90602401600060405180830381865afa92505050801561084657506040513d6000823e601f3d908101601f1916820160405261084391908101906116fc565b60015b1561084e5790505b600060029054906101000a90046001600160a01b03166001600160a01b03166316f19831837f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad87856040516020016108a89392919061179e565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016108d49291906117c8565b600060405180830381600087803b1580156108ee57600080fd5b505af1158015610902573d6000803e3d6000fd5b50506040516001600160a01b038087169350871691507f85920d35e6c72f6b2affffa04298b0cecfeba86e4a9f407df661f1cb8ab5e61790600090a35050919050565b600054610100900460ff16158080156109655750600054600160ff909116105b8061097f5750303b15801561097f575060005460ff166001145b6109e25760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610427565b6000805460ff191660011790558015610a05576000805461ff0019166101001790555b6001600160a01b03851615801590610a2557506001600160a01b03841615155b8015610a3957506001600160a01b03831615155b8015610a4d57506001600160a01b03821615155b610aaa5760405162461bcd60e51b815260206004820152602860248201527f526f6f74455243313135355072656469636174653a204241445f494e495449416044820152672624ad20aa24a7a760c11b6064820152608401610427565b6000805462010000600160b01b031916620100006001600160a01b038881169190910291909117909155600180546001600160a01b031990811687841617909155600280548216868416179055600380549091169184169190911790558015610b4d576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6000610b5f8561112c565b604051637921219560e11b81529091506001600160a01b0386169063f242432a90610b949033903090889088906004016117f4565b600060405180830381600087803b158015610bae57600080fd5b505af1158015610bc2573d6000803e3d6000fd5b5050600054600254604080517f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82160208201526001600160a01b038b8116828401523360608301528a8116608083015260a082018a905260c08083018a90528351808403909101815260e08301938490526316f1983160e01b90935262010000909404841695506316f198319450610c5f939092169160e4016117c8565b600060405180830381600087803b158015610c7957600080fd5b505af1158015610c8d573d6000803e3d6000fd5b505060408051338152602081018790529081018590526001600160a01b03808816935084811692508816907fbcbdf0c69f975f2a4085544938b054f02e77e87cbd055146d388c0be7120e04f9060600160405180910390a45050505050565b6000610cf78861112c565b905060005b84811015610da357886001600160a01b031663f242432a3330898986818110610d2757610d2761182c565b90506020020135888887818110610d4057610d4061182c565b905060200201356040518563ffffffff1660e01b8152600401610d6694939291906117f4565b600060405180830381600087803b158015610d8057600080fd5b505af1158015610d94573d6000803e3d6000fd5b50505050806001019050610cfc565b506000546002546040516001600160a01b03620100009093048316926316f19831921690610e05907faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d18908d9033908e908e908e908e908e908e906020016118bd565b6040516020818303038152906040526040518363ffffffff1660e01b8152600401610e319291906117c8565b600060405180830381600087803b158015610e4b57600080fd5b505af1158015610e5f573d6000803e3d6000fd5b50505050336001600160a01b0316816001600160a01b0316896001600160a01b03167f7e8ab6f25cb177d65db49b7a2197bb2a7df5757434857d411e2523b332a913e68a8a8a8a8a8a604051610eba96959493929190611928565b60405180910390a45050505050505050565b600080808080610ede86880188611971565b6001600160a01b03808616600090815260046020526040902054959a50939850919650945092501680610f1357610f136119cc565b604051637921219560e11b81526001600160a01b0387169063f242432a90610f459030908890889088906004016117f4565b600060405180830381600087803b158015610f5f57600080fd5b505af1158015610f73573d6000803e3d6000fd5b5050604080516001600160a01b0389811682526020820188905291810186905281881693508482169250908916907fc42752949ca07109e6f25adcde4a9407be23e084b1d32ca2bce8aa55bf3dd62590606001610eba565b600080808080610fdd868801886119e2565b6001600160a01b03808616600090815260046020526040902054959b509399509197509550935016905080611014576110146119cc565b60005b83518110156110da57866001600160a01b031663f242432a308784815181106110425761104261182c565b602002602001015187858151811061105c5761105c61182c565b60200260200101518786815181106110765761107661182c565b60200260200101516040518563ffffffff1660e01b815260040161109d94939291906117f4565b600060405180830381600087803b1580156110b757600080fd5b505af11580156110cb573d6000803e3d6000fd5b50505050806001019050611017565b50846001600160a01b0316816001600160a01b0316876001600160a01b03167fce612e98b81c726375e7c2395409e74ee0a29b22f7641ea5a8ce8508616448c0878787604051610eba93929190611b27565b6001600160a01b0380821660009081526004602052604090205416806111585761115582610629565b90505b6001600160a01b03811661116e5761116e6119cc565b919050565b60006020828403121561118557600080fd5b81356001600160e01b03198116811461119d57600080fd5b9392505050565b6001600160a01b03811681146111b957600080fd5b50565b6000806000606084860312156111d157600080fd5b83356111dc816111a4565b95602085013595506040909401359392505050565b60008083601f84011261120357600080fd5b5081356001600160401b0381111561121a57600080fd5b6020830191508360208260051b850101111561123557600080fd5b9250929050565b60008060008060008060006080888a03121561125757600080fd5b8735611262816111a4565b965060208801356001600160401b038082111561127e57600080fd5b61128a8b838c016111f1565b909850965060408a01359150808211156112a357600080fd5b6112af8b838c016111f1565b909650945060608a01359150808211156112c857600080fd5b506112d58a828b016111f1565b989b979a50959850939692959293505050565b6000602082840312156112fa57600080fd5b813561119d816111a4565b6000806000806080858703121561131b57600080fd5b8435611326816111a4565b93506020850135611336816111a4565b93969395505050506040820135916060013590565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156113895761138961134b565b604052919050565b60006001600160401b038211156113aa576113aa61134b565b5060051b60200190565b600082601f8301126113c557600080fd5b813560206113da6113d583611391565b611361565b82815260059290921b840181019181810190868411156113f957600080fd5b8286015b8481101561141457803583529183019183016113fd565b509695505050505050565b60006001600160401b038211156114385761143861134b565b50601f01601f191660200190565b600082601f83011261145757600080fd5b81356114656113d58261141f565b81815284602083860101111561147a57600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a086880312156114af57600080fd5b85356114ba816111a4565b945060208601356114ca816111a4565b935060408601356001600160401b03808211156114e657600080fd5b6114f289838a016113b4565b9450606088013591508082111561150857600080fd5b61151489838a016113b4565b9350608088013591508082111561152a57600080fd5b5061153788828901611446565b9150509295509295909350565b600080600080600060a0868803121561155c57600080fd5b8535611567816111a4565b94506020860135611577816111a4565b9350604086013592506060860135915060808601356001600160401b038111156115a057600080fd5b61153788828901611446565b600080600080606085870312156115c257600080fd5b8435935060208501356115d4816111a4565b925060408501356001600160401b03808211156115f057600080fd5b818701915087601f83011261160457600080fd5b81358181111561161357600080fd5b88602082850101111561162557600080fd5b95989497505060200194505050565b6000806000806080858703121561164a57600080fd5b8435611655816111a4565b93506020850135611665816111a4565b92506040850135611675816111a4565b91506060850135611685816111a4565b939692955090935050565b600080858511156116a057600080fd5b838611156116ad57600080fd5b5050820193919092039150565b803560208310156103ae57600019602084900360031b1b1692915050565b60005b838110156116f35781810151838201526020016116db565b50506000910152565b60006020828403121561170e57600080fd5b81516001600160401b0381111561172457600080fd5b8201601f8101841361173557600080fd5b80516117436113d58261141f565b81815285602083850101111561175857600080fd5b6117698260208301602086016116d8565b95945050505050565b6000815180845261178a8160208601602086016116d8565b601f01601f19169290920160200192915050565b8381526001600160a01b038316602082015260606040820181905260009061176990830184611772565b6001600160a01b03831681526040602082018190526000906117ec90830184611772565b949350505050565b6001600160a01b0394851681529290931660208301526040820152606081019190915260a06080820181905260009082015260c00190565b634e487b7160e01b600052603260045260246000fd5b8183526000602080850194508260005b85811015611880578135611865816111a4565b6001600160a01b031687529582019590820190600101611852565b509495945050505050565b81835260006001600160fb1b038311156118a457600080fd5b8260051b80836020870137939093016020019392505050565b8981526001600160a01b0389811660208301528816604082015260c0606082018190526000906118f0908301888a611842565b828103608084015261190381878961188b565b905082810360a084015261191881858761188b565b9c9b505050505050505050505050565b60608152600061193c60608301888a611842565b828103602084015261194f81878961188b565b9050828103604084015261196481858761188b565b9998505050505050505050565b600080600080600060a0868803121561198957600080fd5b8535611994816111a4565b945060208601356119a4816111a4565b935060408601356119b4816111a4565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052600160045260246000fd5b60008060008060008060c087890312156119fb57600080fd5b86359550602080880135611a0e816111a4565b95506040880135611a1e816111a4565b945060608801356001600160401b0380821115611a3a57600080fd5b818a0191508a601f830112611a4e57600080fd5b8135611a5c6113d582611391565b81815260059190911b8301840190848101908d831115611a7b57600080fd5b938501935b82851015611aa2578435611a93816111a4565b82529385019390850190611a80565b9750505060808a0135925080831115611aba57600080fd5b611ac68b848c016113b4565b945060a08a0135925080831115611adc57600080fd5b5050611aea89828a016113b4565b9150509295509295509295565b600081518084526020808501945080840160005b8381101561188057815187529582019590820190600101611b0b565b606080825284519082018190526000906020906080840190828801845b82811015611b695781516001600160a01b031684529284019290840190600101611b44565b50505083810382850152611b7d8187611af7565b9150508281036040840152611b928185611af7565b969550505050505056fea2646970667358221220fca09c260114445d76ac4a1ec1efcbdba6ac7f99d1bccd257e90ed39dcea400d64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ChildMintableERC1155PredicateArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ChildMintableERC1155Predicate","sourceName":"contracts/root/ChildMintableERC1155Predicate.sol","abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"MintableERC1155Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"MintableERC1155DepositBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"MintableERC1155Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address[]","name":"receivers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"MintableERC1155WithdrawBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":true,"internalType":"address","name":"childToken","type":"address"}],"name":"MintableTokenMapped","type":"event"},{"inputs":[],"name":"DEPOSIT_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAP_TOKEN_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_BATCH_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childTokenTemplate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exitHelper","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newStateSender","type":"address"},{"internalType":"address","name":"newExitHelper","type":"address"},{"internalType":"address","name":"newRootERC1155Predicate","type":"address"},{"internalType":"address","name":"newChildTokenTemplate","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onL2StateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rootERC1155Predicate","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rootTokenToChildToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateSender","outputs":[{"internalType":"contract IStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IChildERC1155","name":"childToken","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC1155","name":"childToken","type":"address"},{"internalType":"address[]","name":"receivers","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"withdrawBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IChildERC1155","name":"childToken","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50611f7c806100206000396000f3fe608060405234801561001057600080fd5b50600436106100d55760003560e01c8063b8cd3ec011610087578063b8cd3ec0146101b6578063c5ac2b1c146101c9578063cb10f94c146101f0578063d41f177114610209578063d7c9e3ec14610230578063f43cda8b14610257578063f64512551461026a578063f8c8765e1461029157600080fd5b8063051eb2e2146100da5780637efab4f51461010a57806386937eb41461013357806395c7041c14610148578063b17680651461015b578063b5c5f67214610190578063b68ad1e4146101a3575b600080fd5b6002546100ed906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ed610118366004611564565b6004602052600090815260409020546001600160a01b031681565b6101466101413660046115d3565b6102a4565b005b6001546100ed906001600160a01b031681565b6101827f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b604051908152602001610101565b61014661019e36600461167f565b6102bc565b6003546100ed906001600160a01b031681565b6101466101c43660046116b4565b6102cd565b6101827faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6000546100ed906201000090046001600160a01b031681565b6101827f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101827f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b6101466102653660046116fa565b6102df565b6101827f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61014661029f366004611782565b610514565b6102b38787878787878761062d565b50505050505050565b6102c8833384846109ba565b505050565b6102d9848484846109ba565b50505050565b6001546001600160a01b031633146103565760405162461bcd60e51b815260206004820152602f60248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204f60448201526e27262cafa2ac24aa2fa422a62822a960891b60648201526084015b60405180910390fd5b6002546001600160a01b038481169116146103ce5760405162461bcd60e51b815260206004820152603260248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204f6044820152714e4c595f524f4f545f50524544494341544560701b606482015260840161034d565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216103fd6020600084866117de565b61040691611808565b036104255761042061041b82602081866117de565b610cd5565b6102d9565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d186104546020600084866117de565b61045d91611808565b0361046c576104208282610f40565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad61049b6020600084866117de565b6104a491611808565b036104b35761042082826111ad565b60405162461bcd60e51b815260206004820152603060248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204960448201526f4e56414c49445f5349474e415455524560801b606482015260840161034d565b600054610100900460ff16158080156105345750600054600160ff909116105b8061054e5750303b15801561054e575060005460ff166001145b6105b15760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161034d565b6000805460ff1916600117905580156105d4576000805461ff0019166101001790555b6105e085858585611317565b8015610626576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b8661063781611420565b6106535760405162461bcd60e51b815260040161034d90611826565b6000886001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610693573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106b79190611871565b6001600160a01b038181166000908152600460205260409020549192508a81169116146106f65760405162461bcd60e51b815260040161034d9061188e565b6001600160a01b03811661070c5761070c6118db565b306001600160a01b0316896001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610754573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107789190611871565b6001600160a01b03161461078e5761078e6118db565b868514801561079c57508483145b6107fe5760405162461bcd60e51b815260206004820152602d60248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204960448201526c09cac82989288be988a9c8ea89609b1b606482015260840161034d565b604051631ac8311560e21b81526001600160a01b038a1690636b20c454906108329033908a908a908a908a90600401611923565b6020604051808303816000875af1158015610851573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108759190611967565b6108915760405162461bcd60e51b815260040161034d90611989565b6000546002546040516001600160a01b03620100009093048316926316f198319216906108f2907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908f908f908f908f908f908f90602001611a1c565b6040516020818303038152906040526040518363ffffffff1660e01b815260040161091e929190611acd565b600060405180830381600087803b15801561093857600080fd5b505af115801561094c573d6000803e3d6000fd5b50505050336001600160a01b0316896001600160a01b0316826001600160a01b03167fdd47868997765508c515e1f8ca079846dafbdfd8f98f6e76bca0891810b9f9d18b8b8b8b8b8b6040516109a796959493929190611af9565b60405180910390a4505050505050505050565b836109c481611420565b6109e05760405162461bcd60e51b815260040161034d90611826565b6000856001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a20573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a449190611871565b6001600160a01b03818116600090815260046020526040902054919250878116911614610a835760405162461bcd60e51b815260040161034d9061188e565b6001600160a01b038116610a9957610a996118db565b306001600160a01b0316866001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ae1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b059190611871565b6001600160a01b031614610b1b57610b1b6118db565b604051637a94c56560e11b81526001600160a01b0387169063f5298aca90610b4b90339088908890600401611b42565b6020604051808303816000875af1158015610b6a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8e9190611967565b610baa5760405162461bcd60e51b815260040161034d90611989565b600054600254604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152898116608083015260a0820189905260c08083018990528351808403909101815260e08301938490526316f1983160e01b909352620100009094048416936316f1983193610c429391169160e401611acd565b600060405180830381600087803b158015610c5c57600080fd5b505af1158015610c70573d6000803e3d6000fd5b50505050846001600160a01b0316866001600160a01b0316826001600160a01b03167fda58d73e08ba3c7767de904e68d208be8291494a9faaf70a6d4f736d24ec1f0f338888604051610cc593929190611b42565b60405180910390a4505050505050565b600080808080610ce786880188611b63565b6001600160a01b03808616600090815260046020526040902054959a50939850919650945092501680610d2c5760405162461bcd60e51b815260040161034d9061188e565b610d3581611420565b610d4157610d416118db565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d81573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610da59190611871565b9050866001600160a01b0316816001600160a01b031614610dc857610dc86118db565b6001600160a01b038116610dde57610dde6118db565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e26573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e4a9190611871565b6001600160a01b031614610e6057610e606118db565b604051630ab714fb60e11b81526001600160a01b0383169063156e29f690610e9090889088908890600401611b42565b6020604051808303816000875af1158015610eaf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ed39190611967565b610eef5760405162461bcd60e51b815260040161034d90611bbe565b846001600160a01b0316826001600160a01b0316886001600160a01b03167f76a4cd47013e7e58abe817eedbb692d1614d71080b527412ebfb3f91a085a6de8988886040516109a793929190611b42565b600080808080610f5286880188611cdc565b6001600160a01b03808616600090815260046020526040902054959b509399509197509550935016905080610f995760405162461bcd60e51b815260040161034d9061188e565b610fa281611420565b610fae57610fae6118db565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fee573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110129190611871565b9050866001600160a01b0316816001600160a01b031614611035576110356118db565b6001600160a01b03811661104b5761104b6118db565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015611093573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b79190611871565b6001600160a01b0316146110cd576110cd6118db565b604051635712868360e01b81526001600160a01b038316906357128683906110fd90889088908890600401611e21565b6020604051808303816000875af115801561111c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111409190611967565b61115c5760405162461bcd60e51b815260040161034d90611bbe565b856001600160a01b0316826001600160a01b0316886001600160a01b03167fc244cac13e0edc863dc5a2c3bd2b935f605b2ed225fb052eaef869bc24b76fde8888886040516109a793929190611e21565b6000806111bc83850185611e96565b9093509150506001600160a01b0382166111d8576111d86118db565b6001600160a01b038281166000908152600460205260409020541615611200576112006118db565b6003546040516bffffffffffffffffffffffff19606085901b166020820152600091611250916001600160a01b0390911690603401604051602081830303815290604052805190602001206114b5565b6001600160a01b0384811660009081526004602081905260409182902080546001600160a01b031916938516938417905590516379ccf11760e11b8152929350909163f399e22e916112a6918791879101611acd565b600060405180830381600087803b1580156112c057600080fd5b505af11580156112d4573d6000803e3d6000fd5b50506040516001600160a01b038085169350861691507f0a1eaf9aa124c3f84c9dd77f7016af0f16f67639abb913af1697387db01f5ca590600090a35050505050565b6001600160a01b0384161580159061133757506001600160a01b03831615155b801561134b57506001600160a01b03821615155b801561135f57506001600160a01b03811615155b6113c55760405162461bcd60e51b815260206004820152603160248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204260448201527020a22fa4a724aa24a0a624ad20aa24a7a760791b606482015260840161034d565b600080546001600160a01b03958616620100000262010000600160b01b0319909116179055600180549385166001600160a01b0319948516179055600280549285169284169290921790915560038054919093169116179055565b6000816001600160a01b03163b60000361143c57506000919050565b6040516301ffc9a760e01b8152636cdb3d1360e11b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa9250505080156114a3575060408051601f3d908101601f191682019092526114a091810190611967565b60015b6114af57506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b0381166114af5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b604482015260640161034d565b6001600160a01b038116811461156157600080fd5b50565b60006020828403121561157657600080fd5b81356115818161154c565b9392505050565b60008083601f84011261159a57600080fd5b5081356001600160401b038111156115b157600080fd5b6020830191508360208260051b85010111156115cc57600080fd5b9250929050565b60008060008060008060006080888a0312156115ee57600080fd5b87356115f98161154c565b965060208801356001600160401b038082111561161557600080fd5b6116218b838c01611588565b909850965060408a013591508082111561163a57600080fd5b6116468b838c01611588565b909650945060608a013591508082111561165f57600080fd5b5061166c8a828b01611588565b989b979a50959850939692959293505050565b60008060006060848603121561169457600080fd5b833561169f8161154c565b95602085013595506040909401359392505050565b600080600080608085870312156116ca57600080fd5b84356116d58161154c565b935060208501356116e58161154c565b93969395505050506040820135916060013590565b6000806000806060858703121561171057600080fd5b8435935060208501356117228161154c565b925060408501356001600160401b038082111561173e57600080fd5b818701915087601f83011261175257600080fd5b81358181111561176157600080fd5b88602082850101111561177357600080fd5b95989497505060200194505050565b6000806000806080858703121561179857600080fd5b84356117a38161154c565b935060208501356117b38161154c565b925060408501356117c38161154c565b915060608501356117d38161154c565b939692955090935050565b600080858511156117ee57600080fd5b838611156117fb57600080fd5b5050820193919092039150565b803560208310156114af57600019602084900360031b1b1692915050565b6020808252602b908201527f4368696c644d696e7461626c65455243313135355072656469636174653a204e60408201526a13d517d0d3d395149050d560aa1b606082015260800190565b60006020828403121561188357600080fd5b81516115818161154c565b6020808252602d908201527f4368696c644d696e7461626c65455243313135355072656469636174653a205560408201526c2726a0a82822a22faa27a5a2a760991b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b0383111561190a57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b038616815260606020820181905260009061194890830186886118f1565b828103604084015261195b8185876118f1565b98975050505050505050565b60006020828403121561197957600080fd5b8151801515811461158157600080fd5b6020808252602a908201527f4368696c644d696e7461626c65455243313135355072656469636174653a204260408201526915549397d1905253115160b21b606082015260800190565b8183526000602080850194508260005b85811015611a115781356119f68161154c565b6001600160a01b0316875295820195908201906001016119e3565b509495945050505050565b8981526001600160a01b0389811660208301528816604082015260c060608201819052600090611a4f908301888a6119d3565b8281036080840152611a628187896118f1565b905082810360a0840152611a778185876118f1565b9c9b505050505050505050505050565b6000815180845260005b81811015611aad57602081850181015186830182015201611a91565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b0383168152604060208201819052600090611af190830184611a87565b949350505050565b606081526000611b0d60608301888a6119d3565b8281036020840152611b208187896118f1565b90508281036040840152611b358185876118f1565b9998505050505050505050565b6001600160a01b039390931683526020830191909152604082015260600190565b600080600080600060a08688031215611b7b57600080fd5b8535611b868161154c565b94506020860135611b968161154c565b93506040860135611ba68161154c565b94979396509394606081013594506080013592915050565b6020808252602a908201527f4368696c644d696e7461626c65455243313135355072656469636174653a204d60408201526912539517d1905253115160b21b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611c4657611c46611c08565b604052919050565b60006001600160401b03821115611c6757611c67611c08565b5060051b60200190565b600082601f830112611c8257600080fd5b81356020611c97611c9283611c4e565b611c1e565b82815260059290921b84018101918181019086841115611cb657600080fd5b8286015b84811015611cd15780358352918301918301611cba565b509695505050505050565b60008060008060008060c08789031215611cf557600080fd5b86359550602080880135611d088161154c565b95506040880135611d188161154c565b945060608801356001600160401b0380821115611d3457600080fd5b818a0191508a601f830112611d4857600080fd5b8135611d56611c9282611c4e565b81815260059190911b8301840190848101908d831115611d7557600080fd5b938501935b82851015611d9c578435611d8d8161154c565b82529385019390850190611d7a565b9750505060808a0135925080831115611db457600080fd5b611dc08b848c01611c71565b945060a08a0135925080831115611dd657600080fd5b5050611de489828a01611c71565b9150509295509295509295565b600081518084526020808501945080840160005b83811015611a1157815187529582019590820190600101611e05565b606080825284519082018190526000906020906080840190828801845b82811015611e635781516001600160a01b031684529284019290840190600101611e3e565b50505083810382850152611e778187611df1565b9150508281036040840152611e8c8185611df1565b9695505050505050565b600080600060608486031215611eab57600080fd5b83359250602080850135611ebe8161154c565b925060408501356001600160401b0380821115611eda57600080fd5b818701915087601f830112611eee57600080fd5b813581811115611f0057611f00611c08565b611f12601f8201601f19168501611c1e565b91508082528884828501011115611f2857600080fd5b8084840185840137600084828401015250809350505050925092509256fea26469706673582212202548c33121341dfe54a3591ddfb48a0522912106419666b488c71144be4fd23264736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100d55760003560e01c8063b8cd3ec011610087578063b8cd3ec0146101b6578063c5ac2b1c146101c9578063cb10f94c146101f0578063d41f177114610209578063d7c9e3ec14610230578063f43cda8b14610257578063f64512551461026a578063f8c8765e1461029157600080fd5b8063051eb2e2146100da5780637efab4f51461010a57806386937eb41461013357806395c7041c14610148578063b17680651461015b578063b5c5f67214610190578063b68ad1e4146101a3575b600080fd5b6002546100ed906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ed610118366004611564565b6004602052600090815260409020546001600160a01b031681565b6101466101413660046115d3565b6102a4565b005b6001546100ed906001600160a01b031681565b6101827f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286981565b604051908152602001610101565b61014661019e36600461167f565b6102bc565b6003546100ed906001600160a01b031681565b6101466101c43660046116b4565b6102cd565b6101827faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d1881565b6000546100ed906201000090046001600160a01b031681565b6101827f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f82181565b6101827f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed281565b6101466102653660046116fa565b6102df565b6101827f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad81565b61014661029f366004611782565b610514565b6102b38787878787878761062d565b50505050505050565b6102c8833384846109ba565b505050565b6102d9848484846109ba565b50505050565b6001546001600160a01b031633146103565760405162461bcd60e51b815260206004820152602f60248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204f60448201526e27262cafa2ac24aa2fa422a62822a960891b60648201526084015b60405180910390fd5b6002546001600160a01b038481169116146103ce5760405162461bcd60e51b815260206004820152603260248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204f6044820152714e4c595f524f4f545f50524544494341544560701b606482015260840161034d565b7f87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f8216103fd6020600084866117de565b61040691611808565b036104255761042061041b82602081866117de565b610cd5565b6102d9565b7faf50c8eab81226bc79eee3a10e3fe25db1a2be7241130e392b0675df839b6d186104546020600084866117de565b61045d91611808565b0361046c576104208282610f40565b7f2cef46a936bdc5b7e6e8c71aa04560c41cf7d88bb26901a7e7f4936ff02accad61049b6020600084866117de565b6104a491611808565b036104b35761042082826111ad565b60405162461bcd60e51b815260206004820152603060248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204960448201526f4e56414c49445f5349474e415455524560801b606482015260840161034d565b600054610100900460ff16158080156105345750600054600160ff909116105b8061054e5750303b15801561054e575060005460ff166001145b6105b15760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161034d565b6000805460ff1916600117905580156105d4576000805461ff0019166101001790555b6105e085858585611317565b8015610626576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b8661063781611420565b6106535760405162461bcd60e51b815260040161034d90611826565b6000886001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610693573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106b79190611871565b6001600160a01b038181166000908152600460205260409020549192508a81169116146106f65760405162461bcd60e51b815260040161034d9061188e565b6001600160a01b03811661070c5761070c6118db565b306001600160a01b0316896001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610754573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107789190611871565b6001600160a01b03161461078e5761078e6118db565b868514801561079c57508483145b6107fe5760405162461bcd60e51b815260206004820152602d60248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204960448201526c09cac82989288be988a9c8ea89609b1b606482015260840161034d565b604051631ac8311560e21b81526001600160a01b038a1690636b20c454906108329033908a908a908a908a90600401611923565b6020604051808303816000875af1158015610851573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108759190611967565b6108915760405162461bcd60e51b815260040161034d90611989565b6000546002546040516001600160a01b03620100009093048316926316f198319216906108f2907f5fb452c5a8f2b7c7ef2984e2f1063c7ee9b80b110cdc98ccb98f6654e10b5ed290869033908f908f908f908f908f908f90602001611a1c565b6040516020818303038152906040526040518363ffffffff1660e01b815260040161091e929190611acd565b600060405180830381600087803b15801561093857600080fd5b505af115801561094c573d6000803e3d6000fd5b50505050336001600160a01b0316896001600160a01b0316826001600160a01b03167fdd47868997765508c515e1f8ca079846dafbdfd8f98f6e76bca0891810b9f9d18b8b8b8b8b8b6040516109a796959493929190611af9565b60405180910390a4505050505050505050565b836109c481611420565b6109e05760405162461bcd60e51b815260040161034d90611826565b6000856001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a20573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a449190611871565b6001600160a01b03818116600090815260046020526040902054919250878116911614610a835760405162461bcd60e51b815260040161034d9061188e565b6001600160a01b038116610a9957610a996118db565b306001600160a01b0316866001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ae1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b059190611871565b6001600160a01b031614610b1b57610b1b6118db565b604051637a94c56560e11b81526001600160a01b0387169063f5298aca90610b4b90339088908890600401611b42565b6020604051808303816000875af1158015610b6a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8e9190611967565b610baa5760405162461bcd60e51b815260040161034d90611989565b600054600254604080517f7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e98286960208201526001600160a01b0385811682840152336060830152898116608083015260a0820189905260c08083018990528351808403909101815260e08301938490526316f1983160e01b909352620100009094048416936316f1983193610c429391169160e401611acd565b600060405180830381600087803b158015610c5c57600080fd5b505af1158015610c70573d6000803e3d6000fd5b50505050846001600160a01b0316866001600160a01b0316826001600160a01b03167fda58d73e08ba3c7767de904e68d208be8291494a9faaf70a6d4f736d24ec1f0f338888604051610cc593929190611b42565b60405180910390a4505050505050565b600080808080610ce786880188611b63565b6001600160a01b03808616600090815260046020526040902054959a50939850919650945092501680610d2c5760405162461bcd60e51b815260040161034d9061188e565b610d3581611420565b610d4157610d416118db565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d81573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610da59190611871565b9050866001600160a01b0316816001600160a01b031614610dc857610dc86118db565b6001600160a01b038116610dde57610dde6118db565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e26573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e4a9190611871565b6001600160a01b031614610e6057610e606118db565b604051630ab714fb60e11b81526001600160a01b0383169063156e29f690610e9090889088908890600401611b42565b6020604051808303816000875af1158015610eaf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ed39190611967565b610eef5760405162461bcd60e51b815260040161034d90611bbe565b846001600160a01b0316826001600160a01b0316886001600160a01b03167f76a4cd47013e7e58abe817eedbb692d1614d71080b527412ebfb3f91a085a6de8988886040516109a793929190611b42565b600080808080610f5286880188611cdc565b6001600160a01b03808616600090815260046020526040902054959b509399509197509550935016905080610f995760405162461bcd60e51b815260040161034d9061188e565b610fa281611420565b610fae57610fae6118db565b6000816001600160a01b0316631f2d00656040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fee573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110129190611871565b9050866001600160a01b0316816001600160a01b031614611035576110356118db565b6001600160a01b03811661104b5761104b6118db565b306001600160a01b0316826001600160a01b031663e61987056040518163ffffffff1660e01b8152600401602060405180830381865afa158015611093573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b79190611871565b6001600160a01b0316146110cd576110cd6118db565b604051635712868360e01b81526001600160a01b038316906357128683906110fd90889088908890600401611e21565b6020604051808303816000875af115801561111c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111409190611967565b61115c5760405162461bcd60e51b815260040161034d90611bbe565b856001600160a01b0316826001600160a01b0316886001600160a01b03167fc244cac13e0edc863dc5a2c3bd2b935f605b2ed225fb052eaef869bc24b76fde8888886040516109a793929190611e21565b6000806111bc83850185611e96565b9093509150506001600160a01b0382166111d8576111d86118db565b6001600160a01b038281166000908152600460205260409020541615611200576112006118db565b6003546040516bffffffffffffffffffffffff19606085901b166020820152600091611250916001600160a01b0390911690603401604051602081830303815290604052805190602001206114b5565b6001600160a01b0384811660009081526004602081905260409182902080546001600160a01b031916938516938417905590516379ccf11760e11b8152929350909163f399e22e916112a6918791879101611acd565b600060405180830381600087803b1580156112c057600080fd5b505af11580156112d4573d6000803e3d6000fd5b50506040516001600160a01b038085169350861691507f0a1eaf9aa124c3f84c9dd77f7016af0f16f67639abb913af1697387db01f5ca590600090a35050505050565b6001600160a01b0384161580159061133757506001600160a01b03831615155b801561134b57506001600160a01b03821615155b801561135f57506001600160a01b03811615155b6113c55760405162461bcd60e51b815260206004820152603160248201527f4368696c644d696e7461626c65455243313135355072656469636174653a204260448201527020a22fa4a724aa24a0a624ad20aa24a7a760791b606482015260840161034d565b600080546001600160a01b03958616620100000262010000600160b01b0319909116179055600180549385166001600160a01b0319948516179055600280549285169284169290921790915560038054919093169116179055565b6000816001600160a01b03163b60000361143c57506000919050565b6040516301ffc9a760e01b8152636cdb3d1360e11b60048201526001600160a01b038316906301ffc9a790602401602060405180830381865afa9250505080156114a3575060408051601f3d908101601f191682019092526114a091810190611967565b60015b6114af57506000919050565b92915050565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b0381166114af5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b604482015260640161034d565b6001600160a01b038116811461156157600080fd5b50565b60006020828403121561157657600080fd5b81356115818161154c565b9392505050565b60008083601f84011261159a57600080fd5b5081356001600160401b038111156115b157600080fd5b6020830191508360208260051b85010111156115cc57600080fd5b9250929050565b60008060008060008060006080888a0312156115ee57600080fd5b87356115f98161154c565b965060208801356001600160401b038082111561161557600080fd5b6116218b838c01611588565b909850965060408a013591508082111561163a57600080fd5b6116468b838c01611588565b909650945060608a013591508082111561165f57600080fd5b5061166c8a828b01611588565b989b979a50959850939692959293505050565b60008060006060848603121561169457600080fd5b833561169f8161154c565b95602085013595506040909401359392505050565b600080600080608085870312156116ca57600080fd5b84356116d58161154c565b935060208501356116e58161154c565b93969395505050506040820135916060013590565b6000806000806060858703121561171057600080fd5b8435935060208501356117228161154c565b925060408501356001600160401b038082111561173e57600080fd5b818701915087601f83011261175257600080fd5b81358181111561176157600080fd5b88602082850101111561177357600080fd5b95989497505060200194505050565b6000806000806080858703121561179857600080fd5b84356117a38161154c565b935060208501356117b38161154c565b925060408501356117c38161154c565b915060608501356117d38161154c565b939692955090935050565b600080858511156117ee57600080fd5b838611156117fb57600080fd5b5050820193919092039150565b803560208310156114af57600019602084900360031b1b1692915050565b6020808252602b908201527f4368696c644d696e7461626c65455243313135355072656469636174653a204e60408201526a13d517d0d3d395149050d560aa1b606082015260800190565b60006020828403121561188357600080fd5b81516115818161154c565b6020808252602d908201527f4368696c644d696e7461626c65455243313135355072656469636174653a205560408201526c2726a0a82822a22faa27a5a2a760991b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b81835260006001600160fb1b0383111561190a57600080fd5b8260051b80836020870137939093016020019392505050565b6001600160a01b038616815260606020820181905260009061194890830186886118f1565b828103604084015261195b8185876118f1565b98975050505050505050565b60006020828403121561197957600080fd5b8151801515811461158157600080fd5b6020808252602a908201527f4368696c644d696e7461626c65455243313135355072656469636174653a204260408201526915549397d1905253115160b21b606082015260800190565b8183526000602080850194508260005b85811015611a115781356119f68161154c565b6001600160a01b0316875295820195908201906001016119e3565b509495945050505050565b8981526001600160a01b0389811660208301528816604082015260c060608201819052600090611a4f908301888a6119d3565b8281036080840152611a628187896118f1565b905082810360a0840152611a778185876118f1565b9c9b505050505050505050505050565b6000815180845260005b81811015611aad57602081850181015186830182015201611a91565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160a01b0383168152604060208201819052600090611af190830184611a87565b949350505050565b606081526000611b0d60608301888a6119d3565b8281036020840152611b208187896118f1565b90508281036040840152611b358185876118f1565b9998505050505050505050565b6001600160a01b039390931683526020830191909152604082015260600190565b600080600080600060a08688031215611b7b57600080fd5b8535611b868161154c565b94506020860135611b968161154c565b93506040860135611ba68161154c565b94979396509394606081013594506080013592915050565b6020808252602a908201527f4368696c644d696e7461626c65455243313135355072656469636174653a204d60408201526912539517d1905253115160b21b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715611c4657611c46611c08565b604052919050565b60006001600160401b03821115611c6757611c67611c08565b5060051b60200190565b600082601f830112611c8257600080fd5b81356020611c97611c9283611c4e565b611c1e565b82815260059290921b84018101918181019086841115611cb657600080fd5b8286015b84811015611cd15780358352918301918301611cba565b509695505050505050565b60008060008060008060c08789031215611cf557600080fd5b86359550602080880135611d088161154c565b95506040880135611d188161154c565b945060608801356001600160401b0380821115611d3457600080fd5b818a0191508a601f830112611d4857600080fd5b8135611d56611c9282611c4e565b81815260059190911b8301840190848101908d831115611d7557600080fd5b938501935b82851015611d9c578435611d8d8161154c565b82529385019390850190611d7a565b9750505060808a0135925080831115611db457600080fd5b611dc08b848c01611c71565b945060a08a0135925080831115611dd657600080fd5b5050611de489828a01611c71565b9150509295509295509295565b600081518084526020808501945080840160005b83811015611a1157815187529582019590820190600101611e05565b606080825284519082018190526000906020906080840190828801845b82811015611e635781516001600160a01b031684529284019290840190600101611e3e565b50505083810382850152611e778187611df1565b9150508281036040840152611e8c8185611df1565b9695505050505050565b600080600060608486031215611eab57600080fd5b83359250602080850135611ebe8161154c565b925060408501356001600160401b0380821115611eda57600080fd5b818701915087601f830112611eee57600080fd5b813581811115611f0057611f00611c08565b611f12601f8201601f19168501611c1e565b91508082528884828501011115611f2857600080fd5b8084840185840137600084828401015250809350505050925092509256fea26469706673582212202548c33121341dfe54a3591ddfb48a0522912106419666b488c71144be4fd23264736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var CustomSupernetManagerArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"CustomSupernetManager","sourceName":"contracts/root/staking/CustomSupernetManager.sol","abi":[{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"InvalidSignature","type":"error"},{"inputs":[{"internalType":"string","name":"message","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"}],"name":"AddedToWhitelist","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"GenesisBalanceAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amountValidators","type":"uint256"}],"name":"GenesisFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"}],"name":"RemovedFromWhitelist","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"}],"name":"ValidatorDeactivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256[4]","name":"blsKey","type":"uint256[4]"}],"name":"ValidatorRegistered","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"addGenesisBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domain","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"enableStaking","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"finalizeGenesis","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"genesisBalances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"genesisSet","outputs":[{"components":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"initialStake","type":"uint256"}],"internalType":"struct GenesisValidator[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"validator_","type":"address"}],"name":"getValidator","outputs":[{"components":[{"internalType":"uint256[4]","name":"blsKey","type":"uint256[4]"},{"internalType":"uint256","name":"stake","type":"uint256"},{"internalType":"bool","name":"isWhitelisted","type":"bool"},{"internalType":"bool","name":"isActive","type":"bool"}],"internalType":"struct Validator","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"id","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newStakeManager","type":"address"},{"internalType":"address","name":"newBls","type":"address"},{"internalType":"address","name":"newStateSender","type":"address"},{"internalType":"address","name":"newMatic","type":"address"},{"internalType":"address","name":"newChildValidatorSet","type":"address"},{"internalType":"address","name":"newExitHelper","type":"address"},{"internalType":"address","name":"newRootERC20Predicate","type":"address"},{"internalType":"string","name":"newDomain","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id_","type":"uint256"}],"name":"onInit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onL2StateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"onStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[2]","name":"signature","type":"uint256[2]"},{"internalType":"uint256[4]","name":"pubkey","type":"uint256[4]"}],"name":"register","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"validators","outputs":[{"internalType":"uint256","name":"stake","type":"uint256"},{"internalType":"bool","name":"isWhitelisted","type":"bool"},{"internalType":"bool","name":"isActive","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"validators_","type":"address[]"}],"name":"whitelistValidators","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506121a0806100206000396000f3fe608060405234801561001057600080fd5b506004361061012c5760003560e01c80638da5cb5b116100ad578063e30c397811610071578063e30c397814610248578063e53b87a814610259578063f2fde38b14610279578063f43cda8b1461028c578063fa52c7d81461029f57600080fd5b80638da5cb5b146101e65780639e19e9c11461020b578063af640d0f14610220578063c2fb26a614610237578063d11aca621461024057600080fd5b806379ba5097116100f457806379ba50971461019d578063819dc9fb146101a557806385758cc7146101b857806386202194146101c05780638c087b1c146101d357600080fd5b80631904bb2e146101315780631f6590b81461015a578063344a5e641461016f578063542c85f214610182578063715018a614610195575b600080fd5b61014461013f366004611a3d565b6102f2565b6040516101519190611a5a565b60405180910390f35b61016d610168366004611aae565b61037c565b005b61016d61017d366004611af0565b610449565b61016d610190366004611b09565b6104c8565b61016d610523565b61016d610537565b61016d6101b3366004611b94565b6105b1565b61016d610819565b61016d6101ce366004611af0565b61086a565b61016d6101e1366004611cc8565b610b17565b6033546001600160a01b03165b6040516001600160a01b039091168152602001610151565b610213610b4f565b6040516101519190611cf4565b61022960985481565b604051908152602001610151565b61022960d05481565b61016d610b60565b6065546001600160a01b03166101f3565b610229610267366004611a3d565b60d66020526000908152604090205481565b61016d610287366004611a3d565b610b9d565b61016d61029a366004611d4c565b610c0e565b6102d56102ad366004611a3d565b60d4602052600090815260409020600481015460059091015460ff8082169161010090041683565b604080519384529115156020840152151590820152606001610151565b6102fa61196b565b6001600160a01b038216600090815260d46020526040908190208151610100810190925281608081018260048282826020028201915b8154815260200190600101908083116103305750505091835250506004820154602082015260059091015460ff8082161515604084015261010090910416151560609091015292915050565b33600090815260d460205260409020600581015460ff166103d15760405163973d02cb60e01b815260206004820152600960248201526815d2125511531254d560ba1b60448201526064015b60405180910390fd5b6103dc338484610ce2565b6103e881836004611999565b5060058101805461ff00191661010017905561040333610dda565b336001600160a01b03167f110ae3ae039a4632f836a35dc9b1282df88780883fb28779f22c2f7ccde8dd0a8360405161043c9190611dd5565b60405180910390a2505050565b6097546001600160a01b031633146104735760405162461bcd60e51b81526004016103c890611de4565b609854156104c35760405162461bcd60e51b815260206004820152601f60248201527f53757065726e65744d616e616765723a2049445f414c52454144595f5345540060448201526064016103c8565b609855565b6104d0610e26565b8060005b8181101561051d5761050b8484838181106104f1576104f1611e27565b90506020020160208101906105069190611a3d565b610e80565b8061051581611e53565b9150506104d4565b50505050565b61052b610e26565b6105356000610ecf565b565b60655433906001600160a01b031681146105a55760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016103c8565b6105ae81610ecf565b50565b600054610100900460ff16158080156105d15750600054600160ff909116105b806105eb5750303b1580156105eb575060005460ff166001145b61064e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016103c8565b6000805460ff191660011790558015610671576000805461ff0019166101001790555b6001600160a01b0389161580159061069157506001600160a01b03881615155b80156106a557506001600160a01b03871615155b80156106b957506001600160a01b03861615155b80156106cd57506001600160a01b03851615155b80156106e157506001600160a01b03841615155b80156106ed5750815115155b6107295760405162461bcd60e51b815260206004820152600d60248201526c1253959053125117d253941555609a1b60448201526064016103c8565b61073289610ee8565b60cb80546001600160a01b03199081166001600160a01b038b81169190911790925560cc805482168a841617905560cd8054821689841617905560ce8054821688841617905560cf8054821687841617905560d580549091169185169190911790556040516107a5908390602001611e90565b60408051601f19818403018152919052805160209091012060d0556107c8610f31565b801561080e576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b610821610e26565b61082b60d1610f60565b7f87f41ee3facb6317b1c2811e539539ac1693525b4460699b4245e8aac9f590cb61085660d1610fda565b5160405190815260200160405180910390a1565b600081116108c85760405162461bcd60e51b815260206004820152602560248201527f437573746f6d53757065726e65744d616e616765723a20494e56414c49445f416044820152641353d5539560da1b60648201526084016103c8565b60d5546001600160a01b031661093f5760405163973d02cb60e01b815260206004820152603560248201527f437573746f6d53757065726e65744d616e616765723a20554e444546494e45446044820152745f524f4f545f45524332305f50524544494341544560581b60648201526084016103c8565b60d55460408051636a00c1d760e01b815290516000926001600160a01b031691636a00c1d7916004808301926020929190829003018187875af115801561098a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ae9190611eac565b90506001600160a01b038116610a225760405163973d02cb60e01b815260206004820152603260248201527f437573746f6d53757065726e65744d616e616765723a20554e444546494e454460448201527117d3905512559157d513d2d15397d493d3d560721b60648201526084016103c8565b610a2c60d1611053565b15610a995760405162461bcd60e51b815260206004820152603760248201527f437573746f6d53757065726e65744d616e616765723a2047454e455349535f53604482015276115517d254d7d053149150511657d19253905312569151604a1b60648201526084016103c8565b610aa660d1336000611078565b33600090815260d6602052604081208054849290610ac5908490611ec9565b909155505060d554610ae6906001600160a01b038381169133911685611193565b604051829033907f1706c887e0f7786a7e97fed7b6f19bd6b31854c47c2012b072b44fdbd652623990600090a35050565b6097546001600160a01b03163314610b415760405162461bcd60e51b81526004016103c890611de4565b610b4b82826111ed565b5050565b6060610b5b60d1610fda565b905090565b610b68610e26565b610b7260d161136b565b6040517fda917aeab736a19e4ba54207413dbe4f8c7d558fde2c14d7e66fc8f7186ea8a390600090a1565b610ba5610e26565b606580546001600160a01b0383166001600160a01b03199091168117909155610bd66033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60cf546001600160a01b031633141580610c36575060ce546001600160a01b03848116911614155b15610c725760405163973d02cb60e01b815260206004820152600b60248201526a2fb2bc34ba2432b63832b960a91b60448201526064016103c8565b7f8ca9a95e41b5eece253c93f5b31eed1253aed6b145d8a6e14d913fdf8e732293610ca1602060008486611edc565b610caa91611f06565b0361051d57600080610cbf8360208187611edc565b810190610ccc9190611cc8565b91509150610cda8282611442565b505050505050565b8135158015610cf357506020820135155b15610d1c57604051633615713d60e21b81526001600160a01b03841660048201526024016103c8565b60cb5460009081906001600160a01b031663ebbdac918585610d3d896114b1565b6040518463ffffffff1660e01b8152600401610d5b93929190611f24565b6040805180830381865afa158015610d77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9b9190611f7d565b91509150801580610daa575081155b15610dd357604051633615713d60e21b81526001600160a01b03861660048201526024016103c8565b5050505050565b6001600160a01b038116600081815260d46020526040808220600501805460ff19169055517fcdd2e9b91a56913d370075169cefa1602ba36be5301664f752192bb1709df7579190a250565b6033546001600160a01b031633146105355760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103c8565b6001600160a01b038116600081815260d46020526040808220600501805460ff19166001179055517fa850ae9193f515cbae8d35e8925bd2be26627fc91bce650b8652ed254e9cab039190a250565b606580546001600160a01b03191690556105ae81611573565b600054610100900460ff16610f0f5760405162461bcd60e51b81526004016103c890611fb0565b609780546001600160a01b0319166001600160a01b0392909216919091179055565b600054610100900460ff16610f585760405162461bcd60e51b81526004016103c890611fb0565b6105356115c5565b6000600182015460ff166002811115610f7b57610f7b611ffb565b14610fc85760405162461bcd60e51b815260206004820152601d60248201527f47656e657369734c69623a20616c72656164792066696e616c697a656400000060448201526064016103c8565b6001908101805460ff19169091179055565b606081600001805480602002602001604051908101604052809291908181526020016000905b82821015611048576000848152602090819020604080518082019091526002850290910180546001600160a01b03168252600190810154828401529083529092019101611000565b505050509050919050565b600060025b600183015460ff16600281111561107157611071611ffb565b1492915050565b6000600184015460ff16600281111561109357611093611ffb565b146110a0576110a0612011565b6001600160a01b03821660009081526002840160205260408120549081900361113d5783546110d0906001611ec9565b6001600160a01b03848116600081815260028881016020908152604080842087905580518082019091529384528381018881528a5460018082018d558c86529290942094519390920290930180546001600160a01b0319169290941691909117835551910155905061051d565b600061114985856115f5565b9050600085600001828154811061116257611162611e27565b90600052602060002090600202019050838160010160008282546111869190611ec9565b9091555050505050505050565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b17905261051d908590611632565b6001600160a01b038216600090815260d460205260409020600501548290610100900460ff1661124c5760405163973d02cb60e01b81526020600482015260096024820152682b20a624a220aa27a960b91b60448201526064016103c8565b61125660d1611707565b1561126c5761126760d18484611078565b505050565b61127660d1611053565b1561132f5760cc5460ce54604080517f1bcc0f4c3fad314e585165815f94ecca9b96690a26d6417d7876448a9a867a6960208201526001600160a01b038781168284015260608083018890528351808403909101815260808301938490526316f1983160e01b909352938416936316f19831936112f893911691608401612053565b600060405180830381600087803b15801561131257600080fd5b505af1158015611326573d6000803e3d6000fd5b50505050505050565b60405163973d02cb60e01b815260206004820152601060248201526f5761697420666f722067656e6573697360801b60448201526064016103c8565b600181015460ff16600081600281111561138757611387611ffb565b036113d05760405162461bcd60e51b815260206004820152601960248201527811d95b995cda5cd31a588e881b9bdd08199a5b985b1a5e9959603a1b60448201526064016103c8565b60028160028111156113e4576113e4611ffb565b036114315760405162461bcd60e51b815260206004820152601b60248201527f47656e657369734c69623a20616c726561647920656e61626c6564000000000060448201526064016103c8565b50600101805460ff19166002179055565b609754604051633651bb1d60e01b81526001600160a01b0384811660048301526024820184905290911690633651bb1d90604401600060405180830381600087803b15801561149057600080fd5b505af11580156114a4573d6000803e3d6000fd5b50505050610b4b8261170f565b6114b96119d7565b60cb5460d0546040516bffffffffffffffffffffffff19606086811b8216602084015230901b1660348201524660488201526001600160a01b039092169163a850a90991906068016040516020818303038152906040526040518363ffffffff1660e01b815260040161152d929190612077565b6040805180830381865afa158015611549573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061156d9190612090565b92915050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600054610100900460ff166115ec5760405162461bcd60e51b81526004016103c890611fb0565b61053533610ecf565b6001600160a01b03811660009081526002830160205260408120549081900361162057611620612011565b61162b600182612110565b9392505050565b6000611687826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166117db9092919063ffffffff16565b90508051600014806116a85750808060200190518101906116a89190612123565b6112675760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016103c8565b600080611058565b609754609854604051633b61c5a760e21b81526001600160a01b038481166004830152602482019290925291169063ed87169c90604401602060405180830381865afa158015611763573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611787919061213e565b6000036105ae576001600160a01b038116600081815260d46020526040808220600501805461ff0019169055517f23d934bfe7f1275bc6fd70432159c9cc1c0075d069f89da6a40f43bfe7a94ed39190a250565b60606117ea84846000856117f2565b949350505050565b6060824710156118535760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016103c8565b600080866001600160a01b0316858760405161186f9190611e90565b60006040518083038185875af1925050503d80600081146118ac576040519150601f19603f3d011682016040523d82523d6000602084013e6118b1565b606091505b50915091506118c2878383876118cd565b979650505050505050565b6060831561193c578251600003611935576001600160a01b0385163b6119355760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016103c8565b50816117ea565b6117ea83838151156119515781518083602001fd5b8060405162461bcd60e51b81526004016103c89190612157565b604051806080016040528061197e6119f5565b81526000602082018190526040820181905260609091015290565b82600481019282156119c7579160200282015b828111156119c75782358255916020019190600101906119ac565b506119d3929150611a13565b5090565b60405180604001604052806002906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b5b808211156119d35760008155600101611a14565b6001600160a01b03811681146105ae57600080fd5b600060208284031215611a4f57600080fd5b813561162b81611a28565b815160e08201908260005b6004811015611a84578251825260209283019290910190600101611a65565b505050602083015160808301526040830151151560a0830152606090920151151560c09091015290565b60008060c08385031215611ac157600080fd5b6040830184811115611ad257600080fd5b8392508460c085011115611ae557600080fd5b809150509250929050565b600060208284031215611b0257600080fd5b5035919050565b60008060208385031215611b1c57600080fd5b823567ffffffffffffffff80821115611b3457600080fd5b818501915085601f830112611b4857600080fd5b813581811115611b5757600080fd5b8660208260051b8501011115611b6c57600080fd5b60209290920196919550909350505050565b634e487b7160e01b600052604160045260246000fd5b600080600080600080600080610100898b031215611bb157600080fd5b8835611bbc81611a28565b97506020890135611bcc81611a28565b96506040890135611bdc81611a28565b95506060890135611bec81611a28565b94506080890135611bfc81611a28565b935060a0890135611c0c81611a28565b925060c0890135611c1c81611a28565b915060e089013567ffffffffffffffff80821115611c3957600080fd5b818b0191508b601f830112611c4d57600080fd5b813581811115611c5f57611c5f611b7e565b604051601f8201601f19908116603f01168101908382118183101715611c8757611c87611b7e565b816040528281528e6020848701011115611ca057600080fd5b8260208601602083013760006020848301015280955050505050509295985092959890939650565b60008060408385031215611cdb57600080fd5b8235611ce681611a28565b946020939093013593505050565b602080825282518282018190526000919060409081850190868401855b82811015611d3f57815180516001600160a01b03168552860151868501529284019290850190600101611d11565b5091979650505050505050565b60008060008060608587031215611d6257600080fd5b843593506020850135611d7481611a28565b9250604085013567ffffffffffffffff80821115611d9157600080fd5b818701915087601f830112611da557600080fd5b813581811115611db457600080fd5b886020828501011115611dc657600080fd5b95989497505060200194505050565b60808181019083833792915050565b60208082526023908201527f53757065726e65744d616e616765723a204f4e4c595f5354414b455f4d414e4160408201526223a2a960e91b606082015260800190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b600060018201611e6557611e65611e3d565b5060010190565b60005b83811015611e87578181015183820152602001611e6f565b50506000910152565b60008251611ea2818460208701611e6c565b9190910192915050565b600060208284031215611ebe57600080fd5b815161162b81611a28565b8082018082111561156d5761156d611e3d565b60008085851115611eec57600080fd5b83861115611ef957600080fd5b5050820193919092039150565b8035602083101561156d57600019602084900360031b1b1692915050565b61010081016040858337608084604084013760c082018360005b6002811015611f5d578151835260209283019290910190600101611f3e565b505050949350505050565b80518015158114611f7857600080fd5b919050565b60008060408385031215611f9057600080fd5b611f9983611f68565b9150611fa760208401611f68565b90509250929050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052600160045260246000fd5b6000815180845261203f816020860160208601611e6c565b601f01601f19169290920160200192915050565b6001600160a01b03831681526040602082018190526000906117ea90830184612027565b8281526040602082015260006117ea6040830184612027565b6000604082840312156120a257600080fd5b82601f8301126120b157600080fd5b6040516040810181811067ffffffffffffffff821117156120d4576120d4611b7e565b80604052508060408401858111156120eb57600080fd5b845b818110156121055780518352602092830192016120ed565b509195945050505050565b8181038181111561156d5761156d611e3d565b60006020828403121561213557600080fd5b61162b82611f68565b60006020828403121561215057600080fd5b5051919050565b60208152600061162b602083018461202756fea2646970667358221220c854c4bd92b5e40db8e0e9b90d5bc01c31cbec4f50c64ece828835f89a96927664736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061012c5760003560e01c80638da5cb5b116100ad578063e30c397811610071578063e30c397814610248578063e53b87a814610259578063f2fde38b14610279578063f43cda8b1461028c578063fa52c7d81461029f57600080fd5b80638da5cb5b146101e65780639e19e9c11461020b578063af640d0f14610220578063c2fb26a614610237578063d11aca621461024057600080fd5b806379ba5097116100f457806379ba50971461019d578063819dc9fb146101a557806385758cc7146101b857806386202194146101c05780638c087b1c146101d357600080fd5b80631904bb2e146101315780631f6590b81461015a578063344a5e641461016f578063542c85f214610182578063715018a614610195575b600080fd5b61014461013f366004611a3d565b6102f2565b6040516101519190611a5a565b60405180910390f35b61016d610168366004611aae565b61037c565b005b61016d61017d366004611af0565b610449565b61016d610190366004611b09565b6104c8565b61016d610523565b61016d610537565b61016d6101b3366004611b94565b6105b1565b61016d610819565b61016d6101ce366004611af0565b61086a565b61016d6101e1366004611cc8565b610b17565b6033546001600160a01b03165b6040516001600160a01b039091168152602001610151565b610213610b4f565b6040516101519190611cf4565b61022960985481565b604051908152602001610151565b61022960d05481565b61016d610b60565b6065546001600160a01b03166101f3565b610229610267366004611a3d565b60d66020526000908152604090205481565b61016d610287366004611a3d565b610b9d565b61016d61029a366004611d4c565b610c0e565b6102d56102ad366004611a3d565b60d4602052600090815260409020600481015460059091015460ff8082169161010090041683565b604080519384529115156020840152151590820152606001610151565b6102fa61196b565b6001600160a01b038216600090815260d46020526040908190208151610100810190925281608081018260048282826020028201915b8154815260200190600101908083116103305750505091835250506004820154602082015260059091015460ff8082161515604084015261010090910416151560609091015292915050565b33600090815260d460205260409020600581015460ff166103d15760405163973d02cb60e01b815260206004820152600960248201526815d2125511531254d560ba1b60448201526064015b60405180910390fd5b6103dc338484610ce2565b6103e881836004611999565b5060058101805461ff00191661010017905561040333610dda565b336001600160a01b03167f110ae3ae039a4632f836a35dc9b1282df88780883fb28779f22c2f7ccde8dd0a8360405161043c9190611dd5565b60405180910390a2505050565b6097546001600160a01b031633146104735760405162461bcd60e51b81526004016103c890611de4565b609854156104c35760405162461bcd60e51b815260206004820152601f60248201527f53757065726e65744d616e616765723a2049445f414c52454144595f5345540060448201526064016103c8565b609855565b6104d0610e26565b8060005b8181101561051d5761050b8484838181106104f1576104f1611e27565b90506020020160208101906105069190611a3d565b610e80565b8061051581611e53565b9150506104d4565b50505050565b61052b610e26565b6105356000610ecf565b565b60655433906001600160a01b031681146105a55760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016103c8565b6105ae81610ecf565b50565b600054610100900460ff16158080156105d15750600054600160ff909116105b806105eb5750303b1580156105eb575060005460ff166001145b61064e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016103c8565b6000805460ff191660011790558015610671576000805461ff0019166101001790555b6001600160a01b0389161580159061069157506001600160a01b03881615155b80156106a557506001600160a01b03871615155b80156106b957506001600160a01b03861615155b80156106cd57506001600160a01b03851615155b80156106e157506001600160a01b03841615155b80156106ed5750815115155b6107295760405162461bcd60e51b815260206004820152600d60248201526c1253959053125117d253941555609a1b60448201526064016103c8565b61073289610ee8565b60cb80546001600160a01b03199081166001600160a01b038b81169190911790925560cc805482168a841617905560cd8054821689841617905560ce8054821688841617905560cf8054821687841617905560d580549091169185169190911790556040516107a5908390602001611e90565b60408051601f19818403018152919052805160209091012060d0556107c8610f31565b801561080e576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b610821610e26565b61082b60d1610f60565b7f87f41ee3facb6317b1c2811e539539ac1693525b4460699b4245e8aac9f590cb61085660d1610fda565b5160405190815260200160405180910390a1565b600081116108c85760405162461bcd60e51b815260206004820152602560248201527f437573746f6d53757065726e65744d616e616765723a20494e56414c49445f416044820152641353d5539560da1b60648201526084016103c8565b60d5546001600160a01b031661093f5760405163973d02cb60e01b815260206004820152603560248201527f437573746f6d53757065726e65744d616e616765723a20554e444546494e45446044820152745f524f4f545f45524332305f50524544494341544560581b60648201526084016103c8565b60d55460408051636a00c1d760e01b815290516000926001600160a01b031691636a00c1d7916004808301926020929190829003018187875af115801561098a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ae9190611eac565b90506001600160a01b038116610a225760405163973d02cb60e01b815260206004820152603260248201527f437573746f6d53757065726e65744d616e616765723a20554e444546494e454460448201527117d3905512559157d513d2d15397d493d3d560721b60648201526084016103c8565b610a2c60d1611053565b15610a995760405162461bcd60e51b815260206004820152603760248201527f437573746f6d53757065726e65744d616e616765723a2047454e455349535f53604482015276115517d254d7d053149150511657d19253905312569151604a1b60648201526084016103c8565b610aa660d1336000611078565b33600090815260d6602052604081208054849290610ac5908490611ec9565b909155505060d554610ae6906001600160a01b038381169133911685611193565b604051829033907f1706c887e0f7786a7e97fed7b6f19bd6b31854c47c2012b072b44fdbd652623990600090a35050565b6097546001600160a01b03163314610b415760405162461bcd60e51b81526004016103c890611de4565b610b4b82826111ed565b5050565b6060610b5b60d1610fda565b905090565b610b68610e26565b610b7260d161136b565b6040517fda917aeab736a19e4ba54207413dbe4f8c7d558fde2c14d7e66fc8f7186ea8a390600090a1565b610ba5610e26565b606580546001600160a01b0383166001600160a01b03199091168117909155610bd66033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60cf546001600160a01b031633141580610c36575060ce546001600160a01b03848116911614155b15610c725760405163973d02cb60e01b815260206004820152600b60248201526a2fb2bc34ba2432b63832b960a91b60448201526064016103c8565b7f8ca9a95e41b5eece253c93f5b31eed1253aed6b145d8a6e14d913fdf8e732293610ca1602060008486611edc565b610caa91611f06565b0361051d57600080610cbf8360208187611edc565b810190610ccc9190611cc8565b91509150610cda8282611442565b505050505050565b8135158015610cf357506020820135155b15610d1c57604051633615713d60e21b81526001600160a01b03841660048201526024016103c8565b60cb5460009081906001600160a01b031663ebbdac918585610d3d896114b1565b6040518463ffffffff1660e01b8152600401610d5b93929190611f24565b6040805180830381865afa158015610d77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9b9190611f7d565b91509150801580610daa575081155b15610dd357604051633615713d60e21b81526001600160a01b03861660048201526024016103c8565b5050505050565b6001600160a01b038116600081815260d46020526040808220600501805460ff19169055517fcdd2e9b91a56913d370075169cefa1602ba36be5301664f752192bb1709df7579190a250565b6033546001600160a01b031633146105355760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103c8565b6001600160a01b038116600081815260d46020526040808220600501805460ff19166001179055517fa850ae9193f515cbae8d35e8925bd2be26627fc91bce650b8652ed254e9cab039190a250565b606580546001600160a01b03191690556105ae81611573565b600054610100900460ff16610f0f5760405162461bcd60e51b81526004016103c890611fb0565b609780546001600160a01b0319166001600160a01b0392909216919091179055565b600054610100900460ff16610f585760405162461bcd60e51b81526004016103c890611fb0565b6105356115c5565b6000600182015460ff166002811115610f7b57610f7b611ffb565b14610fc85760405162461bcd60e51b815260206004820152601d60248201527f47656e657369734c69623a20616c72656164792066696e616c697a656400000060448201526064016103c8565b6001908101805460ff19169091179055565b606081600001805480602002602001604051908101604052809291908181526020016000905b82821015611048576000848152602090819020604080518082019091526002850290910180546001600160a01b03168252600190810154828401529083529092019101611000565b505050509050919050565b600060025b600183015460ff16600281111561107157611071611ffb565b1492915050565b6000600184015460ff16600281111561109357611093611ffb565b146110a0576110a0612011565b6001600160a01b03821660009081526002840160205260408120549081900361113d5783546110d0906001611ec9565b6001600160a01b03848116600081815260028881016020908152604080842087905580518082019091529384528381018881528a5460018082018d558c86529290942094519390920290930180546001600160a01b0319169290941691909117835551910155905061051d565b600061114985856115f5565b9050600085600001828154811061116257611162611e27565b90600052602060002090600202019050838160010160008282546111869190611ec9565b9091555050505050505050565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b17905261051d908590611632565b6001600160a01b038216600090815260d460205260409020600501548290610100900460ff1661124c5760405163973d02cb60e01b81526020600482015260096024820152682b20a624a220aa27a960b91b60448201526064016103c8565b61125660d1611707565b1561126c5761126760d18484611078565b505050565b61127660d1611053565b1561132f5760cc5460ce54604080517f1bcc0f4c3fad314e585165815f94ecca9b96690a26d6417d7876448a9a867a6960208201526001600160a01b038781168284015260608083018890528351808403909101815260808301938490526316f1983160e01b909352938416936316f19831936112f893911691608401612053565b600060405180830381600087803b15801561131257600080fd5b505af1158015611326573d6000803e3d6000fd5b50505050505050565b60405163973d02cb60e01b815260206004820152601060248201526f5761697420666f722067656e6573697360801b60448201526064016103c8565b600181015460ff16600081600281111561138757611387611ffb565b036113d05760405162461bcd60e51b815260206004820152601960248201527811d95b995cda5cd31a588e881b9bdd08199a5b985b1a5e9959603a1b60448201526064016103c8565b60028160028111156113e4576113e4611ffb565b036114315760405162461bcd60e51b815260206004820152601b60248201527f47656e657369734c69623a20616c726561647920656e61626c6564000000000060448201526064016103c8565b50600101805460ff19166002179055565b609754604051633651bb1d60e01b81526001600160a01b0384811660048301526024820184905290911690633651bb1d90604401600060405180830381600087803b15801561149057600080fd5b505af11580156114a4573d6000803e3d6000fd5b50505050610b4b8261170f565b6114b96119d7565b60cb5460d0546040516bffffffffffffffffffffffff19606086811b8216602084015230901b1660348201524660488201526001600160a01b039092169163a850a90991906068016040516020818303038152906040526040518363ffffffff1660e01b815260040161152d929190612077565b6040805180830381865afa158015611549573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061156d9190612090565b92915050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600054610100900460ff166115ec5760405162461bcd60e51b81526004016103c890611fb0565b61053533610ecf565b6001600160a01b03811660009081526002830160205260408120549081900361162057611620612011565b61162b600182612110565b9392505050565b6000611687826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166117db9092919063ffffffff16565b90508051600014806116a85750808060200190518101906116a89190612123565b6112675760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016103c8565b600080611058565b609754609854604051633b61c5a760e21b81526001600160a01b038481166004830152602482019290925291169063ed87169c90604401602060405180830381865afa158015611763573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611787919061213e565b6000036105ae576001600160a01b038116600081815260d46020526040808220600501805461ff0019169055517f23d934bfe7f1275bc6fd70432159c9cc1c0075d069f89da6a40f43bfe7a94ed39190a250565b60606117ea84846000856117f2565b949350505050565b6060824710156118535760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016103c8565b600080866001600160a01b0316858760405161186f9190611e90565b60006040518083038185875af1925050503d80600081146118ac576040519150601f19603f3d011682016040523d82523d6000602084013e6118b1565b606091505b50915091506118c2878383876118cd565b979650505050505050565b6060831561193c578251600003611935576001600160a01b0385163b6119355760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016103c8565b50816117ea565b6117ea83838151156119515781518083602001fd5b8060405162461bcd60e51b81526004016103c89190612157565b604051806080016040528061197e6119f5565b81526000602082018190526040820181905260609091015290565b82600481019282156119c7579160200282015b828111156119c75782358255916020019190600101906119ac565b506119d3929150611a13565b5090565b60405180604001604052806002906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b5b808211156119d35760008155600101611a14565b6001600160a01b03811681146105ae57600080fd5b600060208284031215611a4f57600080fd5b813561162b81611a28565b815160e08201908260005b6004811015611a84578251825260209283019290910190600101611a65565b505050602083015160808301526040830151151560a0830152606090920151151560c09091015290565b60008060c08385031215611ac157600080fd5b6040830184811115611ad257600080fd5b8392508460c085011115611ae557600080fd5b809150509250929050565b600060208284031215611b0257600080fd5b5035919050565b60008060208385031215611b1c57600080fd5b823567ffffffffffffffff80821115611b3457600080fd5b818501915085601f830112611b4857600080fd5b813581811115611b5757600080fd5b8660208260051b8501011115611b6c57600080fd5b60209290920196919550909350505050565b634e487b7160e01b600052604160045260246000fd5b600080600080600080600080610100898b031215611bb157600080fd5b8835611bbc81611a28565b97506020890135611bcc81611a28565b96506040890135611bdc81611a28565b95506060890135611bec81611a28565b94506080890135611bfc81611a28565b935060a0890135611c0c81611a28565b925060c0890135611c1c81611a28565b915060e089013567ffffffffffffffff80821115611c3957600080fd5b818b0191508b601f830112611c4d57600080fd5b813581811115611c5f57611c5f611b7e565b604051601f8201601f19908116603f01168101908382118183101715611c8757611c87611b7e565b816040528281528e6020848701011115611ca057600080fd5b8260208601602083013760006020848301015280955050505050509295985092959890939650565b60008060408385031215611cdb57600080fd5b8235611ce681611a28565b946020939093013593505050565b602080825282518282018190526000919060409081850190868401855b82811015611d3f57815180516001600160a01b03168552860151868501529284019290850190600101611d11565b5091979650505050505050565b60008060008060608587031215611d6257600080fd5b843593506020850135611d7481611a28565b9250604085013567ffffffffffffffff80821115611d9157600080fd5b818701915087601f830112611da557600080fd5b813581811115611db457600080fd5b886020828501011115611dc657600080fd5b95989497505060200194505050565b60808181019083833792915050565b60208082526023908201527f53757065726e65744d616e616765723a204f4e4c595f5354414b455f4d414e4160408201526223a2a960e91b606082015260800190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b600060018201611e6557611e65611e3d565b5060010190565b60005b83811015611e87578181015183820152602001611e6f565b50506000910152565b60008251611ea2818460208701611e6c565b9190910192915050565b600060208284031215611ebe57600080fd5b815161162b81611a28565b8082018082111561156d5761156d611e3d565b60008085851115611eec57600080fd5b83861115611ef957600080fd5b5050820193919092039150565b8035602083101561156d57600019602084900360031b1b1692915050565b61010081016040858337608084604084013760c082018360005b6002811015611f5d578151835260209283019290910190600101611f3e565b505050949350505050565b80518015158114611f7857600080fd5b919050565b60008060408385031215611f9057600080fd5b611f9983611f68565b9150611fa760208401611f68565b90509250929050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052600160045260246000fd5b6000815180845261203f816020860160208601611e6c565b601f01601f19169290920160200192915050565b6001600160a01b03831681526040602082018190526000906117ea90830184612027565b8281526040602082015260006117ea6040830184612027565b6000604082840312156120a257600080fd5b82601f8301126120b157600080fd5b6040516040810181811067ffffffffffffffff821117156120d4576120d4611b7e565b80604052508060408401858111156120eb57600080fd5b845b818110156121055780518352602092830192016120ed565b509195945050505050565b8181038181111561156d5761156d611e3d565b60006020828403121561213557600080fd5b61162b82611f68565b60006020828403121561215057600080fd5b5051919050565b60208152600061162b602083018461202756fea2646970667358221220c854c4bd92b5e40db8e0e9b90d5bc01c31cbec4f50c64ece828835f89a96927664736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var StakeManagerArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"StakeManager","sourceName":"contracts/root/staking/StakeManager.sol","abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"manager","type":"address"}],"name":"ChildManagerRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakeAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakeRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"validator","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakeWithdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"manager","type":"address"}],"name":"idFor","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newStakingToken","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"managerOf","outputs":[{"internalType":"contract ISupernetManager","name":"manager","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"manager","type":"address"}],"name":"registerChildChain","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"releaseStakeOf","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stakeFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"stakeOf","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStake","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"totalStakeOf","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"totalStakeOfChild","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"validator","type":"address"}],"name":"withdrawableStake","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610e59806100206000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c8063c73a7a1f11610071578063c73a7a1f14610135578063d5364bbf14610148578063d7fbee3d1461015b578063e3f56eaa14610186578063ed87169c14610199578063f90423fe146101ac57600080fd5b80633651bb1d146100b957806339ed8c90146100ce5780638b0e9f3f146100f4578063b64ddbf6146100fc578063c4d66de81461010f578063c606627214610122575b600080fd5b6100cc6100c7366004610cd2565b6101bf565b005b6100e16100dc366004610cfc565b610220565b6040519081526020015b60405180910390f35b6036546100e1565b6100e161010a366004610d15565b610236565b6100cc61011d366004610d15565b610342565b6100cc610130366004610cd2565b610467565b6100cc610143366004610d30565b610472565b6100e1610156366004610d15565b61058f565b61016e610169366004610cfc565b6105ad565b6040516001600160a01b0390911681526020016100eb565b6100e1610194366004610d15565b6105b8565b6100e16101a7366004610cd2565b6105d6565b6100e16101ba366004610d15565b610602565b60006101ca33610602565b90506101d783828461060d565b826001600160a01b0316817fcbc2141407acdf7a77731afda2c1369f3c46e04194b90a9d527385c17608eb0d8460405161021391815260200190565b60405180910390a3505050565b6000818152603860205260408120545b92915050565b6001600160a01b038116600090815260036020526040812054156102a15760405162461bcd60e51b815260206004820152601c60248201527f5374616b654d616e616765723a2049445f414c52454144595f5345540000000060448201526064015b60405180910390fd5b6102aa826106e0565b604051630d12979960e21b8152600481018290529091506001600160a01b0383169063344a5e6490602401600060405180830381600087803b1580156102ef57600080fd5b505af1158015610303573d6000803e3d6000fd5b50506040516001600160a01b03851692508391507f64566eaf28160aee8cce7464dbf8eb54939f81ac929a8ebeea66865ff57963c490600090a3919050565b600054610100900460ff16158080156103625750600054600160ff909116105b8061037c5750303b15801561037c575060005460ff166001145b6103df5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610298565b6000805460ff191660011790558015610402576000805461ff0019166101001790555b606d80546001600160a01b0319166001600160a01b0384161790558015610463576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b610463338383610791565b811580159061048357506001548211155b6104ca5760405162461bcd60e51b815260206004820152601860248201527714dd185ad953585b9859d95c8e881253959053125117d25160421b6044820152606401610298565b606d546104e2906001600160a01b03163330846107f7565b6104ed338383610868565b60006104f8836105ad565b6040516323021ec760e21b8152336004820152602481018490529091506001600160a01b03821690638c087b1c90604401600060405180830381600087803b15801561054357600080fd5b505af1158015610557573d6000803e3d6000fd5b50506040518481523392508591507fc1d3c31619aec7561f6fa519052617aed252a25edaddc5d7428681180516837a90602001610213565b6001600160a01b0381166000908152603a6020526040812054610230565b600061023082610904565b6001600160a01b038116600090815260396020526040812054610230565b6001600160a01b03821660009081526037602090815260408083208484529091528120545b9392505050565b600061023082610978565b6001600160a01b038316600090815260376020908152604080832085845290915281208054839290610640908490610d68565b909155505060008281526038602052604081208054839290610663908490610d68565b90915550506001600160a01b03831660009081526039602052604081208054839290610690908490610d68565b9250508190555080603660008282546106a99190610d68565b90915550506001600160a01b0383166000908152603a6020526040812080548392906106d6908490610d7b565b9091555050505050565b60006001600160a01b0382166107475760405162461bcd60e51b815260206004820152602660248201527f5374616b654d616e616765724368696c64446174613a20494e56414c49445f4160448201526544445245535360d01b6064820152608401610298565b5060018054810190819055600081815260026020908152604080832080546001600160a01b039096166001600160a01b031990961686179055938252600390529190912081905590565b61079b83826109f0565b606d546107b2906001600160a01b03168383610a21565b816001600160a01b0316836001600160a01b03167fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda38360405161021391815260200190565b6040516001600160a01b03808516602483015283166044820152606481018290526108629085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610a56565b50505050565b6001600160a01b03831660009081526037602090815260408083208584529091528120805483929061089b908490610d7b565b9091555050600082815260386020526040812080548392906108be908490610d7b565b90915550506001600160a01b038316600090815260396020526040812080548392906108eb908490610d7b565b9250508190555080603660008282546106d69190610d7b565b6000818152600260205260409020546001600160a01b0316806109735760405162461bcd60e51b815260206004820152602160248201527f5374616b654d616e616765724368696c64446174613a20494e56414c49445f496044820152601160fa1b6064820152608401610298565b919050565b6001600160a01b038116600090815260036020526040812054908190036109735760405162461bcd60e51b815260206004820152602660248201527f5374616b654d616e616765724368696c64446174613a20494e56414c49445f4d60448201526520a720a3a2a960d11b6064820152608401610298565b6001600160a01b0382166000908152603a602052604081208054839290610a18908490610d68565b90915550505050565b6040516001600160a01b038316602482015260448101829052610a5190849063a9059cbb60e01b9060640161082b565b505050565b6000610aab826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610b2b9092919063ffffffff16565b9050805160001480610acc575080806020019051810190610acc9190610d8e565b610a515760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610298565b6060610b3a8484600085610b42565b949350505050565b606082471015610ba35760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610298565b600080866001600160a01b03168587604051610bbf9190610dd4565b60006040518083038185875af1925050503d8060008114610bfc576040519150601f19603f3d011682016040523d82523d6000602084013e610c01565b606091505b5091509150610c1287838387610c1d565b979650505050505050565b60608315610c8c578251600003610c85576001600160a01b0385163b610c855760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610298565b5081610b3a565b610b3a8383815115610ca15781518083602001fd5b8060405162461bcd60e51b81526004016102989190610df0565b80356001600160a01b038116811461097357600080fd5b60008060408385031215610ce557600080fd5b610cee83610cbb565b946020939093013593505050565b600060208284031215610d0e57600080fd5b5035919050565b600060208284031215610d2757600080fd5b6105fb82610cbb565b60008060408385031215610d4357600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8181038181111561023057610230610d52565b8082018082111561023057610230610d52565b600060208284031215610da057600080fd5b815180151581146105fb57600080fd5b60005b83811015610dcb578181015183820152602001610db3565b50506000910152565b60008251610de6818460208701610db0565b9190910192915050565b6020815260008251806020840152610e0f816040850160208701610db0565b601f01601f1916919091016040019291505056fea26469706673582212207f5dfe1cb46bc115076fa23b9ffb2421ed989feee340cc0546c7e357469d57e864736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100b45760003560e01c8063c73a7a1f11610071578063c73a7a1f14610135578063d5364bbf14610148578063d7fbee3d1461015b578063e3f56eaa14610186578063ed87169c14610199578063f90423fe146101ac57600080fd5b80633651bb1d146100b957806339ed8c90146100ce5780638b0e9f3f146100f4578063b64ddbf6146100fc578063c4d66de81461010f578063c606627214610122575b600080fd5b6100cc6100c7366004610cd2565b6101bf565b005b6100e16100dc366004610cfc565b610220565b6040519081526020015b60405180910390f35b6036546100e1565b6100e161010a366004610d15565b610236565b6100cc61011d366004610d15565b610342565b6100cc610130366004610cd2565b610467565b6100cc610143366004610d30565b610472565b6100e1610156366004610d15565b61058f565b61016e610169366004610cfc565b6105ad565b6040516001600160a01b0390911681526020016100eb565b6100e1610194366004610d15565b6105b8565b6100e16101a7366004610cd2565b6105d6565b6100e16101ba366004610d15565b610602565b60006101ca33610602565b90506101d783828461060d565b826001600160a01b0316817fcbc2141407acdf7a77731afda2c1369f3c46e04194b90a9d527385c17608eb0d8460405161021391815260200190565b60405180910390a3505050565b6000818152603860205260408120545b92915050565b6001600160a01b038116600090815260036020526040812054156102a15760405162461bcd60e51b815260206004820152601c60248201527f5374616b654d616e616765723a2049445f414c52454144595f5345540000000060448201526064015b60405180910390fd5b6102aa826106e0565b604051630d12979960e21b8152600481018290529091506001600160a01b0383169063344a5e6490602401600060405180830381600087803b1580156102ef57600080fd5b505af1158015610303573d6000803e3d6000fd5b50506040516001600160a01b03851692508391507f64566eaf28160aee8cce7464dbf8eb54939f81ac929a8ebeea66865ff57963c490600090a3919050565b600054610100900460ff16158080156103625750600054600160ff909116105b8061037c5750303b15801561037c575060005460ff166001145b6103df5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610298565b6000805460ff191660011790558015610402576000805461ff0019166101001790555b606d80546001600160a01b0319166001600160a01b0384161790558015610463576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b610463338383610791565b811580159061048357506001548211155b6104ca5760405162461bcd60e51b815260206004820152601860248201527714dd185ad953585b9859d95c8e881253959053125117d25160421b6044820152606401610298565b606d546104e2906001600160a01b03163330846107f7565b6104ed338383610868565b60006104f8836105ad565b6040516323021ec760e21b8152336004820152602481018490529091506001600160a01b03821690638c087b1c90604401600060405180830381600087803b15801561054357600080fd5b505af1158015610557573d6000803e3d6000fd5b50506040518481523392508591507fc1d3c31619aec7561f6fa519052617aed252a25edaddc5d7428681180516837a90602001610213565b6001600160a01b0381166000908152603a6020526040812054610230565b600061023082610904565b6001600160a01b038116600090815260396020526040812054610230565b6001600160a01b03821660009081526037602090815260408083208484529091528120545b9392505050565b600061023082610978565b6001600160a01b038316600090815260376020908152604080832085845290915281208054839290610640908490610d68565b909155505060008281526038602052604081208054839290610663908490610d68565b90915550506001600160a01b03831660009081526039602052604081208054839290610690908490610d68565b9250508190555080603660008282546106a99190610d68565b90915550506001600160a01b0383166000908152603a6020526040812080548392906106d6908490610d7b565b9091555050505050565b60006001600160a01b0382166107475760405162461bcd60e51b815260206004820152602660248201527f5374616b654d616e616765724368696c64446174613a20494e56414c49445f4160448201526544445245535360d01b6064820152608401610298565b5060018054810190819055600081815260026020908152604080832080546001600160a01b039096166001600160a01b031990961686179055938252600390529190912081905590565b61079b83826109f0565b606d546107b2906001600160a01b03168383610a21565b816001600160a01b0316836001600160a01b03167fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda38360405161021391815260200190565b6040516001600160a01b03808516602483015283166044820152606481018290526108629085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610a56565b50505050565b6001600160a01b03831660009081526037602090815260408083208584529091528120805483929061089b908490610d7b565b9091555050600082815260386020526040812080548392906108be908490610d7b565b90915550506001600160a01b038316600090815260396020526040812080548392906108eb908490610d7b565b9250508190555080603660008282546106d69190610d7b565b6000818152600260205260409020546001600160a01b0316806109735760405162461bcd60e51b815260206004820152602160248201527f5374616b654d616e616765724368696c64446174613a20494e56414c49445f496044820152601160fa1b6064820152608401610298565b919050565b6001600160a01b038116600090815260036020526040812054908190036109735760405162461bcd60e51b815260206004820152602660248201527f5374616b654d616e616765724368696c64446174613a20494e56414c49445f4d60448201526520a720a3a2a960d11b6064820152608401610298565b6001600160a01b0382166000908152603a602052604081208054839290610a18908490610d68565b90915550505050565b6040516001600160a01b038316602482015260448101829052610a5190849063a9059cbb60e01b9060640161082b565b505050565b6000610aab826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610b2b9092919063ffffffff16565b9050805160001480610acc575080806020019051810190610acc9190610d8e565b610a515760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610298565b6060610b3a8484600085610b42565b949350505050565b606082471015610ba35760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610298565b600080866001600160a01b03168587604051610bbf9190610dd4565b60006040518083038185875af1925050503d8060008114610bfc576040519150601f19603f3d011682016040523d82523d6000602084013e610c01565b606091505b5091509150610c1287838387610c1d565b979650505050505050565b60608315610c8c578251600003610c85576001600160a01b0385163b610c855760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610298565b5081610b3a565b610b3a8383815115610ca15781518083602001fd5b8060405162461bcd60e51b81526004016102989190610df0565b80356001600160a01b038116811461097357600080fd5b60008060408385031215610ce557600080fd5b610cee83610cbb565b946020939093013593505050565b600060208284031215610d0e57600080fd5b5035919050565b600060208284031215610d2757600080fd5b6105fb82610cbb565b60008060408385031215610d4357600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8181038181111561023057610230610d52565b8082018082111561023057610230610d52565b600060208284031215610da057600080fd5b815180151581146105fb57600080fd5b60005b83811015610dcb578181015183820152602001610db3565b50506000910152565b60008251610de6818460208701610db0565b9190910192915050565b6020815260008251806020840152610e0f816040850160208701610db0565b601f01601f1916919091016040019291505056fea26469706673582212207f5dfe1cb46bc115076fa23b9ffb2421ed989feee340cc0546c7e357469d57e864736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var RewardPoolArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"RewardPool","sourceName":"contracts/child/validator/RewardPool.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"epochId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalReward","type":"uint256"}],"name":"RewardDistributed","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"epochId","type":"uint256"},{"components":[{"internalType":"address","name":"validator","type":"address"},{"internalType":"uint256","name":"signedBlocks","type":"uint256"}],"internalType":"struct Uptime[]","name":"uptime","type":"tuple[]"}],"name":"distributeRewardFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newRewardToken","type":"address"},{"internalType":"address","name":"newRewardWallet","type":"address"},{"internalType":"address","name":"newValidatorSet","type":"address"},{"internalType":"uint256","name":"newBaseReward","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"paidRewardPerEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"pendingRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardToken","outputs":[{"internalType":"contract IERC20Upgradeable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"validatorSet","outputs":[{"internalType":"contract IValidatorSet","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawReward","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610ee1806100206000396000f3fe608060405234801561001057600080fd5b50600436106101165760003560e01c80638a9cd82d116100a2578063c885bc5811610071578063c885bc581461020f578063cf756fdf14610217578063e0563ab11461022a578063f7c618c114610233578063fb75b2c71461024c57600080fd5b80638a9cd82d146101d45780639426e226146101e9578063947287cf146101fc57806397e5230d1461020557600080fd5b80633b878c22116100e95780633b878c221461019d57806351351d53146101a657806355b01e4d146101b45780635ea5df79146101c257806376ad03bc146101cb57600080fd5b806307358b991461011b57806307b3e2521461014e578063284017f51461017457806331d7a2621461017d575b600080fd5b61013b610129366004610be0565b60366020526000908152604090205481565b6040519081526020015b60405180910390f35b61015c6004600360981b0181565b6040516001600160a01b039091168152602001610145565b61015c61202081565b61013b61018b366004610c15565b60376020526000908152604090205481565b61015c61101081565b61015c6002600160a01b0381565b61015c6004600160991b0181565b61013b61138881565b61013b60355481565b6101e76101e2366004610c37565b61025f565b005b60345461015c906001600160a01b031681565b61013b61520881565b61013b620249f081565b6101e76106a9565b6101e7610225366004610cb6565b6106df565b61015c61203081565b60325461015c906201000090046001600160a01b031681565b60335461015c906001600160a01b031681565b336002600160a01b03146102a85760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b600083815260366020526040902054156103045760405162461bcd60e51b815260206004820152601a60248201527f5245574152445f414c52454144595f4449535452494255544544000000000000604482015260640161029f565b603454604051633f490b0560e21b8152600481018590526000916001600160a01b03169063fd242c1490602401602060405180830381865afa15801561034e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103729190610d01565b9050806000036103ba5760405162461bcd60e51b8152602060048201526013602482015272115413d0d217d393d517d0d3d3535255151151606a1b604482015260640161029f565b60345460408051636265600360e01b815290516000926001600160a01b03169163626560039160048083019260209291908290030181865afa158015610404573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104289190610d01565b90506000818360355461043b9190610d30565b6104459190610d4d565b603454604051630981b24d60e41b8152600481018990529192506000916001600160a01b039091169063981b24d090602401602060405180830381865afa158015610494573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b89190610d01565b9050846000805b828110156106495760008989838181106104db576104db610d6f565b9050604002018036038101906104f19190610d85565b905087816020015111156105475760405162461bcd60e51b815260206004820152601b60248201527f5349474e45445f424c4f434b535f455843454544535f544f54414c0000000000604482015260640161029f565b603454815160405163277166bf60e11b81526001600160a01b039182166004820152602481018e90526000929190911690634ee2cd7e90604401602060405180830381865afa15801561059e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105c29190610d01565b905060006105d08a88610d30565b60208401516105df848b610d30565b6105e99190610d30565b6105f39190610d4d565b83516001600160a01b0316600090815260376020526040812080549293508392909190610621908490610dea565b9091555061063190508186610dea565b9450505050808061064190610dfd565b9150506104bf565b506000898152603660205260409020819055610664816108b1565b887feaf3d57629d9b1ce95715ccd98d6f5bf48023be1d5a06e09f64ab7f6d8be01d58260405161069691815260200190565b60405180910390a2505050505050505050565b336000818152603760205260408120805491905560325490916106dc916201000090046001600160a01b031690836108d5565b50565b603254610100900460ff16158080156106ff5750603254600160ff909116105b806107195750303b158015610719575060325460ff166001145b61077c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161029f565b6032805460ff19166001179055801561079f576032805461ff0019166101001790555b6001600160a01b038516158015906107bf57506001600160a01b03841615155b80156107d357506001600160a01b03831615155b61080e5760405162461bcd60e51b815260206004820152600c60248201526b5a45524f5f4144445245535360a01b604482015260640161029f565b603280546001600160a01b03808816620100000262010000600160b01b031990921691909117909155603380548683166001600160a01b0319918216179091556034805492861692909116919091179055603582905580156108aa576032805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6033546032546106dc916001600160a01b036201000090920482169116308461093d565b6040516001600160a01b03831660248201526044810182905261093890849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915261097b565b505050565b6040516001600160a01b03808516602483015283166044820152606481018290526109759085906323b872dd60e01b90608401610901565b50505050565b60006109d0826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610a509092919063ffffffff16565b90508051600014806109f15750808060200190518101906109f19190610e16565b6109385760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161029f565b6060610a5f8484600085610a67565b949350505050565b606082471015610ac85760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161029f565b600080866001600160a01b03168587604051610ae49190610e5c565b60006040518083038185875af1925050503d8060008114610b21576040519150601f19603f3d011682016040523d82523d6000602084013e610b26565b606091505b5091509150610b3787838387610b42565b979650505050505050565b60608315610bb1578251600003610baa576001600160a01b0385163b610baa5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161029f565b5081610a5f565b610a5f8383815115610bc65781518083602001fd5b8060405162461bcd60e51b815260040161029f9190610e78565b600060208284031215610bf257600080fd5b5035919050565b80356001600160a01b0381168114610c1057600080fd5b919050565b600060208284031215610c2757600080fd5b610c3082610bf9565b9392505050565b600080600060408486031215610c4c57600080fd5b83359250602084013567ffffffffffffffff80821115610c6b57600080fd5b818601915086601f830112610c7f57600080fd5b813581811115610c8e57600080fd5b8760208260061b8501011115610ca357600080fd5b6020830194508093505050509250925092565b60008060008060808587031215610ccc57600080fd5b610cd585610bf9565b9350610ce360208601610bf9565b9250610cf160408601610bf9565b9396929550929360600135925050565b600060208284031215610d1357600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b8082028115828204841417610d4757610d47610d1a565b92915050565b600082610d6a57634e487b7160e01b600052601260045260246000fd5b500490565b634e487b7160e01b600052603260045260246000fd5b600060408284031215610d9757600080fd5b6040516040810181811067ffffffffffffffff82111715610dc857634e487b7160e01b600052604160045260246000fd5b604052610dd483610bf9565b8152602083013560208201528091505092915050565b80820180821115610d4757610d47610d1a565b600060018201610e0f57610e0f610d1a565b5060010190565b600060208284031215610e2857600080fd5b81518015158114610c3057600080fd5b60005b83811015610e53578181015183820152602001610e3b565b50506000910152565b60008251610e6e818460208701610e38565b9190910192915050565b6020815260008251806020840152610e97816040850160208701610e38565b601f01601f1916919091016040019291505056fea26469706673582212200c9fa9211b5f5937d8516e90589d94c68470826588820f396136e3f3ed465c6c64736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106101165760003560e01c80638a9cd82d116100a2578063c885bc5811610071578063c885bc581461020f578063cf756fdf14610217578063e0563ab11461022a578063f7c618c114610233578063fb75b2c71461024c57600080fd5b80638a9cd82d146101d45780639426e226146101e9578063947287cf146101fc57806397e5230d1461020557600080fd5b80633b878c22116100e95780633b878c221461019d57806351351d53146101a657806355b01e4d146101b45780635ea5df79146101c257806376ad03bc146101cb57600080fd5b806307358b991461011b57806307b3e2521461014e578063284017f51461017457806331d7a2621461017d575b600080fd5b61013b610129366004610be0565b60366020526000908152604090205481565b6040519081526020015b60405180910390f35b61015c6004600360981b0181565b6040516001600160a01b039091168152602001610145565b61015c61202081565b61013b61018b366004610c15565b60376020526000908152604090205481565b61015c61101081565b61015c6002600160a01b0381565b61015c6004600160991b0181565b61013b61138881565b61013b60355481565b6101e76101e2366004610c37565b61025f565b005b60345461015c906001600160a01b031681565b61013b61520881565b61013b620249f081565b6101e76106a9565b6101e7610225366004610cb6565b6106df565b61015c61203081565b60325461015c906201000090046001600160a01b031681565b60335461015c906001600160a01b031681565b336002600160a01b03146102a85760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b600083815260366020526040902054156103045760405162461bcd60e51b815260206004820152601a60248201527f5245574152445f414c52454144595f4449535452494255544544000000000000604482015260640161029f565b603454604051633f490b0560e21b8152600481018590526000916001600160a01b03169063fd242c1490602401602060405180830381865afa15801561034e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103729190610d01565b9050806000036103ba5760405162461bcd60e51b8152602060048201526013602482015272115413d0d217d393d517d0d3d3535255151151606a1b604482015260640161029f565b60345460408051636265600360e01b815290516000926001600160a01b03169163626560039160048083019260209291908290030181865afa158015610404573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104289190610d01565b90506000818360355461043b9190610d30565b6104459190610d4d565b603454604051630981b24d60e41b8152600481018990529192506000916001600160a01b039091169063981b24d090602401602060405180830381865afa158015610494573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b89190610d01565b9050846000805b828110156106495760008989838181106104db576104db610d6f565b9050604002018036038101906104f19190610d85565b905087816020015111156105475760405162461bcd60e51b815260206004820152601b60248201527f5349474e45445f424c4f434b535f455843454544535f544f54414c0000000000604482015260640161029f565b603454815160405163277166bf60e11b81526001600160a01b039182166004820152602481018e90526000929190911690634ee2cd7e90604401602060405180830381865afa15801561059e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105c29190610d01565b905060006105d08a88610d30565b60208401516105df848b610d30565b6105e99190610d30565b6105f39190610d4d565b83516001600160a01b0316600090815260376020526040812080549293508392909190610621908490610dea565b9091555061063190508186610dea565b9450505050808061064190610dfd565b9150506104bf565b506000898152603660205260409020819055610664816108b1565b887feaf3d57629d9b1ce95715ccd98d6f5bf48023be1d5a06e09f64ab7f6d8be01d58260405161069691815260200190565b60405180910390a2505050505050505050565b336000818152603760205260408120805491905560325490916106dc916201000090046001600160a01b031690836108d5565b50565b603254610100900460ff16158080156106ff5750603254600160ff909116105b806107195750303b158015610719575060325460ff166001145b61077c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161029f565b6032805460ff19166001179055801561079f576032805461ff0019166101001790555b6001600160a01b038516158015906107bf57506001600160a01b03841615155b80156107d357506001600160a01b03831615155b61080e5760405162461bcd60e51b815260206004820152600c60248201526b5a45524f5f4144445245535360a01b604482015260640161029f565b603280546001600160a01b03808816620100000262010000600160b01b031990921691909117909155603380548683166001600160a01b0319918216179091556034805492861692909116919091179055603582905580156108aa576032805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b6033546032546106dc916001600160a01b036201000090920482169116308461093d565b6040516001600160a01b03831660248201526044810182905261093890849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915261097b565b505050565b6040516001600160a01b03808516602483015283166044820152606481018290526109759085906323b872dd60e01b90608401610901565b50505050565b60006109d0826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610a509092919063ffffffff16565b90508051600014806109f15750808060200190518101906109f19190610e16565b6109385760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161029f565b6060610a5f8484600085610a67565b949350505050565b606082471015610ac85760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161029f565b600080866001600160a01b03168587604051610ae49190610e5c565b60006040518083038185875af1925050503d8060008114610b21576040519150601f19603f3d011682016040523d82523d6000602084013e610b26565b606091505b5091509150610b3787838387610b42565b979650505050505050565b60608315610bb1578251600003610baa576001600160a01b0385163b610baa5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161029f565b5081610a5f565b610a5f8383815115610bc65781518083602001fd5b8060405162461bcd60e51b815260040161029f9190610e78565b600060208284031215610bf257600080fd5b5035919050565b80356001600160a01b0381168114610c1057600080fd5b919050565b600060208284031215610c2757600080fd5b610c3082610bf9565b9392505050565b600080600060408486031215610c4c57600080fd5b83359250602084013567ffffffffffffffff80821115610c6b57600080fd5b818601915086601f830112610c7f57600080fd5b813581811115610c8e57600080fd5b8760208260061b8501011115610ca357600080fd5b6020830194508093505050509250925092565b60008060008060808587031215610ccc57600080fd5b610cd585610bf9565b9350610ce360208601610bf9565b9250610cf160408601610bf9565b9396929550929360600135925050565b600060208284031215610d1357600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b8082028115828204841417610d4757610d47610d1a565b92915050565b600082610d6a57634e487b7160e01b600052601260045260246000fd5b500490565b634e487b7160e01b600052603260045260246000fd5b600060408284031215610d9757600080fd5b6040516040810181811067ffffffffffffffff82111715610dc857634e487b7160e01b600052604160045260246000fd5b604052610dd483610bf9565b8152602083013560208201528091505092915050565b80820180821115610d4757610d47610d1a565b600060018201610e0f57610e0f610d1a565b5060010190565b600060208284031215610e2857600080fd5b81518015158114610c3057600080fd5b60005b83811015610e53578181015183820152602001610e3b565b50506000910152565b60008251610e6e818460208701610e38565b9190910192915050565b6020815260008251806020840152610e97816040850160208701610e38565b601f01601f1916919091016040019291505056fea26469706673582212200c9fa9211b5f5937d8516e90589d94c68470826588820f396136e3f3ed465c6c64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var ValidatorSetArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"ValidatorSet","sourceName":"contracts/child/validator/ValidatorSet.sol","abi":[{"inputs":[{"internalType":"string","name":"only","type":"string"}],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"startBlock","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"endBlock","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"epochRoot","type":"bytes32"}],"name":"NewEpoch","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Snapshot","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawalRegistered","type":"event"},{"inputs":[],"name":"ALLOWLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKLIST_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EPOCH_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TOKEN_CONTRACT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NATIVE_TRANSFER_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"READ_ADDRESSLIST_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALIDATOR_PKCHECK_PRECOMPILE_GAS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAWAL_WAIT_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"epochNumber","type":"uint256"}],"name":"balanceOfAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"components":[{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"bytes32","name":"epochRoot","type":"bytes32"}],"internalType":"struct Epoch","name":"epoch","type":"tuple"}],"name":"commitEpoch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"currentEpochId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"epochEndBlocks","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"epochs","outputs":[{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"bytes32","name":"epochRoot","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newStateSender","type":"address"},{"internalType":"address","name":"newStateReceiver","type":"address"},{"internalType":"address","name":"newRootChainManager","type":"address"},{"internalType":"uint256","name":"newEpochSize","type":"uint256"},{"components":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"stake","type":"uint256"}],"internalType":"struct ValidatorInit[]","name":"initialValidators","type":"tuple[]"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onStateReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"pendingWithdrawals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"epochId","type":"uint256"}],"name":"totalBlocks","outputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"epochNumber","type":"uint256"}],"name":"totalSupplyAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"withdrawable","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b5061215c806100206000396000f3fe608060405234801561001057600080fd5b50600436106102015760003560e01c806361cc276311610125578063c6b61e4c116100ad578063ea0fee4f1161007c578063ea0fee4f1461043d578063eacdc5ff14610445578063eeb499451461044e578063f3f4370314610461578063fd242c141461047457600080fd5b8063c6b61e4c146103c4578063ce513b6f1461040e578063dd62ed3e14610421578063e0563ab11461043457600080fd5b806395d89b41116100f457806395d89b411461037957806397e5230d14610381578063981b24d01461038b578063a457c2d71461039e578063a9059cbb146103b157600080fd5b806361cc276314610341578063626560031461035457806370a082311461035d578063947287cf1461037057600080fd5b8063313ce567116101a85780633fd50001116101775780633fd50001146102f65780634ee2cd7e1461030957806351351d531461031c57806355b01e4d1461032a5780635ea5df791461033857600080fd5b8063313ce567146102c357806339509351146102d25780633b878c22146102e55780633ccfd60b146102ee57600080fd5b806306fdde031461020657806307b3e25214610224578063095ea7b31461024a5780630f50287c1461026d57806318160ddd1461028257806323b872dd14610294578063284017f5146102a75780632e17de78146102b0575b600080fd5b61020e610487565b60405161021b9190611aeb565b60405180910390f35b6102326004600360981b0181565b6040516001600160a01b03909116815260200161021b565b61025d610258366004611b13565b610519565b604051901515815260200161021b565b61028061027b366004611b3f565b610533565b005b6035545b60405190815260200161021b565b61025d6102a2366004611b77565b6107c5565b61023261202081565b6102806102be366004611bb8565b6107e9565b6040516012815260200161021b565b61025d6102e0366004611b13565b610800565b61023261101081565b610280610822565b610286610304366004611bb8565b610933565b610286610317366004611b13565b610954565b6102326002600160a01b0381565b6102326004600160991b0181565b61028661138881565b61028061034f366004611c41565b610967565b61028660cc5481565b61028661036b366004611d5a565b610c05565b61028661520881565b61020e610c20565b610286620249f081565b610286610399366004611bb8565b610c2f565b61025d6103ac366004611b13565b610c3a565b61025d6103bf366004611b13565b610cb5565b6103f36103d2366004611bb8565b60ce6020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161021b565b61028661041c366004611d5a565b610cc3565b61028661042f366004611d77565b610cf1565b61023261203081565b610286600181565b61028660cd5481565b61028061045c366004611db0565b610d1c565b61028661046f366004611d5a565b610dee565b610286610482366004611bb8565b610e15565b60606036805461049690611e39565b80601f01602080910402602001604051908101604052809291908181526020018280546104c290611e39565b801561050f5780601f106104e45761010080835404028352916020019161050f565b820191906000526020600020905b8154815290600101906020018083116104f257829003601f168201915b5050505050905090565b600033610527818585610e5f565b60019150505b92915050565b336002600160a01b031461057c5760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b60cd80546000918261058d83611e89565b9190505590508083146105d85760405162461bcd60e51b815260206004820152601360248201527215539156141150d5115117d15413d0d217d251606a1b6044820152606401610573565b81356020830135116106225760405162461bcd60e51b81526020600482015260136024820152721393d7d09313d0d2d4d7d0d3d3535255151151606a1b6044820152606401610573565b60cc5461063483356020850135611ea2565b61063f906001611eb5565b6106499190611ede565b156106a45760405162461bcd60e51b815260206004820152602560248201527f45504f43485f4d5553545f42455f444956495349424c455f42595f45504f43486044820152645f53495a4560d81b6064820152608401610573565b813560ce60006106b5600185611ea2565b81526020019081526020016000206001015460016106d39190611eb5565b146107165760405162461bcd60e51b8152602060048201526013602482015272494e56414c49445f53544152545f424c4f434b60681b6044820152606401610573565b600081815260ce60205260409020829061074782828135815560208201356001820155604082013560028201555050565b505060cf80546001810182556000919091526020838101357facb8d954e2cfef495862221e91bd7523613cf8808827cb33edfe4904cc51bf299092018290556040805190850135815284359186917f0ce8712c4dee4bd5a691f0bc1c39594671591e77395f8ebf6a3fb5f63fbea66a910160405180910390a4505050565b6000336107d3858285610f84565b6107de858585610ff8565b506001949350505050565b6107f3338261119c565b6107fd33826112c7565b50565b6000336105278185856108138383610cf1565b61081d9190611eb5565b610e5f565b33600090815260d06020526040812060cd549091908190610844908490611340565b808555604051828152919350915033907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a260c95460cb54604080517f8ca9a95e41b5eece253c93f5b31eed1253aed6b145d8a6e14d913fdf8e7322936020820152338183015260608082018790528251808303909101815260808201928390526316f1983160e01b9092526001600160a01b03938416936316f19831936108fc93911691608401611ef2565b600060405180830381600087803b15801561091657600080fd5b505af115801561092a573d6000803e3d6000fd5b50505050505050565b60cf818154811061094357600080fd5b600091825260209091200154905081565b600061096083836113b2565b9392505050565b600054610100900460ff16158080156109875750600054600160ff909116105b806109a15750303b1580156109a1575060005460ff166001145b610a045760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610573565b6000805460ff191660011790558015610a27576000805461ff0019166101001790555b6001600160a01b03861615801590610a4757506001600160a01b03851615155b8015610a5b57506001600160a01b03841615155b610a975760405162461bcd60e51b815260206004820152600d60248201526c1253959053125117d253941555609a1b6044820152606401610573565b610ae16040518060400160405280600c81526020016b15985b1a59185d1bdc94d95d60a21b815250604051806040016040528060048152602001631594d15560e21b8152506113fb565b60c980546001600160a01b038089166001600160a01b03199283161790925560ca805488841690831617905560cb80549287169290911691909117905560cc83905560005b8251811015610b7b57610b73838281518110610b4457610b44611f16565b602002602001015160000151848381518110610b6257610b62611f16565b602002602001015160200151611430565b600101610b26565b5060cf80546001818101835560009283527facb8d954e2cfef495862221e91bd7523613cf8808827cb33edfe4904cc51bf299091019190915560cd558015610bfd576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b6001600160a01b031660009081526033602052604090205490565b60606037805461049690611e39565b600061052d8261143a565b60003381610c488286610cf1565b905083811015610ca85760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610573565b6107de8286868403610e5f565b600033610527818585610ff8565b60cd546001600160a01b038216600090815260d0602052604081209091610cea9190611340565b5092915050565b6001600160a01b03918216600090815260346020908152604080832093909416825291909152205490565b60ca546001600160a01b031633148015610d43575060cb546001600160a01b038481169116145b610d805760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a2a72222a960911b6044820152606401610573565b7f1bcc0f4c3fad314e585165815f94ecca9b96690a26d6417d7876448a9a867a69610daf602060008486611f2c565b610db891611f56565b03610de857600080610dcd8360208187611f2c565b810190610dda9190611b13565b91509150610bfd8282611430565b50505050565b60cd546001600160a01b038216600090815260d060205260408120909161052d9190611465565b600081815260ce60205260408120600101548015610e5657600083815260ce6020526040902054610e469082611ea2565b610e51906001611eb5565b610960565b60009392505050565b6001600160a01b038316610ec15760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610573565b6001600160a01b038216610f225760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610573565b6001600160a01b0383811660008181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000610f908484610cf1565b90506000198114610de85781811015610feb5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610573565b610de88484848403610e5f565b6001600160a01b03831661105c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610573565b6001600160a01b0382166110be5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610573565b6110c9838383611503565b6001600160a01b038316600090815260336020526040902054818110156111415760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610573565b6001600160a01b0380851660008181526033602052604080822086860390559286168082529083902080548601905591516000805160206121078339815191529061118f9086815260200190565b60405180910390a3610de8565b6001600160a01b0382166111fc5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610573565b61120882600083611503565b6001600160a01b0382166000908152603360205260409020548181101561127c5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610573565b6001600160a01b03831660008181526033602090815260408083208686039055603580548790039055518581529192916000805160206121078339815191529101610f77565b505050565b6112f981600160cd546112da9190611eb5565b6001600160a01b038516600090815260d060205260409020919061156c565b816001600160a01b03167f655c1cd0236fb6dc4916f34c8ff10e3b18fcaea5b344dfc16c36fbb1bdfc5df28260405161133491815260200190565b60405180910390a25050565b81546000905b83600101548110156113ab5760008181526002850160209081526040918290208251808401909352805483526001015490820181905284101561138957506113ab565b80516113959084611eb5565b92505080806113a390611e89565b915050611346565b9250929050565b6001600160a01b0382166000908152606560205260408120819081906113d99085906116e2565b91509150816113f0576113eb85610c05565b6113f2565b805b95945050505050565b600054610100900460ff166114225760405162461bcd60e51b815260040161057390611f74565b61142c82826117d0565b5050565b61142c8282611810565b600080600061144a8460666116e2565b915091508161145b5760355461145d565b805b949350505050565b600182015460009080820361147e57600091505061052d565b600061148b600183611ea2565b90505b845481106114fb5760008181526002860160209081526040918290208251808401909352805483526001015490820181905285106114cc57506114fb565b80516114d89085611eb5565b9350816000036114e857506114fb565b50806114f381611fbf565b91505061148e565b505092915050565b6001600160a01b038316158061152057506001600160a01b038216155b6115615760405162461bcd60e51b81526020600482015260126024820152712a2920a729a322a92fa327a92124a22222a760711b6044820152606401610573565b6112c28383836118cb565b816000036115c75760405162461bcd60e51b815260206004820152602260248201527f5769746864726177616c51756575654c69623a20494e56414c49445f414d4f55604482015261139560f21b6064820152608401610573565b8254600184015481810361161e576040805180820182528581526020808201868152600085815260028a01909252928120915182559151600191820155860180549161161283611e89565b91905055505050505050565b60006002860181611630600185611ea2565b81526020019081526020016000206001015490508084101561165457611654611fd6565b838110156116a3576040805180820182528681526020808201878152600086815260028b01909252928120915182559151600191820155870180549161169983611e89565b9190505550610bfd565b846002870160006116b5600186611ea2565b815260200190815260200160002060000160008282546116d59190611eb5565b9091555050505050505050565b6000806000841161172e5760405162461bcd60e51b815260206004820152601660248201527504552433230536e617073686f743a20696420697320360541b6044820152606401610573565b60cd548411156117805760405162461bcd60e51b815260206004820152601d60248201527f4552433230536e617073686f743a206e6f6e6578697374656e742069640000006044820152606401610573565b600061178c8486611913565b845490915081036117a45760008092509250506113ab565b60018460010182815481106117bb576117bb611f16565b906000526020600020015492509250506113ab565b600054610100900460ff166117f75760405162461bcd60e51b815260040161057390611f74565b60366118038382612032565b5060376112c28282612032565b6001600160a01b0382166118665760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610573565b61187260008383611503565b80603560008282546118849190611eb5565b90915550506001600160a01b038216600081815260336020908152604080832080548601905551848152600080516020612107833981519152910160405180910390a35050565b6001600160a01b0383166118ea576118e2826119c0565b6112c26119ea565b6001600160a01b038216611901576118e2836119c0565b61190a836119c0565b6112c2826119c0565b815460009081036119265750600061052d565b82546000905b8082101561197357600061194083836119fa565b6000878152602090209091508590820154111561195f5780915061196d565b61196a816001611eb5565b92505b5061192c565b60008211801561199f57508361199c8661198e600186611ea2565b600091825260209091200190565b54145b156119b8576119af600183611ea2565b9250505061052d565b50905061052d565b6001600160a01b03811660009081526065602052604090206107fd906119e583610c05565b611a15565b6119f860666119e560355490565b565b6000611a0960028484186120f2565b61096090848416611eb5565b6000611a2060cd5490565b905080611a2c84611a60565b10156112c2578254600180820185556000858152602080822090930193909355938401805494850181558252902090910155565b80546000908103611a7357506000919050565b81548290611a8390600190611ea2565b81548110611a9357611a93611f16565b90600052602060002001549050919050565b6000815180845260005b81811015611acb57602081850181015186830182015201611aaf565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006109606020830184611aa5565b6001600160a01b03811681146107fd57600080fd5b60008060408385031215611b2657600080fd5b8235611b3181611afe565b946020939093013593505050565b6000808284036080811215611b5357600080fd5b833592506060601f1982011215611b6957600080fd5b506020830190509250929050565b600080600060608486031215611b8c57600080fd5b8335611b9781611afe565b92506020840135611ba781611afe565b929592945050506040919091013590565b600060208284031215611bca57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715611c0a57611c0a611bd1565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715611c3957611c39611bd1565b604052919050565b600080600080600060a08688031215611c5957600080fd5b8535611c6481611afe565b9450602086810135611c7581611afe565b9450604087810135611c8681611afe565b945060608801359350608088013567ffffffffffffffff80821115611caa57600080fd5b818a0191508a601f830112611cbe57600080fd5b813581811115611cd057611cd0611bd1565b611cde858260051b01611c10565b818152858101925060069190911b83018501908c821115611cfe57600080fd5b928501925b81841015611d475784848e031215611d1b5760008081fd5b611d23611be7565b8435611d2e81611afe565b8152848701358782015283529284019291850191611d03565b8096505050505050509295509295909350565b600060208284031215611d6c57600080fd5b813561096081611afe565b60008060408385031215611d8a57600080fd5b8235611d9581611afe565b91506020830135611da581611afe565b809150509250929050565b60008060008060608587031215611dc657600080fd5b843593506020850135611dd881611afe565b9250604085013567ffffffffffffffff80821115611df557600080fd5b818701915087601f830112611e0957600080fd5b813581811115611e1857600080fd5b886020828501011115611e2a57600080fd5b95989497505060200194505050565b600181811c90821680611e4d57607f821691505b602082108103611e6d57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600060018201611e9b57611e9b611e73565b5060010190565b8181038181111561052d5761052d611e73565b8082018082111561052d5761052d611e73565b634e487b7160e01b600052601260045260246000fd5b600082611eed57611eed611ec8565b500690565b6001600160a01b038316815260406020820181905260009061145d90830184611aa5565b634e487b7160e01b600052603260045260246000fd5b60008085851115611f3c57600080fd5b83861115611f4957600080fd5b5050820193919092039150565b8035602083101561052d57600019602084900360031b1b1692915050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b600081611fce57611fce611e73565b506000190190565b634e487b7160e01b600052600160045260246000fd5b601f8211156112c257600081815260208120601f850160051c810160208610156120135750805b601f850160051c820191505b81811015610bfd5782815560010161201f565b815167ffffffffffffffff81111561204c5761204c611bd1565b6120608161205a8454611e39565b84611fec565b602080601f831160018114612095576000841561207d5750858301515b600019600386901b1c1916600185901b178555610bfd565b600085815260208120601f198616915b828110156120c4578886015182559484019460019091019084016120a5565b50858210156120e25787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60008261210157612101611ec8565b50049056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220a7f49f3b74e4ac0e9fc814278d95b8601a69f04a86da95d921d3e639218e2b7264736f6c63430008130033","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106102015760003560e01c806361cc276311610125578063c6b61e4c116100ad578063ea0fee4f1161007c578063ea0fee4f1461043d578063eacdc5ff14610445578063eeb499451461044e578063f3f4370314610461578063fd242c141461047457600080fd5b8063c6b61e4c146103c4578063ce513b6f1461040e578063dd62ed3e14610421578063e0563ab11461043457600080fd5b806395d89b41116100f457806395d89b411461037957806397e5230d14610381578063981b24d01461038b578063a457c2d71461039e578063a9059cbb146103b157600080fd5b806361cc276314610341578063626560031461035457806370a082311461035d578063947287cf1461037057600080fd5b8063313ce567116101a85780633fd50001116101775780633fd50001146102f65780634ee2cd7e1461030957806351351d531461031c57806355b01e4d1461032a5780635ea5df791461033857600080fd5b8063313ce567146102c357806339509351146102d25780633b878c22146102e55780633ccfd60b146102ee57600080fd5b806306fdde031461020657806307b3e25214610224578063095ea7b31461024a5780630f50287c1461026d57806318160ddd1461028257806323b872dd14610294578063284017f5146102a75780632e17de78146102b0575b600080fd5b61020e610487565b60405161021b9190611aeb565b60405180910390f35b6102326004600360981b0181565b6040516001600160a01b03909116815260200161021b565b61025d610258366004611b13565b610519565b604051901515815260200161021b565b61028061027b366004611b3f565b610533565b005b6035545b60405190815260200161021b565b61025d6102a2366004611b77565b6107c5565b61023261202081565b6102806102be366004611bb8565b6107e9565b6040516012815260200161021b565b61025d6102e0366004611b13565b610800565b61023261101081565b610280610822565b610286610304366004611bb8565b610933565b610286610317366004611b13565b610954565b6102326002600160a01b0381565b6102326004600160991b0181565b61028661138881565b61028061034f366004611c41565b610967565b61028660cc5481565b61028661036b366004611d5a565b610c05565b61028661520881565b61020e610c20565b610286620249f081565b610286610399366004611bb8565b610c2f565b61025d6103ac366004611b13565b610c3a565b61025d6103bf366004611b13565b610cb5565b6103f36103d2366004611bb8565b60ce6020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161021b565b61028661041c366004611d5a565b610cc3565b61028661042f366004611d77565b610cf1565b61023261203081565b610286600181565b61028660cd5481565b61028061045c366004611db0565b610d1c565b61028661046f366004611d5a565b610dee565b610286610482366004611bb8565b610e15565b60606036805461049690611e39565b80601f01602080910402602001604051908101604052809291908181526020018280546104c290611e39565b801561050f5780601f106104e45761010080835404028352916020019161050f565b820191906000526020600020905b8154815290600101906020018083116104f257829003601f168201915b5050505050905090565b600033610527818585610e5f565b60019150505b92915050565b336002600160a01b031461057c5760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b60cd80546000918261058d83611e89565b9190505590508083146105d85760405162461bcd60e51b815260206004820152601360248201527215539156141150d5115117d15413d0d217d251606a1b6044820152606401610573565b81356020830135116106225760405162461bcd60e51b81526020600482015260136024820152721393d7d09313d0d2d4d7d0d3d3535255151151606a1b6044820152606401610573565b60cc5461063483356020850135611ea2565b61063f906001611eb5565b6106499190611ede565b156106a45760405162461bcd60e51b815260206004820152602560248201527f45504f43485f4d5553545f42455f444956495349424c455f42595f45504f43486044820152645f53495a4560d81b6064820152608401610573565b813560ce60006106b5600185611ea2565b81526020019081526020016000206001015460016106d39190611eb5565b146107165760405162461bcd60e51b8152602060048201526013602482015272494e56414c49445f53544152545f424c4f434b60681b6044820152606401610573565b600081815260ce60205260409020829061074782828135815560208201356001820155604082013560028201555050565b505060cf80546001810182556000919091526020838101357facb8d954e2cfef495862221e91bd7523613cf8808827cb33edfe4904cc51bf299092018290556040805190850135815284359186917f0ce8712c4dee4bd5a691f0bc1c39594671591e77395f8ebf6a3fb5f63fbea66a910160405180910390a4505050565b6000336107d3858285610f84565b6107de858585610ff8565b506001949350505050565b6107f3338261119c565b6107fd33826112c7565b50565b6000336105278185856108138383610cf1565b61081d9190611eb5565b610e5f565b33600090815260d06020526040812060cd549091908190610844908490611340565b808555604051828152919350915033907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a260c95460cb54604080517f8ca9a95e41b5eece253c93f5b31eed1253aed6b145d8a6e14d913fdf8e7322936020820152338183015260608082018790528251808303909101815260808201928390526316f1983160e01b9092526001600160a01b03938416936316f19831936108fc93911691608401611ef2565b600060405180830381600087803b15801561091657600080fd5b505af115801561092a573d6000803e3d6000fd5b50505050505050565b60cf818154811061094357600080fd5b600091825260209091200154905081565b600061096083836113b2565b9392505050565b600054610100900460ff16158080156109875750600054600160ff909116105b806109a15750303b1580156109a1575060005460ff166001145b610a045760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610573565b6000805460ff191660011790558015610a27576000805461ff0019166101001790555b6001600160a01b03861615801590610a4757506001600160a01b03851615155b8015610a5b57506001600160a01b03841615155b610a975760405162461bcd60e51b815260206004820152600d60248201526c1253959053125117d253941555609a1b6044820152606401610573565b610ae16040518060400160405280600c81526020016b15985b1a59185d1bdc94d95d60a21b815250604051806040016040528060048152602001631594d15560e21b8152506113fb565b60c980546001600160a01b038089166001600160a01b03199283161790925560ca805488841690831617905560cb80549287169290911691909117905560cc83905560005b8251811015610b7b57610b73838281518110610b4457610b44611f16565b602002602001015160000151848381518110610b6257610b62611f16565b602002602001015160200151611430565b600101610b26565b5060cf80546001818101835560009283527facb8d954e2cfef495862221e91bd7523613cf8808827cb33edfe4904cc51bf299091019190915560cd558015610bfd576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b6001600160a01b031660009081526033602052604090205490565b60606037805461049690611e39565b600061052d8261143a565b60003381610c488286610cf1565b905083811015610ca85760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610573565b6107de8286868403610e5f565b600033610527818585610ff8565b60cd546001600160a01b038216600090815260d0602052604081209091610cea9190611340565b5092915050565b6001600160a01b03918216600090815260346020908152604080832093909416825291909152205490565b60ca546001600160a01b031633148015610d43575060cb546001600160a01b038481169116145b610d805760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a2a72222a960911b6044820152606401610573565b7f1bcc0f4c3fad314e585165815f94ecca9b96690a26d6417d7876448a9a867a69610daf602060008486611f2c565b610db891611f56565b03610de857600080610dcd8360208187611f2c565b810190610dda9190611b13565b91509150610bfd8282611430565b50505050565b60cd546001600160a01b038216600090815260d060205260408120909161052d9190611465565b600081815260ce60205260408120600101548015610e5657600083815260ce6020526040902054610e469082611ea2565b610e51906001611eb5565b610960565b60009392505050565b6001600160a01b038316610ec15760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610573565b6001600160a01b038216610f225760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610573565b6001600160a01b0383811660008181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000610f908484610cf1565b90506000198114610de85781811015610feb5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610573565b610de88484848403610e5f565b6001600160a01b03831661105c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610573565b6001600160a01b0382166110be5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610573565b6110c9838383611503565b6001600160a01b038316600090815260336020526040902054818110156111415760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610573565b6001600160a01b0380851660008181526033602052604080822086860390559286168082529083902080548601905591516000805160206121078339815191529061118f9086815260200190565b60405180910390a3610de8565b6001600160a01b0382166111fc5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610573565b61120882600083611503565b6001600160a01b0382166000908152603360205260409020548181101561127c5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610573565b6001600160a01b03831660008181526033602090815260408083208686039055603580548790039055518581529192916000805160206121078339815191529101610f77565b505050565b6112f981600160cd546112da9190611eb5565b6001600160a01b038516600090815260d060205260409020919061156c565b816001600160a01b03167f655c1cd0236fb6dc4916f34c8ff10e3b18fcaea5b344dfc16c36fbb1bdfc5df28260405161133491815260200190565b60405180910390a25050565b81546000905b83600101548110156113ab5760008181526002850160209081526040918290208251808401909352805483526001015490820181905284101561138957506113ab565b80516113959084611eb5565b92505080806113a390611e89565b915050611346565b9250929050565b6001600160a01b0382166000908152606560205260408120819081906113d99085906116e2565b91509150816113f0576113eb85610c05565b6113f2565b805b95945050505050565b600054610100900460ff166114225760405162461bcd60e51b815260040161057390611f74565b61142c82826117d0565b5050565b61142c8282611810565b600080600061144a8460666116e2565b915091508161145b5760355461145d565b805b949350505050565b600182015460009080820361147e57600091505061052d565b600061148b600183611ea2565b90505b845481106114fb5760008181526002860160209081526040918290208251808401909352805483526001015490820181905285106114cc57506114fb565b80516114d89085611eb5565b9350816000036114e857506114fb565b50806114f381611fbf565b91505061148e565b505092915050565b6001600160a01b038316158061152057506001600160a01b038216155b6115615760405162461bcd60e51b81526020600482015260126024820152712a2920a729a322a92fa327a92124a22222a760711b6044820152606401610573565b6112c28383836118cb565b816000036115c75760405162461bcd60e51b815260206004820152602260248201527f5769746864726177616c51756575654c69623a20494e56414c49445f414d4f55604482015261139560f21b6064820152608401610573565b8254600184015481810361161e576040805180820182528581526020808201868152600085815260028a01909252928120915182559151600191820155860180549161161283611e89565b91905055505050505050565b60006002860181611630600185611ea2565b81526020019081526020016000206001015490508084101561165457611654611fd6565b838110156116a3576040805180820182528681526020808201878152600086815260028b01909252928120915182559151600191820155870180549161169983611e89565b9190505550610bfd565b846002870160006116b5600186611ea2565b815260200190815260200160002060000160008282546116d59190611eb5565b9091555050505050505050565b6000806000841161172e5760405162461bcd60e51b815260206004820152601660248201527504552433230536e617073686f743a20696420697320360541b6044820152606401610573565b60cd548411156117805760405162461bcd60e51b815260206004820152601d60248201527f4552433230536e617073686f743a206e6f6e6578697374656e742069640000006044820152606401610573565b600061178c8486611913565b845490915081036117a45760008092509250506113ab565b60018460010182815481106117bb576117bb611f16565b906000526020600020015492509250506113ab565b600054610100900460ff166117f75760405162461bcd60e51b815260040161057390611f74565b60366118038382612032565b5060376112c28282612032565b6001600160a01b0382166118665760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610573565b61187260008383611503565b80603560008282546118849190611eb5565b90915550506001600160a01b038216600081815260336020908152604080832080548601905551848152600080516020612107833981519152910160405180910390a35050565b6001600160a01b0383166118ea576118e2826119c0565b6112c26119ea565b6001600160a01b038216611901576118e2836119c0565b61190a836119c0565b6112c2826119c0565b815460009081036119265750600061052d565b82546000905b8082101561197357600061194083836119fa565b6000878152602090209091508590820154111561195f5780915061196d565b61196a816001611eb5565b92505b5061192c565b60008211801561199f57508361199c8661198e600186611ea2565b600091825260209091200190565b54145b156119b8576119af600183611ea2565b9250505061052d565b50905061052d565b6001600160a01b03811660009081526065602052604090206107fd906119e583610c05565b611a15565b6119f860666119e560355490565b565b6000611a0960028484186120f2565b61096090848416611eb5565b6000611a2060cd5490565b905080611a2c84611a60565b10156112c2578254600180820185556000858152602080822090930193909355938401805494850181558252902090910155565b80546000908103611a7357506000919050565b81548290611a8390600190611ea2565b81548110611a9357611a93611f16565b90600052602060002001549050919050565b6000815180845260005b81811015611acb57602081850181015186830182015201611aaf565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006109606020830184611aa5565b6001600160a01b03811681146107fd57600080fd5b60008060408385031215611b2657600080fd5b8235611b3181611afe565b946020939093013593505050565b6000808284036080811215611b5357600080fd5b833592506060601f1982011215611b6957600080fd5b506020830190509250929050565b600080600060608486031215611b8c57600080fd5b8335611b9781611afe565b92506020840135611ba781611afe565b929592945050506040919091013590565b600060208284031215611bca57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715611c0a57611c0a611bd1565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715611c3957611c39611bd1565b604052919050565b600080600080600060a08688031215611c5957600080fd5b8535611c6481611afe565b9450602086810135611c7581611afe565b9450604087810135611c8681611afe565b945060608801359350608088013567ffffffffffffffff80821115611caa57600080fd5b818a0191508a601f830112611cbe57600080fd5b813581811115611cd057611cd0611bd1565b611cde858260051b01611c10565b818152858101925060069190911b83018501908c821115611cfe57600080fd5b928501925b81841015611d475784848e031215611d1b5760008081fd5b611d23611be7565b8435611d2e81611afe565b8152848701358782015283529284019291850191611d03565b8096505050505050509295509295909350565b600060208284031215611d6c57600080fd5b813561096081611afe565b60008060408385031215611d8a57600080fd5b8235611d9581611afe565b91506020830135611da581611afe565b809150509250929050565b60008060008060608587031215611dc657600080fd5b843593506020850135611dd881611afe565b9250604085013567ffffffffffffffff80821115611df557600080fd5b818701915087601f830112611e0957600080fd5b813581811115611e1857600080fd5b886020828501011115611e2a57600080fd5b95989497505060200194505050565b600181811c90821680611e4d57607f821691505b602082108103611e6d57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600060018201611e9b57611e9b611e73565b5060010190565b8181038181111561052d5761052d611e73565b8082018082111561052d5761052d611e73565b634e487b7160e01b600052601260045260246000fd5b600082611eed57611eed611ec8565b500690565b6001600160a01b038316815260406020820181905260009061145d90830184611aa5565b634e487b7160e01b600052603260045260246000fd5b60008085851115611f3c57600080fd5b83861115611f4957600080fd5b5050820193919092039150565b8035602083101561052d57600019602084900360031b1b1692915050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b600081611fce57611fce611e73565b506000190190565b634e487b7160e01b600052600160045260246000fd5b601f8211156112c257600081815260208120601f850160051c810160208610156120135750805b601f850160051c820191505b81811015610bfd5782815560010161201f565b815167ffffffffffffffff81111561204c5761204c611bd1565b6120608161205a8454611e39565b84611fec565b602080601f831160018114612095576000841561207d5750858301515b600019600386901b1c1916600185901b178555610bfd565b600085815260208120601f198616915b828110156120c4578886015182559484019460019091019084016120a5565b50858210156120e25787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60008261210157612101611ec8565b50049056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220a7f49f3b74e4ac0e9fc814278d95b8601a69f04a86da95d921d3e639218e2b7264736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var EIP1559BurnArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"EIP1559Burn","sourceName":"contracts/child/EIP1559Burn.sol","abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"burner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"NativeTokenBurnt","type":"event"},{"inputs":[],"name":"burnDestination","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"childERC20Predicate","outputs":[{"internalType":"contract IChildERC20Predicate","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IChildERC20Predicate","name":"newChildERC20Predicate","type":"address"},{"internalType":"address","name":"newBurnDestination","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610425806100206000396000f3fe6080604052600436106100435760003560e01c80630fca96871461004f5780633ccfd60b1461008b578063485cc955146100a2578063d57184e4146100c257600080fd5b3661004a57005b600080fd5b34801561005b57600080fd5b5060015461006f906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b34801561009757600080fd5b506100a06100e8565b005b3480156100ae57600080fd5b506100a06100bd3660046103b6565b6101fc565b3480156100ce57600080fd5b5060005461006f906201000090046001600160a01b031681565b6000546201000090046001600160a01b031661014b5760405162461bcd60e51b815260206004820152601a60248201527f454950313535394275726e3a20554e494e495449414c495a454400000000000060448201526064015b60405180910390fd5b6000546001546040516361d9ad3f60e11b815261101060048201526001600160a01b0391821660248201524760448201819052926201000090049091169063c3b35a7e90606401600060405180830381600087803b1580156101ac57600080fd5b505af11580156101c0573d6000803e3d6000fd5b50506040518381523392507feb347a9a474ae97b7b32a903d84decf8fc80dc9cd6129085f5097de5cd3b3256915060200160405180910390a250565b600054610100900460ff161580801561021c5750600054600160ff909116105b806102365750303b158015610236575060005460ff166001145b6102995760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610142565b6000805460ff1916600117905580156102bc576000805461ff0019166101001790555b6001600160a01b0383166103125760405162461bcd60e51b815260206004820152601f60248201527f454950313535394275726e3a204241445f494e495449414c495a4154494f4e006044820152606401610142565b6000805462010000600160b01b031916620100006001600160a01b038681169190910291909117909155600180546001600160a01b0319169184169190911790558015610399576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b6001600160a01b03811681146103b357600080fd5b50565b600080604083850312156103c957600080fd5b82356103d48161039e565b915060208301356103e48161039e565b80915050925092905056fea26469706673582212204ef36b0d6b6d4a5e32117ffe6009ea0f1d44da93380a511f1e364bb3e312af6b64736f6c63430008130033","deployedBytecode":"0x6080604052600436106100435760003560e01c80630fca96871461004f5780633ccfd60b1461008b578063485cc955146100a2578063d57184e4146100c257600080fd5b3661004a57005b600080fd5b34801561005b57600080fd5b5060015461006f906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b34801561009757600080fd5b506100a06100e8565b005b3480156100ae57600080fd5b506100a06100bd3660046103b6565b6101fc565b3480156100ce57600080fd5b5060005461006f906201000090046001600160a01b031681565b6000546201000090046001600160a01b031661014b5760405162461bcd60e51b815260206004820152601a60248201527f454950313535394275726e3a20554e494e495449414c495a454400000000000060448201526064015b60405180910390fd5b6000546001546040516361d9ad3f60e11b815261101060048201526001600160a01b0391821660248201524760448201819052926201000090049091169063c3b35a7e90606401600060405180830381600087803b1580156101ac57600080fd5b505af11580156101c0573d6000803e3d6000fd5b50506040518381523392507feb347a9a474ae97b7b32a903d84decf8fc80dc9cd6129085f5097de5cd3b3256915060200160405180910390a250565b600054610100900460ff161580801561021c5750600054600160ff909116105b806102365750303b158015610236575060005460ff166001145b6102995760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610142565b6000805460ff1916600117905580156102bc576000805461ff0019166101001790555b6001600160a01b0383166103125760405162461bcd60e51b815260206004820152601f60248201527f454950313535394275726e3a204241445f494e495449414c495a4154494f4e006044820152606401610142565b6000805462010000600160b01b031916620100006001600160a01b038681169190910291909117909155600180546001600160a01b0319169184169190911790558015610399576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b6001600160a01b03811681146103b357600080fd5b50565b600080604083850312156103c957600080fd5b82356103d48161039e565b915060208301356103e48161039e565b80915050925092905056fea26469706673582212204ef36b0d6b6d4a5e32117ffe6009ea0f1d44da93380a511f1e364bb3e312af6b64736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var GenesisProxyArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"GenesisProxy","sourceName":"contracts/lib/GenesisProxy.sol","abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"address","name":"initiator","type":"address"}],"name":"protectSetUpProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"logic","type":"address"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setUpProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x60806040523480156200001157600080fd5b506000806040518060200160405280600081525082816200003b828260006200004e60201b60201c565b506200004990508262000080565b600080fd5b6200005983620000f2565b600082511180620000675750805b156200007b5762000079838362000134565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f620000c260008051602062000446833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a1620000ef8162000163565b50565b620000fd8162000201565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606200015c8383604051806060016040528060278152602001620004666027913962000298565b9392505050565b6001600160a01b038116620001ce5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b80600080516020620004468339815191525b80546001600160a01b0319166001600160a01b039290921691909117905550565b6001600160a01b0381163b620002705760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401620001c5565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc620001e0565b6060600080856001600160a01b031685604051620002b79190620003f2565b600060405180830381855af49150503d8060008114620002f4576040519150601f19603f3d011682016040523d82523d6000602084013e620002f9565b606091505b5090925090506200030d8683838762000317565b9695505050505050565b606083156200038b57825160000362000383576001600160a01b0385163b620003835760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401620001c5565b508162000397565b6200039783836200039f565b949350505050565b815115620003b05781518083602001fd5b8060405162461bcd60e51b8152600401620001c5919062000410565b60005b83811015620003e9578181015183820152602001620003cf565b50506000910152565b6000825162000406818460208701620003cc565b9190910192915050565b602081526000825180602084015262000431816040850160208701620003cc565b601f01601f1916919091016040019291505056feb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564","deployedBytecode":"0x60806040526004361061002d5760003560e01c806317cf3e3414610044578063da2655db146100645761003c565b3661003c5761003a610084565b005b61003a610084565b34801561005057600080fd5b5061003a61005f3660046108d2565b6101d6565b34801561007057600080fd5b5061003a61007f366004610934565b6102a5565b61008c610300565b6001600160a01b031633036101cc5760606001600160e01b0319600035166364d3180d60e11b81016100c7576100c0610333565b91506101c4565b63587086bd60e11b6001600160e01b03198216016100e7576100c061038a565b63070d7c6960e41b6001600160e01b0319821601610107576100c06103d0565b621eb96f60e61b6001600160e01b0319821601610126576100c0610401565b63a39f25e560e01b6001600160e01b0319821601610146576100c0610441565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101d4610455565b565b600080516020610a3f833981519152546000196001600160a01b038216016102315760405162461bcd60e51b815260206004820152600e60248201526d0416c7265616479207365742d75760941b60448201526064016101bb565b336001600160a01b038216146102785760405162461bcd60e51b815260206004820152600c60248201526b155b985d5d1a1bdc9a5e995960a21b60448201526064016101bb565b61028183610465565b61028d848360006104bc565b6001600080516020610a3f8339815191525550505050565b600080516020610a3f83398151915280549082905580156102fc5760405162461bcd60e51b8152602060048201526011602482015270105b1c9958591e481c1c9bdd1958dd1959607a1b60448201526064016101bb565b5050565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b606061033d6104e8565b600061034c3660048184610951565b8101906103599190610934565b9050610376816040518060200160405280600081525060006104bc565b505060408051602081019091526000815290565b606060008061039c3660048184610951565b8101906103a9919061097b565b915091506103b9828260016104bc565b604051806020016040528060008152509250505090565b60606103da6104e8565b60006103e93660048184610951565b8101906103f69190610934565b905061037681610465565b606061040b6104e8565b6000610415610300565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b606061044b6104e8565b60006104156104f3565b6101d46104606104f3565b610502565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61048e610300565b604080516001600160a01b03928316815291841660208301520160405180910390a16104b981610526565b50565b6104c5836105cf565b6000825111806104d25750805b156104e3576104e1838361060f565b505b505050565b34156101d457600080fd5b60006104fd61063b565b905090565b3660008037600080366000845af43d6000803e808015610521573d6000f35b3d6000fd5b6001600160a01b03811661058b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084016101bb565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b6105d881610663565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606106348383604051806060016040528060278152602001610a5f602791396106f7565b9392505050565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610324565b6001600160a01b0381163b6106d05760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016101bb565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6105ae565b6060600080856001600160a01b03168560405161071491906109ef565b600060405180830381855af49150503d806000811461074f576040519150601f19603f3d011682016040523d82523d6000602084013e610754565b606091505b50915091506107658683838761076f565b9695505050505050565b606083156107de5782516000036107d7576001600160a01b0385163b6107d75760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101bb565b50816107e8565b6107e883836107f0565b949350505050565b8151156108005781518083602001fd5b8060405162461bcd60e51b81526004016101bb9190610a0b565b6001600160a01b03811681146104b957600080fd5b634e487b7160e01b600052604160045260246000fd5b600082601f83011261085657600080fd5b813567ffffffffffffffff808211156108715761087161082f565b604051601f8301601f19908116603f011681019082821181831017156108995761089961082f565b816040528381528660208588010111156108b257600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806000606084860312156108e757600080fd5b83356108f28161081a565b925060208401356109028161081a565b9150604084013567ffffffffffffffff81111561091e57600080fd5b61092a86828701610845565b9150509250925092565b60006020828403121561094657600080fd5b81356106348161081a565b6000808585111561096157600080fd5b8386111561096e57600080fd5b5050820193919092039150565b6000806040838503121561098e57600080fd5b82356109998161081a565b9150602083013567ffffffffffffffff8111156109b557600080fd5b6109c185828601610845565b9150509250929050565b60005b838110156109e65781810151838201526020016109ce565b50506000910152565b60008251610a018184602087016109cb565b9190910192915050565b6020815260008251806020840152610a2a8160408501602087016109cb565b601f01601f1916919091016040019291505056fe16561015e0650c143c10fb1907c52a56b654e2f0922ca3245bde5beff81a333d416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220abb148ad739bda14bfde881802dab93d2ab651041781eed877949635c7179ba364736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` -var TransparentUpgradeableProxyArtifact string = `{"_format":"hh-sol-artifact-1","contractName":"TransparentUpgradeableProxy","sourceName":"@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol","abi":[{"inputs":[{"internalType":"address","name":"_logic","type":"address"},{"internalType":"address","name":"admin_","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"payable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405260405162000e3a38038062000e3a833981016040819052620000269162000424565b828162000036828260006200004d565b50620000449050826200007f565b50505062000557565b6200005883620000f1565b600082511180620000665750805b156200007a5762000078838362000133565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f620000c160008051602062000df3833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a1620000ee8162000162565b50565b620000fc8162000200565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606200015b838360405180606001604052806027815260200162000e136027913962000297565b9392505050565b6001600160a01b038116620001cd5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b8060008051602062000df38339815191525b80546001600160a01b0319166001600160a01b039290921691909117905550565b6001600160a01b0381163b6200026f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401620001c4565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc620001df565b6060600080856001600160a01b031685604051620002b6919062000504565b600060405180830381855af49150503d8060008114620002f3576040519150601f19603f3d011682016040523d82523d6000602084013e620002f8565b606091505b5090925090506200030c8683838762000316565b9695505050505050565b606083156200038a57825160000362000382576001600160a01b0385163b620003825760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401620001c4565b508162000396565b6200039683836200039e565b949350505050565b815115620003af5781518083602001fd5b8060405162461bcd60e51b8152600401620001c4919062000522565b80516001600160a01b0381168114620003e357600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b838110156200041b57818101518382015260200162000401565b50506000910152565b6000806000606084860312156200043a57600080fd5b6200044584620003cb565b92506200045560208501620003cb565b60408501519092506001600160401b03808211156200047357600080fd5b818601915086601f8301126200048857600080fd5b8151818111156200049d576200049d620003e8565b604051601f8201601f19908116603f01168101908382118183101715620004c857620004c8620003e8565b81604052828152896020848701011115620004e257600080fd5b620004f5836020830160208801620003fe565b80955050505050509250925092565b6000825162000518818460208701620003fe565b9190910192915050565b602081526000825180602084015262000543816040850160208701620003fe565b601f01601f19169190910160400192915050565b61088c80620005676000396000f3fe60806040523661001357610011610017565b005b6100115b61001f610169565b6001600160a01b0316330361015f5760606001600160e01b0319600035166364d3180d60e11b810161005a5761005361019c565b9150610157565b63587086bd60e11b6001600160e01b031982160161007a576100536101f3565b63070d7c6960e41b6001600160e01b031982160161009a57610053610239565b621eb96f60e61b6001600160e01b03198216016100b95761005361026a565b63a39f25e560e01b6001600160e01b03198216016100d9576100536102aa565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101676102be565b565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a66102ce565b60006101b53660048184610683565b8101906101c291906106c9565b90506101df816040518060200160405280600081525060006102d9565b505060408051602081019091526000815290565b60606000806102053660048184610683565b81019061021291906106fa565b91509150610222828260016102d9565b604051806020016040528060008152509250505090565b60606102436102ce565b60006102523660048184610683565b81019061025f91906106c9565b90506101df81610305565b60606102746102ce565b600061027e610169565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102b46102ce565b600061027e61035c565b6101676102c961035c565b61036b565b341561016757600080fd5b6102e28361038f565b6000825111806102ef5750805b15610300576102fe83836103cf565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61032e610169565b604080516001600160a01b03928316815291841660208301520160405180910390a1610359816103fb565b50565b60006103666104a4565b905090565b3660008037600080366000845af43d6000803e80801561038a573d6000f35b3d6000fd5b610398816104cc565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606103f4838360405180606001604052806027815260200161083060279139610560565b9392505050565b6001600160a01b0381166104605760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018d565b6001600160a01b0381163b6105395760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610483565b6060600080856001600160a01b03168560405161057d91906107e0565b600060405180830381855af49150503d80600081146105b8576040519150601f19603f3d011682016040523d82523d6000602084013e6105bd565b606091505b50915091506105ce868383876105d8565b9695505050505050565b60608315610647578251600003610640576001600160a01b0385163b6106405760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014e565b5081610651565b6106518383610659565b949350505050565b8151156106695781518083602001fd5b8060405162461bcd60e51b815260040161014e91906107fc565b6000808585111561069357600080fd5b838611156106a057600080fd5b5050820193919092039150565b80356001600160a01b03811681146106c457600080fd5b919050565b6000602082840312156106db57600080fd5b6103f4826106ad565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561070d57600080fd5b610716836106ad565b9150602083013567ffffffffffffffff8082111561073357600080fd5b818501915085601f83011261074757600080fd5b813581811115610759576107596106e4565b604051601f8201601f19908116603f01168101908382118183101715610781576107816106e4565b8160405282815288602084870101111561079a57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107d75781810151838201526020016107bf565b50506000910152565b600082516107f28184602087016107bc565b9190910192915050565b602081526000825180602084015261081b8160408501602087016107bc565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e47bdad79754770e5755a6f8224b03af73f674035cf42b93fa6fc0731c0dbb6864736f6c63430008130033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564","deployedBytecode":"0x60806040523661001357610011610017565b005b6100115b61001f610169565b6001600160a01b0316330361015f5760606001600160e01b0319600035166364d3180d60e11b810161005a5761005361019c565b9150610157565b63587086bd60e11b6001600160e01b031982160161007a576100536101f3565b63070d7c6960e41b6001600160e01b031982160161009a57610053610239565b621eb96f60e61b6001600160e01b03198216016100b95761005361026a565b63a39f25e560e01b6001600160e01b03198216016100d9576100536102aa565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101676102be565b565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a66102ce565b60006101b53660048184610683565b8101906101c291906106c9565b90506101df816040518060200160405280600081525060006102d9565b505060408051602081019091526000815290565b60606000806102053660048184610683565b81019061021291906106fa565b91509150610222828260016102d9565b604051806020016040528060008152509250505090565b60606102436102ce565b60006102523660048184610683565b81019061025f91906106c9565b90506101df81610305565b60606102746102ce565b600061027e610169565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102b46102ce565b600061027e61035c565b6101676102c961035c565b61036b565b341561016757600080fd5b6102e28361038f565b6000825111806102ef5750805b15610300576102fe83836103cf565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61032e610169565b604080516001600160a01b03928316815291841660208301520160405180910390a1610359816103fb565b50565b60006103666104a4565b905090565b3660008037600080366000845af43d6000803e80801561038a573d6000f35b3d6000fd5b610398816104cc565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606103f4838360405180606001604052806027815260200161083060279139610560565b9392505050565b6001600160a01b0381166104605760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018d565b6001600160a01b0381163b6105395760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610483565b6060600080856001600160a01b03168560405161057d91906107e0565b600060405180830381855af49150503d80600081146105b8576040519150601f19603f3d011682016040523d82523d6000602084013e6105bd565b606091505b50915091506105ce868383876105d8565b9695505050505050565b60608315610647578251600003610640576001600160a01b0385163b6106405760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014e565b5081610651565b6106518383610659565b949350505050565b8151156106695781518083602001fd5b8060405162461bcd60e51b815260040161014e91906107fc565b6000808585111561069357600080fd5b838611156106a057600080fd5b5050820193919092039150565b80356001600160a01b03811681146106c457600080fd5b919050565b6000602082840312156106db57600080fd5b6103f4826106ad565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561070d57600080fd5b610716836106ad565b9150602083013567ffffffffffffffff8082111561073357600080fd5b818501915085601f83011261074757600080fd5b813581811115610759576107596106e4565b604051601f8201601f19908116603f01168101908382118183101715610781576107816106e4565b8160405282815288602084870101111561079a57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107d75781810151838201526020016107bf565b50506000910152565b600082516107f28184602087016107bc565b9190910192915050565b602081526000825180602084015261081b8160408501602087016107bc565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e47bdad79754770e5755a6f8224b03af73f674035cf42b93fa6fc0731c0dbb6864736f6c63430008130033","linkReferences":{},"deployedLinkReferences":{}}` diff --git a/consensus/polybft/contractsapi/helper.go b/consensus/polybft/contractsapi/helper.go deleted file mode 100644 index 49a8d05..0000000 --- a/consensus/polybft/contractsapi/helper.go +++ /dev/null @@ -1,55 +0,0 @@ -package contractsapi - -import ( - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - "github.com/xgr-network/xgr-node/types" -) - -// StateTransactionInput is an abstraction for different state transaction inputs -type StateTransactionInput interface { - // EncodeAbi contains logic for encoding arbitrary data into ABI format - EncodeAbi() ([]byte, error) - // DecodeAbi contains logic for decoding given ABI data - DecodeAbi(b []byte) error -} - -// EventAbi is an interface representing an event generated in contractsapi -type EventAbi interface { - // Sig returns the event ABI signature or ID (which is unique for all event types) - Sig() ethgo.Hash - // Encode does abi encoding of given event - Encode() ([]byte, error) - // ParseLog parses the provided receipt log to given event type - ParseLog(log *ethgo.Log) (bool, error) -} - -var ( - // stateSyncABIType is a specific case where we need to encode state sync event as a tuple of tuple - stateSyncABIType = abi.MustNewType( - "tuple(tuple(uint256 id, address sender, address receiver, bytes data))") - - // GetCheckpointBlockABIResponse is the ABI type for getCheckpointBlock function return value - GetCheckpointBlockABIResponse = abi.MustNewType("tuple(bool isFound, uint256 checkpointBlock)") -) - -// ToABI converts StateSyncEvent to ABI -func (sse *StateSyncedEvent) EncodeAbi() ([]byte, error) { - return stateSyncABIType.Encode([]interface{}{sse}) -} - -var ( - _ StateTransactionInput = &CommitEpochValidatorSetFn{} - _ StateTransactionInput = &DistributeRewardForRewardPoolFn{} -) - -// IsStake indicates if transfer event (from ERC20 implementation) mints tokens to a non zero address -func (t *TransferEvent) IsStake() bool { - return t.To != types.ZeroAddress && t.From == types.ZeroAddress -} - -// IsUnstake indicates if transfer event (from ERC20 implementation) burns tokens from a non zero address -// meaning, it transfers them to zero address -func (t *TransferEvent) IsUnstake() bool { - return t.To == types.ZeroAddress && t.From != types.ZeroAddress -} diff --git a/consensus/polybft/contractsapi/init.go b/consensus/polybft/contractsapi/init.go deleted file mode 100644 index 6de3b23..0000000 --- a/consensus/polybft/contractsapi/init.go +++ /dev/null @@ -1,305 +0,0 @@ -package contractsapi - -import ( - "embed" - "log" - "path" - - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi/artifact" -) - -const ( - testContractsDir = "test-contracts" -) - -var ( - // core-contracts smart contracts - CheckpointManager *artifact.Artifact - ExitHelper *artifact.Artifact - StateSender *artifact.Artifact - RootERC20Predicate *artifact.Artifact - RootERC721Predicate *artifact.Artifact - RootERC1155Predicate *artifact.Artifact - ChildMintableERC20Predicate *artifact.Artifact - ChildMintableERC721Predicate *artifact.Artifact - ChildMintableERC1155Predicate *artifact.Artifact - BLS *artifact.Artifact - BLS256 *artifact.Artifact - System *artifact.Artifact - Merkle *artifact.Artifact - ChildValidatorSet *artifact.Artifact - NativeERC20 *artifact.Artifact - NativeERC20Mintable *artifact.Artifact - StateReceiver *artifact.Artifact - ChildERC20 *artifact.Artifact - ChildERC20Predicate *artifact.Artifact - ChildERC20PredicateACL *artifact.Artifact - RootMintableERC20Predicate *artifact.Artifact - RootMintableERC20PredicateACL *artifact.Artifact - ChildERC721 *artifact.Artifact - ChildERC721Predicate *artifact.Artifact - ChildERC721PredicateACL *artifact.Artifact - RootMintableERC721Predicate *artifact.Artifact - RootMintableERC721PredicateACL *artifact.Artifact - ChildERC1155 *artifact.Artifact - ChildERC1155Predicate *artifact.Artifact - ChildERC1155PredicateACL *artifact.Artifact - RootMintableERC1155Predicate *artifact.Artifact - RootMintableERC1155PredicateACL *artifact.Artifact - L2StateSender *artifact.Artifact - CustomSupernetManager *artifact.Artifact - StakeManager *artifact.Artifact - RewardPool *artifact.Artifact - ValidatorSet *artifact.Artifact - RootERC721 *artifact.Artifact - RootERC1155 *artifact.Artifact - EIP1559Burn *artifact.Artifact - GenesisProxy *artifact.Artifact - TransparentUpgradeableProxy *artifact.Artifact - - // test smart contracts - //go:embed test-contracts/* - testContracts embed.FS - TestWriteBlockMetadata *artifact.Artifact - RootERC20 *artifact.Artifact - TestSimple *artifact.Artifact - TestRewardToken *artifact.Artifact -) - -func init() { - var err error - - CheckpointManager, err = artifact.DecodeArtifact([]byte(CheckpointManagerArtifact)) - if err != nil { - log.Fatal(err) - } - - ExitHelper, err = artifact.DecodeArtifact([]byte(ExitHelperArtifact)) - if err != nil { - log.Fatal(err) - } - - L2StateSender, err = artifact.DecodeArtifact([]byte(L2StateSenderArtifact)) - if err != nil { - log.Fatal(err) - } - - BLS, err = artifact.DecodeArtifact([]byte(BLSArtifact)) - if err != nil { - log.Fatal(err) - } - - BLS256, err = artifact.DecodeArtifact([]byte(BN256G2Artifact)) - if err != nil { - log.Fatal(err) - } - - Merkle, err = artifact.DecodeArtifact([]byte(MerkleArtifact)) - if err != nil { - log.Fatal(err) - } - - StateSender, err = artifact.DecodeArtifact([]byte(StateSenderArtifact)) - if err != nil { - log.Fatal(err) - } - - RootERC20Predicate, err = artifact.DecodeArtifact([]byte(RootERC20PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - RootERC721Predicate, err = artifact.DecodeArtifact([]byte(RootERC721PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - RootERC1155Predicate, err = artifact.DecodeArtifact([]byte(RootERC1155PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - ChildMintableERC20Predicate, err = artifact.DecodeArtifact([]byte(ChildMintableERC20PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - ChildMintableERC721Predicate, err = artifact.DecodeArtifact([]byte(ChildMintableERC721PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - ChildMintableERC1155Predicate, err = artifact.DecodeArtifact([]byte(ChildMintableERC1155PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - StateReceiver, err = artifact.DecodeArtifact([]byte(StateReceiverArtifact)) - if err != nil { - log.Fatal(err) - } - - System, err = artifact.DecodeArtifact([]byte(SystemArtifact)) - if err != nil { - log.Fatal(err) - } - - ChildERC20, err = artifact.DecodeArtifact([]byte(ChildERC20Artifact)) - if err != nil { - log.Fatal(err) - } - - ChildERC20Predicate, err = artifact.DecodeArtifact([]byte(ChildERC20PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - ChildERC20PredicateACL, err = artifact.DecodeArtifact([]byte(ChildERC20PredicateACLArtifact)) - if err != nil { - log.Fatal(err) - } - - RootMintableERC20Predicate, err = artifact.DecodeArtifact([]byte(RootMintableERC20PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - RootMintableERC20PredicateACL, err = artifact.DecodeArtifact([]byte(RootMintableERC20PredicateACLArtifact)) - if err != nil { - log.Fatal(err) - } - - ChildERC721, err = artifact.DecodeArtifact([]byte(ChildERC721Artifact)) - if err != nil { - log.Fatal(err) - } - - ChildERC721Predicate, err = artifact.DecodeArtifact([]byte(ChildERC721PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - ChildERC721PredicateACL, err = artifact.DecodeArtifact([]byte(ChildERC721PredicateACLArtifact)) - if err != nil { - log.Fatal(err) - } - - RootMintableERC721Predicate, err = artifact.DecodeArtifact([]byte(RootMintableERC721PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - RootMintableERC721PredicateACL, err = artifact.DecodeArtifact([]byte(RootMintableERC721PredicateACLArtifact)) - if err != nil { - log.Fatal(err) - } - - ChildERC1155, err = artifact.DecodeArtifact([]byte(ChildERC1155Artifact)) - if err != nil { - log.Fatal(err) - } - - ChildERC1155Predicate, err = artifact.DecodeArtifact([]byte(ChildERC1155PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - ChildERC1155PredicateACL, err = artifact.DecodeArtifact([]byte(ChildERC1155PredicateACLArtifact)) - if err != nil { - log.Fatal(err) - } - - RootMintableERC1155Predicate, err = artifact.DecodeArtifact([]byte(RootMintableERC1155PredicateArtifact)) - if err != nil { - log.Fatal(err) - } - - RootMintableERC1155PredicateACL, err = artifact.DecodeArtifact([]byte(RootMintableERC1155PredicateACLArtifact)) - if err != nil { - log.Fatal(err) - } - - NativeERC20, err = artifact.DecodeArtifact([]byte(NativeERC20Artifact)) - if err != nil { - log.Fatal(err) - } - - NativeERC20Mintable, err = artifact.DecodeArtifact([]byte(NativeERC20MintableArtifact)) - if err != nil { - log.Fatal(err) - } - - RootERC20, err = artifact.DecodeArtifact([]byte(MockERC20Artifact)) - if err != nil { - log.Fatal(err) - } - - RootERC721, err = artifact.DecodeArtifact([]byte(MockERC721Artifact)) - if err != nil { - log.Fatal(err) - } - - RootERC1155, err = artifact.DecodeArtifact([]byte(MockERC1155Artifact)) - if err != nil { - log.Fatal(err) - } - - TestWriteBlockMetadata, err = artifact.DecodeArtifact(readTestContractContent("TestWriteBlockMetadata.json")) - if err != nil { - log.Fatal(err) - } - - TestSimple, err = artifact.DecodeArtifact(readTestContractContent("TestSimple.json")) - if err != nil { - log.Fatal(err) - } - - TestRewardToken, err = artifact.DecodeArtifact(readTestContractContent("TestRewardToken.json")) - if err != nil { - log.Fatal(err) - } - - CustomSupernetManager, err = artifact.DecodeArtifact([]byte(CustomSupernetManagerArtifact)) - if err != nil { - log.Fatal(err) - } - - StakeManager, err = artifact.DecodeArtifact([]byte(StakeManagerArtifact)) - if err != nil { - log.Fatal(err) - } - - RewardPool, err = artifact.DecodeArtifact([]byte(RewardPoolArtifact)) - if err != nil { - log.Fatal(err) - } - - ValidatorSet, err = artifact.DecodeArtifact([]byte(ValidatorSetArtifact)) - if err != nil { - log.Fatal(err) - } - - EIP1559Burn, err = artifact.DecodeArtifact([]byte(EIP1559BurnArtifact)) - if err != nil { - log.Fatal(err) - } - - GenesisProxy, err = artifact.DecodeArtifact([]byte(GenesisProxyArtifact)) - if err != nil { - log.Fatal(err) - } - - TransparentUpgradeableProxy, err = artifact.DecodeArtifact([]byte(TransparentUpgradeableProxyArtifact)) - if err != nil { - log.Fatal(err) - } -} - -func readTestContractContent(contractFileName string) []byte { - contractRaw, err := testContracts.ReadFile(path.Join(testContractsDir, contractFileName)) - if err != nil { - log.Fatal(err) - } - - return contractRaw -} diff --git a/consensus/polybft/contractsapi/init_test.go b/consensus/polybft/contractsapi/init_test.go deleted file mode 100644 index c64d965..0000000 --- a/consensus/polybft/contractsapi/init_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package contractsapi - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestArtifactNotEmpty(t *testing.T) { - require.NotEmpty(t, CheckpointManager.Bytecode) - require.NotEmpty(t, CheckpointManager.DeployedBytecode) - require.NotEmpty(t, CheckpointManager.Abi) - - require.NotEmpty(t, ExitHelper.Bytecode) - require.NotEmpty(t, ExitHelper.DeployedBytecode) - require.NotEmpty(t, ExitHelper.Abi) - - require.NotEmpty(t, L2StateSender.Bytecode) - require.NotEmpty(t, L2StateSender.DeployedBytecode) - require.NotEmpty(t, L2StateSender.Abi) - - require.NotEmpty(t, BLS.Bytecode) - require.NotEmpty(t, BLS.DeployedBytecode) - require.NotEmpty(t, BLS.Abi) - - require.NotEmpty(t, BLS256.Bytecode) - require.NotEmpty(t, BLS256.DeployedBytecode) - require.NotEmpty(t, BLS256.Abi) -} diff --git a/consensus/polybft/contractsapi/test-contracts/TestRewardToken.json b/consensus/polybft/contractsapi/test-contracts/TestRewardToken.json deleted file mode 100644 index 86f12b2..0000000 --- a/consensus/polybft/contractsapi/test-contracts/TestRewardToken.json +++ /dev/null @@ -1,330 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "TestRewardToken", - "sourceName": "contracts/mocks/TestRewardToken.sol", - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x608060405234801561001057600080fd5b50610a87806100206000396000f3fe608060405234801561001057600080fd5b50600436106100bf5760003560e01c806340c10f191161007c57806340c10f191461014c57806370a082311461016157806395d89b411461018a5780639dc29fac14610192578063a457c2d7146101a5578063a9059cbb146101b8578063dd62ed3e146101cb57600080fd5b806306fdde03146100c4578063095ea7b3146100e257806318160ddd1461010557806323b872dd14610117578063313ce5671461012a5780633950935114610139575b600080fd5b6100cc6101de565b6040516100d991906108b1565b60405180910390f35b6100f56100f036600461091b565b610270565b60405190151581526020016100d9565b6035545b6040519081526020016100d9565b6100f5610125366004610945565b61028a565b604051601281526020016100d9565b6100f561014736600461091b565b6102ae565b61015f61015a36600461091b565b6102d0565b005b61010961016f366004610981565b6001600160a01b031660009081526033602052604090205490565b6100cc6102de565b61015f6101a036600461091b565b6102ed565b6100f56101b336600461091b565b6102f7565b6100f56101c636600461091b565b610377565b6101096101d93660046109a3565b610385565b6060603680546101ed906109d6565b80601f0160208091040260200160405190810160405280929190818152602001828054610219906109d6565b80156102665780601f1061023b57610100808354040283529160200191610266565b820191906000526020600020905b81548152906001019060200180831161024957829003601f168201915b5050505050905090565b60003361027e8185856103b0565b60019150505b92915050565b6000336102988582856104d5565b6102a385858561054f565b506001949350505050565b60003361027e8185856102c18383610385565b6102cb9190610a10565b6103b0565b6102da82826106e8565b5050565b6060603780546101ed906109d6565b6102da8282610797565b600033816103058286610385565b90508381101561036a5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b6102a382868684036103b0565b60003361027e81858561054f565b6001600160a01b03918216600090815260346020908152604080832093909416825291909152205490565b6001600160a01b0383166104125760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610361565b6001600160a01b0382166104735760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610361565b6001600160a01b0383811660008181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b60006104e18484610385565b90506000198114610549578181101561053c5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610361565b61054984848484036103b0565b50505050565b6001600160a01b0383166105b35760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610361565b6001600160a01b0382166106155760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610361565b6001600160a01b0383166000908152603360205260409020548181101561068d5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610361565b6001600160a01b038085166000818152603360205260408082208686039055928616808252908390208054860190559151600080516020610a32833981519152906106db9086815260200190565b60405180910390a3610549565b6001600160a01b03821661073e5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610361565b80603560008282546107509190610a10565b90915550506001600160a01b038216600081815260336020908152604080832080548601905551848152600080516020610a32833981519152910160405180910390a35050565b6001600160a01b0382166107f75760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610361565b6001600160a01b0382166000908152603360205260409020548181101561086b5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610361565b6001600160a01b0383166000818152603360209081526040808320868603905560358054879003905551858152919291600080516020610a3283398151915291016104c8565b600060208083528351808285015260005b818110156108de578581018301518582016040015282016108c2565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461091657600080fd5b919050565b6000806040838503121561092e57600080fd5b610937836108ff565b946020939093013593505050565b60008060006060848603121561095a57600080fd5b610963846108ff565b9250610971602085016108ff565b9150604084013590509250925092565b60006020828403121561099357600080fd5b61099c826108ff565b9392505050565b600080604083850312156109b657600080fd5b6109bf836108ff565b91506109cd602084016108ff565b90509250929050565b600181811c908216806109ea57607f821691505b602082108103610a0a57634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561028457634e487b7160e01b600052601160045260246000fdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212207a3087f7ea340d96d27d018cb7cdeb374c187cb5bf45382f42a2c2793bd19ff264736f6c63430008130033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100bf5760003560e01c806340c10f191161007c57806340c10f191461014c57806370a082311461016157806395d89b411461018a5780639dc29fac14610192578063a457c2d7146101a5578063a9059cbb146101b8578063dd62ed3e146101cb57600080fd5b806306fdde03146100c4578063095ea7b3146100e257806318160ddd1461010557806323b872dd14610117578063313ce5671461012a5780633950935114610139575b600080fd5b6100cc6101de565b6040516100d991906108b1565b60405180910390f35b6100f56100f036600461091b565b610270565b60405190151581526020016100d9565b6035545b6040519081526020016100d9565b6100f5610125366004610945565b61028a565b604051601281526020016100d9565b6100f561014736600461091b565b6102ae565b61015f61015a36600461091b565b6102d0565b005b61010961016f366004610981565b6001600160a01b031660009081526033602052604090205490565b6100cc6102de565b61015f6101a036600461091b565b6102ed565b6100f56101b336600461091b565b6102f7565b6100f56101c636600461091b565b610377565b6101096101d93660046109a3565b610385565b6060603680546101ed906109d6565b80601f0160208091040260200160405190810160405280929190818152602001828054610219906109d6565b80156102665780601f1061023b57610100808354040283529160200191610266565b820191906000526020600020905b81548152906001019060200180831161024957829003601f168201915b5050505050905090565b60003361027e8185856103b0565b60019150505b92915050565b6000336102988582856104d5565b6102a385858561054f565b506001949350505050565b60003361027e8185856102c18383610385565b6102cb9190610a10565b6103b0565b6102da82826106e8565b5050565b6060603780546101ed906109d6565b6102da8282610797565b600033816103058286610385565b90508381101561036a5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b6102a382868684036103b0565b60003361027e81858561054f565b6001600160a01b03918216600090815260346020908152604080832093909416825291909152205490565b6001600160a01b0383166104125760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610361565b6001600160a01b0382166104735760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610361565b6001600160a01b0383811660008181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b60006104e18484610385565b90506000198114610549578181101561053c5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610361565b61054984848484036103b0565b50505050565b6001600160a01b0383166105b35760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610361565b6001600160a01b0382166106155760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610361565b6001600160a01b0383166000908152603360205260409020548181101561068d5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610361565b6001600160a01b038085166000818152603360205260408082208686039055928616808252908390208054860190559151600080516020610a32833981519152906106db9086815260200190565b60405180910390a3610549565b6001600160a01b03821661073e5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610361565b80603560008282546107509190610a10565b90915550506001600160a01b038216600081815260336020908152604080832080548601905551848152600080516020610a32833981519152910160405180910390a35050565b6001600160a01b0382166107f75760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610361565b6001600160a01b0382166000908152603360205260409020548181101561086b5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610361565b6001600160a01b0383166000818152603360209081526040808320868603905560358054879003905551858152919291600080516020610a3283398151915291016104c8565b600060208083528351808285015260005b818110156108de578581018301518582016040015282016108c2565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461091657600080fd5b919050565b6000806040838503121561092e57600080fd5b610937836108ff565b946020939093013593505050565b60008060006060848603121561095a57600080fd5b610963846108ff565b9250610971602085016108ff565b9150604084013590509250925092565b60006020828403121561099357600080fd5b61099c826108ff565b9392505050565b600080604083850312156109b657600080fd5b6109bf836108ff565b91506109cd602084016108ff565b90509250929050565b600181811c908216806109ea57607f821691505b602082108103610a0a57634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561028457634e487b7160e01b600052601160045260246000fdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212207a3087f7ea340d96d27d018cb7cdeb374c187cb5bf45382f42a2c2793bd19ff264736f6c63430008130033", - "linkReferences": {}, - "deployedLinkReferences": {} -} diff --git a/consensus/polybft/contractsapi/test-contracts/TestSimple.json b/consensus/polybft/contractsapi/test-contracts/TestSimple.json deleted file mode 100644 index 2772c44..0000000 --- a/consensus/polybft/contractsapi/test-contracts/TestSimple.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "TestSimple", - "sourceName": "contracts/TestSimple.sol", - "abi": [ - { - "inputs": [], - "name": "getValue", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_val", - "type": "uint256" - } - ], - "name": "setValue", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063209652551461003b5780635524107714610059575b600080fd5b610043610075565b60405161005091906100a1565b60405180910390f35b610073600480360381019061006e91906100ed565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009b81610088565b82525050565b60006020820190506100b66000830184610092565b92915050565b600080fd5b6100ca81610088565b81146100d557600080fd5b50565b6000813590506100e7816100c1565b92915050565b600060208284031215610103576101026100bc565b5b6000610111848285016100d8565b9150509291505056fea2646970667358221220470fde08a8031bec841c7cc72e8ff6af3fefb578f1a87c4d89d5fb45f57a532464736f6c63430008110033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063209652551461003b5780635524107714610059575b600080fd5b610043610075565b60405161005091906100a1565b60405180910390f35b610073600480360381019061006e91906100ed565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009b81610088565b82525050565b60006020820190506100b66000830184610092565b92915050565b600080fd5b6100ca81610088565b81146100d557600080fd5b50565b6000813590506100e7816100c1565b92915050565b600060208284031215610103576101026100bc565b5b6000610111848285016100d8565b9150509291505056fea2646970667358221220470fde08a8031bec841c7cc72e8ff6af3fefb578f1a87c4d89d5fb45f57a532464736f6c63430008110033", - "linkReferences": {}, - "deployedLinkReferences": {} -} - - diff --git a/consensus/polybft/contractsapi/test-contracts/TestSimple.sol b/consensus/polybft/contractsapi/test-contracts/TestSimple.sol deleted file mode 100644 index 9366467..0000000 --- a/consensus/polybft/contractsapi/test-contracts/TestSimple.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -contract TestSimple { - uint256 val; - - function getValue() public view returns (uint256) { - return val; - } - - function setValue(uint256 _val) public { - val = _val; - } -} diff --git a/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.json b/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.json deleted file mode 100644 index 5b92e99..0000000 --- a/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "TestWriteBlockMetadata", - "sourceName": "test-contracts/TestWriteBlockMetadata.sol", - "abi": [ - { - "inputs": [], - "name": "coinbase", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "data", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "init", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x608060405234801561001057600080fd5b50610187806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063a6ae0aac14610046578063e1c7392a14610076578063f0ba8440146100f6575b600080fd5b600154610059906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100f46000805460018181018355828052447f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56392830155825480820184554390830155825480820184554290830155825480820184554590830155825480820190935546929091019190915580546001600160a01b03191641179055565b005b610109610104366004610138565b610117565b60405190815260200161006d565b6000818154811061012757600080fd5b600091825260209091200154905081565b60006020828403121561014a57600080fd5b503591905056fea26469706673582212200950a847916ef267078b133380353b5117947fac6a936af48086ceed389149f164736f6c63430008110033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063a6ae0aac14610046578063e1c7392a14610076578063f0ba8440146100f6575b600080fd5b600154610059906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100f46000805460018181018355828052447f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56392830155825480820184554390830155825480820184554290830155825480820184554590830155825480820190935546929091019190915580546001600160a01b03191641179055565b005b610109610104366004610138565b610117565b60405190815260200161006d565b6000818154811061012757600080fd5b600091825260209091200154905081565b60006020828403121561014a57600080fd5b503591905056fea26469706673582212200950a847916ef267078b133380353b5117947fac6a936af48086ceed389149f164736f6c63430008110033", - "linkReferences": {}, - "deployedLinkReferences": {} -} diff --git a/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.sol b/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.sol deleted file mode 100644 index c24384b..0000000 --- a/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -contract TestWriteBlockMetadata { - uint[] public data; - address public coinbase; - // bytes32 public hash; - - function init() external { - data.push(block.difficulty); - data.push(block.number); - data.push(block.timestamp); - data.push(block.gaslimit); - data.push(block.chainid); - coinbase = block.coinbase; - data.push(block.basefee); - // hash = blockhash(block.number); - } -} \ No newline at end of file diff --git a/consensus/polybft/extra.go b/consensus/polybft/extra.go deleted file mode 100644 index 3cbc817..0000000 --- a/consensus/polybft/extra.go +++ /dev/null @@ -1,505 +0,0 @@ -package polybft - -import ( - "fmt" - "math/big" - - "github.com/hashicorp/go-hclog" - "github.com/umbracle/ethgo/abi" - "github.com/umbracle/fastrlp" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/crypto" - "github.com/xgr-network/xgr-node/types" -) - -const ( - // ExtraVanity represents a fixed number of extra-data bytes reserved for proposer vanity - ExtraVanity = 32 - - // ExtraSeal represents the fixed number of extra-data bytes reserved for proposer seal - ExtraSeal = 65 -) - -// PolyBFTMixDigest represents a hash of "PolyBFT Mix" to identify whether the block is from PolyBFT consensus engine -var PolyBFTMixDigest = types.StringToHash("adce6e5230abe012342a44e4e9b6d05997d6f015387ae0e59be924afc7ec70c1") - -// Extra defines the structure of the extra field for Istanbul -type Extra struct { - Validators *validator.ValidatorSetDelta - Parent *Signature - Committed *Signature - Checkpoint *CheckpointData -} - -// MarshalRLPTo defines the marshal function wrapper for Extra -func (i *Extra) MarshalRLPTo(dst []byte) []byte { - ar := &fastrlp.Arena{} - - return append(make([]byte, ExtraVanity), i.MarshalRLPWith(ar).MarshalTo(dst)...) -} - -// MarshalRLPWith defines the marshal function implementation for Extra -func (i *Extra) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { - vv := ar.NewArray() - - // Validators - if i.Validators == nil { - vv.Set(ar.NewNullArray()) - } else { - vv.Set(i.Validators.MarshalRLPWith(ar)) - } - - // Parent Signatures - if i.Parent == nil { - vv.Set(ar.NewNullArray()) - } else { - vv.Set(i.Parent.MarshalRLPWith(ar)) - } - - // Committed Signatures - if i.Committed == nil { - vv.Set(ar.NewNullArray()) - } else { - vv.Set(i.Committed.MarshalRLPWith(ar)) - } - - // Checkpoint - if i.Checkpoint == nil { - vv.Set(ar.NewNullArray()) - } else { - vv.Set(i.Checkpoint.MarshalRLPWith(ar)) - } - - return vv -} - -// UnmarshalRLP defines the unmarshal function wrapper for Extra -func (i *Extra) UnmarshalRLP(input []byte) error { - return fastrlp.UnmarshalRLP(input[ExtraVanity:], i) -} - -// UnmarshalRLPWith defines the unmarshal implementation for Extra -func (i *Extra) UnmarshalRLPWith(v *fastrlp.Value) error { - const expectedElements = 4 - - elems, err := v.GetElems() - if err != nil { - return err - } - - if num := len(elems); num != expectedElements { - return fmt.Errorf("incorrect elements count to decode Extra, expected %d but found %d", expectedElements, num) - } - - // Validators - if elems[0].Elems() > 0 { - i.Validators = &validator.ValidatorSetDelta{} - if err := i.Validators.UnmarshalRLPWith(elems[0]); err != nil { - return err - } - } - - // Parent Signatures - if elems[1].Elems() > 0 { - i.Parent = &Signature{} - if err := i.Parent.UnmarshalRLPWith(elems[1]); err != nil { - return err - } - } - - // Committed Signatures - if elems[2].Elems() > 0 { - i.Committed = &Signature{} - if err := i.Committed.UnmarshalRLPWith(elems[2]); err != nil { - return err - } - } - - // Checkpoint - if elems[3].Elems() > 0 { - i.Checkpoint = &CheckpointData{} - if err := i.Checkpoint.UnmarshalRLPWith(elems[3]); err != nil { - return err - } - } - - return nil -} - -// ValidateFinalizedData contains extra data validations for finalized headers -func (i *Extra) ValidateFinalizedData(header *types.Header, parent *types.Header, parents []*types.Header, - chainID uint64, consensusBackend polybftBackend, domain []byte, logger hclog.Logger) error { - // validate committed signatures - blockNumber := header.Number - if i.Committed == nil { - return fmt.Errorf("failed to verify signatures for block %d, because signatures are not present", blockNumber) - } - - if i.Checkpoint == nil { - return fmt.Errorf("failed to verify signatures for block %d, because checkpoint data are not present", blockNumber) - } - - // validate current block signatures - checkpointHash, err := i.Checkpoint.Hash(chainID, blockNumber, header.Hash) - if err != nil { - return fmt.Errorf("failed to calculate proposal hash: %w", err) - } - - validators, err := consensusBackend.GetValidators(blockNumber-1, parents) - if err != nil { - return fmt.Errorf("failed to validate header for block %d. could not retrieve block validators:%w", blockNumber, err) - } - - if err := i.Committed.Verify(blockNumber, validators, checkpointHash, domain, logger); err != nil { - return fmt.Errorf("failed to verify signatures for block %d (proposal hash %s): %w", - blockNumber, checkpointHash, err) - } - - parentExtra, err := GetIbftExtra(parent.ExtraData) - if err != nil { - return fmt.Errorf("failed to verify signatures for block %d: %w", blockNumber, err) - } - - // validate parent signatures - if err := i.ValidateParentSignatures(blockNumber, consensusBackend, parents, - parent, parentExtra, chainID, domain, logger); err != nil { - return err - } - - return i.Checkpoint.ValidateBasic(parentExtra.Checkpoint) -} - -// ValidateParentSignatures validates signatures for parent block -func (i *Extra) ValidateParentSignatures(blockNumber uint64, consensusBackend polybftBackend, parents []*types.Header, - parent *types.Header, parentExtra *Extra, chainID uint64, domain []byte, logger hclog.Logger) error { - // skip block 1 because genesis does not have committed signatures - if blockNumber <= 1 { - return nil - } - - if i.Parent == nil { - return fmt.Errorf("failed to verify signatures for parent of block %d because signatures are not present", - blockNumber) - } - - parentValidators, err := consensusBackend.GetValidators(blockNumber-2, parents) - if err != nil { - return fmt.Errorf( - "failed to validate header for block %d. could not retrieve parent validators: %w", - blockNumber, - err, - ) - } - - parentCheckpointHash, err := parentExtra.Checkpoint.Hash(chainID, parent.Number, parent.Hash) - if err != nil { - return fmt.Errorf("failed to calculate parent proposal hash: %w", err) - } - - parentBlockNumber := blockNumber - 1 - if err := i.Parent.Verify(parentBlockNumber, parentValidators, parentCheckpointHash, domain, logger); err != nil { - return fmt.Errorf("failed to verify signatures for parent of block %d (proposal hash: %s): %w", - blockNumber, parentCheckpointHash, err) - } - - return nil -} - -// Signature represents aggregated signatures of signers accompanied with a bitmap -// (in order to be able to determine identities of each signer) -type Signature struct { - AggregatedSignature []byte - Bitmap []byte -} - -// MarshalRLPWith marshals Signature object into RLP format -func (s *Signature) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { - committed := ar.NewArray() - if s.AggregatedSignature == nil { - committed.Set(ar.NewNull()) - } else { - committed.Set(ar.NewBytes(s.AggregatedSignature)) - } - - if s.Bitmap == nil { - committed.Set(ar.NewNull()) - } else { - committed.Set(ar.NewBytes(s.Bitmap)) - } - - return committed -} - -// UnmarshalRLPWith unmarshals Signature object from the RLP format -func (s *Signature) UnmarshalRLPWith(v *fastrlp.Value) error { - vals, err := v.GetElems() - if err != nil { - return fmt.Errorf("array type expected for signature struct") - } - - // there should be exactly two elements (aggregated signature and bitmap) - if num := len(vals); num != 2 { - return fmt.Errorf("incorrect elements count to decode Signature, expected 2 but found %d", num) - } - - s.AggregatedSignature, err = vals[0].GetBytes(nil) - if err != nil { - return err - } - - s.Bitmap, err = vals[1].GetBytes(nil) - if err != nil { - return err - } - - return nil -} - -// Verify is used to verify aggregated signature based on current validator set, message hash and domain -func (s *Signature) Verify(blockNumber uint64, validators validator.AccountSet, - hash types.Hash, domain []byte, logger hclog.Logger) error { - signers, err := validators.GetFilteredValidators(s.Bitmap) - if err != nil { - return err - } - - validatorSet := validator.NewValidatorSet(validators, logger) - if !validatorSet.HasQuorum(blockNumber, signers.GetAddressesAsSet()) { - return fmt.Errorf("quorum not reached") - } - - blsPublicKeys := make([]*bls.PublicKey, len(signers)) - for i, validator := range signers { - blsPublicKeys[i] = validator.BlsKey - } - - aggs, err := bls.UnmarshalSignature(s.AggregatedSignature) - if err != nil { - return err - } - - if !aggs.VerifyAggregated(blsPublicKeys, hash[:], domain) { - return fmt.Errorf("could not verify aggregated signature") - } - - return nil -} - -var checkpointDataABIType = abi.MustNewType(`tuple( - uint256 chainId, - uint256 blockNumber, - bytes32 blockHash, - uint256 blockRound, - uint256 epochNumber, - bytes32 eventRoot, - bytes32 currentValidatorsHash, - bytes32 nextValidatorsHash)`) - -// CheckpointData represents data needed for checkpointing mechanism -type CheckpointData struct { - BlockRound uint64 - EpochNumber uint64 - CurrentValidatorsHash types.Hash - NextValidatorsHash types.Hash - EventRoot types.Hash -} - -// MarshalRLPWith defines the marshal function implementation for CheckpointData -func (c *CheckpointData) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { - vv := ar.NewArray() - // BlockRound - vv.Set(ar.NewUint(c.BlockRound)) - // EpochNumber - vv.Set(ar.NewUint(c.EpochNumber)) - // CurrentValidatorsHash - vv.Set(ar.NewBytes(c.CurrentValidatorsHash.Bytes())) - // NextValidatorsHash - vv.Set(ar.NewBytes(c.NextValidatorsHash.Bytes())) - // EventRoot - vv.Set(ar.NewBytes(c.EventRoot.Bytes())) - - return vv -} - -// UnmarshalRLPWith unmarshals CheckpointData object from the RLP format -func (c *CheckpointData) UnmarshalRLPWith(v *fastrlp.Value) error { - vals, err := v.GetElems() - if err != nil { - return fmt.Errorf("array type expected for CheckpointData struct") - } - - // there should be exactly 5 elements: - // BlockRound, EpochNumber, CurrentValidatorsHash, NextValidatorsHash, EventRoot - if num := len(vals); num != 5 { - return fmt.Errorf("incorrect elements count to decode CheckpointData, expected 5 but found %d", num) - } - - // BlockRound - c.BlockRound, err = vals[0].GetUint64() - if err != nil { - return err - } - - // EpochNumber - c.EpochNumber, err = vals[1].GetUint64() - if err != nil { - return err - } - - // CurrentValidatorsHash - currentValidatorsHashRaw, err := vals[2].GetBytes(nil) - if err != nil { - return err - } - - c.CurrentValidatorsHash = types.BytesToHash(currentValidatorsHashRaw) - - // NextValidatorsHash - nextValidatorsHashRaw, err := vals[3].GetBytes(nil) - if err != nil { - return err - } - - c.NextValidatorsHash = types.BytesToHash(nextValidatorsHashRaw) - - // EventRoot - eventRootRaw, err := vals[4].GetBytes(nil) - if err != nil { - return err - } - - c.EventRoot = types.BytesToHash(eventRootRaw) - - return nil -} - -// Copy returns deep copy of CheckpointData instance -func (c *CheckpointData) Copy() *CheckpointData { - newCheckpointData := new(CheckpointData) - *newCheckpointData = *c - - return newCheckpointData -} - -// Hash calculates keccak256 hash of the CheckpointData. -// CheckpointData is ABI encoded and then hashed. -func (c *CheckpointData) Hash(chainID uint64, blockNumber uint64, blockHash types.Hash) (types.Hash, error) { - checkpointMap := map[string]interface{}{ - "chainId": new(big.Int).SetUint64(chainID), - "blockNumber": new(big.Int).SetUint64(blockNumber), - "blockHash": blockHash, - "blockRound": new(big.Int).SetUint64(c.BlockRound), - "epochNumber": new(big.Int).SetUint64(c.EpochNumber), - "eventRoot": c.EventRoot, - "currentValidatorsHash": c.CurrentValidatorsHash, - "nextValidatorsHash": c.NextValidatorsHash, - } - - abiEncoded, err := checkpointDataABIType.Encode(checkpointMap) - if err != nil { - return types.ZeroHash, err - } - - return types.BytesToHash(crypto.Keccak256(abiEncoded)), nil -} - -// ValidateBasic encapsulates basic validation logic for checkpoint data. -// It only checks epoch numbers validity and whether validators hashes are non-empty. -func (c *CheckpointData) ValidateBasic(parentCheckpoint *CheckpointData) error { - if c.EpochNumber != parentCheckpoint.EpochNumber && - c.EpochNumber != parentCheckpoint.EpochNumber+1 { - // epoch-beginning block - // epoch number must be incremented by one compared to parent block's checkpoint - return fmt.Errorf("invalid epoch number for epoch-beginning block") - } - - if c.CurrentValidatorsHash == types.ZeroHash { - return fmt.Errorf("current validators hash must not be empty") - } - - if c.NextValidatorsHash == types.ZeroHash { - return fmt.Errorf("next validators hash must not be empty") - } - - return nil -} - -// Validate encapsulates validation logic for checkpoint data -// (with regards to current and next epoch validators) -func (c *CheckpointData) Validate(parentCheckpoint *CheckpointData, - currentValidators validator.AccountSet, nextValidators validator.AccountSet, - exitRootHash types.Hash) error { - if err := c.ValidateBasic(parentCheckpoint); err != nil { - return err - } - - // check if currentValidatorsHash, present in CheckpointData is correct - currentValidatorsHash, err := currentValidators.Hash() - if err != nil { - return fmt.Errorf("failed to calculate current validators hash: %w", err) - } - - if currentValidatorsHash != c.CurrentValidatorsHash { - return fmt.Errorf("current validators hashes don't match") - } - - // check if nextValidatorsHash, present in CheckpointData is correct - nextValidatorsHash, err := nextValidators.Hash() - if err != nil { - return fmt.Errorf("failed to calculate next validators hash: %w", err) - } - - if nextValidatorsHash != c.NextValidatorsHash { - return fmt.Errorf("next validators hashes don't match") - } - - // epoch ending blocks have validator set transitions - if !currentValidators.Equals(nextValidators) && - c.EpochNumber != parentCheckpoint.EpochNumber { - // epoch ending blocks should have the same epoch number as parent block - // (as they belong to the same epoch) - return fmt.Errorf("epoch number should not change for epoch-ending block") - } - - // exit root hash of proposer and - // validator that validates proposal have to match - if exitRootHash != c.EventRoot { - return fmt.Errorf("exit root hash not as expected") - } - - return nil -} - -// GetIbftExtraClean returns unmarshaled extra field from the passed in header, -// but without signatures for the given header (it only includes signatures for the parent block) -func GetIbftExtraClean(extraRaw []byte) ([]byte, error) { - extra, err := GetIbftExtra(extraRaw) - if err != nil { - return nil, err - } - - ibftExtra := &Extra{ - Parent: extra.Parent, - Validators: extra.Validators, - Checkpoint: extra.Checkpoint, - Committed: &Signature{}, - } - - return ibftExtra.MarshalRLPTo(nil), nil -} - -// GetIbftExtra returns the istanbul extra data field from the passed in header -func GetIbftExtra(extraRaw []byte) (*Extra, error) { - if len(extraRaw) < ExtraVanity { - return nil, fmt.Errorf("wrong extra size: %d", len(extraRaw)) - } - - extra := &Extra{} - - if err := extra.UnmarshalRLP(extraRaw); err != nil { - return nil, err - } - - return extra, nil -} diff --git a/consensus/polybft/extra_test.go b/consensus/polybft/extra_test.go deleted file mode 100644 index 259267f..0000000 --- a/consensus/polybft/extra_test.go +++ /dev/null @@ -1,779 +0,0 @@ -package polybft - -import ( - "crypto/rand" - "errors" - "fmt" - "math/big" - mrand "math/rand" - "testing" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/umbracle/fastrlp" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/crypto" - "github.com/xgr-network/xgr-node/types" -) - -func TestExtra_Encoding(t *testing.T) { - t.Parallel() - - digest := crypto.Keccak256([]byte("Dummy content to sign")) - keys := createRandomTestKeys(t, 2) - parentSig, err := keys[0].Sign(digest) - require.NoError(t, err) - - committedSig, err := keys[1].Sign(digest) - require.NoError(t, err) - - bmp := bitmap.Bitmap{} - bmp.Set(1) - bmp.Set(4) - - addedValidators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C"}).GetPublicIdentities() - - removedValidators := bitmap.Bitmap{} - removedValidators.Set(2) - - // different extra data for marshall/unmarshall - var cases = []struct { - extra *Extra - }{ - { - &Extra{}, - }, - { - &Extra{ - Validators: &validator.ValidatorSetDelta{}, - Parent: &Signature{}, - Committed: &Signature{}, - }, - }, - { - &Extra{ - Validators: &validator.ValidatorSetDelta{}, - }, - }, - { - &Extra{ - Validators: &validator.ValidatorSetDelta{ - Added: addedValidators, - }, - Parent: &Signature{}, - Committed: &Signature{}, - }, - }, - { - &Extra{ - Validators: &validator.ValidatorSetDelta{ - Removed: removedValidators, - }, - Parent: &Signature{AggregatedSignature: parentSig, Bitmap: bmp}, - Committed: &Signature{}, - }, - }, - { - &Extra{ - Validators: &validator.ValidatorSetDelta{ - Added: addedValidators, - Updated: addedValidators[1:], - Removed: removedValidators, - }, - Parent: &Signature{}, - Committed: &Signature{AggregatedSignature: committedSig, Bitmap: bmp}, - }, - }, - { - &Extra{ - Parent: &Signature{AggregatedSignature: parentSig, Bitmap: bmp}, - Committed: &Signature{AggregatedSignature: committedSig, Bitmap: bmp}, - }, - }, - { - &Extra{ - Parent: &Signature{AggregatedSignature: parentSig, Bitmap: bmp}, - Committed: &Signature{AggregatedSignature: committedSig, Bitmap: bmp}, - Checkpoint: &CheckpointData{ - BlockRound: 0, - EpochNumber: 3, - CurrentValidatorsHash: types.BytesToHash(generateRandomBytes(t)), - NextValidatorsHash: types.BytesToHash(generateRandomBytes(t)), - EventRoot: types.BytesToHash(generateRandomBytes(t)), - }, - }, - }, - } - - for _, c := range cases { - data := c.extra.MarshalRLPTo(nil) - extra := &Extra{} - assert.NoError(t, extra.UnmarshalRLP(data)) - assert.Equal(t, c.extra, extra) - } -} - -func TestExtra_UnmarshalRLPWith_NegativeCases(t *testing.T) { - t.Parallel() - - t.Run("Incorrect RLP marshalled data type", func(t *testing.T) { - t.Parallel() - - extra := &Extra{} - ar := &fastrlp.Arena{} - require.Error(t, extra.UnmarshalRLPWith(ar.NewBool(false))) - }) - - t.Run("Incorrect count of RLP marshalled array elements", func(t *testing.T) { - t.Parallel() - - extra := &Extra{} - ar := &fastrlp.Arena{} - require.ErrorContains(t, extra.UnmarshalRLPWith(ar.NewArray()), "incorrect elements count to decode Extra, expected 4 but found 0") - }) - - t.Run("Incorrect ValidatorSetDelta marshalled", func(t *testing.T) { - t.Parallel() - - extra := &Extra{} - ar := &fastrlp.Arena{} - extraMarshalled := ar.NewArray() - deltaMarshalled := ar.NewArray() - deltaMarshalled.Set(ar.NewBytes([]byte{0x73})) - extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta - extraMarshalled.Set(ar.NewBytes([]byte{})) // Seal - extraMarshalled.Set(ar.NewBytes([]byte{})) // Parent - extraMarshalled.Set(ar.NewBytes([]byte{})) // Committed - require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) - }) - - t.Run("Incorrect Seal marshalled", func(t *testing.T) { - t.Parallel() - - extra := &Extra{} - ar := &fastrlp.Arena{} - extraMarshalled := ar.NewArray() - deltaMarshalled := new(validator.ValidatorSetDelta).MarshalRLPWith(ar) - extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta - extraMarshalled.Set(ar.NewBytes([]byte{})) // Parent - extraMarshalled.Set(ar.NewBytes([]byte{})) // Committed - require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) - }) - - t.Run("Incorrect Parent signatures marshalled", func(t *testing.T) { - t.Parallel() - - extra := &Extra{} - ar := &fastrlp.Arena{} - extraMarshalled := ar.NewArray() - deltaMarshalled := new(validator.ValidatorSetDelta).MarshalRLPWith(ar) - extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta - extraMarshalled.Set(ar.NewBytes([]byte{})) // Seal - // Parent - parentArr := ar.NewArray() - parentArr.Set(ar.NewBytes([]byte{})) - extraMarshalled.Set(parentArr) - extraMarshalled.Set(ar.NewBytes([]byte{})) // Committed - require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) - }) - - t.Run("Incorrect Committed signatures marshalled", func(t *testing.T) { - t.Parallel() - - extra := &Extra{} - ar := &fastrlp.Arena{} - extraMarshalled := ar.NewArray() - deltaMarshalled := new(validator.ValidatorSetDelta).MarshalRLPWith(ar) - extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta - extraMarshalled.Set(ar.NewBytes([]byte{})) // Seal - - // Parent - key, err := wallet.GenerateAccount() - require.NoError(t, err) - - parentSignature := createSignature(t, []*wallet.Account{key}, types.BytesToHash([]byte("This is test hash")), signer.DomainCheckpointManager) - extraMarshalled.Set(parentSignature.MarshalRLPWith(ar)) - - // Committed - committedArr := ar.NewArray() - committedArr.Set(ar.NewBytes([]byte{})) - extraMarshalled.Set(committedArr) - require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) - }) - - t.Run("Incorrect Checkpoint data marshalled", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - extraMarshalled := ar.NewArray() - deltaMarshalled := new(validator.ValidatorSetDelta).MarshalRLPWith(ar) - extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta - extraMarshalled.Set(ar.NewBytes([]byte{})) // Seal - - // Parent - key, err := wallet.GenerateAccount() - require.NoError(t, err) - - parentSignature := createSignature(t, []*wallet.Account{key}, types.BytesToHash(generateRandomBytes(t)), signer.DomainCheckpointManager) - extraMarshalled.Set(parentSignature.MarshalRLPWith(ar)) - - // Committed - committedSignature := createSignature(t, []*wallet.Account{key}, types.BytesToHash(generateRandomBytes(t)), signer.DomainCheckpointManager) - extraMarshalled.Set(committedSignature.MarshalRLPWith(ar)) - - // Checkpoint data - checkpointDataArr := ar.NewArray() - checkpointDataArr.Set(ar.NewBytes(generateRandomBytes(t))) - extraMarshalled.Set(checkpointDataArr) - - extra := &Extra{} - require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) - }) -} - -func TestExtra_ValidateFinalizedData_UnhappyPath(t *testing.T) { - t.Parallel() - - const ( - headerNum = 10 - chainID = uint64(20) - ) - - header := &types.Header{ - Number: headerNum, - Hash: types.BytesToHash(generateRandomBytes(t)), - } - parent := &types.Header{ - Number: headerNum - 1, - Hash: types.BytesToHash(generateRandomBytes(t)), - } - - validators := validator.NewTestValidators(t, 6) - - polyBackendMock := new(polybftBackendMock) - polyBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(nil, errors.New("validators not found")) - - // missing Committed field - extra := &Extra{} - err := extra.ValidateFinalizedData( - header, parent, nil, chainID, nil, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.ErrorContains(t, err, fmt.Sprintf("failed to verify signatures for block %d, because signatures are not present", headerNum)) - - // missing Checkpoint field - extra = &Extra{Committed: &Signature{}} - err = extra.ValidateFinalizedData( - header, parent, nil, chainID, polyBackendMock, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.ErrorContains(t, err, fmt.Sprintf("failed to verify signatures for block %d, because checkpoint data are not present", headerNum)) - - // failed to retrieve validators from snapshot - checkpoint := &CheckpointData{ - EpochNumber: 10, - BlockRound: 2, - EventRoot: types.BytesToHash(generateRandomBytes(t)), - } - extra = &Extra{Committed: &Signature{}, Checkpoint: checkpoint} - err = extra.ValidateFinalizedData( - header, parent, nil, chainID, polyBackendMock, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.ErrorContains(t, err, - fmt.Sprintf("failed to validate header for block %d. could not retrieve block validators:validators not found", headerNum)) - - // failed to verify signatures (quorum not reached) - polyBackendMock = new(polybftBackendMock) - polyBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validators.GetPublicIdentities()) - - noQuorumSignature := createSignature(t, validators.GetPrivateIdentities("0", "1"), types.BytesToHash([]byte("FooBar")), signer.DomainCheckpointManager) - extra = &Extra{Committed: noQuorumSignature, Checkpoint: checkpoint} - checkpointHash, err := checkpoint.Hash(chainID, headerNum, header.Hash) - require.NoError(t, err) - - err = extra.ValidateFinalizedData( - header, parent, nil, chainID, polyBackendMock, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.ErrorContains(t, err, - fmt.Sprintf("failed to verify signatures for block %d (proposal hash %s): quorum not reached", headerNum, checkpointHash)) - - // incorrect parent extra size - validSignature := createSignature(t, validators.GetPrivateIdentities(), checkpointHash, signer.DomainCheckpointManager) - extra = &Extra{Committed: validSignature, Checkpoint: checkpoint} - err = extra.ValidateFinalizedData( - header, parent, nil, chainID, polyBackendMock, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.ErrorContains(t, err, - fmt.Sprintf("failed to verify signatures for block %d: wrong extra size: 0", headerNum)) -} - -func TestExtra_ValidateParentSignatures(t *testing.T) { - t.Parallel() - - const ( - chainID = 15 - headerNum = 23 - ) - - polyBackendMock := new(polybftBackendMock) - polyBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(nil, errors.New("no validators")) - - // validation is skipped for blocks 0 and 1 - extra := &Extra{} - err := extra.ValidateParentSignatures( - 1, polyBackendMock, nil, nil, nil, chainID, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.NoError(t, err) - - // parent signatures not present - err = extra.ValidateParentSignatures( - headerNum, polyBackendMock, nil, nil, nil, chainID, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.ErrorContains(t, err, fmt.Sprintf("failed to verify signatures for parent of block %d because signatures are not present", headerNum)) - - // validators not found - validators := validator.NewTestValidators(t, 5) - incorrectHash := types.BytesToHash([]byte("Hello World")) - invalidSig := createSignature(t, validators.GetPrivateIdentities(), incorrectHash, signer.DomainCheckpointManager) - extra = &Extra{Parent: invalidSig} - err = extra.ValidateParentSignatures( - headerNum, polyBackendMock, nil, nil, nil, chainID, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.ErrorContains(t, err, - fmt.Sprintf("failed to validate header for block %d. could not retrieve parent validators: no validators", headerNum)) - - // incorrect hash is signed - polyBackendMock = new(polybftBackendMock) - polyBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validators.GetPublicIdentities()) - - parent := &types.Header{Number: headerNum - 1, Hash: types.BytesToHash(generateRandomBytes(t))} - parentCheckpoint := &CheckpointData{EpochNumber: 3, BlockRound: 5} - parentExtra := &Extra{Checkpoint: parentCheckpoint} - - parentCheckpointHash, err := parentCheckpoint.Hash(chainID, parent.Number, parent.Hash) - require.NoError(t, err) - - err = extra.ValidateParentSignatures( - headerNum, polyBackendMock, nil, parent, parentExtra, chainID, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.ErrorContains(t, err, - fmt.Sprintf("failed to verify signatures for parent of block %d (proposal hash: %s): could not verify aggregated signature", headerNum, parentCheckpointHash)) - - // valid signature provided - validSig := createSignature(t, validators.GetPrivateIdentities(), parentCheckpointHash, signer.DomainCheckpointManager) - extra = &Extra{Parent: validSig} - err = extra.ValidateParentSignatures( - headerNum, polyBackendMock, nil, parent, parentExtra, chainID, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.NoError(t, err) -} - -func TestSignature_Verify(t *testing.T) { - t.Parallel() - - t.Run("Valid signatures", func(t *testing.T) { - t.Parallel() - - numValidators := 100 - msgHash := types.Hash{0x1} - - vals := validator.NewTestValidators(t, numValidators) - validatorsMetadata := vals.GetPublicIdentities() - validatorSet := vals.ToValidatorSet() - - var signatures bls.Signatures - bitmap := bitmap.Bitmap{} - signers := make(map[types.Address]struct{}, len(validatorsMetadata)) - - for i, val := range vals.GetValidators() { - bitmap.Set(uint64(i)) - - tempSign, err := val.Account.Bls.Sign(msgHash[:], signer.DomainCheckpointManager) - require.NoError(t, err) - - signatures = append(signatures, tempSign) - aggs, err := signatures.Aggregate().Marshal() - assert.NoError(t, err) - - s := &Signature{ - AggregatedSignature: aggs, - Bitmap: bitmap, - } - - err = s.Verify(10, validatorsMetadata, msgHash, signer.DomainCheckpointManager, hclog.NewNullLogger()) - signers[val.Address()] = struct{}{} - - if !validatorSet.HasQuorum(10, signers) { - assert.ErrorContains(t, err, "quorum not reached", "failed for %d", i) - } else { - assert.NoError(t, err) - } - } - }) - - t.Run("Invalid bitmap provided", func(t *testing.T) { - t.Parallel() - - validatorSet := validator.NewTestValidators(t, 3).GetPublicIdentities() - bmp := bitmap.Bitmap{} - - // Make bitmap invalid, by setting some flag larger than length of validator set to 1 - bmp.Set(uint64(validatorSet.Len() + 1)) - s := &Signature{Bitmap: bmp} - - err := s.Verify(0, validatorSet, types.Hash{0x1}, signer.DomainCheckpointManager, hclog.NewNullLogger()) - require.Error(t, err) - }) -} - -func TestSignature_UnmarshalRLPWith_NegativeCases(t *testing.T) { - t.Parallel() - - t.Run("Incorrect RLP marshalled data type", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - signature := Signature{} - require.ErrorContains(t, signature.UnmarshalRLPWith(ar.NewNull()), "array type expected for signature struct") - }) - - t.Run("Incorrect AggregatedSignature field data type", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - signature := Signature{} - signatureMarshalled := ar.NewArray() - signatureMarshalled.Set(ar.NewNull()) - signatureMarshalled.Set(ar.NewNull()) - require.ErrorContains(t, signature.UnmarshalRLPWith(signatureMarshalled), "value is not of type bytes") - }) - - t.Run("Incorrect Bitmap field data type", func(t *testing.T) { - ar := &fastrlp.Arena{} - signature := Signature{} - signatureMarshalled := ar.NewArray() - signatureMarshalled.Set(ar.NewBytes([]byte{0x5, 0x90})) - signatureMarshalled.Set(ar.NewNull()) - require.ErrorContains(t, signature.UnmarshalRLPWith(signatureMarshalled), "value is not of type bytes") - }) -} - -func TestSignature_VerifyRandom(t *testing.T) { - t.Parallel() - - numValidators := 100 - vals := validator.NewTestValidators(t, numValidators) - msgHash := types.Hash{0x1} - - var signature bls.Signatures - - bitmap := bitmap.Bitmap{} - valIndxsRnd := mrand.Perm(numValidators)[:numValidators*2/3+1] - - accounts := vals.GetValidators() - - for _, index := range valIndxsRnd { - bitmap.Set(uint64(index)) - - tempSign, err := accounts[index].Account.Bls.Sign(msgHash[:], signer.DomainCheckpointManager) - require.NoError(t, err) - - signature = append(signature, tempSign) - } - - aggs, err := signature.Aggregate().Marshal() - require.NoError(t, err) - - s := &Signature{ - AggregatedSignature: aggs, - Bitmap: bitmap, - } - - err = s.Verify(1, vals.GetPublicIdentities(), msgHash, signer.DomainCheckpointManager, hclog.NewNullLogger()) - assert.NoError(t, err) -} - -func TestExtra_InitGenesisValidatorsDelta(t *testing.T) { - t.Parallel() - - t.Run("Happy path", func(t *testing.T) { - t.Parallel() - - const validatorsCount = 7 - vals := validator.NewTestValidators(t, validatorsCount) - - delta := &validator.ValidatorSetDelta{ - Added: make(validator.AccountSet, validatorsCount), - Removed: bitmap.Bitmap{}, - } - - var i int - for _, val := range vals.Validators { - delta.Added[i] = &validator.ValidatorMetadata{ - Address: types.Address(val.Account.Ecdsa.Address()), - BlsKey: val.Account.Bls.PublicKey(), - VotingPower: new(big.Int).SetUint64(val.VotingPower), - } - i++ - } - - extra := Extra{Validators: delta} - - genesis := &chain.Genesis{ - ExtraData: extra.MarshalRLPTo(nil), - } - - genesisExtra, err := GetIbftExtra(genesis.ExtraData) - assert.NoError(t, err) - assert.Len(t, genesisExtra.Validators.Added, validatorsCount) - assert.Empty(t, genesisExtra.Validators.Removed) - }) - - t.Run("Invalid Extra data", func(t *testing.T) { - t.Parallel() - - genesis := &chain.Genesis{ - ExtraData: append(make([]byte, ExtraVanity), []byte{0x2, 0x3}...), - } - - _, err := GetIbftExtra(genesis.ExtraData) - - require.Error(t, err) - }) -} - -func Test_GetIbftExtraClean(t *testing.T) { - t.Parallel() - - key, err := wallet.GenerateAccount() - require.NoError(t, err) - - extra := &Extra{ - Validators: &validator.ValidatorSetDelta{ - Added: validator.AccountSet{ - &validator.ValidatorMetadata{ - Address: types.BytesToAddress([]byte{11, 22}), - BlsKey: key.Bls.PublicKey(), - VotingPower: new(big.Int).SetUint64(1000), - IsActive: true, - }, - }, - }, - Committed: &Signature{ - AggregatedSignature: []byte{23, 24}, - Bitmap: []byte{11}, - }, - Parent: &Signature{ - AggregatedSignature: []byte{0, 1}, - Bitmap: []byte{1}, - }, - Checkpoint: &CheckpointData{ - BlockRound: 1, - EpochNumber: 1, - CurrentValidatorsHash: types.BytesToHash([]byte{2, 3}), - NextValidatorsHash: types.BytesToHash([]byte{4, 5}), - EventRoot: types.BytesToHash([]byte{6, 7}), - }, - } - - extraClean, err := GetIbftExtraClean(extra.MarshalRLPTo(nil)) - require.NoError(t, err) - - extraTwo := &Extra{} - require.NoError(t, extraTwo.UnmarshalRLP(extraClean)) - require.True(t, extra.Validators.Equals(extra.Validators)) - require.Equal(t, extra.Checkpoint.BlockRound, extraTwo.Checkpoint.BlockRound) - require.Equal(t, extra.Checkpoint.EpochNumber, extraTwo.Checkpoint.EpochNumber) - require.Equal(t, extra.Checkpoint.CurrentValidatorsHash, extraTwo.Checkpoint.CurrentValidatorsHash) - require.Equal(t, extra.Checkpoint.NextValidatorsHash, extraTwo.Checkpoint.NextValidatorsHash) - require.Equal(t, extra.Checkpoint.NextValidatorsHash, extraTwo.Checkpoint.NextValidatorsHash) - require.Equal(t, extra.Parent.AggregatedSignature, extraTwo.Parent.AggregatedSignature) - require.Equal(t, extra.Parent.Bitmap, extraTwo.Parent.Bitmap) - - require.Nil(t, extraTwo.Committed.AggregatedSignature) - require.Nil(t, extraTwo.Committed.Bitmap) -} - -func Test_GetIbftExtraClean_Fail(t *testing.T) { - t.Parallel() - - randomBytes := [ExtraVanity]byte{} - _, err := rand.Read(randomBytes[:]) - require.NoError(t, err) - - extra, err := GetIbftExtraClean(append(randomBytes[:], []byte{0x12, 0x6}...)) - require.Error(t, err) - require.Nil(t, extra) -} - -func TestCheckpointData_Hash(t *testing.T) { - const ( - chainID = uint64(1) - blockNumber = uint64(27) - ) - - blockHash := types.BytesToHash(generateRandomBytes(t)) - origCheckpoint := &CheckpointData{ - BlockRound: 0, - EpochNumber: 3, - CurrentValidatorsHash: types.BytesToHash(generateRandomBytes(t)), - NextValidatorsHash: types.BytesToHash(generateRandomBytes(t)), - EventRoot: types.BytesToHash(generateRandomBytes(t)), - } - copyCheckpoint := &CheckpointData{} - *copyCheckpoint = *origCheckpoint - - origHash, err := origCheckpoint.Hash(chainID, blockNumber, blockHash) - require.NoError(t, err) - - copyHash, err := copyCheckpoint.Hash(chainID, blockNumber, blockHash) - require.NoError(t, err) - - require.Equal(t, origHash, copyHash) -} - -func TestCheckpointData_Validate(t *testing.T) { - t.Parallel() - - currentValidators := validator.NewTestValidators(t, 5).GetPublicIdentities() - nextValidators := validator.NewTestValidators(t, 3).GetPublicIdentities() - - currentValidatorsHash, err := currentValidators.Hash() - require.NoError(t, err) - - nextValidatorsHash, err := nextValidators.Hash() - require.NoError(t, err) - - cases := []struct { - name string - parentEpochNumber uint64 - epochNumber uint64 - currentValidators validator.AccountSet - nextValidators validator.AccountSet - currentValidatorsHash types.Hash - nextValidatorsHash types.Hash - exitRootHash types.Hash - errString string - }{ - { - name: "Valid (validator set changes)", - parentEpochNumber: 2, - epochNumber: 2, - currentValidators: currentValidators, - nextValidators: nextValidators, - currentValidatorsHash: currentValidatorsHash, - nextValidatorsHash: nextValidatorsHash, - errString: "", - }, - { - name: "Valid (validator set remains the same)", - parentEpochNumber: 2, - epochNumber: 2, - currentValidators: currentValidators, - nextValidators: currentValidators, - currentValidatorsHash: currentValidatorsHash, - nextValidatorsHash: currentValidatorsHash, - errString: "", - }, - { - name: "Invalid (gap in epoch numbers)", - parentEpochNumber: 2, - epochNumber: 6, - errString: "invalid epoch number for epoch-beginning block", - }, - { - name: "Invalid (empty currentValidatorsHash)", - currentValidators: currentValidators, - nextValidators: currentValidators, - errString: "current validators hash must not be empty", - }, - { - name: "Invalid (empty nextValidatorsHash)", - currentValidators: currentValidators, - nextValidators: currentValidators, - currentValidatorsHash: currentValidatorsHash, - errString: "next validators hash must not be empty", - }, - { - name: "Invalid (incorrect currentValidatorsHash)", - currentValidators: currentValidators, - nextValidators: currentValidators, - currentValidatorsHash: nextValidatorsHash, - nextValidatorsHash: nextValidatorsHash, - errString: "current validators hashes don't match", - }, - { - name: "Invalid (incorrect nextValidatorsHash)", - currentValidators: nextValidators, - nextValidators: nextValidators, - currentValidatorsHash: nextValidatorsHash, - nextValidatorsHash: currentValidatorsHash, - errString: "next validators hashes don't match", - }, - { - name: "Invalid (validator set and epoch numbers change)", - parentEpochNumber: 2, - epochNumber: 3, - currentValidators: currentValidators, - nextValidators: nextValidators, - currentValidatorsHash: currentValidatorsHash, - nextValidatorsHash: nextValidatorsHash, - errString: "epoch number should not change for epoch-ending block", - }, - { - name: "Invalid exit root hash", - parentEpochNumber: 2, - epochNumber: 2, - currentValidators: currentValidators, - nextValidators: currentValidators, - currentValidatorsHash: currentValidatorsHash, - nextValidatorsHash: currentValidatorsHash, - exitRootHash: types.BytesToHash([]byte{0, 1, 2, 3, 4, 5, 6, 7}), - errString: "exit root hash not as expected", - }, - } - - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - checkpoint := &CheckpointData{ - EpochNumber: c.epochNumber, - CurrentValidatorsHash: c.currentValidatorsHash, - NextValidatorsHash: c.nextValidatorsHash, - EventRoot: c.exitRootHash, - } - parentCheckpoint := &CheckpointData{EpochNumber: c.parentEpochNumber} - err := checkpoint.Validate(parentCheckpoint, c.currentValidators, c.nextValidators, types.ZeroHash) - - if c.errString != "" { - require.ErrorContains(t, err, c.errString) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestCheckpointData_Copy(t *testing.T) { - t.Parallel() - - validatorAccs := validator.NewTestValidators(t, 5) - currentValidatorsHash, err := validatorAccs.GetPublicIdentities("0", "1", "2").Hash() - require.NoError(t, err) - - nextValidatorsHash, err := validatorAccs.GetPublicIdentities("1", "3", "4").Hash() - require.NoError(t, err) - - eventRoot := generateRandomBytes(t) - original := &CheckpointData{ - BlockRound: 1, - EpochNumber: 5, - CurrentValidatorsHash: currentValidatorsHash, - NextValidatorsHash: nextValidatorsHash, - EventRoot: types.BytesToHash(eventRoot), - } - - copied := original.Copy() - require.Equal(t, original, copied) - require.NotSame(t, original, copied) - - // alter arbitrary field on copied instance - copied.BlockRound = 10 - require.NotEqual(t, original.BlockRound, copied.BlockRound) -} diff --git a/consensus/polybft/fsm.go b/consensus/polybft/fsm.go deleted file mode 100644 index 2269824..0000000 --- a/consensus/polybft/fsm.go +++ /dev/null @@ -1,717 +0,0 @@ -package polybft - -import ( - "bytes" - "errors" - "fmt" - "math/big" - "time" - - "github.com/0xPolygon/go-ibft/messages" - "github.com/0xPolygon/go-ibft/messages/proto" - "github.com/armon/go-metrics" - hcf "github.com/hashicorp/go-hclog" - - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/state" - "github.com/xgr-network/xgr-node/types" -) - -type blockBuilder interface { - Reset() error - WriteTx(*types.Transaction) error - Fill() - Build(func(h *types.Header)) (*types.FullBlock, error) - GetState() *state.Transition - Receipts() []*types.Receipt -} - -var ( - errCommitEpochTxDoesNotExist = errors.New("commit epoch transaction is not found in the epoch ending block") - errCommitEpochTxNotExpected = errors.New("didn't expect commit epoch transaction in a non epoch ending block") - errCommitEpochTxSingleExpected = errors.New("only one commit epoch transaction is allowed " + - "in an epoch ending block") - errDistributeRewardsTxDoesNotExist = errors.New("distribute rewards transaction is " + - "not found in the epoch ending block") - errDistributeRewardsTxNotExpected = errors.New("didn't expect distribute rewards transaction " + - "in a non epoch ending block") - errDistributeRewardsTxSingleExpected = errors.New("only one distribute rewards transaction is " + - "allowed in an epoch ending block") - errProposalDontMatch = errors.New("failed to insert proposal, because the validated proposal " + - "is either nil or it does not match the received one") - errValidatorSetDeltaMismatch = errors.New("validator set delta mismatch") - errValidatorsUpdateInNonEpochEnding = errors.New("trying to update validator set in a non epoch ending block") - errValidatorDeltaNilInEpochEndingBlock = errors.New("validator set delta is nil in epoch ending block") -) - -type fsm struct { - // PolyBFT consensus protocol configuration - config *PolyBFTConfig - - // parent block header - parent *types.Header - - // backend implements methods for retrieving data from block chain - backend blockchainBackend - - // polybftBackend implements methods needed from the polybft - polybftBackend polybftBackend - - // validators is the list of validators for this round - validators validator.ValidatorSet - - // proposerSnapshot keeps information about new proposer - proposerSnapshot *ProposerSnapshot - - // blockBuilder is the block builder for proposers - blockBuilder blockBuilder - - // epochNumber denotes current epoch number - epochNumber uint64 - - // commitEpochInput holds info about a single epoch - // It is populated only for epoch-ending blocks. - commitEpochInput *contractsapi.CommitEpochValidatorSetFn - - // distributeRewardsInput holds info about validators work in a single epoch - // mainly, how many blocks they signed during given epoch - // It is populated only for epoch-ending blocks. - distributeRewardsInput *contractsapi.DistributeRewardForRewardPoolFn - - // isEndOfEpoch indicates if epoch reached its end - isEndOfEpoch bool - - // isEndOfSprint indicates if sprint reached its end - isEndOfSprint bool - - // proposerCommitmentToRegister is a commitment that is registered via state transaction by proposer - proposerCommitmentToRegister *CommitmentMessageSigned - - // logger instance - logger hcf.Logger - - // target is the block being computed - target *types.FullBlock - - // exitEventRootHash is the calculated root hash for given checkpoint block - exitEventRootHash types.Hash - - // newValidatorsDelta carries the updates of validator set on epoch ending block - newValidatorsDelta *validator.ValidatorSetDelta -} - -// BuildProposal builds a proposal for the current round (used if proposer) -func (f *fsm) BuildProposal(currentRound uint64) ([]byte, error) { - start := time.Now().UTC() - defer metrics.SetGauge([]string{consensusMetricsPrefix, "block_building_time"}, - float32(time.Now().UTC().Sub(start).Seconds())) - - parent := f.parent - - extraParent, err := GetIbftExtra(parent.ExtraData) - if err != nil { - return nil, err - } - - extra := &Extra{Parent: extraParent.Committed} - // for non-epoch ending blocks, currentValidatorsHash is the same as the nextValidatorsHash - nextValidators := f.validators.Accounts() - - if err := f.blockBuilder.Reset(); err != nil { - return nil, fmt.Errorf("failed to initialize block builder: %w", err) - } - - if f.isEndOfEpoch { - tx, err := f.createCommitEpochTx() - if err != nil { - return nil, err - } - - if err := f.blockBuilder.WriteTx(tx); err != nil { - return nil, fmt.Errorf("failed to apply commit epoch transaction: %w", err) - } - - tx, err = f.createDistributeRewardsTx() - if err != nil { - return nil, err - } - - if err := f.blockBuilder.WriteTx(tx); err != nil { - return nil, fmt.Errorf("failed to apply distribute rewards transaction: %w", err) - } - } - - if f.config.IsBridgeEnabled() { - if err := f.applyBridgeCommitmentTx(); err != nil { - return nil, err - } - } - - // fill the block with transactions - f.blockBuilder.Fill() - - if f.isEndOfEpoch { - nextValidators, err = nextValidators.ApplyDelta(f.newValidatorsDelta) - if err != nil { - return nil, err - } - - extra.Validators = f.newValidatorsDelta - } - - currentValidatorsHash, err := f.validators.Accounts().Hash() - if err != nil { - return nil, err - } - - nextValidatorsHash, err := nextValidators.Hash() - if err != nil { - return nil, err - } - - extra.Checkpoint = &CheckpointData{ - BlockRound: currentRound, - EpochNumber: f.epochNumber, - CurrentValidatorsHash: currentValidatorsHash, - NextValidatorsHash: nextValidatorsHash, - EventRoot: f.exitEventRootHash, - } - - f.logger.Debug("[Build Proposal]", "Current validators hash", currentValidatorsHash, - "Next validators hash", nextValidatorsHash) - - stateBlock, err := f.blockBuilder.Build(func(h *types.Header) { - h.ExtraData = extra.MarshalRLPTo(nil) - h.MixHash = PolyBFTMixDigest - }) - - if err != nil { - return nil, err - } - - if f.logger.IsDebug() { - checkpointHash, err := extra.Checkpoint.Hash(f.backend.GetChainID(), f.Height(), stateBlock.Block.Hash()) - if err != nil { - return nil, fmt.Errorf("failed to calculate proposal hash: %w", err) - } - - f.logger.Debug("[FSM Build Proposal]", - "txs", len(stateBlock.Block.Transactions), - "proposal hash", checkpointHash.String()) - } - - f.target = stateBlock - - return stateBlock.Block.MarshalRLP(), nil -} - -// applyBridgeCommitmentTx builds state transaction which contains data for bridge commitment registration -func (f *fsm) applyBridgeCommitmentTx() error { - if f.proposerCommitmentToRegister != nil { - bridgeCommitmentTx, err := f.createBridgeCommitmentTx() - if err != nil { - return fmt.Errorf("creation of bridge commitment transaction failed: %w", err) - } - - if err := f.blockBuilder.WriteTx(bridgeCommitmentTx); err != nil { - return fmt.Errorf("failed to apply bridge commitment state transaction. Error: %w", err) - } - } - - return nil -} - -// createBridgeCommitmentTx builds bridge commitment registration transaction -func (f *fsm) createBridgeCommitmentTx() (*types.Transaction, error) { - inputData, err := f.proposerCommitmentToRegister.EncodeAbi() - if err != nil { - return nil, fmt.Errorf("failed to encode input data for bridge commitment registration: %w", err) - } - - return createStateTransactionWithData(f.Height(), contracts.StateReceiverContract, inputData), nil -} - -// getValidatorsTransition applies delta to the current validators, -func (f *fsm) getValidatorsTransition(delta *validator.ValidatorSetDelta) (validator.AccountSet, error) { - nextValidators, err := f.validators.Accounts().ApplyDelta(delta) - if err != nil { - return nil, err - } - - f.logger.Debug("getValidatorsTransition", "Next validators", nextValidators) - - return nextValidators, nil -} - -// createCommitEpochTx create a StateTransaction, which invokes ValidatorSet smart contract -// and sends all the necessary metadata to it. -func (f *fsm) createCommitEpochTx() (*types.Transaction, error) { - input, err := f.commitEpochInput.EncodeAbi() - if err != nil { - return nil, err - } - - return createStateTransactionWithData(f.Height(), contracts.ValidatorSetContract, input), nil -} - -// createDistributeRewardsTx create a StateTransaction, which invokes RewardPool smart contract -// and sends all the necessary metadata to it. -func (f *fsm) createDistributeRewardsTx() (*types.Transaction, error) { - input, err := f.distributeRewardsInput.EncodeAbi() - if err != nil { - return nil, err - } - - return createStateTransactionWithData(f.Height(), contracts.RewardPoolContract, input), nil -} - -// ValidateCommit is used to validate that a given commit is valid -func (f *fsm) ValidateCommit(signerAddr []byte, seal []byte, proposalHash []byte) error { - from := types.BytesToAddress(signerAddr) - - validator := f.validators.Accounts().GetValidatorMetadata(from) - if validator == nil { - return fmt.Errorf("unable to resolve validator %s", from) - } - - signature, err := bls.UnmarshalSignature(seal) - if err != nil { - return fmt.Errorf("failed to unmarshall signature: %w", err) - } - - if !signature.Verify(validator.BlsKey, proposalHash, signer.DomainCheckpointManager) { - return fmt.Errorf("incorrect commit signature from %s", from) - } - - return nil -} - -// Validate validates a raw proposal (used if non-proposer) -func (f *fsm) Validate(proposal []byte) error { - var block types.Block - if err := block.UnmarshalRLP(proposal); err != nil { - return fmt.Errorf("failed to validate, cannot decode block data. Error: %w", err) - } - - // validate header fields - if err := validateHeaderFields(f.parent, block.Header, f.config.BlockTimeDrift); err != nil { - return fmt.Errorf( - "failed to validate header (parent header# %d, current header#%d): %w", - f.parent.Number, - block.Number(), - err, - ) - } - - extra, err := GetIbftExtra(block.Header.ExtraData) - if err != nil { - return fmt.Errorf("cannot get extra data:%w", err) - } - - parentExtra, err := GetIbftExtra(f.parent.ExtraData) - if err != nil { - return err - } - - if extra.Checkpoint == nil { - return fmt.Errorf("checkpoint data for block %d is missing", block.Number()) - } - - if parentExtra.Checkpoint == nil { - return fmt.Errorf("checkpoint data for parent block %d is missing", f.parent.Number) - } - - if err := extra.ValidateParentSignatures(block.Number(), f.polybftBackend, nil, f.parent, parentExtra, - f.backend.GetChainID(), signer.DomainCheckpointManager, f.logger); err != nil { - return err - } - - if err := f.VerifyStateTransactions(block.Transactions); err != nil { - return err - } - - currentValidators := f.validators.Accounts() - - // validate validators delta - if f.isEndOfEpoch { - if extra.Validators == nil { - return errValidatorDeltaNilInEpochEndingBlock - } - - if !extra.Validators.Equals(f.newValidatorsDelta) { - return errValidatorSetDeltaMismatch - } - } else if extra.Validators != nil { - // delta should be nil in non epoch ending blocks - return errValidatorsUpdateInNonEpochEnding - } - - nextValidators, err := f.getValidatorsTransition(extra.Validators) - if err != nil { - return err - } - - // validate checkpoint data - if err := extra.Checkpoint.Validate(parentExtra.Checkpoint, - currentValidators, nextValidators, f.exitEventRootHash); err != nil { - return err - } - - if f.logger.IsTrace() && block.Number() > 1 { - validators, err := f.polybftBackend.GetValidators(block.Number()-2, nil) - if err != nil { - return fmt.Errorf("failed to retrieve validators:%w", err) - } - - f.logger.Trace("[FSM Validate]", "Block", block.Number(), "parent validators", validators) - } - - stateBlock, err := f.backend.ProcessBlock(f.parent, &block) - if err != nil { - return err - } - - if f.logger.IsDebug() { - checkpointHash, err := extra.Checkpoint.Hash(f.backend.GetChainID(), block.Number(), block.Hash()) - if err != nil { - return fmt.Errorf("failed to calculate proposal hash: %w", err) - } - - f.logger.Debug("[FSM Validate]", "txs", len(block.Transactions), "proposal hash", checkpointHash) - } - - f.target = stateBlock - - return nil -} - -// ValidateSender validates sender address and signature -func (f *fsm) ValidateSender(msg *proto.Message) error { - msgNoSig, err := msg.PayloadNoSig() - if err != nil { - return err - } - - signerAddress, err := wallet.RecoverAddressFromSignature(msg.Signature, msgNoSig) - if err != nil { - return fmt.Errorf("failed to recover address from signature: %w", err) - } - - // verify the signature came from the sender - if !bytes.Equal(msg.From, signerAddress.Bytes()) { - return fmt.Errorf("signer address %s doesn't match From field", signerAddress.String()) - } - - // verify the sender is in the active validator set - if !f.validators.Includes(signerAddress) { - return fmt.Errorf("signer address %s is not included in validator set", signerAddress.String()) - } - - return nil -} - -func (f *fsm) VerifyStateTransactions(transactions []*types.Transaction) error { - var ( - commitmentTxExists bool - commitEpochTxExists bool - distributeRewardsTxExists bool - ) - - for _, tx := range transactions { - if tx.Type != types.StateTx { - continue - } - - decodedStateTx, err := decodeStateTransaction(tx.Input) - if err != nil { - return fmt.Errorf("unknown state transaction: tx = %v, err = %w", tx.Hash, err) - } - - switch stateTxData := decodedStateTx.(type) { - case *CommitmentMessageSigned: - if !f.isEndOfSprint { - return fmt.Errorf("found commitment tx in block which should not contain it (tx hash=%s)", tx.Hash) - } - - if commitmentTxExists { - return fmt.Errorf("only one commitment tx is allowed per block (tx hash=%s)", tx.Hash) - } - - commitmentTxExists = true - - if err = verifyBridgeCommitmentTx(f.Height(), tx.Hash, stateTxData, f.validators); err != nil { - return err - } - case *contractsapi.CommitEpochValidatorSetFn: - if commitEpochTxExists { - // if we already validated commit epoch tx, - // that means someone added more than one commit epoch tx to block, - // which is invalid - return errCommitEpochTxSingleExpected - } - - commitEpochTxExists = true - - if err := f.verifyCommitEpochTx(tx); err != nil { - return fmt.Errorf("error while verifying commit epoch transaction. error: %w", err) - } - case *contractsapi.DistributeRewardForRewardPoolFn: - if distributeRewardsTxExists { - // if we already validated distribute rewards tx, - // that means someone added more than one distribute rewards tx to block, - // which is invalid - return errDistributeRewardsTxSingleExpected - } - - distributeRewardsTxExists = true - - if err := f.verifyDistributeRewardsTx(tx); err != nil { - return fmt.Errorf("error while verifying distribute rewards transaction. error: %w", err) - } - default: - return fmt.Errorf("invalid state transaction data type: %v", stateTxData) - } - } - - if f.isEndOfEpoch { - if !commitEpochTxExists { - // this is a check if commit epoch transaction is not in the list of transactions at all - // but it should be - return errCommitEpochTxDoesNotExist - } - - if !distributeRewardsTxExists { - // this is a check if distribute rewards transaction is not in the list of transactions at all - // but it should be - return errDistributeRewardsTxDoesNotExist - } - } - - return nil -} - -// Insert inserts the sealed proposal -func (f *fsm) Insert(proposal []byte, committedSeals []*messages.CommittedSeal) (*types.FullBlock, error) { - newBlock := f.target - - var proposedBlock types.Block - if err := proposedBlock.UnmarshalRLP(proposal); err != nil { - return nil, fmt.Errorf("failed to insert proposal, block unmarshaling failed: %w", err) - } - - if newBlock == nil || newBlock.Block.Hash() != proposedBlock.Hash() { - // if this is the case, we will let syncer insert the block - return nil, errProposalDontMatch - } - - // In this function we should try to return little to no errors since - // at this point everything we have to do is just commit something that - // we should have already computed beforehand. - extra, err := GetIbftExtra(newBlock.Block.Header.ExtraData) - if err != nil { - return nil, fmt.Errorf("failed to insert proposal, due to not being able to extract extra data: %w", err) - } - - // create map for faster access to indexes - nodeIDIndexMap := make(map[types.Address]int, f.validators.Len()) - for i, addr := range f.validators.Accounts().GetAddresses() { - nodeIDIndexMap[addr] = i - } - - // populated bitmap according to nodeId from validator set and committed seals - // also populate slice of signatures - bitmap := bitmap.Bitmap{} - signatures := make(bls.Signatures, 0, len(committedSeals)) - - for _, commSeal := range committedSeals { - signerAddr := types.BytesToAddress(commSeal.Signer) - - index, exists := nodeIDIndexMap[signerAddr] - if !exists { - return nil, fmt.Errorf("invalid node id = %s", signerAddr.String()) - } - - s, err := bls.UnmarshalSignature(commSeal.Signature) - if err != nil { - return nil, fmt.Errorf("invalid signature = %s", commSeal.Signature) - } - - signatures = append(signatures, s) - - bitmap.Set(uint64(index)) - } - - aggregatedSignature, err := signatures.Aggregate().Marshal() - if err != nil { - return nil, fmt.Errorf("could not aggregate seals: %w", err) - } - - // include aggregated signature of all committed seals - // also includes bitmap which contains all indexes from validator set which provides there seals - extra.Committed = &Signature{ - AggregatedSignature: aggregatedSignature, - Bitmap: bitmap, - } - - // Write extra data to header - newBlock.Block.Header.ExtraData = extra.MarshalRLPTo(nil) - - if err := f.backend.CommitBlock(newBlock); err != nil { - return nil, err - } - - return newBlock, nil -} - -// Height returns the height for the current round -func (f *fsm) Height() uint64 { - return f.parent.Number + 1 -} - -// ValidatorSet returns the validator set for the current round -func (f *fsm) ValidatorSet() validator.ValidatorSet { - return f.validators -} - -// verifyCommitEpochTx creates commit epoch transaction and compares its hash with the one extracted from the block. -func (f *fsm) verifyCommitEpochTx(commitEpochTx *types.Transaction) error { - if f.isEndOfEpoch { - localCommitEpochTx, err := f.createCommitEpochTx() - if err != nil { - return err - } - - if commitEpochTx.Hash != localCommitEpochTx.Hash { - return fmt.Errorf( - "invalid commit epoch transaction. Expected '%s', but got '%s' commit epoch transaction hash", - localCommitEpochTx.Hash, - commitEpochTx.Hash, - ) - } - - return nil - } - - return errCommitEpochTxNotExpected -} - -// verifyDistributeRewardsTx creates distribute rewards transaction -// and compares its hash with the one extracted from the block. -func (f *fsm) verifyDistributeRewardsTx(distributeRewardsTx *types.Transaction) error { - if f.isEndOfEpoch { - localDistributeRewardsTx, err := f.createDistributeRewardsTx() - if err != nil { - return err - } - - if distributeRewardsTx.Hash != localDistributeRewardsTx.Hash { - return fmt.Errorf( - "invalid distribute rewards transaction. Expected '%s', but got '%s' distribute rewards hash", - localDistributeRewardsTx.Hash, - distributeRewardsTx.Hash, - ) - } - - return nil - } - - return errDistributeRewardsTxNotExpected -} - -// verifyBridgeCommitmentTx validates bridge commitment transaction -func verifyBridgeCommitmentTx(blockNumber uint64, txHash types.Hash, - commitment *CommitmentMessageSigned, - validators validator.ValidatorSet) error { - signers, err := validators.Accounts().GetFilteredValidators(commitment.AggSignature.Bitmap) - if err != nil { - return fmt.Errorf("failed to retrieve signers for state tx (%s): %w", txHash, err) - } - - if !validators.HasQuorum(blockNumber, signers.GetAddressesAsSet()) { - return fmt.Errorf("quorum size not reached for state tx (%s)", txHash) - } - - commitmentHash, err := commitment.Hash() - if err != nil { - return err - } - - signature, err := bls.UnmarshalSignature(commitment.AggSignature.AggregatedSignature) - if err != nil { - return fmt.Errorf("error for state tx (%s) while unmarshaling signature: %w", txHash, err) - } - - verified := signature.VerifyAggregated(signers.GetBlsKeys(), commitmentHash.Bytes(), signer.DomainStateReceiver) - if !verified { - return fmt.Errorf("invalid signature for state tx (%s)", txHash) - } - - return nil -} - -func validateHeaderFields(parent *types.Header, header *types.Header, blockTimeDrift uint64) error { - // header extra data must be higher or equal to ExtraVanity = 32 in order to be compliant with Ethereum blocks - if len(header.ExtraData) < ExtraVanity { - return fmt.Errorf("extra-data shorter than %d bytes (%d)", ExtraVanity, len(header.ExtraData)) - } - // verify parent hash - if parent.Hash != header.ParentHash { - return fmt.Errorf("incorrect header parent hash (parent=%s, header parent=%s)", parent.Hash, header.ParentHash) - } - // verify parent number - if header.Number != parent.Number+1 { - return fmt.Errorf("invalid number") - } - // verify time is from the future - if header.Timestamp > (uint64(time.Now().UTC().Unix()) + blockTimeDrift) { - return fmt.Errorf("block from the future. block timestamp: %s, configured block time drift %d seconds", - time.Unix(int64(header.Timestamp), 0).Format(time.RFC3339), blockTimeDrift) - } - // verify header nonce is zero - if header.Nonce != types.ZeroNonce { - return fmt.Errorf("invalid nonce") - } - // verify that the gasUsed is <= gasLimit - if header.GasUsed > header.GasLimit { - return fmt.Errorf("invalid gas limit: have %v, max %v", header.GasUsed, header.GasLimit) - } - // verify time has passed - if header.Timestamp <= parent.Timestamp { - return fmt.Errorf("timestamp older than parent") - } - // verify mix digest - if header.MixHash != PolyBFTMixDigest { - return fmt.Errorf("mix digest is not correct") - } - // difficulty must be > 0 - if header.Difficulty <= 0 { - return fmt.Errorf("difficulty should be greater than zero") - } - // calculated header hash must be correct - if header.Hash != types.HeaderHash(header) { - return fmt.Errorf("invalid header hash") - } - - return nil -} - -// createStateTransactionWithData creates a state transaction -// with provided target address and inputData parameter which is ABI encoded byte array. -func createStateTransactionWithData(blockNumber uint64, target types.Address, inputData []byte) *types.Transaction { - tx := &types.Transaction{ - From: contracts.SystemCaller, - To: &target, - Type: types.StateTx, - Input: inputData, - Gas: types.StateTransactionGasLimit, - GasPrice: big.NewInt(0), - } - - return tx.ComputeHash(blockNumber) -} diff --git a/consensus/polybft/fsm_test.go b/consensus/polybft/fsm_test.go deleted file mode 100644 index 64c62ad..0000000 --- a/consensus/polybft/fsm_test.go +++ /dev/null @@ -1,1677 +0,0 @@ -package polybft - -import ( - "errors" - "fmt" - "math/big" - "math/rand" - "testing" - "time" - - "github.com/0xPolygon/go-ibft/messages" - "github.com/0xPolygon/go-ibft/messages/proto" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/types" -) - -func TestFSM_ValidateHeader(t *testing.T) { - t.Parallel() - - blockTimeDrift := uint64(1) - extra := createTestExtra(validator.AccountSet{}, validator.AccountSet{}, 0, 0, 0) - parent := &types.Header{Number: 0, Hash: types.BytesToHash([]byte{1, 2, 3})} - header := &types.Header{Number: 0} - - // extra data - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "extra-data shorter than") - header.ExtraData = extra - - // parent hash - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "incorrect header parent hash") - header.ParentHash = parent.Hash - - // sequence number - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "invalid number") - header.Number = 1 - - // failed timestamp - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "timestamp older than parent") - header.Timestamp = 10 - - // failed nonce - header.SetNonce(1) - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "invalid nonce") - - header.SetNonce(0) - - // failed gas - header.GasLimit = 10 - header.GasUsed = 11 - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "invalid gas limit") - header.GasLimit = 10 - header.GasUsed = 10 - - // mix digest - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "mix digest is not correct") - header.MixHash = PolyBFTMixDigest - - // difficulty - header.Difficulty = 0 - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "difficulty should be greater than zero") - - header.Difficulty = 1 - header.Hash = types.BytesToHash([]byte{11, 22, 33}) - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "invalid header hash") - header.Timestamp = uint64(time.Now().UTC().Unix() + 150) - require.ErrorContains(t, validateHeaderFields(parent, header, blockTimeDrift), "block from the future") - - header.Timestamp = uint64(time.Now().UTC().Unix()) - - header.ComputeHash() - require.NoError(t, validateHeaderFields(parent, header, blockTimeDrift)) -} - -func TestFSM_verifyCommitEpochTx(t *testing.T) { - t.Parallel() - - fsm := &fsm{ - isEndOfEpoch: true, - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - parent: &types.Header{}, - } - - // include commit epoch transaction to the epoch ending block - commitEpochTx, err := fsm.createCommitEpochTx() - assert.NoError(t, err) - assert.NotNil(t, commitEpochTx) - - assert.NoError(t, fsm.verifyCommitEpochTx(commitEpochTx)) - - // submit tampered commit epoch transaction to the epoch ending block - alteredCommitEpochTx := &types.Transaction{ - To: &contracts.ValidatorSetContract, - Input: []byte{}, - Gas: 0, - Type: types.StateTx, - } - assert.ErrorContains(t, fsm.verifyCommitEpochTx(alteredCommitEpochTx), "invalid commit epoch transaction") - - // submit validators commit epoch transaction to the non-epoch ending block - fsm.isEndOfEpoch = false - commitEpochTx, err = fsm.createCommitEpochTx() - assert.NoError(t, err) - assert.NotNil(t, commitEpochTx) - assert.ErrorContains(t, fsm.verifyCommitEpochTx(commitEpochTx), errCommitEpochTxNotExpected.Error()) -} - -func TestFSM_BuildProposal_WithoutCommitEpochTxGood(t *testing.T) { - t.Parallel() - - const ( - accountCount = 5 - committedCount = 4 - parentCount = 3 - confirmedStateSyncsCount = 5 - parentBlockNumber = 1023 - currentRound = 1 - ) - - eventRoot := types.ZeroHash - - validators := validator.NewTestValidators(t, accountCount) - validatorSet := validators.GetPublicIdentities() - extra := createTestExtra(validatorSet, validator.AccountSet{}, accountCount-1, committedCount, parentCount) - - parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} - parent.ComputeHash() - stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, extra) - mBlockBuilder := newBlockBuilderMock(stateBlock) - - blockchainMock := &blockchainMock{} - runtime := &consensusRuntime{ - logger: hclog.NewNullLogger(), - config: &runtimeConfig{ - Key: wallet.NewKey(validators.GetPrivateIdentities()[0]), - blockchain: blockchainMock, - }, - } - - fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: blockchainMock, - validators: validators.ToValidatorSet(), exitEventRootHash: eventRoot, logger: hclog.NewNullLogger()} - - proposal, err := fsm.BuildProposal(currentRound) - assert.NoError(t, err) - assert.NotNil(t, proposal) - - currentValidatorsHash, err := validatorSet.Hash() - require.NoError(t, err) - - rlpBlock := stateBlock.Block.MarshalRLP() - assert.Equal(t, rlpBlock, proposal) - - block := types.Block{} - require.NoError(t, block.UnmarshalRLP(proposal)) - - checkpoint := &CheckpointData{ - BlockRound: currentRound, - EpochNumber: fsm.epochNumber, - EventRoot: eventRoot, - CurrentValidatorsHash: currentValidatorsHash, - NextValidatorsHash: currentValidatorsHash, - } - - checkpointHash, err := checkpoint.Hash(fsm.backend.GetChainID(), block.Number(), block.Hash()) - require.NoError(t, err) - - msg := runtime.BuildPrePrepareMessage(proposal, nil, &proto.View{}) - require.Equal(t, checkpointHash.Bytes(), msg.GetPreprepareData().ProposalHash) - - mBlockBuilder.AssertExpectations(t) -} - -func TestFSM_BuildProposal_WithCommitEpochTxGood(t *testing.T) { - t.Parallel() - - const ( - accountCount = 5 - committedCount = 4 - parentCount = 3 - confirmedStateSyncsCount = 5 - currentRound = 0 - parentBlockNumber = 1023 - ) - - eventRoot := types.ZeroHash - - validators := validator.NewTestValidators(t, accountCount) - extra := createTestExtra(validators.GetPublicIdentities(), validator.AccountSet{}, accountCount-1, committedCount, parentCount) - - parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} - parent.ComputeHash() - stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, extra) - - mBlockBuilder := newBlockBuilderMock(stateBlock) - mBlockBuilder.On("WriteTx", mock.Anything).Return(error(nil)).Twice() - - blockChainMock := new(blockchainMock) - - runtime := &consensusRuntime{ - logger: hclog.NewNullLogger(), - config: &runtimeConfig{ - Key: wallet.NewKey(validators.GetPrivateIdentities()[0]), - blockchain: blockChainMock, - }, - } - - fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: blockChainMock, - isEndOfEpoch: true, - validators: validators.ToValidatorSet(), - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - distributeRewardsInput: createTestDistributeRewardsInput(t, 0, nil, 10), - exitEventRootHash: eventRoot, - logger: hclog.NewNullLogger(), - } - - proposal, err := fsm.BuildProposal(currentRound) - assert.NoError(t, err) - assert.NotNil(t, proposal) - - block := types.Block{} - require.NoError(t, block.UnmarshalRLP(proposal)) - - assert.Equal(t, stateBlock.Block.MarshalRLP(), proposal) - - currentValidatorsHash, err := validators.GetPublicIdentities().Hash() - require.NoError(t, err) - - nextValidatorsHash, err := validators.ToValidatorSet().Accounts().Hash() - require.NoError(t, err) - - checkpoint := &CheckpointData{ - BlockRound: currentRound, - EpochNumber: fsm.epochNumber, - EventRoot: eventRoot, - CurrentValidatorsHash: currentValidatorsHash, - NextValidatorsHash: nextValidatorsHash, - } - - checkpointHash, err := checkpoint.Hash(fsm.backend.GetChainID(), block.Number(), block.Hash()) - require.NoError(t, err) - - msg := runtime.BuildPrePrepareMessage(proposal, nil, &proto.View{}) - require.Equal(t, checkpointHash.Bytes(), msg.GetPreprepareData().ProposalHash) - - mBlockBuilder.AssertExpectations(t) -} - -func TestFSM_BuildProposal_EpochEndingBlock_FailedToApplyStateTx(t *testing.T) { - t.Parallel() - - const ( - accountCount = 5 - committedCount = 4 - parentCount = 3 - parentBlockNumber = 1023 - ) - - validators := validator.NewTestValidators(t, accountCount) - extra := createTestExtra(validators.GetPublicIdentities(), validator.AccountSet{}, accountCount-1, committedCount, parentCount) - - parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} - - mBlockBuilder := new(blockBuilderMock) - mBlockBuilder.On("WriteTx", mock.Anything).Return(errors.New("error")).Once() - mBlockBuilder.On("Reset").Return(error(nil)).Once() - - validatorSet := validator.NewValidatorSet(validators.GetPublicIdentities(), hclog.NewNullLogger()) - - fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, backend: &blockchainMock{}, - isEndOfEpoch: true, - validators: validatorSet, - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - exitEventRootHash: types.ZeroHash, - } - - _, err := fsm.BuildProposal(0) - assert.ErrorContains(t, err, "failed to apply commit epoch transaction") - mBlockBuilder.AssertExpectations(t) -} - -func TestFSM_BuildProposal_EpochEndingBlock_ValidatorsDeltaExists(t *testing.T) { - t.Parallel() - - const ( - validatorsCount = 6 - remainingValidatorsCount = 3 - signaturesCount = 4 - parentBlockNumber = 49 - ) - - validators := validator.NewTestValidators(t, validatorsCount).GetPublicIdentities() - extra := createTestExtraObject(validators, validator.AccountSet{}, validatorsCount-1, signaturesCount, signaturesCount) - extra.Validators = nil - - extraData := extra.MarshalRLPTo(nil) - parent := &types.Header{Number: parentBlockNumber, ExtraData: extraData} - parent.ComputeHash() - stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, extraData) - - blockBuilderMock := newBlockBuilderMock(stateBlock) - blockBuilderMock.On("WriteTx", mock.Anything).Return(error(nil)).Twice() - - addedValidators := validator.NewTestValidators(t, 2).GetPublicIdentities() - removedValidators := [3]uint64{3, 4, 5} - removedBitmap := &bitmap.Bitmap{} - - for _, i := range removedValidators { - removedBitmap.Set(i) - } - - newDelta := &validator.ValidatorSetDelta{ - Added: addedValidators, - Updated: validator.AccountSet{}, - Removed: *removedBitmap, - } - - blockChainMock := new(blockchainMock) - - validatorSet := validator.NewValidatorSet(validators, hclog.NewNullLogger()) - - fsm := &fsm{ - parent: parent, - blockBuilder: blockBuilderMock, - config: &PolyBFTConfig{}, - backend: blockChainMock, - isEndOfEpoch: true, - validators: validatorSet, - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - distributeRewardsInput: createTestDistributeRewardsInput(t, 0, validators, 10), - exitEventRootHash: types.ZeroHash, - logger: hclog.NewNullLogger(), - newValidatorsDelta: newDelta, - } - - proposal, err := fsm.BuildProposal(0) - assert.NoError(t, err) - assert.NotNil(t, proposal) - - blockExtra, err := GetIbftExtra(stateBlock.Block.Header.ExtraData) - assert.NoError(t, err) - assert.Len(t, blockExtra.Validators.Added, 2) - assert.False(t, blockExtra.Validators.IsEmpty()) - - for _, addedValidator := range addedValidators { - assert.True(t, blockExtra.Validators.Added.ContainsAddress(addedValidator.Address)) - } - - for _, removedValidator := range removedValidators { - assert.True( - t, - blockExtra.Validators.Removed.IsSet(removedValidator), - fmt.Sprintf("Expected validator at index %d to be marked as removed, but it wasn't", removedValidator), - ) - } - - blockBuilderMock.AssertExpectations(t) - blockChainMock.AssertExpectations(t) -} - -func TestFSM_BuildProposal_NonEpochEndingBlock_ValidatorsDeltaNil(t *testing.T) { - t.Parallel() - - const ( - accountCount = 6 - signaturesCount = 4 - parentBlockNumber = 9 - ) - - testValidators := validator.NewTestValidators(t, accountCount) - extra := createTestExtra(testValidators.GetPublicIdentities(), validator.AccountSet{}, accountCount-1, signaturesCount, signaturesCount) - parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} - parent.ComputeHash() - stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, extra) - - blockBuilderMock := &blockBuilderMock{} - blockBuilderMock.On("Build", mock.Anything).Return(stateBlock).Once() - blockBuilderMock.On("Fill").Once() - blockBuilderMock.On("Reset").Return(error(nil)).Once() - - fsm := &fsm{parent: parent, blockBuilder: blockBuilderMock, - config: &PolyBFTConfig{}, backend: &blockchainMock{}, - isEndOfEpoch: false, validators: testValidators.ToValidatorSet(), - exitEventRootHash: types.ZeroHash, logger: hclog.NewNullLogger()} - - proposal, err := fsm.BuildProposal(0) - assert.NoError(t, err) - assert.NotNil(t, proposal) - - blockExtra, err := GetIbftExtra(stateBlock.Block.Header.ExtraData) - assert.NoError(t, err) - assert.Nil(t, blockExtra.Validators) - - blockBuilderMock.AssertExpectations(t) -} - -func TestFSM_BuildProposal_EpochEndingBlock_FailToGetNextValidatorsHash(t *testing.T) { - t.Parallel() - - const ( - accountCount = 6 - signaturesCount = 4 - parentBlockNumber = 49 - ) - - testValidators := validator.NewTestValidators(t, accountCount) - allAccounts := testValidators.GetPublicIdentities() - extra := createTestExtraObject(allAccounts, validator.AccountSet{}, accountCount-1, signaturesCount, signaturesCount) - extra.Validators = nil - - newValidatorDelta := &validator.ValidatorSetDelta{ - // this will prompt an error since all the validators are already in the validator set - Added: testValidators.ToValidatorSet().Accounts(), - } - - parent := &types.Header{Number: parentBlockNumber, ExtraData: extra.MarshalRLPTo(nil)} - - blockBuilderMock := new(blockBuilderMock) - blockBuilderMock.On("WriteTx", mock.Anything).Return(error(nil)).Twice() - blockBuilderMock.On("Reset").Return(error(nil)).Once() - blockBuilderMock.On("Fill").Once() - - fsm := &fsm{parent: parent, - blockBuilder: blockBuilderMock, - config: &PolyBFTConfig{}, - isEndOfEpoch: true, - validators: testValidators.ToValidatorSet(), - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - distributeRewardsInput: createTestDistributeRewardsInput(t, 0, allAccounts, 10), - exitEventRootHash: types.ZeroHash, - newValidatorsDelta: newValidatorDelta, - } - - proposal, err := fsm.BuildProposal(0) - assert.ErrorContains(t, err, "already present in the validators snapshot") - assert.Nil(t, proposal) - - blockBuilderMock.AssertNotCalled(t, "Build") - blockBuilderMock.AssertExpectations(t) -} - -func TestFSM_VerifyStateTransactions_MiddleOfEpochWithTransaction(t *testing.T) { - t.Parallel() - - fsm := &fsm{commitEpochInput: createTestCommitEpochInput(t, 0, 10), parent: &types.Header{}} - tx, err := fsm.createCommitEpochTx() - assert.NoError(t, err) - err = fsm.VerifyStateTransactions([]*types.Transaction{tx}) - assert.ErrorContains(t, err, err.Error()) -} - -func TestFSM_VerifyStateTransactions_MiddleOfEpochWithoutTransaction(t *testing.T) { - t.Parallel() - - fsm := &fsm{commitEpochInput: createTestCommitEpochInput(t, 0, 10), parent: &types.Header{}} - err := fsm.VerifyStateTransactions([]*types.Transaction{}) - assert.NoError(t, err) -} - -func TestFSM_VerifyStateTransactions_EndOfEpochWithoutTransaction(t *testing.T) { - t.Parallel() - - fsm := &fsm{isEndOfEpoch: true, commitEpochInput: createTestCommitEpochInput(t, 0, 10)} - assert.EqualError(t, fsm.VerifyStateTransactions([]*types.Transaction{}), - "commit epoch transaction is not found in the epoch ending block") -} - -func TestFSM_VerifyStateTransactions_EndOfEpochWrongCommitEpochTx(t *testing.T) { - t.Parallel() - - fsm := &fsm{ - isEndOfEpoch: true, - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - parent: &types.Header{}, - } - commitEpochInput, err := createTestCommitEpochInput(t, 1, 5).EncodeAbi() - require.NoError(t, err) - - commitEpochTx := createStateTransactionWithData(1, contracts.ValidatorSetContract, commitEpochInput) - assert.ErrorContains(t, fsm.VerifyStateTransactions([]*types.Transaction{commitEpochTx}), "invalid commit epoch transaction") -} - -func TestFSM_VerifyStateTransactions_CommitmentTransactionAndSprintIsFalse(t *testing.T) { - t.Parallel() - - fsm := &fsm{parent: &types.Header{}} - - encodedCommitment, err := createTestCommitmentMessage(t, 1).EncodeAbi() - require.NoError(t, err) - - tx := createStateTransactionWithData(1, contracts.StateReceiverContract, encodedCommitment) - assert.ErrorContains(t, fsm.VerifyStateTransactions([]*types.Transaction{tx}), - "found commitment tx in block which should not contain it") -} - -func TestFSM_VerifyStateTransactions_EndOfEpochMoreThanOneCommitEpochTx(t *testing.T) { - t.Parallel() - - txs := make([]*types.Transaction, 2) - fsm := &fsm{ - isEndOfEpoch: true, - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - parent: &types.Header{}, - } - - commitEpochTxOne, err := fsm.createCommitEpochTx() - require.NoError(t, err) - - txs[0] = commitEpochTxOne - - commitEpochTxTwo := createTestCommitEpochInput(t, 0, 100) - input, err := commitEpochTxTwo.EncodeAbi() - require.NoError(t, err) - - txs[1] = createStateTransactionWithData(1, types.ZeroAddress, input) - - assert.ErrorIs(t, fsm.VerifyStateTransactions(txs), errCommitEpochTxSingleExpected) -} - -func TestFSM_VerifyStateTransactions_StateTransactionPass(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidators(t, 5) - - validatorSet := validator.NewValidatorSet(validators.GetPublicIdentities(), hclog.NewNullLogger()) - - fsm := &fsm{ - parent: &types.Header{Number: 1}, - isEndOfEpoch: true, - isEndOfSprint: true, - validators: validatorSet, - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - distributeRewardsInput: createTestDistributeRewardsInput(t, 0, validators.GetPublicIdentities(), 10), - logger: hclog.NewNullLogger(), - } - - // add commit epoch commitEpochTx to the end of transactions list - commitEpochTx, err := fsm.createCommitEpochTx() - require.NoError(t, err) - - distributeRewardsTx, err := fsm.createDistributeRewardsTx() - require.NoError(t, err) - - err = fsm.VerifyStateTransactions([]*types.Transaction{commitEpochTx, distributeRewardsTx}) - require.NoError(t, err) -} - -func TestFSM_VerifyStateTransactions_StateTransactionQuorumNotReached(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidators(t, 5) - commitment := createTestCommitment(t, validators.GetPrivateIdentities()) - commitment.AggSignature = Signature{ - AggregatedSignature: []byte{1, 2}, - Bitmap: []byte{}, - } - - validatorSet := validator.NewValidatorSet(validators.GetPublicIdentities(), hclog.NewNullLogger()) - - fsm := &fsm{ - parent: &types.Header{Number: 2}, - isEndOfEpoch: true, - isEndOfSprint: true, - validators: validatorSet, - proposerCommitmentToRegister: commitment, - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - distributeRewardsInput: createTestDistributeRewardsInput(t, 0, nil, 10), - logger: hclog.NewNullLogger(), - } - - bridgeCommitmentTx, err := fsm.createBridgeCommitmentTx() - require.NoError(t, err) - - // add commit epoch commitEpochTx to the end of transactions list - commitEpochTx, err := fsm.createCommitEpochTx() - require.NoError(t, err) - - stateTxs := []*types.Transaction{commitEpochTx, bridgeCommitmentTx} - - err = fsm.VerifyStateTransactions(stateTxs) - require.ErrorContains(t, err, "quorum size not reached") -} - -func TestFSM_VerifyStateTransactions_StateTransactionInvalidSignature(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidators(t, 5) - commitment := createTestCommitment(t, validators.GetPrivateIdentities()) - nonValidators := validator.NewTestValidators(t, 3) - aggregatedSigs := bls.Signatures{} - - nonValidators.IterAcct(nil, func(t *validator.TestValidator) { - aggregatedSigs = append(aggregatedSigs, t.MustSign([]byte("dummyHash"), signer.DomainStateReceiver)) - }) - - sig, err := aggregatedSigs.Aggregate().Marshal() - require.NoError(t, err) - - commitment.AggSignature.AggregatedSignature = sig - - validatorSet := validator.NewValidatorSet(validators.GetPublicIdentities(), hclog.NewNullLogger()) - - fsm := &fsm{ - parent: &types.Header{Number: 1}, - isEndOfEpoch: true, - isEndOfSprint: true, - validators: validatorSet, - proposerCommitmentToRegister: commitment, - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - distributeRewardsInput: createTestDistributeRewardsInput(t, 0, nil, 10), - logger: hclog.NewNullLogger(), - } - - bridgeCommitmentTx, err := fsm.createBridgeCommitmentTx() - require.NoError(t, err) - - // add commit epoch commitEpochTx to the end of transactions list - commitEpochTx, err := fsm.createCommitEpochTx() - require.NoError(t, err) - - err = fsm.VerifyStateTransactions([]*types.Transaction{commitEpochTx, bridgeCommitmentTx}) - require.ErrorContains(t, err, "invalid signature") -} - -func TestFSM_ValidateCommit_WrongValidator(t *testing.T) { - t.Parallel() - - const ( - accountsCount = 5 - parentBlockNumber = 10 - ) - - validators := validator.NewTestValidators(t, accountsCount) - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: createTestExtra(validators.GetPublicIdentities(), validator.AccountSet{}, 5, 3, 3), - } - parent.ComputeHash() - stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, parent.ExtraData) - mBlockBuilder := newBlockBuilderMock(stateBlock) - - fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: &blockchainMock{}, - validators: validators.ToValidatorSet(), logger: hclog.NewNullLogger(), exitEventRootHash: types.ZeroHash} - - _, err := fsm.BuildProposal(0) - require.NoError(t, err) - - err = fsm.ValidateCommit([]byte("0x7467674"), types.ZeroAddress.Bytes(), []byte{}) - require.ErrorContains(t, err, "unable to resolve validator") -} - -func TestFSM_ValidateCommit_InvalidHash(t *testing.T) { - t.Parallel() - - const ( - accountsCount = 5 - parentBlockNumber = 10 - ) - - validators := validator.NewTestValidators(t, accountsCount) - - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: createTestExtra(validators.GetPublicIdentities(), validator.AccountSet{}, 5, 3, 3), - } - parent.ComputeHash() - stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, parent.ExtraData) - mBlockBuilder := newBlockBuilderMock(stateBlock) - - fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: &blockchainMock{}, - validators: validators.ToValidatorSet(), exitEventRootHash: types.ZeroHash, logger: hclog.NewNullLogger()} - - _, err := fsm.BuildProposal(0) - require.NoError(t, err) - - nonValidatorAcc := validator.NewTestValidator(t, "non_validator", 1) - wrongSignature, err := nonValidatorAcc.MustSign([]byte("Foo"), signer.DomainCheckpointManager).Marshal() - require.NoError(t, err) - - err = fsm.ValidateCommit(validators.GetValidator("0").Address().Bytes(), wrongSignature, []byte{}) - require.ErrorContains(t, err, "incorrect commit signature from") -} - -func TestFSM_ValidateCommit_Good(t *testing.T) { - t.Parallel() - - const parentBlockNumber = 10 - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E"}) - validatorsMetadata := validators.GetPublicIdentities() - - parent := &types.Header{Number: parentBlockNumber, ExtraData: createTestExtra(validatorsMetadata, validator.AccountSet{}, 5, 3, 3)} - parent.ComputeHash() - stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, parent.ExtraData) - mBlockBuilder := newBlockBuilderMock(stateBlock) - - validatorSet := validator.NewValidatorSet(validatorsMetadata, hclog.NewNullLogger()) - - fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: &blockchainMock{}, - validators: validatorSet, - exitEventRootHash: types.ZeroHash, - logger: hclog.NewNullLogger()} - - proposal, err := fsm.BuildProposal(0) - require.NoError(t, err) - - block := types.Block{} - require.NoError(t, block.UnmarshalRLP(proposal)) - - validator := validators.GetValidator("A") - seal, err := validator.MustSign(block.Hash().Bytes(), signer.DomainCheckpointManager).Marshal() - require.NoError(t, err) - err = fsm.ValidateCommit(validator.Key().Address().Bytes(), seal, block.Hash().Bytes()) - require.NoError(t, err) -} - -func TestFSM_Validate_ExitEventRootNotExpected(t *testing.T) { - t.Parallel() - - const ( - accountsCount = 5 - parentBlockNumber = 25 - signaturesCount = 3 - ) - - validators := validator.NewTestValidators(t, accountsCount) - parentExtra := createTestExtraObject(validators.GetPublicIdentities(), validator.AccountSet{}, 4, signaturesCount, signaturesCount) - parentExtra.Validators = nil - - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: parentExtra.MarshalRLPTo(nil), - } - parent.ComputeHash() - - polybftBackendMock := new(polybftBackendMock) - polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validators.GetPublicIdentities(), nil).Once() - - extra := createTestExtraObject(validators.GetPublicIdentities(), validator.AccountSet{}, 4, signaturesCount, signaturesCount) - extra.Validators = nil - parentCheckpointHash, err := extra.Checkpoint.Hash(0, parentBlockNumber, parent.Hash) - require.NoError(t, err) - - currentValSetHash, err := validators.GetPublicIdentities().Hash() - require.NoError(t, err) - - extra.Parent = createSignature(t, validators.GetPrivateIdentities(), parentCheckpointHash, signer.DomainCheckpointManager) - extra.Checkpoint.EpochNumber = 1 - extra.Checkpoint.CurrentValidatorsHash = currentValSetHash - extra.Checkpoint.NextValidatorsHash = currentValSetHash - - stateBlock := createDummyStateBlock(parent.Number+1, types.Hash{100, 15}, extra.MarshalRLPTo(nil)) - - proposalHash, err := extra.Checkpoint.Hash(0, stateBlock.Block.Number(), stateBlock.Block.Hash()) - require.NoError(t, err) - - stateBlock.Block.Header.Hash = proposalHash - stateBlock.Block.Header.ParentHash = parent.Hash - stateBlock.Block.Header.Timestamp = uint64(time.Now().UTC().Unix()) - stateBlock.Block.Transactions = []*types.Transaction{} - - proposal := stateBlock.Block.MarshalRLP() - - fsm := &fsm{ - parent: parent, - backend: new(blockchainMock), - validators: validators.ToValidatorSet(), - logger: hclog.NewNullLogger(), - polybftBackend: polybftBackendMock, - config: &PolyBFTConfig{BlockTimeDrift: 1}, - exitEventRootHash: types.BytesToHash([]byte{0, 1, 2, 3, 4}), // expect this to be in proposal extra - } - - err = fsm.Validate(proposal) - require.ErrorContains(t, err, "exit root hash not as expected") - - polybftBackendMock.AssertExpectations(t) -} - -func TestFSM_Validate_EpochEndingBlock_MismatchInDeltas(t *testing.T) { - t.Parallel() - - const ( - accountsCount = 5 - parentBlockNumber = 25 - signaturesCount = 3 - ) - - validators := validator.NewTestValidators(t, accountsCount) - parentExtra := createTestExtraObject(validators.GetPublicIdentities(), validator.AccountSet{}, 4, signaturesCount, signaturesCount) - parentExtra.Validators = nil - - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: parentExtra.MarshalRLPTo(nil), - } - parent.ComputeHash() - - polybftBackendMock := new(polybftBackendMock) - polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validators.GetPublicIdentities(), nil).Once() - - extra := createTestExtraObject(validators.GetPublicIdentities(), validator.AccountSet{}, 4, signaturesCount, signaturesCount) - parentCheckpointHash, err := extra.Checkpoint.Hash(0, parentBlockNumber, parent.Hash) - require.NoError(t, err) - - extra.Validators = &validator.ValidatorSetDelta{} // this will cause test to fail - extra.Parent = createSignature(t, validators.GetPrivateIdentities(), parentCheckpointHash, signer.DomainCheckpointManager) - - stateBlock := createDummyStateBlock(parent.Number+1, types.Hash{100, 15}, extra.MarshalRLPTo(nil)) - - proposalHash, err := new(CheckpointData).Hash(0, stateBlock.Block.Number(), stateBlock.Block.Hash()) - require.NoError(t, err) - - commitEpoch := createTestCommitEpochInput(t, 1, 10) - commitEpochTxInput, err := commitEpoch.EncodeAbi() - require.NoError(t, err) - - distributeRewards := createTestDistributeRewardsInput(t, 1, validators.GetPublicIdentities(), 10) - distributeRewardsTxInput, err := distributeRewards.EncodeAbi() - require.NoError(t, err) - - stateBlock.Block.Header.Hash = proposalHash - stateBlock.Block.Header.ParentHash = parent.Hash - stateBlock.Block.Header.Timestamp = uint64(time.Now().UTC().Unix()) - stateBlock.Block.Transactions = []*types.Transaction{ - createStateTransactionWithData(1, contracts.ValidatorSetContract, commitEpochTxInput), - createStateTransactionWithData(1, contracts.RewardPoolContract, distributeRewardsTxInput), - } - - proposal := stateBlock.Block.MarshalRLP() - - blockchainMock := new(blockchainMock) - blockchainMock.On("ProcessBlock", mock.Anything, mock.Anything). - Return(stateBlock, error(nil)). - Maybe() - - // a new validator is added to delta which proposers block does not have - privateKey, err := bls.GenerateBlsKey() - require.NoError(t, err) - - newValidatorDelta := &validator.ValidatorSetDelta{ - Added: validator.AccountSet{&validator.ValidatorMetadata{ - Address: types.BytesToAddress([]byte{0, 1, 2, 3}), - BlsKey: privateKey.PublicKey(), - VotingPower: new(big.Int).SetUint64(1), - IsActive: true, - }}, - } - - fsm := &fsm{ - parent: parent, - backend: blockchainMock, - validators: validators.ToValidatorSet(), - logger: hclog.NewNullLogger(), - isEndOfEpoch: true, - commitEpochInput: commitEpoch, - distributeRewardsInput: distributeRewards, - polybftBackend: polybftBackendMock, - newValidatorsDelta: newValidatorDelta, - config: &PolyBFTConfig{BlockTimeDrift: 1}, - } - - err = fsm.Validate(proposal) - require.ErrorIs(t, err, errValidatorSetDeltaMismatch) - - polybftBackendMock.AssertExpectations(t) - blockchainMock.AssertExpectations(t) -} - -func TestFSM_Validate_EpochEndingBlock_UpdatingValidatorSetInNonEpochEndingBlock(t *testing.T) { - t.Parallel() - - const ( - accountsCount = 5 - parentBlockNumber = 25 - signaturesCount = 3 - ) - - validators := validator.NewTestValidators(t, accountsCount) - parentExtra := createTestExtraObject(validators.GetPublicIdentities(), validator.AccountSet{}, 4, signaturesCount, signaturesCount) - parentExtra.Validators = nil - - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: parentExtra.MarshalRLPTo(nil), - } - parent.ComputeHash() - - polybftBackendMock := new(polybftBackendMock) - polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validators.GetPublicIdentities(), nil).Once() - - // a new validator is added to delta which proposers block does not have - privateKey, err := bls.GenerateBlsKey() - require.NoError(t, err) - - newValidatorDelta := &validator.ValidatorSetDelta{ - Added: validator.AccountSet{&validator.ValidatorMetadata{ - Address: types.BytesToAddress([]byte{0, 1, 2, 3}), - BlsKey: privateKey.PublicKey(), - VotingPower: new(big.Int).SetUint64(1), - IsActive: true, - }}, - } - - extra := createTestExtraObject(validators.GetPublicIdentities(), validator.AccountSet{}, 4, signaturesCount, signaturesCount) - parentCheckpointHash, err := extra.Checkpoint.Hash(0, parentBlockNumber, parent.Hash) - require.NoError(t, err) - - extra.Validators = newValidatorDelta // this will cause test to fail - extra.Parent = createSignature(t, validators.GetPrivateIdentities(), parentCheckpointHash, signer.DomainCheckpointManager) - - stateBlock := createDummyStateBlock(parent.Number+1, types.Hash{100, 15}, extra.MarshalRLPTo(nil)) - - proposalHash, err := new(CheckpointData).Hash(0, stateBlock.Block.Number(), stateBlock.Block.Hash()) - require.NoError(t, err) - - stateBlock.Block.Header.Hash = proposalHash - stateBlock.Block.Header.ParentHash = parent.Hash - stateBlock.Block.Header.Timestamp = uint64(time.Now().UTC().Unix()) - - proposal := stateBlock.Block.MarshalRLP() - - blockchainMock := new(blockchainMock) - blockchainMock.On("ProcessBlock", mock.Anything, mock.Anything). - Return(stateBlock, error(nil)). - Maybe() - - fsm := &fsm{ - parent: parent, - backend: blockchainMock, - validators: validators.ToValidatorSet(), - logger: hclog.NewNullLogger(), - polybftBackend: polybftBackendMock, - config: &PolyBFTConfig{BlockTimeDrift: 1}, - } - - err = fsm.Validate(proposal) - require.ErrorIs(t, err, errValidatorsUpdateInNonEpochEnding) - - polybftBackendMock.AssertExpectations(t) - blockchainMock.AssertExpectations(t) -} - -func TestFSM_Validate_IncorrectHeaderParentHash(t *testing.T) { - t.Parallel() - - const ( - accountsCount = 5 - parentBlockNumber = 25 - signaturesCount = 3 - ) - - validators := validator.NewTestValidators(t, accountsCount) - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: createTestExtra(validators.GetPublicIdentities(), validator.AccountSet{}, 4, signaturesCount, signaturesCount), - } - parent.ComputeHash() - - fsm := &fsm{ - parent: parent, - backend: &blockchainMock{}, - validators: validators.ToValidatorSet(), - logger: hclog.NewNullLogger(), - config: &PolyBFTConfig{ - BlockTimeDrift: 1, - }, - } - - stateBlock := createDummyStateBlock(parent.Number+1, types.Hash{100, 15}, parent.ExtraData) - - hash, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), stateBlock.Block.Number(), stateBlock.Block.Hash()) - require.NoError(t, err) - - stateBlock.Block.Header.Hash = hash - proposal := stateBlock.Block.MarshalRLP() - - err = fsm.Validate(proposal) - require.ErrorContains(t, err, "incorrect header parent hash") -} - -func TestFSM_Validate_InvalidNumber(t *testing.T) { - t.Parallel() - - const ( - accountsCount = 5 - parentBlockNumber = 10 - signaturesCount = 3 - ) - - validators := validator.NewTestValidators(t, accountsCount) - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: createTestExtra(validators.GetPublicIdentities(), validator.AccountSet{}, 4, signaturesCount, signaturesCount), - } - parent.ComputeHash() - - // try some invalid block numbers, parentBlockNumber + 1 should be correct - for _, blockNum := range []uint64{parentBlockNumber - 1, parentBlockNumber, parentBlockNumber + 2} { - stateBlock := createDummyStateBlock(blockNum, parent.Hash, parent.ExtraData) - mBlockBuilder := newBlockBuilderMock(stateBlock) - fsm := &fsm{ - parent: parent, - blockBuilder: mBlockBuilder, - backend: &blockchainMock{}, - validators: validators.ToValidatorSet(), - logger: hclog.NewNullLogger(), - config: &PolyBFTConfig{BlockTimeDrift: 1}, - } - - proposalHash, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), stateBlock.Block.Number(), stateBlock.Block.Hash()) - require.NoError(t, err) - - stateBlock.Block.Header.Hash = proposalHash - proposal := stateBlock.Block.MarshalRLP() - - err = fsm.Validate(proposal) - require.ErrorContains(t, err, "invalid number") - } -} - -func TestFSM_Validate_TimestampOlder(t *testing.T) { - t.Parallel() - - const parentBlockNumber = 10 - - validators := validator.NewTestValidators(t, 5) - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: createTestExtra(validators.GetPublicIdentities(), validator.AccountSet{}, 4, 3, 3), - Timestamp: uint64(time.Now().UTC().Unix()), - } - parent.ComputeHash() - - // try some invalid times - for _, blockTime := range []uint64{parent.Timestamp - 1, parent.Timestamp} { - header := &types.Header{ - Number: parentBlockNumber + 1, - ParentHash: parent.Hash, - Timestamp: blockTime, - ExtraData: parent.ExtraData, - } - stateBlock := &types.FullBlock{Block: consensus.BuildBlock(consensus.BuildBlockParams{Header: header})} - fsm := &fsm{ - parent: parent, - backend: &blockchainMock{}, - validators: validators.ToValidatorSet(), - logger: hclog.NewNullLogger(), - config: &PolyBFTConfig{ - BlockTimeDrift: 1, - }} - - checkpointHash, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), header.Number, header.Hash) - require.NoError(t, err) - - stateBlock.Block.Header.Hash = checkpointHash - proposal := stateBlock.Block.MarshalRLP() - - err = fsm.Validate(proposal) - assert.ErrorContains(t, err, "timestamp older than parent") - } -} - -func TestFSM_Validate_IncorrectMixHash(t *testing.T) { - t.Parallel() - - const parentBlockNumber = 10 - - validators := validator.NewTestValidators(t, 5) - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: createTestExtra(validators.GetPublicIdentities(), validator.AccountSet{}, 4, 3, 3), - Timestamp: uint64(100), - } - parent.ComputeHash() - - header := &types.Header{ - Number: parentBlockNumber + 1, - ParentHash: parent.Hash, - Timestamp: parent.Timestamp + 1, - MixHash: types.Hash{}, - ExtraData: parent.ExtraData, - } - - buildBlock := &types.FullBlock{Block: consensus.BuildBlock(consensus.BuildBlockParams{Header: header})} - - fsm := &fsm{ - parent: parent, - backend: &blockchainMock{}, - validators: validators.ToValidatorSet(), - logger: hclog.NewNullLogger(), - config: &PolyBFTConfig{ - BlockTimeDrift: 1, - }, - } - rlpBlock := buildBlock.Block.MarshalRLP() - - _, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), header.Number, header.Hash) - require.NoError(t, err) - - err = fsm.Validate(rlpBlock) - assert.ErrorContains(t, err, "mix digest is not correct") -} - -func TestFSM_Insert_Good(t *testing.T) { - t.Parallel() - - const ( - accountCount = 5 - parentBlockNumber = uint64(10) - signaturesCount = 3 - ) - - setupFn := func() (*fsm, []*messages.CommittedSeal, *types.FullBlock, *blockchainMock) { - validators := validator.NewTestValidators(t, accountCount) - allAccounts := validators.GetPrivateIdentities() - validatorsMetadata := validators.GetPublicIdentities() - - extraParent := createTestExtra(validatorsMetadata, validator.AccountSet{}, len(allAccounts)-1, signaturesCount, signaturesCount) - parent := &types.Header{Number: parentBlockNumber, ExtraData: extraParent} - extraBlock := createTestExtra(validatorsMetadata, validator.AccountSet{}, len(allAccounts)-1, signaturesCount, signaturesCount) - block := consensus.BuildBlock( - consensus.BuildBlockParams{ - Header: &types.Header{Number: parentBlockNumber + 1, ParentHash: parent.Hash, ExtraData: extraBlock}, - }) - - builtBlock := &types.FullBlock{Block: block} - - builderMock := newBlockBuilderMock(builtBlock) - chainMock := &blockchainMock{} - chainMock.On("CommitBlock", mock.Anything).Return(error(nil)).Once() - chainMock.On("ProcessBlock", mock.Anything, mock.Anything). - Return(builtBlock, error(nil)). - Maybe() - - f := &fsm{ - parent: parent, - blockBuilder: builderMock, - target: builtBlock, - backend: chainMock, - validators: validator.NewValidatorSet(validatorsMetadata[0:len(validatorsMetadata)-1], hclog.NewNullLogger()), - logger: hclog.NewNullLogger(), - } - - seals := make([]*messages.CommittedSeal, signaturesCount) - - for i := 0; i < signaturesCount; i++ { - sign, err := allAccounts[i].Bls.Sign(builtBlock.Block.Hash().Bytes(), signer.DomainCheckpointManager) - require.NoError(t, err) - sigRaw, err := sign.Marshal() - require.NoError(t, err) - - seals[i] = &messages.CommittedSeal{ - Signer: validatorsMetadata[i].Address.Bytes(), - Signature: sigRaw, - } - } - - return f, seals, builtBlock, chainMock - } - - t.Run("Insert with target block defined", func(t *testing.T) { - t.Parallel() - - fsm, seals, builtBlock, chainMock := setupFn() - proposal := builtBlock.Block.MarshalRLP() - fullBlock, err := fsm.Insert(proposal, seals) - - require.NoError(t, err) - require.Equal(t, parentBlockNumber+1, fullBlock.Block.Number()) - chainMock.AssertExpectations(t) - }) - - t.Run("Insert with target block undefined", func(t *testing.T) { - t.Parallel() - - fsm, seals, builtBlock, _ := setupFn() - fsm.target = nil - proposal := builtBlock.Block.MarshalRLP() - _, err := fsm.Insert(proposal, seals) - - require.ErrorIs(t, err, errProposalDontMatch) - }) - - t.Run("Insert with target block hash not match", func(t *testing.T) { - t.Parallel() - - fsm, seals, builtBlock, _ := setupFn() - proposal := builtBlock.Block.MarshalRLP() - fsm.target = builtBlock - fsm.target.Block.Header.Hash = types.BytesToHash(generateRandomBytes(t)) - _, err := fsm.Insert(proposal, seals) - - require.ErrorIs(t, err, errProposalDontMatch) - }) -} - -func TestFSM_Insert_InvalidNode(t *testing.T) { - t.Parallel() - - const ( - accountCount = 5 - parentBlockNumber = 10 - signaturesCount = 3 - ) - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E"}) - validatorsMetadata := validators.GetPublicIdentities() - - parent := &types.Header{Number: parentBlockNumber} - parent.ComputeHash() - - extraBlock := createTestExtra(validatorsMetadata, validator.AccountSet{}, len(validators.Validators)-1, signaturesCount, signaturesCount) - finalBlock := consensus.BuildBlock( - consensus.BuildBlockParams{ - Header: &types.Header{Number: parentBlockNumber + 1, ParentHash: parent.Hash, ExtraData: extraBlock}, - }) - - buildBlock := &types.FullBlock{Block: finalBlock, Receipts: []*types.Receipt{}} - mBlockBuilder := newBlockBuilderMock(buildBlock) - - validatorSet := validator.NewValidatorSet(validatorsMetadata[0:len(validatorsMetadata)-1], hclog.NewNullLogger()) - - fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, backend: &blockchainMock{}, - validators: validatorSet, - } - - proposal := buildBlock.Block.MarshalRLP() - validatorA := validators.GetValidator("A") - validatorB := validators.GetValidator("B") - proposalHash := buildBlock.Block.Hash().Bytes() - sigA, err := validatorA.MustSign(proposalHash, signer.DomainCheckpointManager).Marshal() - require.NoError(t, err) - - sigB, err := validatorB.MustSign(proposalHash, signer.DomainCheckpointManager).Marshal() - require.NoError(t, err) - - // create test account outside of validator set - nonValidatorAccount := validator.NewTestValidator(t, "non_validator", 1) - nonValidatorSignature, err := nonValidatorAccount.MustSign(proposalHash, signer.DomainCheckpointManager).Marshal() - require.NoError(t, err) - - commitedSeals := []*messages.CommittedSeal{ - {Signer: validatorA.Address().Bytes(), Signature: sigA}, - {Signer: validatorB.Address().Bytes(), Signature: sigB}, - {Signer: nonValidatorAccount.Address().Bytes(), Signature: nonValidatorSignature}, // this one should fail - } - - fsm.target = buildBlock - - _, err = fsm.Insert(proposal, commitedSeals) - assert.ErrorContains(t, err, "invalid node id") -} - -func TestFSM_Height(t *testing.T) { - t.Parallel() - - parentNumber := uint64(3) - parent := &types.Header{Number: parentNumber} - fsm := &fsm{parent: parent} - assert.Equal(t, parentNumber+1, fsm.Height()) -} - -func TestFSM_DecodeCommitmentStateTxs(t *testing.T) { - t.Parallel() - - const ( - commitmentsCount = 8 - from = 15 - eventsSize = 40 - ) - - _, signedCommitment, _ := buildCommitmentAndStateSyncs(t, eventsSize, uint64(3), from) - - f := &fsm{ - proposerCommitmentToRegister: signedCommitment, - commitEpochInput: createTestCommitEpochInput(t, 0, 10), - distributeRewardsInput: createTestDistributeRewardsInput(t, 0, nil, 10), - logger: hclog.NewNullLogger(), - parent: &types.Header{}, - } - - bridgeCommitmentTx, err := f.createBridgeCommitmentTx() - require.NoError(t, err) - - decodedData, err := decodeStateTransaction(bridgeCommitmentTx.Input) - require.NoError(t, err) - - decodedCommitmentMsg, ok := decodedData.(*CommitmentMessageSigned) - require.True(t, ok) - require.Equal(t, signedCommitment, decodedCommitmentMsg) -} - -func TestFSM_DecodeCommitEpochStateTx(t *testing.T) { - t.Parallel() - - commitEpoch := createTestCommitEpochInput(t, 0, 10) - input, err := commitEpoch.EncodeAbi() - require.NoError(t, err) - require.NotNil(t, input) - - tx := createStateTransactionWithData(1, contracts.ValidatorSetContract, input) - decodedInputData, err := decodeStateTransaction(tx.Input) - require.NoError(t, err) - - decodedCommitEpoch, ok := decodedInputData.(*contractsapi.CommitEpochValidatorSetFn) - require.True(t, ok) - require.True(t, commitEpoch.ID.Cmp(decodedCommitEpoch.ID) == 0) - require.NotNil(t, decodedCommitEpoch.Epoch) - require.True(t, commitEpoch.Epoch.StartBlock.Cmp(decodedCommitEpoch.Epoch.StartBlock) == 0) - require.True(t, commitEpoch.Epoch.EndBlock.Cmp(decodedCommitEpoch.Epoch.EndBlock) == 0) -} - -func TestFSM_VerifyStateTransaction_ValidBothTypesOfStateTransactions(t *testing.T) { - t.Parallel() - - var ( - commitments [2]*PendingCommitment - stateSyncs [2][]*contractsapi.StateSyncedEvent - signedCommitments [2]*CommitmentMessageSigned - ) - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E"}) - commitments[0], signedCommitments[0], stateSyncs[0] = buildCommitmentAndStateSyncs(t, 10, uint64(3), 2) - commitments[1], signedCommitments[1], stateSyncs[1] = buildCommitmentAndStateSyncs(t, 10, uint64(3), 12) - - executeForValidators := func(aliases ...string) error { - for _, sc := range signedCommitments { - // add register commitment state transaction - hash, err := sc.Hash() - require.NoError(t, err) - signature := createSignature(t, validators.GetPrivateIdentities(aliases...), hash, signer.DomainStateReceiver) - sc.AggSignature = *signature - } - - f := &fsm{ - parent: &types.Header{Number: 9}, - isEndOfSprint: true, - validators: validators.ToValidatorSet(), - } - - var txns []*types.Transaction - - for i, sc := range signedCommitments { - inputData, err := sc.EncodeAbi() - require.NoError(t, err) - - if i == 0 { - tx := createStateTransactionWithData(1, contracts.StateReceiverContract, inputData) - txns = append(txns, tx) - } - } - - return f.VerifyStateTransactions(txns) - } - - assert.NoError(t, executeForValidators("A", "B", "C", "D")) - assert.ErrorContains(t, executeForValidators("A", "B", "C"), "quorum size not reached for state tx") -} - -func TestFSM_VerifyStateTransaction_InvalidTypeOfStateTransactions(t *testing.T) { - t.Parallel() - - f := &fsm{ - isEndOfSprint: true, - } - - var txns []*types.Transaction - txns = append(txns, - createStateTransactionWithData(1, contracts.StateReceiverContract, []byte{9, 3, 1, 1})) - - require.ErrorContains(t, f.VerifyStateTransactions(txns), "unknown state transaction") -} - -func TestFSM_VerifyStateTransaction_QuorumNotReached(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - _, commitmentMessageSigned, _ := buildCommitmentAndStateSyncs(t, 10, uint64(3), 2) - f := &fsm{ - parent: &types.Header{Number: 9}, - isEndOfSprint: true, - validators: validators.ToValidatorSet(), - } - - hash, err := commitmentMessageSigned.Hash() - require.NoError(t, err) - - var txns []*types.Transaction - - signature := createSignature(t, validators.GetPrivateIdentities("A", "B"), hash, signer.DomainStateReceiver) - commitmentMessageSigned.AggSignature = *signature - - inputData, err := commitmentMessageSigned.EncodeAbi() - require.NoError(t, err) - - txns = append(txns, - createStateTransactionWithData(1, contracts.StateReceiverContract, inputData)) - - err = f.VerifyStateTransactions(txns) - require.ErrorContains(t, err, "quorum size not reached for state tx") -} - -func TestFSM_VerifyStateTransaction_InvalidSignature(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - _, commitmentMessageSigned, _ := buildCommitmentAndStateSyncs(t, 10, uint64(3), 2) - f := &fsm{ - parent: &types.Header{Number: 9}, - isEndOfSprint: true, - validators: validators.ToValidatorSet(), - } - - hash, err := commitmentMessageSigned.Hash() - require.NoError(t, err) - - var txns []*types.Transaction - - signature := createSignature(t, validators.GetPrivateIdentities("A", "B", "C", "D"), hash, signer.DomainStateReceiver) - invalidValidator := validator.NewTestValidator(t, "G", 1) - invalidSignature, err := invalidValidator.MustSign([]byte("malicious message"), signer.DomainStateReceiver).Marshal() - require.NoError(t, err) - - commitmentMessageSigned.AggSignature = Signature{ - Bitmap: signature.Bitmap, - AggregatedSignature: invalidSignature, - } - - inputData, err := commitmentMessageSigned.EncodeAbi() - require.NoError(t, err) - - txns = append(txns, - createStateTransactionWithData(1, contracts.StateReceiverContract, inputData)) - - require.ErrorContains(t, f.VerifyStateTransactions(txns), "invalid signature for state tx") -} - -func TestFSM_VerifyStateTransaction_TwoCommitmentMessages(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - _, commitmentMessageSigned, _ := buildCommitmentAndStateSyncs(t, 10, uint64(3), 2) - - validatorSet := validator.NewValidatorSet(validators.GetPublicIdentities(), hclog.NewNullLogger()) - - f := &fsm{ - parent: &types.Header{Number: 9}, - isEndOfSprint: true, - validators: validatorSet, - } - - hash, err := commitmentMessageSigned.Hash() - require.NoError(t, err) - - var txns []*types.Transaction - - signature := createSignature(t, validators.GetPrivateIdentities("A", "B", "C", "D"), hash, signer.DomainStateReceiver) - commitmentMessageSigned.AggSignature = *signature - - inputData, err := commitmentMessageSigned.EncodeAbi() - require.NoError(t, err) - - txns = append(txns, - createStateTransactionWithData(1, contracts.StateReceiverContract, inputData)) - inputData, err = commitmentMessageSigned.EncodeAbi() - require.NoError(t, err) - - txns = append(txns, - createStateTransactionWithData(1, contracts.StateReceiverContract, inputData)) - err = f.VerifyStateTransactions(txns) - require.ErrorContains(t, err, "only one commitment tx is allowed per block") -} - -func TestFSM_Validate_FailToVerifySignatures(t *testing.T) { - t.Parallel() - - const ( - accountsCount = 5 - parentBlockNumber = 10 - signaturesCount = 3 - ) - - validators := validator.NewTestValidators(t, accountsCount) - validatorsMetadata := validators.GetPublicIdentities() - - extra := createTestExtraObject(validatorsMetadata, validator.AccountSet{}, 4, signaturesCount, signaturesCount) - validatorsHash, err := validatorsMetadata.Hash() - require.NoError(t, err) - - extra.Checkpoint = &CheckpointData{CurrentValidatorsHash: validatorsHash, NextValidatorsHash: validatorsHash} - parent := &types.Header{ - Number: parentBlockNumber, - ExtraData: extra.MarshalRLPTo(nil), - } - parent.ComputeHash() - - polybftBackendMock := new(polybftBackendMock) - polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validatorsMetadata, nil).Once() - - validatorSet := validator.NewValidatorSet(validatorsMetadata, hclog.NewNullLogger()) - - fsm := &fsm{ - parent: parent, - backend: &blockchainMock{}, - polybftBackend: polybftBackendMock, - validators: validatorSet, - logger: hclog.NewNullLogger(), - config: &PolyBFTConfig{ - BlockTimeDrift: 1, - }, - } - - finalBlock := consensus.BuildBlock(consensus.BuildBlockParams{ - Header: &types.Header{ - Number: parentBlockNumber + 1, - ParentHash: parent.Hash, - Timestamp: parent.Timestamp + 1, - MixHash: PolyBFTMixDigest, - Difficulty: 1, - ExtraData: parent.ExtraData, - }, - }) - - checkpointHash, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), finalBlock.Number(), finalBlock.Hash()) - require.NoError(t, err) - - finalBlock.Header.Hash = checkpointHash - proposal := finalBlock.MarshalRLP() - - assert.ErrorContains(t, fsm.Validate(proposal), "failed to verify signatures") - - polybftBackendMock.AssertExpectations(t) -} - -func createDummyStateBlock(blockNumber uint64, parentHash types.Hash, extraData []byte) *types.FullBlock { - finalBlock := consensus.BuildBlock(consensus.BuildBlockParams{ - Header: &types.Header{ - Number: blockNumber, - ParentHash: parentHash, - Difficulty: 1, - ExtraData: extraData, - MixHash: PolyBFTMixDigest, - }, - }) - - return &types.FullBlock{Block: finalBlock} -} - -func createTestExtra( - allAccounts, - previousValidatorSet validator.AccountSet, - validatorsCount, - committedSignaturesCount, - parentSignaturesCount int, -) []byte { - extraData := createTestExtraObject(allAccounts, previousValidatorSet, validatorsCount, committedSignaturesCount, parentSignaturesCount) - - return extraData.MarshalRLPTo(nil) -} - -func createTestCommitment(t *testing.T, accounts []*wallet.Account) *CommitmentMessageSigned { - t.Helper() - - bitmap := bitmap.Bitmap{} - stateSyncEvents := make([]*contractsapi.StateSyncedEvent, len(accounts)) - - for i := 0; i < len(accounts); i++ { - stateSyncEvents[i] = &contractsapi.StateSyncedEvent{ - ID: big.NewInt(int64(i)), - Sender: types.Address(accounts[i].Ecdsa.Address()), - Receiver: types.Address(accounts[0].Ecdsa.Address()), - Data: []byte{}, - } - - bitmap.Set(uint64(i)) - } - - commitment, err := NewPendingCommitment(1, stateSyncEvents) - require.NoError(t, err) - - hash, err := commitment.Hash() - require.NoError(t, err) - - var signatures bls.Signatures - - for _, a := range accounts { - signature, err := a.Bls.Sign(hash.Bytes(), signer.DomainStateReceiver) - assert.NoError(t, err) - - signatures = append(signatures, signature) - } - - aggregatedSignature, err := signatures.Aggregate().Marshal() - assert.NoError(t, err) - - signature := Signature{ - AggregatedSignature: aggregatedSignature, - Bitmap: bitmap, - } - - assert.NoError(t, err) - - return &CommitmentMessageSigned{ - Message: commitment.StateSyncCommitment, - AggSignature: signature, - } -} - -func newBlockBuilderMock(stateBlock *types.FullBlock) *blockBuilderMock { - mBlockBuilder := new(blockBuilderMock) - mBlockBuilder.On("Build", mock.Anything).Return(stateBlock).Once() - mBlockBuilder.On("Fill", mock.Anything).Once() - mBlockBuilder.On("Reset", mock.Anything).Return(error(nil)).Once() - - return mBlockBuilder -} - -func createTestExtraObject(allAccounts, - previousValidatorSet validator.AccountSet, - validatorsCount, - committedSignaturesCount, - parentSignaturesCount int) *Extra { - accountCount := len(allAccounts) - dummySignature := [64]byte{} - bitmapCommitted, bitmapParent := bitmap.Bitmap{}, bitmap.Bitmap{} - extraData := &Extra{} - extraData.Validators = generateValidatorDelta(validatorsCount, allAccounts, previousValidatorSet) - - for j := range rand.Perm(accountCount)[:committedSignaturesCount] { - bitmapCommitted.Set(uint64(j)) - } - - for j := range rand.Perm(accountCount)[:parentSignaturesCount] { - bitmapParent.Set(uint64(j)) - } - - extraData.Parent = &Signature{Bitmap: bitmapCommitted, AggregatedSignature: dummySignature[:]} - extraData.Committed = &Signature{Bitmap: bitmapParent, AggregatedSignature: dummySignature[:]} - extraData.Checkpoint = &CheckpointData{} - - return extraData -} - -func generateValidatorDelta(validatorCount int, allAccounts, previousValidatorSet validator.AccountSet) (vd *validator.ValidatorSetDelta) { - oldMap := make(map[types.Address]int, previousValidatorSet.Len()) - for i, x := range previousValidatorSet { - oldMap[x.Address] = i - } - - vd = &validator.ValidatorSetDelta{} - vd.Removed = bitmap.Bitmap{} - - for _, id := range rand.Perm(len(allAccounts))[:validatorCount] { - _, exists := oldMap[allAccounts[id].Address] - if !exists { - vd.Added = append(vd.Added, allAccounts[id]) - } - - delete(oldMap, allAccounts[id].Address) - } - - for _, v := range oldMap { - vd.Removed.Set(uint64(v)) - } - - return -} diff --git a/consensus/polybft/handlers.go b/consensus/polybft/handlers.go deleted file mode 100644 index 2c45015..0000000 --- a/consensus/polybft/handlers.go +++ /dev/null @@ -1,38 +0,0 @@ -package polybft - -import ( - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" -) - -type PostBlockRequest struct { - // FullBlock is a reference of the executed block - FullBlock *types.FullBlock - // Epoch is the epoch number of the executed block - Epoch uint64 - // IsEpochEndingBlock indicates if this was the last block of given epoch - IsEpochEndingBlock bool - // DBTx is the opened transaction on state store (in our case boltDB) - // used to save necessary data on PostBlock - DBTx *bolt.Tx -} - -type PostEpochRequest struct { - // NewEpochID is the id of the new epoch - NewEpochID uint64 - - // FirstBlockOfEpoch is the number of the epoch beginning block - FirstBlockOfEpoch uint64 - - // SystemState is the state of the governance smart contracts - // after this block - SystemState SystemState - - // ValidatorSet is the validator set for the new epoch - ValidatorSet validator.ValidatorSet - - // DBTx is the opened transaction on state store (in our case boltDB) - // used to save necessary data on PostEpoch - DBTx *bolt.Tx -} diff --git a/consensus/polybft/hash.go b/consensus/polybft/hash.go deleted file mode 100644 index 9e48631..0000000 --- a/consensus/polybft/hash.go +++ /dev/null @@ -1,32 +0,0 @@ -package polybft - -import ( - "sync" - - "github.com/xgr-network/xgr-node/types" -) - -var setupHeaderHashFuncOnce sync.Once - -// polyBFTHeaderHash defines the custom implementation for getting the header hash, -// because of the extraData field -func setupHeaderHashFunc() { - setupHeaderHashFuncOnce.Do(func() { - originalHeaderHash := types.HeaderHash - - types.HeaderHash = func(h *types.Header) types.Hash { - // when hashing the block for signing we have to remove from - // the extra field the seal and committed seal items - extra, err := GetIbftExtraClean(h.ExtraData) - if err != nil { - return types.ZeroHash - } - - // override extra data without seals and committed seal items - hh := h.Copy() - hh.ExtraData = extra - - return originalHeaderHash(hh) - } - }) -} diff --git a/consensus/polybft/hash_test.go b/consensus/polybft/hash_test.go deleted file mode 100644 index bdd39cc..0000000 --- a/consensus/polybft/hash_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package polybft - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/types" -) - -func Test_setupHeaderHashFunc(t *testing.T) { - extra := &Extra{ - Validators: &validator.ValidatorSetDelta{Removed: bitmap.Bitmap{1}}, - Parent: createSignature(t, []*wallet.Account{generateTestAccount(t)}, types.ZeroHash, signer.DomainCheckpointManager), - Checkpoint: &CheckpointData{}, - Committed: &Signature{}, - } - - header := &types.Header{ - Number: 2, - GasLimit: 10000003, - Timestamp: 18, - } - - header.ExtraData = extra.MarshalRLPTo(nil) - notFullExtraHash := types.HeaderHash(header) - - extra.Committed = createSignature(t, []*wallet.Account{generateTestAccount(t)}, types.ZeroHash, signer.DomainCheckpointManager) - header.ExtraData = extra.MarshalRLPTo(nil) - fullExtraHash := types.HeaderHash(header) - - assert.Equal(t, notFullExtraHash, fullExtraHash) - - header.ExtraData = []byte{1, 2, 3, 4, 100, 200, 255} - assert.Equal(t, types.ZeroHash, types.HeaderHash(header)) // to small extra data -} diff --git a/consensus/polybft/helpers_test.go b/consensus/polybft/helpers_test.go deleted file mode 100644 index 63c39bb..0000000 --- a/consensus/polybft/helpers_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package polybft - -import ( - "crypto/rand" - "fmt" - "math/big" - "os" - "path" - "testing" - "time" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/types" -) - -func createTestKey(t *testing.T) *wallet.Key { - t.Helper() - - return wallet.NewKey(generateTestAccount(t)) -} - -func createRandomTestKeys(t *testing.T, numberOfKeys int) []*wallet.Key { - t.Helper() - - result := make([]*wallet.Key, numberOfKeys, numberOfKeys) - - for i := 0; i < numberOfKeys; i++ { - result[i] = wallet.NewKey(generateTestAccount(t)) - } - - return result -} - -func createSignature(t *testing.T, accounts []*wallet.Account, hash types.Hash, domain []byte) *Signature { - t.Helper() - - var signatures bls.Signatures - - var bmp bitmap.Bitmap - for i, x := range accounts { - bmp.Set(uint64(i)) - - src, err := x.Bls.Sign(hash[:], domain) - require.NoError(t, err) - - signatures = append(signatures, src) - } - - aggs, err := signatures.Aggregate().Marshal() - require.NoError(t, err) - - return &Signature{AggregatedSignature: aggs, Bitmap: bmp} -} - -func createTestCommitEpochInput(t *testing.T, epochID uint64, - epochSize uint64) *contractsapi.CommitEpochValidatorSetFn { - t.Helper() - - var startBlock uint64 = 0 - if epochID > 1 { - startBlock = (epochID - 1) * epochSize - } - - commitEpoch := &contractsapi.CommitEpochValidatorSetFn{ - ID: new(big.Int).SetUint64(epochID), - Epoch: &contractsapi.Epoch{ - StartBlock: new(big.Int).SetUint64(startBlock + 1), - EndBlock: new(big.Int).SetUint64(epochSize * epochID), - EpochRoot: types.Hash{}, - }, - } - - return commitEpoch -} - -func createTestDistributeRewardsInput(t *testing.T, epochID uint64, - validatorSet validator.AccountSet, epochSize uint64) *contractsapi.DistributeRewardForRewardPoolFn { - t.Helper() - - if validatorSet == nil { - validatorSet = validator.NewTestValidators(t, 5).GetPublicIdentities() - } - - uptime := make([]*contractsapi.Uptime, len(validatorSet)) - - for i, v := range validatorSet { - uptime[i] = &contractsapi.Uptime{ - Validator: v.Address, - SignedBlocks: new(big.Int).SetUint64(epochSize), - } - } - - return &contractsapi.DistributeRewardForRewardPoolFn{ - EpochID: new(big.Int).SetUint64(epochID), - Uptime: uptime, - } -} - -func generateStateSyncEvents(t *testing.T, eventsCount int, startIdx uint64) []*contractsapi.StateSyncedEvent { - t.Helper() - - stateSyncEvents := make([]*contractsapi.StateSyncedEvent, eventsCount) - for i := 0; i < eventsCount; i++ { - stateSyncEvents[i] = &contractsapi.StateSyncedEvent{ - ID: big.NewInt(int64(startIdx + uint64(i))), - Sender: types.StringToAddress(fmt.Sprintf("0x5%d", i)), - Data: generateRandomBytes(t), - } - } - - return stateSyncEvents -} - -// generateRandomBytes generates byte array with random data of 32 bytes length -func generateRandomBytes(t *testing.T) (result []byte) { - t.Helper() - - result = make([]byte, types.HashLength) - _, err := rand.Reader.Read(result) - require.NoError(t, err, "Cannot generate random byte array content.") - - return -} - -// getEpochNumber returns epoch number for given blockNumber and epochSize. -// Epoch number is derived as a result of division of block number and epoch size. -// Since epoch number is 1-based (0 block represents special case zero epoch), -// we are incrementing result by one for non epoch-ending blocks. -func getEpochNumber(t *testing.T, blockNumber, epochSize uint64) uint64 { - t.Helper() - - if isEndOfPeriod(blockNumber, epochSize) { - return blockNumber / epochSize - } - - return blockNumber/epochSize + 1 -} - -// newTestState creates new instance of state used by tests. -func newTestState(tb testing.TB) *State { - tb.Helper() - - dir := fmt.Sprintf("/tmp/consensus-temp_%v", time.Now().UTC().Format(time.RFC3339Nano)) - err := os.Mkdir(dir, 0775) - - if err != nil { - tb.Fatal(err) - } - - state, err := newState(path.Join(dir, "my.db"), hclog.NewNullLogger(), make(chan struct{})) - if err != nil { - tb.Fatal(err) - } - - tb.Cleanup(func() { - if err := os.RemoveAll(dir); err != nil { - tb.Fatal(err) - } - }) - - return state -} - -func generateTestAccount(tb testing.TB) *wallet.Account { - tb.Helper() - - acc, err := wallet.GenerateAccount() - require.NoError(tb, err) - - return acc -} - -// createTestBridgeConfig creates test bridge configuration with hard-coded addresses -func createTestBridgeConfig() *BridgeConfig { - return &BridgeConfig{ - StateSenderAddr: types.StringToAddress("1"), - CheckpointManagerAddr: types.StringToAddress("2"), - ExitHelperAddr: types.StringToAddress("3"), - RootERC20PredicateAddr: types.StringToAddress("4"), - ChildMintableERC20PredicateAddr: types.StringToAddress("5"), - RootNativeERC20Addr: types.StringToAddress("6"), - RootERC721PredicateAddr: types.StringToAddress("8"), - ChildMintableERC721PredicateAddr: types.StringToAddress("9"), - RootERC1155PredicateAddr: types.StringToAddress("11"), - ChildMintableERC1155PredicateAddr: types.StringToAddress("12"), - CustomSupernetManagerAddr: types.StringToAddress("13"), - StakeManagerAddr: types.StringToAddress("14"), - JSONRPCEndpoint: "http://localhost:8545", - } -} diff --git a/consensus/polybft/ibft_consensus.go b/consensus/polybft/ibft_consensus.go deleted file mode 100644 index 5f7ebf7..0000000 --- a/consensus/polybft/ibft_consensus.go +++ /dev/null @@ -1,43 +0,0 @@ -package polybft - -import ( - "context" - - "github.com/0xPolygon/go-ibft/core" -) - -// IBFTConsensusWrapper is a convenience wrapper for the go-ibft package -type IBFTConsensusWrapper struct { - *core.IBFT -} - -func newIBFTConsensusWrapper( - logger core.Logger, - backend core.Backend, - transport core.Transport, -) *IBFTConsensusWrapper { - return &IBFTConsensusWrapper{ - IBFT: core.NewIBFT(logger, backend, transport), - } -} - -// runSequence starts the underlying consensus mechanism for the given height. -// It may be called by a single thread at any given time -// It returns channel which will be closed after c.IBFT.RunSequence is done -// and stopSequence function which can be used to halt c.IBFT.RunSequence routine from outside -func (c *IBFTConsensusWrapper) runSequence(height uint64) (<-chan struct{}, func()) { - sequenceDone := make(chan struct{}) - ctx, cancelSequence := context.WithCancel(context.Background()) - - go func() { - c.IBFT.RunSequence(ctx, height) - cancelSequence() - close(sequenceDone) - }() - - return sequenceDone, func() { - // stopSequence terminates the running IBFT sequence gracefully and waits for it to return - cancelSequence() - <-sequenceDone // waits until c.IBFT.RunSequenc routine finishes - } -} diff --git a/consensus/polybft/mocks_test.go b/consensus/polybft/mocks_test.go deleted file mode 100644 index c78e391..0000000 --- a/consensus/polybft/mocks_test.go +++ /dev/null @@ -1,401 +0,0 @@ -package polybft - -import ( - "time" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/mock" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/contract" - "github.com/xgr-network/xgr-node/blockchain" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/helper/progress" - "github.com/xgr-network/xgr-node/state" - "github.com/xgr-network/xgr-node/syncer" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" -) - -var _ blockchainBackend = (*blockchainMock)(nil) - -type blockchainMock struct { - mock.Mock -} - -func (m *blockchainMock) CurrentHeader() *types.Header { - args := m.Called() - - return args.Get(0).(*types.Header) //nolint:forcetypeassert -} - -func (m *blockchainMock) CommitBlock(block *types.FullBlock) error { - args := m.Called(block) - - return args.Error(0) -} - -func (m *blockchainMock) NewBlockBuilder(parent *types.Header, coinbase types.Address, - txPool txPoolInterface, blockTime time.Duration, logger hclog.Logger) (blockBuilder, error) { - args := m.Called() - - return args.Get(0).(blockBuilder), args.Error(1) //nolint:forcetypeassert -} - -func (m *blockchainMock) ProcessBlock(parent *types.Header, block *types.Block) (*types.FullBlock, error) { - args := m.Called(parent, block) - - return args.Get(0).(*types.FullBlock), args.Error(1) //nolint:forcetypeassert -} - -func (m *blockchainMock) GetStateProviderForBlock(block *types.Header) (contract.Provider, error) { - args := m.Called(block) - stateProvider, _ := args.Get(0).(contract.Provider) - - return stateProvider, nil -} - -func (m *blockchainMock) GetStateProvider(transition *state.Transition) contract.Provider { - args := m.Called() - stateProvider, _ := args.Get(0).(contract.Provider) - - return stateProvider -} - -func (m *blockchainMock) GetHeaderByNumber(number uint64) (*types.Header, bool) { - args := m.Called(number) - - if len(args) == 1 { - header, ok := args.Get(0).(*types.Header) - - if ok { - return header, true - } - - getHeaderCallback, ok := args.Get(0).(func(number uint64) *types.Header) - if ok { - h := getHeaderCallback(number) - - return h, h != nil - } - } else if len(args) == 2 { - return args.Get(0).(*types.Header), args.Get(1).(bool) //nolint:forcetypeassert - } - - panic("Unsupported mock for GetHeaderByNumber") //nolint:gocritic -} - -func (m *blockchainMock) GetHeaderByHash(hash types.Hash) (*types.Header, bool) { - args := m.Called(hash) - header, ok := args.Get(0).(*types.Header) - - if ok { - return header, true - } - - getHeaderCallback, ok := args.Get(0).(func(hash types.Hash) *types.Header) - if ok { - h := getHeaderCallback(hash) - - return h, h != nil - } - - panic("Unsupported mock for GetHeaderByHash") //nolint:gocritic -} - -func (m *blockchainMock) GetSystemState(provider contract.Provider) SystemState { - args := m.Called(provider) - - return args.Get(0).(SystemState) //nolint:forcetypeassert -} - -func (m *blockchainMock) SubscribeEvents() blockchain.Subscription { - return nil -} - -func (m *blockchainMock) UnubscribeEvents(blockchain.Subscription) { -} - -func (m *blockchainMock) CalculateGasLimit(number uint64) (uint64, error) { - return 0, nil -} - -func (m *blockchainMock) GetChainID() uint64 { - return 0 -} - -func (m *blockchainMock) GetReceiptsByHash(hash types.Hash) ([]*types.Receipt, error) { - args := m.Called(hash) - - return args.Get(0).([]*types.Receipt), args.Error(1) //nolint:forcetypeassert -} - -var _ polybftBackend = (*polybftBackendMock)(nil) - -type polybftBackendMock struct { - mock.Mock -} - -// GetValidators retrieves validator set for the given block -func (p *polybftBackendMock) GetValidators(blockNumber uint64, parents []*types.Header) (validator.AccountSet, error) { - args := p.Called(blockNumber, parents) - if len(args) == 1 { - accountSet, _ := args.Get(0).(validator.AccountSet) - - return accountSet, nil - } else if len(args) == 2 { - accountSet, _ := args.Get(0).(validator.AccountSet) - - return accountSet, args.Error(1) - } - - panic("polybftBackendMock.GetValidators doesn't support such combination of arguments") //nolint:gocritic -} - -func (p *polybftBackendMock) GetValidatorsWithTx(blockNumber uint64, parents []*types.Header, - dbTx *bolt.Tx) (validator.AccountSet, error) { - args := p.Called(blockNumber, parents, dbTx) - if len(args) == 1 { - accountSet, _ := args.Get(0).(validator.AccountSet) - - return accountSet, nil - } else if len(args) == 2 { - accountSet, _ := args.Get(0).(validator.AccountSet) - - return accountSet, args.Error(1) - } - - panic("polybftBackendMock.GetValidatorsWithTx doesn't support such combination of arguments") //nolint:gocritic -} - -var _ blockBuilder = (*blockBuilderMock)(nil) - -type blockBuilderMock struct { - mock.Mock -} - -func (m *blockBuilderMock) Reset() error { - args := m.Called() - if len(args) == 0 { - return nil - } - - return args.Error(0) -} - -func (m *blockBuilderMock) WriteTx(tx *types.Transaction) error { - args := m.Called(tx) - if len(args) == 0 { - return nil - } - - return args.Error(0) -} - -func (m *blockBuilderMock) Fill() { - m.Called() -} - -// Receipts returns the collection of transaction receipts for given block -func (m *blockBuilderMock) Receipts() []*types.Receipt { - args := m.Called() - - return args.Get(0).([]*types.Receipt) //nolint:forcetypeassert -} - -func (m *blockBuilderMock) Build(handler func(*types.Header)) (*types.FullBlock, error) { - args := m.Called(handler) - builtBlock := args.Get(0).(*types.FullBlock) //nolint:forcetypeassert - - handler(builtBlock.Block.Header) - - return builtBlock, nil -} - -func (m *blockBuilderMock) GetState() *state.Transition { - args := m.Called() - - return args.Get(0).(*state.Transition) //nolint:forcetypeassert -} - -var _ SystemState = (*systemStateMock)(nil) - -type systemStateMock struct { - mock.Mock -} - -func (m *systemStateMock) GetNextCommittedIndex() (uint64, error) { - args := m.Called() - - if len(args) == 1 { - index, _ := args.Get(0).(uint64) - - return index, nil - } else if len(args) == 2 { - index, _ := args.Get(0).(uint64) - - return index, args.Error(1) - } - - return 0, nil -} - -func (m *systemStateMock) GetEpoch() (uint64, error) { - args := m.Called() - if len(args) == 1 { - epochNumber, _ := args.Get(0).(uint64) - - return epochNumber, nil - } else if len(args) == 2 { - epochNumber, _ := args.Get(0).(uint64) - err, ok := args.Get(1).(error) - if ok { - return epochNumber, err - } - - return epochNumber, nil - } - - return 0, nil -} - -var _ contract.Provider = (*stateProviderMock)(nil) - -type stateProviderMock struct { - mock.Mock -} - -func (s *stateProviderMock) Call(ethgo.Address, []byte, *contract.CallOpts) ([]byte, error) { - return nil, nil -} - -func (s *stateProviderMock) Txn(ethgo.Address, ethgo.Key, []byte) (contract.Txn, error) { - return nil, nil -} - -var _ BridgeTransport = (*transportMock)(nil) - -type transportMock struct { - mock.Mock -} - -func (t *transportMock) Multicast(msg interface{}) { - _ = t.Called(msg) -} - -type testHeadersMap struct { - headersByNumber map[uint64]*types.Header -} - -func (t *testHeadersMap) addHeader(header *types.Header) { - if t.headersByNumber == nil { - t.headersByNumber = map[uint64]*types.Header{} - } - - t.headersByNumber[header.Number] = header -} - -func (t *testHeadersMap) getHeader(number uint64) *types.Header { - return t.headersByNumber[number] -} - -func (t *testHeadersMap) getHeaderByHash(hash types.Hash) *types.Header { - for _, header := range t.headersByNumber { - if header.Hash == hash { - return header - } - } - - return nil -} - -func (t *testHeadersMap) getHeaders() []*types.Header { - headers := make([]*types.Header, 0, len(t.headersByNumber)) - for _, header := range t.headersByNumber { - headers = append(headers, header) - } - - return headers -} - -var _ txPoolInterface = (*txPoolMock)(nil) - -type txPoolMock struct { - mock.Mock -} - -func (tp *txPoolMock) Prepare() { - tp.Called() -} - -func (tp *txPoolMock) Length() uint64 { - args := tp.Called() - - return args[0].(uint64) //nolint -} - -func (tp *txPoolMock) Peek() *types.Transaction { - args := tp.Called() - - return args[0].(*types.Transaction) //nolint -} - -func (tp *txPoolMock) Pop(tx *types.Transaction) { - tp.Called(tx) -} - -func (tp *txPoolMock) Drop(tx *types.Transaction) { - tp.Called(tx) -} - -func (tp *txPoolMock) Demote(tx *types.Transaction) { - tp.Called(tx) -} - -func (tp *txPoolMock) SetSealing(v bool) { - tp.Called(v) -} - -func (tp *txPoolMock) ResetWithHeaders(values ...*types.Header) { - tp.Called(values) -} - -var _ syncer.Syncer = (*syncerMock)(nil) - -type syncerMock struct { - mock.Mock -} - -func (tp *syncerMock) Start() error { - args := tp.Called() - - return args.Error(0) -} - -func (tp *syncerMock) Close() error { - args := tp.Called() - - return args.Error(0) -} - -func (tp *syncerMock) GetSyncProgression() *progress.Progression { - args := tp.Called() - - return args[0].(*progress.Progression) //nolint -} - -func (tp *syncerMock) HasSyncPeer() bool { - args := tp.Called() - - return args[0].(bool) //nolint -} - -func (tp *syncerMock) Sync(func(*types.FullBlock) bool) error { - args := tp.Called() - - return args.Error(0) -} - -func init() { - // setup custom hash header func - setupHeaderHashFunc() -} diff --git a/consensus/polybft/polybft.go b/consensus/polybft/polybft.go deleted file mode 100644 index d18ba7e..0000000 --- a/consensus/polybft/polybft.go +++ /dev/null @@ -1,848 +0,0 @@ -// Package polybft implements PBFT consensus algorithm integration and bridge feature -package polybft - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "math/big" - "path/filepath" - "time" - - "github.com/hashicorp/go-hclog" - bolt "go.etcd.io/bbolt" - - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/consensus" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/forkmanager" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/helper/progress" - "github.com/xgr-network/xgr-node/network" - "github.com/xgr-network/xgr-node/state" - "github.com/xgr-network/xgr-node/syncer" - "github.com/xgr-network/xgr-node/types" -) - -const ( - minSyncPeers = 2 - pbftProto = "/pbft/0.2" - bridgeProto = "/bridge/0.2" -) - -var ( - errMissingBridgeConfig = errors.New("invalid genesis configuration, missing bridge configuration") -) - -// polybftBackend is an interface defining polybft methods needed by fsm and sync tracker -type polybftBackend interface { - // GetValidators retrieves validator set for the given block - GetValidators(blockNumber uint64, parents []*types.Header) (validator.AccountSet, error) - - // GetValidators retrieves validator set for the given block - // Function expects that db tx is already open - GetValidatorsWithTx(blockNumber uint64, parents []*types.Header, - dbTx *bolt.Tx) (validator.AccountSet, error) -} - -// Factory is the factory function to create a discovery consensus -func Factory(params *consensus.Params) (consensus.Consensus, error) { - logger := params.Logger.Named("polybft") - - setupHeaderHashFunc() - - polybft := &Polybft{ - config: params, - closeCh: make(chan struct{}), - logger: logger, - txPool: params.TxPool, - } - - // initialize polybft consensus config - customConfigJSON, err := json.Marshal(params.Config.Config) - if err != nil { - return nil, err - } - - err = json.Unmarshal(customConfigJSON, &polybft.consensusConfig) - if err != nil { - return nil, err - } - - return polybft, nil -} - -type Polybft struct { - // closeCh is used to signal that consensus protocol is stopped - closeCh chan struct{} - - // ibft is the ibft engine - ibft *IBFTConsensusWrapper - - // state is reference to the struct which encapsulates consensus data persistence logic - state *State - - // consensus parameters - config *consensus.Params - - // consensusConfig is genesis configuration for polybft consensus protocol - consensusConfig *PolyBFTConfig - - // blockchain is a reference to the blockchain object - blockchain blockchainBackend - - // runtime handles consensus runtime features like epoch, state and event management - runtime *consensusRuntime - - // block time duration - blockTime time.Duration - - // dataDir is the data directory to store the info - dataDir string - - // reference to the syncer - syncer syncer.Syncer - - // topic for consensus engine messages - consensusTopic *network.Topic - - // topic for bridge messages - bridgeTopic *network.Topic - - // key encapsulates ECDSA address and BLS signing logic - key *wallet.Key - - // validatorsCache represents cache of validators snapshots - validatorsCache *validatorsSnapshotCache - - // logger - logger hclog.Logger - - // tx pool as interface - txPool txPoolInterface -} - -func GenesisPostHookFactory(config *chain.Chain, engineName string) func(txn *state.Transition) error { - return func(transition *state.Transition) error { - polyBFTConfig, err := GetPolyBFTConfig(config) - if err != nil { - return err - } - - // calculate initial total supply of native erc20 token - // we skip zero address, since its a special case address - // that is used for minting and burning native token - initialTotalSupply := big.NewInt(0) - - for addr, alloc := range config.Genesis.Alloc { - if addr == types.ZeroAddress { - continue - } - - initialTotalSupply.Add(initialTotalSupply, alloc.Balance) - } - - bridgeCfg := polyBFTConfig.Bridge - if bridgeCfg == nil { - return errMissingBridgeConfig - } - - proxyAddrMapping := contracts.GetProxyImplementationMapping() - - burnContractAddress, isBurnContractSet := getBurnContractAddress(config, polyBFTConfig) - if isBurnContractSet { - proxyAddrMapping[contracts.DefaultBurnContract] = burnContractAddress - } - - if _, ok := config.Genesis.Alloc[contracts.RewardTokenContract]; ok { - proxyAddrMapping[contracts.RewardTokenContract] = contracts.RewardTokenContractV1 - } - - if err = initProxies(transition, polyBFTConfig.ProxyContractsAdmin, proxyAddrMapping); err != nil { - return err - } - - // initialize ValidatorSet SC - if err = initValidatorSet(polyBFTConfig, transition); err != nil { - return err - } - - // approve reward pool - if err = approveRewardPoolAsSpender(polyBFTConfig, transition); err != nil { - return err - } - - // mint reward tokens to reward wallet - if err = mintRewardTokensToWallet(polyBFTConfig, transition); err != nil { - return err - } - - // initialize RewardPool SC - if err = initRewardPool(polyBFTConfig, transition); err != nil { - return err - } - - // check if there are Bridge Allow List Admins and Bridge Block List Admins - // and if there are, get the first address as the Admin - bridgeAllowListAdmin := types.ZeroAddress - if config.Params.BridgeAllowList != nil && len(config.Params.BridgeAllowList.AdminAddresses) > 0 { - bridgeAllowListAdmin = config.Params.BridgeAllowList.AdminAddresses[0] - } - - bridgeBlockListAdmin := types.ZeroAddress - if config.Params.BridgeBlockList != nil && len(config.Params.BridgeBlockList.AdminAddresses) > 0 { - bridgeBlockListAdmin = config.Params.BridgeBlockList.AdminAddresses[0] - } - - // initialize Predicate SCs - if bridgeAllowListAdmin != types.ZeroAddress || bridgeBlockListAdmin != types.ZeroAddress { - // The owner of the contract will be the allow list admin or the block list admin, if any of them is set. - owner := contracts.SystemCaller - useBridgeAllowList := bridgeAllowListAdmin != types.ZeroAddress - useBridgeBlockList := bridgeBlockListAdmin != types.ZeroAddress - - if bridgeAllowListAdmin != types.ZeroAddress { - owner = bridgeAllowListAdmin - } else if bridgeBlockListAdmin != types.ZeroAddress { - owner = bridgeBlockListAdmin - } - - // initialize ChildERC20PredicateAccessList SC - input, err := getInitERC20PredicateACLInput(polyBFTConfig.Bridge, owner, - useBridgeAllowList, useBridgeBlockList, false) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.ChildERC20PredicateContract, input, - "ChildERC20PredicateAccessList", transition); err != nil { - return err - } - - // initialize ChildERC721PredicateAccessList SC - input, err = getInitERC721PredicateACLInput(polyBFTConfig.Bridge, owner, - useBridgeAllowList, useBridgeBlockList, false) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.ChildERC721PredicateContract, input, - "ChildERC721PredicateAccessList", transition); err != nil { - return err - } - - // initialize ChildERC1155PredicateAccessList SC - input, err = getInitERC1155PredicateACLInput(polyBFTConfig.Bridge, owner, - useBridgeAllowList, useBridgeBlockList, false) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.ChildERC1155PredicateContract, input, - "ChildERC1155PredicateAccessList", transition); err != nil { - return err - } - - // initialize RootMintableERC20PredicateAccessList SC - input, err = getInitERC20PredicateACLInput(polyBFTConfig.Bridge, owner, - useBridgeAllowList, useBridgeBlockList, true) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.RootMintableERC20PredicateContract, input, - "RootMintableERC20PredicateAccessList", transition); err != nil { - return err - } - - // initialize RootMintableERC721PredicateAccessList SC - input, err = getInitERC721PredicateACLInput(polyBFTConfig.Bridge, owner, - useBridgeAllowList, useBridgeBlockList, true) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.RootMintableERC721PredicateContract, input, - "RootMintableERC721PredicateAccessList", transition); err != nil { - return err - } - - // initialize RootMintableERC1155PredicateAccessList SC - input, err = getInitERC1155PredicateACLInput(polyBFTConfig.Bridge, owner, - useBridgeAllowList, useBridgeBlockList, true) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.RootMintableERC1155PredicateContract, input, - "RootMintableERC1155PredicateAccessList", transition); err != nil { - return err - } - } else { - // initialize ChildERC20Predicate SC - input, err := getInitERC20PredicateInput(bridgeCfg, false) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.ChildERC20PredicateContract, input, - "ChildERC20Predicate", transition); err != nil { - return err - } - - // initialize ChildERC721Predicate SC - input, err = getInitERC721PredicateInput(bridgeCfg, false) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.ChildERC721PredicateContract, input, - "ChildERC721Predicate", transition); err != nil { - return err - } - - // initialize ChildERC1155Predicate SC - input, err = getInitERC1155PredicateInput(bridgeCfg, false) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.ChildERC1155PredicateContract, input, - "ChildERC1155Predicate", transition); err != nil { - return err - } - - // initialize RootMintableERC20Predicate SC - input, err = getInitERC20PredicateInput(bridgeCfg, true) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.RootMintableERC20PredicateContract, input, - "RootMintableERC20Predicate", transition); err != nil { - return err - } - - // initialize RootMintableERC721Predicate SC - input, err = getInitERC721PredicateInput(bridgeCfg, true) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.RootMintableERC721PredicateContract, input, - "RootMintableERC721Predicate", transition); err != nil { - return err - } - - // initialize RootMintableERC1155Predicate SC - input, err = getInitERC1155PredicateInput(bridgeCfg, true) - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, contracts.RootMintableERC1155PredicateContract, input, - "RootMintableERC1155Predicate", transition); err != nil { - return err - } - } - - if polyBFTConfig.NativeTokenConfig.IsMintable { - // initialize NativeERC20Mintable SC - params := &contractsapi.InitializeNativeERC20MintableFn{ - Predicate_: contracts.ChildERC20PredicateContract, - Owner_: polyBFTConfig.NativeTokenConfig.Owner, - RootToken_: types.ZeroAddress, // in case native mintable token is used, it is always root token - Name_: polyBFTConfig.NativeTokenConfig.Name, - Symbol_: polyBFTConfig.NativeTokenConfig.Symbol, - Decimals_: polyBFTConfig.NativeTokenConfig.Decimals, - TokenSupply_: initialTotalSupply, - } - - input, err := params.EncodeAbi() - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, - contracts.NativeERC20TokenContract, input, "NativeERC20Mintable", transition); err != nil { - return err - } - } else { - // initialize NativeERC20 SC - params := &contractsapi.InitializeNativeERC20Fn{ - Name_: polyBFTConfig.NativeTokenConfig.Name, - Symbol_: polyBFTConfig.NativeTokenConfig.Symbol, - Decimals_: polyBFTConfig.NativeTokenConfig.Decimals, - RootToken_: polyBFTConfig.Bridge.RootNativeERC20Addr, - Predicate_: contracts.ChildERC20PredicateContract, - TokenSupply_: initialTotalSupply, - } - - input, err := params.EncodeAbi() - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, - contracts.NativeERC20TokenContract, input, "NativeERC20", transition); err != nil { - return err - } - - // initialize EIP1559Burn SC - if isBurnContractSet { - burnParams := &contractsapi.InitializeEIP1559BurnFn{ - NewChildERC20Predicate: contracts.ChildERC20PredicateContract, - NewBurnDestination: config.Params.BurnContractDestinationAddress, - } - - input, err = burnParams.EncodeAbi() - if err != nil { - return err - } - - if err = callContract(contracts.SystemCaller, - burnContractAddress, - input, "EIP1559Burn", transition); err != nil { - return err - } - } - } - - return nil - } -} - -func ForkManagerFactory(forks *chain.Forks) error { - // place fork manager handler registration here - return nil -} - -// Initialize initializes the consensus (e.g. setup data) -func (p *Polybft) Initialize() error { - p.logger.Info("initializing polybft...") - - // read account - account, err := wallet.NewAccountFromSecret(p.config.SecretsManager) - if err != nil { - return fmt.Errorf("failed to read account data. Error: %w", err) - } - - // set key - p.key = wallet.NewKey(account) - - // create and set syncer - p.syncer = syncer.NewSyncer( - p.config.Logger.Named("syncer"), - p.config.Network, - p.config.Blockchain, - time.Duration(p.config.BlockTime)*3*time.Second, - ) - - // set blockchain backend - p.blockchain = &blockchainWrapper{ - blockchain: p.config.Blockchain, - executor: p.config.Executor, - } - - // create bridge and consensus topics - if err = p.createTopics(); err != nil { - return fmt.Errorf("cannot create topics: %w", err) - } - - // set block time - p.blockTime = time.Duration(p.config.BlockTime) - - // initialize polybft consensus data directory - p.dataDir = filepath.Join(p.config.Config.Path, "polybft") - // create the data dir if not exists - if err = common.CreateDirSafe(p.dataDir, 0750); err != nil { - return fmt.Errorf("failed to create data directory. Error: %w", err) - } - - stt, err := newState(filepath.Join(p.dataDir, stateFileName), p.logger, p.closeCh) - if err != nil { - return fmt.Errorf("failed to create state instance. Error: %w", err) - } - - p.state = stt - p.validatorsCache = newValidatorsSnapshotCache(p.config.Logger, stt, p.blockchain) - - // create runtime - if err := p.initRuntime(); err != nil { - return err - } - - p.ibft = newIBFTConsensusWrapper(p.logger, p.runtime, p) - - if err = p.subscribeToIbftTopic(); err != nil { - return fmt.Errorf("IBFT topic subscription failed: %w", err) - } - - return nil -} - -func ForkManagerInitialParamsFactory(config *chain.Chain) (*forkmanager.ForkParams, error) { - pbftConfig, err := GetPolyBFTConfig(config) - if err != nil { - return nil, err - } - - return &forkmanager.ForkParams{ - MaxValidatorSetSize: &pbftConfig.MaxValidatorSetSize, - EpochSize: &pbftConfig.EpochSize, - SprintSize: &pbftConfig.SprintSize, - BlockTime: &pbftConfig.BlockTime, - BlockTimeDrift: &pbftConfig.BlockTimeDrift, - }, nil -} - -// Start starts the consensus and servers -func (p *Polybft) Start() error { - p.logger.Info("starting polybft consensus", "signer", p.key.String()) - - // start syncer (also initializes peer map) - if err := p.syncer.Start(); err != nil { - return fmt.Errorf("failed to start syncer. Error: %w", err) - } - - // sync concurrently, retrying indefinitely - go common.RetryForever(context.Background(), time.Second, func(context.Context) error { - blockHandler := func(b *types.FullBlock) bool { - p.runtime.OnBlockInserted(b) - - return false - } - if err := p.syncer.Sync(blockHandler); err != nil { - p.logger.Error("blocks synchronization failed", "error", err) - - return err - } - - return nil - }) - - // start consensus runtime - if err := p.startRuntime(); err != nil { - return fmt.Errorf("consensus runtime start failed: %w", err) - } - - // start state DB process - go p.state.startStatsReleasing() - - // polybft rootchain metrics - go p.publishRootchainMetrics(p.logger.Named("rootchain_metrics")) - - return nil -} - -// initRuntime creates consensus runtime -func (p *Polybft) initRuntime() error { - runtimeConfig := &runtimeConfig{ - PolyBFTConfig: p.consensusConfig, - Key: p.key, - DataDir: p.dataDir, - State: p.state, - blockchain: p.blockchain, - polybftBackend: p, - txPool: p.txPool, - bridgeTopic: p.bridgeTopic, - numBlockConfirmations: p.config.NumBlockConfirmations, - consensusConfig: p.config.Config, - } - - runtime, err := newConsensusRuntime(p.logger, runtimeConfig) - if err != nil { - return err - } - - p.runtime = runtime - - return nil -} - -// startRuntime starts consensus runtime -func (p *Polybft) startRuntime() error { - go p.startConsensusProtocol() - - return nil -} - -func (p *Polybft) startConsensusProtocol() { - // wait to have at least n peers connected. The 2 is just an initial heuristic value - // Most likely we will parametrize this in the future. - if !p.waitForNPeers() { - return - } - - p.logger.Debug("peers connected") - - newBlockSub := p.blockchain.SubscribeEvents() - defer p.blockchain.UnubscribeEvents(newBlockSub) - - syncerBlockCh := make(chan struct{}) - - go func() { - eventCh := newBlockSub.GetEventCh() - - for { - select { - case <-p.closeCh: - return - case ev := <-eventCh: - // The blockchain notification system can eventually deliver - // stale block notifications. These should be ignored - if ev.Source == "syncer" && ev.NewChain[0].Number >= p.blockchain.CurrentHeader().Number { - p.logger.Info("sync block notification received", "block height", ev.NewChain[0].Number, - "current height", p.blockchain.CurrentHeader().Number) - syncerBlockCh <- struct{}{} - } - } - } - }() - - var ( - sequenceCh <-chan struct{} - stopSequence func() - ) - - for { - latestHeader := p.blockchain.CurrentHeader() - - currentValidators, err := p.GetValidators(latestHeader.Number, nil) - if err != nil { - p.logger.Error("failed to query current validator set", "block number", latestHeader.Number, "error", err) - } - - isValidator := currentValidators.ContainsNodeID(p.key.String()) - p.runtime.setIsActiveValidator(isValidator) - - p.txPool.SetSealing(isValidator) // update tx pool - - if isValidator { - // initialize FSM as a stateless ibft backend via runtime as an adapter - err = p.runtime.FSM() - if err != nil { - p.logger.Error("failed to create fsm", "block number", latestHeader.Number, "error", err) - - continue - } - - sequenceCh, stopSequence = p.ibft.runSequence(latestHeader.Number + 1) - } - - now := time.Now().UTC() - - select { - case <-syncerBlockCh: - if isValidator { - stopSequence() - p.logger.Info("canceled sequence", "sequence", latestHeader.Number+1) - } - case <-sequenceCh: - case <-p.closeCh: - if isValidator { - stopSequence() - } - - return - } - - p.logger.Debug("time to run the sequence", "seconds", time.Since(now)) - } -} - -func (p *Polybft) waitForNPeers() bool { - for { - select { - case <-p.closeCh: - return false - case <-time.After(2 * time.Second): - } - - if len(p.config.Network.Peers()) >= minSyncPeers { - break - } - } - - return true -} - -// Close closes the connection -func (p *Polybft) Close() error { - if p.syncer != nil { - if err := p.syncer.Close(); err != nil { - return err - } - } - - close(p.closeCh) - p.runtime.close() - - return nil -} - -// GetSyncProgression retrieves the current sync progression, if any -func (p *Polybft) GetSyncProgression() *progress.Progression { - return p.syncer.GetSyncProgression() -} - -// VerifyHeader implements consensus.Engine and checks whether a header conforms to the consensus rules -func (p *Polybft) VerifyHeader(header *types.Header) error { - // Short circuit if the header is known - if _, ok := p.blockchain.GetHeaderByHash(header.Hash); ok { - return nil - } - - parent, ok := p.blockchain.GetHeaderByHash(header.ParentHash) - if !ok { - return fmt.Errorf( - "unable to get parent header by hash for block number %d", - header.Number, - ) - } - - return p.verifyHeaderImpl(parent, header, p.consensusConfig.BlockTimeDrift, nil) -} - -func (p *Polybft) verifyHeaderImpl(parent, header *types.Header, blockTimeDrift uint64, parents []*types.Header) error { - // validate header fields - if err := validateHeaderFields(parent, header, blockTimeDrift); err != nil { - return fmt.Errorf("failed to validate header for block %d. error = %w", header.Number, err) - } - - // decode the extra data - extra, err := GetIbftExtra(header.ExtraData) - if err != nil { - return fmt.Errorf("failed to verify header for block %d. get extra error = %w", header.Number, err) - } - - // validate extra data - return extra.ValidateFinalizedData( - header, parent, parents, p.blockchain.GetChainID(), p, signer.DomainCheckpointManager, p.logger) -} - -func (p *Polybft) GetValidators(blockNumber uint64, parents []*types.Header) (validator.AccountSet, error) { - return p.validatorsCache.GetSnapshot(blockNumber, parents, nil) -} - -func (p *Polybft) GetValidatorsWithTx(blockNumber uint64, parents []*types.Header, - dbTx *bolt.Tx) (validator.AccountSet, error) { - return p.validatorsCache.GetSnapshot(blockNumber, parents, dbTx) -} - -// ProcessHeaders updates the snapshot based on the verified headers -func (p *Polybft) ProcessHeaders(_ []*types.Header) error { - // Not required - return nil -} - -// GetBlockCreator retrieves the block creator (or signer) given the block header -func (p *Polybft) GetBlockCreator(h *types.Header) (types.Address, error) { - return types.BytesToAddress(h.Miner), nil -} - -// PreCommitState a hook to be called before finalizing state transition on inserting block -func (p *Polybft) PreCommitState(block *types.Block, _ *state.Transition) error { - commitmentTxExists := false - - validators, err := p.GetValidators(block.Number()-1, nil) - if err != nil { - return err - } - - // validate commitment state transactions - for _, tx := range block.Transactions { - if tx.Type != types.StateTx { - continue - } - - decodedStateTx, err := decodeStateTransaction(tx.Input) - if err != nil { - return fmt.Errorf("unknown state transaction: tx=%v, error: %w", tx.Hash, err) - } - - if signedCommitment, ok := decodedStateTx.(*CommitmentMessageSigned); ok { - if commitmentTxExists { - return fmt.Errorf("only one commitment state tx is allowed per block: %v", tx.Hash) - } - - commitmentTxExists = true - - if err := verifyBridgeCommitmentTx( - block.Number(), - tx.Hash, - signedCommitment, - validator.NewValidatorSet(validators, p.logger)); err != nil { - return err - } - } - } - - return nil -} - -// GetBridgeProvider is an implementation of Consensus interface -// Returns an instance of BridgeDataProvider -func (p *Polybft) GetBridgeProvider() consensus.BridgeDataProvider { - return p.runtime -} - -// FilterExtra is an implementation of Consensus interface -func (p *Polybft) FilterExtra(extra []byte) ([]byte, error) { - return GetIbftExtraClean(extra) -} - -// initProxies initializes proxy contracts, that allow upgradeability of contracts implementation -func initProxies(transition *state.Transition, admin types.Address, - proxyToImplMap map[types.Address]types.Address) error { - for proxyAddress, implAddress := range proxyToImplMap { - protectSetupProxyFn := &contractsapi.ProtectSetUpProxyGenesisProxyFn{Initiator: contracts.SystemCaller} - - proxyInput, err := protectSetupProxyFn.EncodeAbi() - if err != nil { - return fmt.Errorf("GenesisProxy.protectSetUpProxy params encoding failed: %w", err) - } - - err = callContract(contracts.SystemCaller, proxyAddress, proxyInput, "GenesisProxy.protectSetUpProxy", transition) - if err != nil { - return err - } - - setUpproxyFn := &contractsapi.SetUpProxyGenesisProxyFn{ - Logic: implAddress, - Admin: admin, - Data: []byte{}, - } - - proxyInput, err = setUpproxyFn.EncodeAbi() - if err != nil { - return fmt.Errorf("GenesisProxy.setUpProxy params encoding failed: %w", err) - } - - err = callContract(contracts.SystemCaller, proxyAddress, proxyInput, "GenesisProxy.setUpProxy", transition) - if err != nil { - return err - } - } - - return nil -} - -func getBurnContractAddress(config *chain.Chain, polyBFTConfig PolyBFTConfig) (types.Address, bool) { - if config.Params.BurnContract != nil && - len(config.Params.BurnContract) == 1 && - !polyBFTConfig.NativeTokenConfig.IsMintable { - for _, address := range config.Params.BurnContract { - if _, ok := config.Genesis.Alloc[address]; ok { - return address, true - } - } - } - - return types.ZeroAddress, false -} diff --git a/consensus/polybft/polybft_config.go b/consensus/polybft/polybft_config.go deleted file mode 100644 index 29a3957..0000000 --- a/consensus/polybft/polybft_config.go +++ /dev/null @@ -1,316 +0,0 @@ -package polybft - -import ( - "encoding/json" - "errors" - "math" - "math/big" - "strconv" - "strings" - - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/types" -) - -const ( - ConsensusName = "polybft" - minNativeTokenParamsNumber = 4 - - defaultNativeTokenName = "Polygon" - defaultNativeTokenSymbol = "MATIC" - defaultNativeTokenDecimals = uint8(18) -) - -var ( - DefaultTokenConfig = &TokenConfig{ - Name: defaultNativeTokenName, - Symbol: defaultNativeTokenSymbol, - Decimals: defaultNativeTokenDecimals, - IsMintable: false, - Owner: types.ZeroAddress, - } - - errInvalidTokenParams = errors.New("native token params were not submitted in proper format " + - "()") -) - -// PolyBFTConfig is the configuration file for the Polybft consensus protocol. -type PolyBFTConfig struct { - // InitialValidatorSet are the genesis validators - InitialValidatorSet []*validator.GenesisValidator `json:"initialValidatorSet"` - - // Bridge is the rootchain bridge configuration - Bridge *BridgeConfig `json:"bridge"` - - // EpochSize is size of epoch - EpochSize uint64 `json:"epochSize"` - - // EpochReward is assigned to validators for blocks sealing - EpochReward uint64 `json:"epochReward"` - - // SprintSize is size of sprint - SprintSize uint64 `json:"sprintSize"` - - // BlockTime is target frequency of blocks production - BlockTime common.Duration `json:"blockTime"` - - // Governance is the initial governance address - Governance types.Address `json:"governance"` - - // NativeTokenConfig defines name, symbol and decimal count of the native token - NativeTokenConfig *TokenConfig `json:"nativeTokenConfig"` - - InitialTrieRoot types.Hash `json:"initialTrieRoot"` - - // SupernetID indicates ID of given supernet generated by stake manager contract - SupernetID int64 `json:"supernetID"` - - // MinValidatorSetSize indicates the minimum size of validator set - MinValidatorSetSize uint64 `json:"minValidatorSetSize"` - - // MaxValidatorSetSize indicates the maximum size of validator set - MaxValidatorSetSize uint64 `json:"maxValidatorSetSize"` - - // RewardConfig defines rewards configuration - RewardConfig *RewardsConfig `json:"rewardConfig"` - - // BlockTimeDrift defines the time slot in which a new block can be created - BlockTimeDrift uint64 `json:"blockTimeDrift"` - - // BlockTrackerPollInterval specifies interval - // at which block tracker polls for blocks on a rootchain - BlockTrackerPollInterval common.Duration `json:"blockTrackerPollInterval,omitempty"` - - // ProxyContractsAdmin is the address that will have the privilege to change both the proxy - // implementation address and the admin - ProxyContractsAdmin types.Address `json:"proxyContractsAdmin,omitempty"` -} - -// LoadPolyBFTConfig loads chain config from provided path and unmarshals PolyBFTConfig -func LoadPolyBFTConfig(chainConfigFile string) (PolyBFTConfig, error) { - chainCfg, err := chain.ImportFromFile(chainConfigFile) - if err != nil { - return PolyBFTConfig{}, err - } - - polybftConfig, err := GetPolyBFTConfig(chainCfg) - if err != nil { - return PolyBFTConfig{}, err - } - - return polybftConfig, err -} - -// GetPolyBFTConfig deserializes provided chain config and returns PolyBFTConfig -func GetPolyBFTConfig(chainConfig *chain.Chain) (PolyBFTConfig, error) { - consensusConfigJSON, err := json.Marshal(chainConfig.Params.Engine[ConsensusName]) - if err != nil { - return PolyBFTConfig{}, err - } - - var polyBFTConfig PolyBFTConfig - if err = json.Unmarshal(consensusConfigJSON, &polyBFTConfig); err != nil { - return PolyBFTConfig{}, err - } - - return polyBFTConfig, nil -} - -// BridgeConfig is the rootchain configuration, needed for bridging -type BridgeConfig struct { - StateSenderAddr types.Address `json:"stateSenderAddress"` - CheckpointManagerAddr types.Address `json:"checkpointManagerAddress"` - ExitHelperAddr types.Address `json:"exitHelperAddress"` - RootERC20PredicateAddr types.Address `json:"erc20PredicateAddress"` - ChildMintableERC20PredicateAddr types.Address `json:"erc20ChildMintablePredicateAddress"` - RootNativeERC20Addr types.Address `json:"nativeERC20Address"` - RootERC721PredicateAddr types.Address `json:"erc721PredicateAddress"` - ChildMintableERC721PredicateAddr types.Address `json:"erc721ChildMintablePredicateAddress"` - RootERC1155PredicateAddr types.Address `json:"erc1155PredicateAddress"` - ChildMintableERC1155PredicateAddr types.Address `json:"erc1155ChildMintablePredicateAddress"` - ChildERC20Addr types.Address `json:"childERC20Address"` - ChildERC721Addr types.Address `json:"childERC721Address"` - ChildERC1155Addr types.Address `json:"childERC1155Address"` - CustomSupernetManagerAddr types.Address `json:"customSupernetManagerAddr"` - StakeManagerAddr types.Address `json:"stakeManagerAddr"` - // only populated if stake-manager-deploy command is executed, and used for e2e tests - StakeTokenAddr types.Address `json:"stakeTokenAddr,omitempty"` - BLSAddress types.Address `json:"blsAddr"` - BN256G2Address types.Address `json:"bn256G2Addr"` - - JSONRPCEndpoint string `json:"jsonRPCEndpoint"` - EventTrackerStartBlocks map[types.Address]uint64 `json:"eventTrackerStartBlocks"` -} - -func (p *PolyBFTConfig) IsBridgeEnabled() bool { - return p.Bridge != nil -} - -// RootchainConfig contains rootchain metadata (such as JSON RPC endpoint and contract addresses) -type RootchainConfig struct { - JSONRPCAddr string - - StateSenderAddress types.Address - CheckpointManagerAddress types.Address - BLSAddress types.Address - BN256G2Address types.Address - ExitHelperAddress types.Address - RootERC20PredicateAddress types.Address - ChildMintableERC20PredicateAddress types.Address - RootNativeERC20Address types.Address - ChildERC20Address types.Address - RootERC721PredicateAddress types.Address - ChildMintableERC721PredicateAddress types.Address - ChildERC721Address types.Address - RootERC1155PredicateAddress types.Address - ChildMintableERC1155PredicateAddress types.Address - ChildERC1155Address types.Address - CustomSupernetManagerAddress types.Address - StakeManagerAddress types.Address - StakeTokenAddress types.Address -} - -// ToBridgeConfig creates BridgeConfig instance -func (r *RootchainConfig) ToBridgeConfig() *BridgeConfig { - return &BridgeConfig{ - JSONRPCEndpoint: r.JSONRPCAddr, - - StateSenderAddr: r.StateSenderAddress, - CheckpointManagerAddr: r.CheckpointManagerAddress, - ExitHelperAddr: r.ExitHelperAddress, - RootERC20PredicateAddr: r.RootERC20PredicateAddress, - ChildMintableERC20PredicateAddr: r.ChildMintableERC20PredicateAddress, - RootNativeERC20Addr: r.RootNativeERC20Address, - RootERC721PredicateAddr: r.RootERC721PredicateAddress, - ChildMintableERC721PredicateAddr: r.ChildMintableERC721PredicateAddress, - RootERC1155PredicateAddr: r.RootERC1155PredicateAddress, - ChildMintableERC1155PredicateAddr: r.ChildMintableERC1155PredicateAddress, - ChildERC20Addr: r.ChildERC20Address, - ChildERC721Addr: r.ChildERC721Address, - ChildERC1155Addr: r.ChildERC1155Address, - CustomSupernetManagerAddr: r.CustomSupernetManagerAddress, - StakeManagerAddr: r.StakeManagerAddress, - BLSAddress: r.BLSAddress, - BN256G2Address: r.BN256G2Address, - } -} - -// TokenConfig is the configuration of native token used by edge network -type TokenConfig struct { - Name string `json:"name"` - Symbol string `json:"symbol"` - Decimals uint8 `json:"decimals"` - IsMintable bool `json:"isMintable"` - Owner types.Address `json:"owner"` -} - -func ParseRawTokenConfig(rawConfig string) (*TokenConfig, error) { - if rawConfig == "" { - return DefaultTokenConfig, nil - } - - params := strings.Split(rawConfig, ":") - if len(params) < minNativeTokenParamsNumber { - return nil, errInvalidTokenParams - } - - // name - name := strings.TrimSpace(params[0]) - if name == "" { - return nil, errInvalidTokenParams - } - - // symbol - symbol := strings.TrimSpace(params[1]) - if symbol == "" { - return nil, errInvalidTokenParams - } - - // decimals - decimals, err := strconv.ParseUint(strings.TrimSpace(params[2]), 10, 8) - if err != nil || decimals > math.MaxUint8 { - return nil, errInvalidTokenParams - } - - // is mintable native token used - isMintable, err := strconv.ParseBool(strings.TrimSpace(params[3])) - if err != nil { - return nil, errInvalidTokenParams - } - - // in case it is mintable native token, it is expected to have 5 parameters provided - if isMintable && len(params) != minNativeTokenParamsNumber+1 { - return nil, errInvalidTokenParams - } - - // owner address - owner := types.ZeroAddress - if isMintable { - owner = types.StringToAddress(strings.TrimSpace(params[4])) - } - - return &TokenConfig{ - Name: name, - Symbol: symbol, - Decimals: uint8(decimals), - IsMintable: isMintable, - Owner: owner, - }, nil -} - -type RewardsConfig struct { - // TokenAddress is the address of reward token on child chain - TokenAddress types.Address - - // WalletAddress is the address of reward wallet on child chain - WalletAddress types.Address - - // WalletAmount is the amount of tokens in reward wallet - WalletAmount *big.Int -} - -func (r *RewardsConfig) MarshalJSON() ([]byte, error) { - raw := &rewardsConfigRaw{ - TokenAddress: r.TokenAddress, - WalletAddress: r.WalletAddress, - WalletAmount: common.EncodeBigInt(r.WalletAmount), - } - - return json.Marshal(raw) -} - -func (r *RewardsConfig) UnmarshalJSON(data []byte) error { - var ( - raw rewardsConfigRaw - err error - ) - - if err = json.Unmarshal(data, &raw); err != nil { - return err - } - - r.TokenAddress = raw.TokenAddress - r.WalletAddress = raw.WalletAddress - - r.WalletAmount, err = common.ParseUint256orHex(raw.WalletAmount) - if err != nil { - return err - } - - return nil -} - -type rewardsConfigRaw struct { - TokenAddress types.Address `json:"rewardTokenAddress"` - WalletAddress types.Address `json:"rewardWalletAddress"` - WalletAmount *string `json:"rewardWalletAmount"` -} - -// BurnContractInfo contains metadata for burn contract, which is part of EIP-1559 specification -type BurnContractInfo struct { - BlockNumber uint64 - Address types.Address - DestinationAddress types.Address -} diff --git a/consensus/polybft/polybft_test.go b/consensus/polybft/polybft_test.go deleted file mode 100644 index e94f805..0000000 --- a/consensus/polybft/polybft_test.go +++ /dev/null @@ -1,354 +0,0 @@ -package polybft - -import ( - "errors" - "testing" - "time" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/consensus" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/helper/progress" - "github.com/xgr-network/xgr-node/txpool" - "github.com/xgr-network/xgr-node/types" -) - -// the test initializes polybft and chain mock (map of headers) after which a new header is verified -// firstly, two invalid situation of header verifications are triggered (missing Committed field and invalid validators for ParentCommitted) -// afterwards, valid inclusion into the block chain is checked -// and at the end there is a situation when header is already a part of blockchain -func TestPolybft_VerifyHeader(t *testing.T) { - t.Parallel() - - const ( - allValidatorsSize = 6 // overall there are 6 validators - validatorSetSize = 5 // only 5 validators are active at the time - fixedEpochSize = uint64(10) - ) - - updateHeaderExtra := func(header *types.Header, - validators *validator.ValidatorSetDelta, - parentSignature *Signature, - checkpointData *CheckpointData, - committedAccounts []*wallet.Account) *Signature { - extra := &Extra{ - Validators: validators, - Parent: parentSignature, - Checkpoint: checkpointData, - Committed: &Signature{}, - } - - if extra.Checkpoint == nil { - extra.Checkpoint = &CheckpointData{} - } - - header.ExtraData = extra.MarshalRLPTo(nil) - header.ComputeHash() - - if len(committedAccounts) > 0 { - checkpointHash, err := extra.Checkpoint.Hash(0, header.Number, header.Hash) - require.NoError(t, err) - - extra.Committed = createSignature(t, committedAccounts, checkpointHash, signer.DomainCheckpointManager) - header.ExtraData = extra.MarshalRLPTo(nil) - } - - return extra.Committed - } - - // create all validators - validators := validator.NewTestValidators(t, allValidatorsSize) - - // create configuration - polyBftConfig := PolyBFTConfig{ - InitialValidatorSet: validators.GetParamValidators(), - EpochSize: fixedEpochSize, - SprintSize: 5, - BlockTimeDrift: 10, - } - - validatorSet := validators.GetPublicIdentities() - accounts := validators.GetPrivateIdentities() - - // calculate validators before and after the end of the first epoch - validatorSetParent, validatorSetCurrent := validatorSet[:len(validatorSet)-1], validatorSet[1:] - accountSetParent, accountSetCurrent := accounts[:len(accounts)-1], accounts[1:] - - // create header map to simulate blockchain - headersMap := &testHeadersMap{} - - // create genesis header - genesisDelta, err := validator.CreateValidatorSetDelta(nil, validatorSetParent) - require.NoError(t, err) - - genesisHeader := &types.Header{Number: 0} - updateHeaderExtra(genesisHeader, genesisDelta, nil, nil, nil) - - // add genesis header to map - headersMap.addHeader(genesisHeader) - - // create headers from 1 to 9 - for i := uint64(1); i < polyBftConfig.EpochSize; i++ { - delta, err := validator.CreateValidatorSetDelta(validatorSetParent, validatorSetParent) - require.NoError(t, err) - - header := &types.Header{Number: i} - updateHeaderExtra(header, delta, nil, &CheckpointData{EpochNumber: 1}, nil) - - // add headers from 1 to 9 to map (blockchain imitation) - headersMap.addHeader(header) - } - - // mock blockchain - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) - blockchainMock.On("GetHeaderByHash", mock.Anything).Return(headersMap.getHeaderByHash) - - // create polybft with appropriate mocks - polybft := &Polybft{ - closeCh: make(chan struct{}), - logger: hclog.NewNullLogger(), - consensusConfig: &polyBftConfig, - blockchain: blockchainMock, - validatorsCache: newValidatorsSnapshotCache( - hclog.NewNullLogger(), - newTestState(t), - blockchainMock, - ), - } - - // create parent header (block 10) - parentDelta, err := validator.CreateValidatorSetDelta(validatorSetParent, validatorSetCurrent) - require.NoError(t, err) - - parentHeader := &types.Header{ - Number: polyBftConfig.EpochSize, - Timestamp: uint64(time.Now().UTC().Unix()), - } - parentCommitment := updateHeaderExtra(parentHeader, parentDelta, nil, &CheckpointData{EpochNumber: 1}, accountSetParent) - - // add parent header to map - headersMap.addHeader(parentHeader) - - // create current header (block 11) with all appropriate fields required for validation - currentDelta, err := validator.CreateValidatorSetDelta(validatorSetCurrent, validatorSetCurrent) - require.NoError(t, err) - - currentHeader := &types.Header{ - Number: polyBftConfig.EpochSize + 1, - ParentHash: parentHeader.Hash, - Timestamp: parentHeader.Timestamp + 1, - MixHash: PolyBFTMixDigest, - Difficulty: 1, - } - updateHeaderExtra(currentHeader, currentDelta, nil, - &CheckpointData{ - EpochNumber: 2, - CurrentValidatorsHash: types.StringToHash("Foo"), - NextValidatorsHash: types.StringToHash("Bar"), - }, nil) - - currentHeader.Hash[0] = currentHeader.Hash[0] + 1 - assert.ErrorContains(t, polybft.VerifyHeader(currentHeader), "invalid header hash") - - // omit Parent field (parent signature) intentionally - updateHeaderExtra(currentHeader, currentDelta, nil, - &CheckpointData{ - EpochNumber: 1, - CurrentValidatorsHash: types.StringToHash("Foo"), - NextValidatorsHash: types.StringToHash("Bar")}, - accountSetCurrent) - - // since parent signature is intentionally disregarded the following error is expected - assert.ErrorContains(t, polybft.VerifyHeader(currentHeader), "failed to verify signatures for parent of block") - - updateHeaderExtra(currentHeader, currentDelta, parentCommitment, - &CheckpointData{ - EpochNumber: 1, - CurrentValidatorsHash: types.StringToHash("Foo"), - NextValidatorsHash: types.StringToHash("Bar")}, - accountSetCurrent) - - assert.NoError(t, polybft.VerifyHeader(currentHeader)) - - // clean validator snapshot cache (re-instantiate it), submit invalid validator set for parent signature and expect the following error - polybft.validatorsCache = newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock) - assert.NoError(t, polybft.validatorsCache.storeSnapshot( - &validatorSnapshot{Epoch: 0, Snapshot: validatorSetCurrent}, nil)) // invalid validator set is submitted - assert.NoError(t, polybft.validatorsCache.storeSnapshot( - &validatorSnapshot{Epoch: 1, Snapshot: validatorSetCurrent}, nil)) - assert.ErrorContains(t, polybft.VerifyHeader(currentHeader), "failed to verify signatures for parent of block") - - // clean validators cache again and set valid snapshots - polybft.validatorsCache = newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock) - assert.NoError(t, polybft.validatorsCache.storeSnapshot( - &validatorSnapshot{Epoch: 0, Snapshot: validatorSetParent}, nil)) - assert.NoError(t, polybft.validatorsCache.storeSnapshot( - &validatorSnapshot{Epoch: 1, Snapshot: validatorSetCurrent}, nil)) - assert.NoError(t, polybft.VerifyHeader(currentHeader)) - - // add current header to the blockchain (headersMap) and try validating again - headersMap.addHeader(currentHeader) - assert.NoError(t, polybft.VerifyHeader(currentHeader)) -} - -func TestPolybft_Close(t *testing.T) { - t.Parallel() - - syncer := &syncerMock{} - syncer.On("Close", mock.Anything).Return(error(nil)).Once() - - polybft := Polybft{ - closeCh: make(chan struct{}), - syncer: syncer, - runtime: &consensusRuntime{ - stateSyncManager: &dummyStateSyncManager{}, - stateSyncRelayer: &dummyStateSyncRelayer{}, - }, - } - - assert.NoError(t, polybft.Close()) - - <-polybft.closeCh - - syncer.AssertExpectations(t) - - errExpected := errors.New("something") - syncer.On("Close", mock.Anything).Return(errExpected).Once() - - polybft.closeCh = make(chan struct{}) - - assert.Error(t, errExpected, polybft.Close()) - - select { - case <-polybft.closeCh: - assert.Fail(t, "channel closing is invoked") - case <-time.After(time.Millisecond * 100): - } - - syncer.AssertExpectations(t) -} - -func TestPolybft_GetSyncProgression(t *testing.T) { - t.Parallel() - - result := &progress.Progression{} - - syncer := &syncerMock{} - syncer.On("GetSyncProgression", mock.Anything).Return(result).Once() - - polybft := Polybft{ - syncer: syncer, - } - - assert.Equal(t, result, polybft.GetSyncProgression()) -} - -func Test_Factory(t *testing.T) { - t.Parallel() - - const epochSize = uint64(141) - - txPool := &txpool.TxPool{} - - params := &consensus.Params{ - TxPool: txPool, - Logger: hclog.Default(), - Config: &consensus.Config{ - Config: map[string]interface{}{ - "EpochSize": epochSize, - }, - }, - } - - r, err := Factory(params) - - require.NoError(t, err) - require.NotNil(t, r) - - polybft, ok := r.(*Polybft) - require.True(t, ok) - - assert.Equal(t, txPool, polybft.txPool) - assert.Equal(t, epochSize, polybft.consensusConfig.EpochSize) - assert.Equal(t, params, polybft.config) -} - -func Test_GenesisPostHookFactory(t *testing.T) { - t.Parallel() - - const ( - epochSize = 15 - maxValidators = 150 - ) - - validators := validator.NewTestValidators(t, 6) - bridgeCfg := createTestBridgeConfig() - cases := []struct { - name string - config *PolyBFTConfig - bridgeAllowList *chain.AddressListConfig - expectedErr error - }{ - { - name: "non-mintable native token; access lists disabled", - config: &PolyBFTConfig{ - InitialValidatorSet: validators.GetParamValidators(), - Bridge: bridgeCfg, - EpochSize: epochSize, - RewardConfig: &RewardsConfig{WalletAmount: ethgo.Ether(1000)}, - NativeTokenConfig: &TokenConfig{Name: "Test", Symbol: "TEST", Decimals: 18}, - MaxValidatorSetSize: maxValidators, - }, - }, - { - name: "mintable native token; access lists enabled", - config: &PolyBFTConfig{ - InitialValidatorSet: validators.GetParamValidators(), - Bridge: bridgeCfg, - EpochSize: epochSize, - RewardConfig: &RewardsConfig{WalletAmount: ethgo.Ether(1000)}, - NativeTokenConfig: &TokenConfig{Name: "Test Mintable", Symbol: "TEST_MNT", Decimals: 18, IsMintable: true}, - MaxValidatorSetSize: maxValidators, - }, - bridgeAllowList: &chain.AddressListConfig{ - AdminAddresses: []types.Address{validators.Validators["0"].Address()}, - EnabledAddresses: []types.Address{validators.Validators["1"].Address()}, - }, - }, - { - name: "missing bridge configuration", - config: &PolyBFTConfig{}, - expectedErr: errMissingBridgeConfig, - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - params := &chain.Params{ - Engine: map[string]interface{}{ConsensusName: tc.config}, - BridgeAllowList: tc.bridgeAllowList, - } - chainConfig := &chain.Chain{Params: params, Genesis: &chain.Genesis{Alloc: make(map[types.Address]*chain.GenesisAccount)}} - initHandler := GenesisPostHookFactory(chainConfig, ConsensusName) - require.NotNil(t, initHandler) - - transition := newTestTransition(t, nil) - if tc.expectedErr == nil { - require.NoError(t, initHandler(transition)) - } else { - require.ErrorIs(t, initHandler(transition), tc.expectedErr) - } - }) - } -} diff --git a/consensus/polybft/proposer_calculator.go b/consensus/polybft/proposer_calculator.go deleted file mode 100644 index 103c2ff..0000000 --- a/consensus/polybft/proposer_calculator.go +++ /dev/null @@ -1,504 +0,0 @@ -package polybft - -import ( - "bytes" - "fmt" - "math/big" - - "github.com/hashicorp/go-hclog" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" -) - -var ( - // priorityWindowSizeFactor - is a constant that when multiplied with the - // total voting power gives the maximum allowed distance between validator - // priorities. - priorityWindowSizeFactor = big.NewInt(2) -) - -// PrioritizedValidator holds ValidatorMetadata together with priority -type PrioritizedValidator struct { - Metadata *validator.ValidatorMetadata - ProposerPriority *big.Int -} - -func (pv PrioritizedValidator) String() string { - return fmt.Sprintf("[%v, voting power %v, priority %v]", pv.Metadata.Address.String(), - pv.Metadata.VotingPower, pv.ProposerPriority) -} - -// ProposerSnapshot represents snapshot of one proposer calculation -type ProposerSnapshot struct { - Height uint64 - Round uint64 - Proposer *PrioritizedValidator - Validators []*PrioritizedValidator -} - -// NewProposerSnapshotFromState create ProposerSnapshot from state if possible or from genesis block -func NewProposerSnapshotFromState(config *runtimeConfig, dbTx *bolt.Tx) (*ProposerSnapshot, error) { - snapshot, err := config.State.ProposerSnapshotStore.getProposerSnapshot(dbTx) - if err != nil { - return nil, err - } - - if snapshot == nil { - // pick validator set from genesis block if snapshot is not saved in db - genesisValidatorsSet, err := config.polybftBackend.GetValidatorsWithTx(0, nil, dbTx) - if err != nil { - return nil, err - } - - snapshot = NewProposerSnapshot(1, genesisValidatorsSet) - } - - return snapshot, nil -} - -// NewProposerSnapshot creates ProposerSnapshot with height and validators with all priorities set to zero -func NewProposerSnapshot(height uint64, validators []*validator.ValidatorMetadata) *ProposerSnapshot { - validatorsSnap := make([]*PrioritizedValidator, len(validators)) - - for i, x := range validators { - validatorsSnap[i] = &PrioritizedValidator{Metadata: x, ProposerPriority: big.NewInt(0)} - } - - return &ProposerSnapshot{ - Round: 0, - Proposer: nil, - Height: height, - Validators: validatorsSnap, - } -} - -// CalcProposer calculates next proposer -func (pcs *ProposerSnapshot) CalcProposer(round, height uint64) (types.Address, error) { - if height != pcs.Height { - return types.ZeroAddress, fmt.Errorf("invalid height - expected %d, got %d", pcs.Height, height) - } - - // optimization -> return current proposer if already calculated for this round - if pcs.Round == round && pcs.Proposer != nil { - return pcs.Proposer.Metadata.Address, nil - } - - // do not change priorities on original snapshot while executing CalcProposer - // if round = 0 then we need one iteration - proposer, err := incrementProposerPriorityNTimes(pcs.Copy(), round+1) - if err != nil { - return types.ZeroAddress, err - } - - pcs.Proposer = proposer - pcs.Round = round - - return proposer.Metadata.Address, nil -} - -// GetLatestProposer returns latest calculated proposer if any -func (pcs *ProposerSnapshot) GetLatestProposer(round, height uint64) (types.Address, error) { - // round must be same as saved one and proposer must exist - if pcs == nil || pcs.Proposer == nil || pcs.Round != round || pcs.Height != height { - return types.ZeroAddress, - fmt.Errorf("get latest proposer not found - height: %d, round: %d, pc height: %d, pc round: %d", - height, round, pcs.Height, pcs.Round) - } - - return pcs.Proposer.Metadata.Address, nil -} - -// GetTotalVotingPower returns total voting power from all the validators -func (pcs ProposerSnapshot) GetTotalVotingPower() *big.Int { - totalVotingPower := new(big.Int) - for _, v := range pcs.Validators { - totalVotingPower.Add(totalVotingPower, v.Metadata.VotingPower) - } - - return totalVotingPower -} - -// Copy Returns copy of current ProposerSnapshot object -func (pcs *ProposerSnapshot) Copy() *ProposerSnapshot { - var proposer *PrioritizedValidator - - valCopy := make([]*PrioritizedValidator, len(pcs.Validators)) - - for i, val := range pcs.Validators { - valCopy[i] = &PrioritizedValidator{Metadata: val.Metadata.Copy(), ProposerPriority: val.ProposerPriority} - - if pcs.Proposer != nil && pcs.Proposer.Metadata.Address == val.Metadata.Address { - proposer = valCopy[i] - } - } - - return &ProposerSnapshot{ - Validators: valCopy, - Height: pcs.Height, - Round: pcs.Round, - Proposer: proposer, - } -} - -func (pcs *ProposerSnapshot) toMap() map[types.Address]*PrioritizedValidator { - validatorMap := make(map[types.Address]*PrioritizedValidator) - for _, v := range pcs.Validators { - validatorMap[v.Metadata.Address] = v - } - - return validatorMap -} - -type ProposerCalculator struct { - // current snapshot - snapshot *ProposerSnapshot - - // runtime configuration - config *runtimeConfig - - // state to save snapshot - state *State - - // logger instance - logger hclog.Logger -} - -// NewProposerCalculator creates a new proposer calculator object -func NewProposerCalculator(config *runtimeConfig, logger hclog.Logger, - dbTx *bolt.Tx) (*ProposerCalculator, error) { - snap, err := NewProposerSnapshotFromState(config, dbTx) - - if err != nil { - return nil, err - } - - pc := &ProposerCalculator{ - snapshot: snap, - config: config, - state: config.State, - logger: logger, - } - - // If the node was previously stopped, leaving the proposer calculator in an inconsistent state, - // proposer calculator needs to be updated. - blockNumber := config.blockchain.CurrentHeader().Number - if pc.snapshot.Height <= blockNumber { - if err = pc.update(blockNumber, dbTx); err != nil { - return nil, err - } - } - - return pc, nil -} - -// NewProposerCalculator creates a new proposer calculator object -func NewProposerCalculatorFromSnapshot(pcs *ProposerSnapshot, config *runtimeConfig, - logger hclog.Logger) *ProposerCalculator { - return &ProposerCalculator{ - snapshot: pcs.Copy(), - config: config, - state: config.State, - logger: logger, - } -} - -// Get copy of the proposers' snapshot -func (pc *ProposerCalculator) GetSnapshot() (*ProposerSnapshot, bool) { - if pc.snapshot == nil { - return nil, false - } - - return pc.snapshot.Copy(), true -} - -// PostBlock is called on every insert of finalized block (either from consensus or syncer) -// It will update priorities and save the updated snapshot to db -func (pc *ProposerCalculator) PostBlock(req *PostBlockRequest) error { - return pc.update(req.FullBlock.Block.Number(), req.DBTx) -} - -func (pc *ProposerCalculator) update(blockNumber uint64, dbTx *bolt.Tx) error { - pc.logger.Debug("Update proposers snapshot started", "target block", blockNumber) - - from := pc.snapshot.Height - - // using a for loop if in some previous block, an error occurred while updating snapshot - // so that we can recalculate it to have accurate priorities. - // Note, this will change once we introduce component wide global transaction - for height := from; height <= blockNumber; height++ { - if err := pc.updatePerBlock(height, dbTx); err != nil { - return err - } - - pc.logger.Debug("Proposer snapshot has been updated", - "block", height, "validators", pc.snapshot.Validators) - } - - if err := pc.state.ProposerSnapshotStore.writeProposerSnapshot(pc.snapshot, dbTx); err != nil { - return fmt.Errorf("cannot save proposers snapshot for block %d: %w", blockNumber, err) - } - - pc.logger.Info("Proposer snapshot update has been finished", - "target block", blockNumber+1, "validators", len(pc.snapshot.Validators)) - - return nil -} - -// Updates ProposerSnapshot to block block with number `blockNumber` -func (pc *ProposerCalculator) updatePerBlock(blockNumber uint64, dbTx *bolt.Tx) error { - if pc.snapshot.Height != blockNumber { - return fmt.Errorf("proposers snapshot update called for wrong block. block number=%d, snapshot block number=%d", - blockNumber, pc.snapshot.Height) - } - - _, extra, err := getBlockData(blockNumber, pc.config.blockchain) - if err != nil { - return fmt.Errorf("cannot get block header and extra while updating proposers snapshot %d: %w", blockNumber, err) - } - - var newValidatorSet validator.AccountSet = nil - - if extra.Validators != nil && !extra.Validators.IsEmpty() { - newValidatorSet, err = pc.config.polybftBackend.GetValidatorsWithTx(blockNumber, nil, dbTx) - if err != nil { - return fmt.Errorf("cannot get validators for block %d: %w", blockNumber, err) - } - } - - // if round = 0 then we need one iteration - _, err = incrementProposerPriorityNTimes(pc.snapshot, extra.Checkpoint.BlockRound+1) - if err != nil { - return fmt.Errorf("failed to update proposers snapshot for block %d: %w", blockNumber, err) - } - - // update to new validator set and center if needed - if err = updateValidators(pc.snapshot, newValidatorSet); err != nil { - return fmt.Errorf("cannot update validators: %w", err) - } - - pc.snapshot.Height = blockNumber + 1 // snapshot (validator priorities) is prepared for the next block - pc.snapshot.Round = 0 - pc.snapshot.Proposer = nil - - return nil -} - -// algorithm functions receive snapshot and do appropriate calculations and changes -func incrementProposerPriorityNTimes(snapshot *ProposerSnapshot, times uint64) (*PrioritizedValidator, error) { - if len(snapshot.Validators) == 0 { - return nil, fmt.Errorf("validator set cannot be nul or empty") - } - - if times <= 0 { - return nil, fmt.Errorf("cannot call IncrementProposerPriority with non-positive times") - } - - var ( - proposer *PrioritizedValidator - err error - tvp = snapshot.GetTotalVotingPower() - ) - - if err := updateWithChangeSet(snapshot, tvp); err != nil { - return nil, err - } - - for i := uint64(0); i < times; i++ { - if proposer, err = incrementProposerPriority(snapshot, tvp); err != nil { - return nil, fmt.Errorf("cannot increment proposer priority: %w", err) - } - } - - snapshot.Proposer = proposer - snapshot.Round = times - 1 - - return proposer, nil -} - -func updateValidators(snapshot *ProposerSnapshot, newValidatorSet validator.AccountSet) error { - if newValidatorSet.Len() == 0 { - return nil - } - - snapshotValidators := snapshot.toMap() - newValidators := make([]*PrioritizedValidator, len(newValidatorSet)) - - // compute total voting power of removed validators and current validator - removedValidatorsVotingPower := new(big.Int) - newValidatorsVotingPower := new(big.Int) - - for address, val := range snapshotValidators { - if !newValidatorSet.ContainsNodeID(address.String()) { - removedValidatorsVotingPower.Add(removedValidatorsVotingPower, val.Metadata.VotingPower) - } - } - - for _, v := range newValidatorSet { - newValidatorsVotingPower.Add(newValidatorsVotingPower, v.VotingPower) - } - - tvpAfterUpdatesBeforeRemovals := new(big.Int).Add(newValidatorsVotingPower, removedValidatorsVotingPower) - - for i, newValidator := range newValidatorSet { - if val, exists := snapshotValidators[newValidator.Address]; exists { - // old validators have the same priority - newValidators[i] = &PrioritizedValidator{ - Metadata: newValidator, - ProposerPriority: val.ProposerPriority, - } - } else { - // added validator has priority = -C*totalVotingPowerBeforeRemoval (with C ~= 1.125) - coefficient := new(big.Int).Div(tvpAfterUpdatesBeforeRemovals, big.NewInt(8)) - newValidators[i] = &PrioritizedValidator{ - Metadata: newValidator, - ProposerPriority: new(big.Int).Neg(tvpAfterUpdatesBeforeRemovals.Add(tvpAfterUpdatesBeforeRemovals, coefficient)), - } - } - } - - snapshot.Validators = newValidators - - // after validator set changes, center values around 0 and scale - if err := updateWithChangeSet(snapshot, snapshot.GetTotalVotingPower()); err != nil { - return fmt.Errorf("cannot update validator changeset: %w", err) - } - - return nil -} - -func incrementProposerPriority(snapshot *ProposerSnapshot, totalVotingPower *big.Int) (*PrioritizedValidator, error) { - for _, val := range snapshot.Validators { - // Check for overflow for sum. - newPrio := new(big.Int).Add(val.ProposerPriority, val.Metadata.VotingPower) - val.ProposerPriority = newPrio - } - // Decrement the validator with most ProposerPriority. - mostest, err := getValWithMostPriority(snapshot) - if err != nil { - return nil, fmt.Errorf("cannot get validator with most priority: %w", err) - } - - mostest.ProposerPriority.Sub(mostest.ProposerPriority, totalVotingPower) - - return mostest, nil -} - -func updateWithChangeSet(snapshot *ProposerSnapshot, totalVotingPower *big.Int) error { - if err := rescalePriorities(snapshot, totalVotingPower); err != nil { - return fmt.Errorf("cannot rescale priorities: %w", err) - } - - if err := shiftByAvgProposerPriority(snapshot); err != nil { - return fmt.Errorf("cannot shift proposer priorities: %w", err) - } - - return nil -} - -func shiftByAvgProposerPriority(snapshot *ProposerSnapshot) error { - avgProposerPriority, err := computeAvgProposerPriority(snapshot) - if err != nil { - return fmt.Errorf("cannot compute proposer priority: %w", err) - } - - for _, val := range snapshot.Validators { - val.ProposerPriority.Sub(val.ProposerPriority, avgProposerPriority) - } - - return nil -} - -func getValWithMostPriority(snapshot *ProposerSnapshot) (result *PrioritizedValidator, err error) { - if len(snapshot.Validators) == 0 { - return nil, fmt.Errorf("validators cannot be nil or empty") - } - - for _, curr := range snapshot.Validators { - // pick curr as result if it has greater priority - // or if it has same priority but "smaller" address - if isBetterProposer(curr, result) { - result = curr - } - } - - return result, nil -} - -func computeAvgProposerPriority(snapshot *ProposerSnapshot) (*big.Int, error) { - if len(snapshot.Validators) == 0 { - return nil, fmt.Errorf("validator set cannot be nul or empty") - } - - validatorsCount := big.NewInt(int64(len(snapshot.Validators))) - sum := new(big.Int) - - for _, val := range snapshot.Validators { - sum = sum.Add(sum, val.ProposerPriority) - } - - return sum.Div(sum, validatorsCount), nil -} - -// rescalePriorities rescales the priorities such that the distance between the -// maximum and minimum is smaller than `diffMax`. -func rescalePriorities(snapshot *ProposerSnapshot, totalVotingPower *big.Int) error { - if len(snapshot.Validators) == 0 { - return fmt.Errorf("validator set cannot be nul or empty") - } - - // Cap the difference between priorities to be proportional to 2*totalPower by - // re-normalizing priorities, i.e., rescale all priorities by multiplying with: - // 2*totalVotingPower/(maxPriority - minPriority) - diffMax := new(big.Int).Mul(priorityWindowSizeFactor, totalVotingPower) - - // Calculating ceil(diff/diffMax): - // Re-normalization is performed by dividing by an integer for simplicity. - // NOTE: This may make debugging priority issues easier as well. - diff := computeMaxMinPriorityDiff(snapshot.Validators) - ratio := common.BigIntDivCeil(diff, diffMax) - - if diff.Cmp(diffMax) > 0 { - for _, val := range snapshot.Validators { - val.ProposerPriority.Quo(val.ProposerPriority, ratio) - } - } - - return nil -} - -// computeMaxMinPriorityDiff computes the difference between the max and min ProposerPriority of that set. -func computeMaxMinPriorityDiff(validators []*PrioritizedValidator) *big.Int { - min, max := validators[0].ProposerPriority, validators[0].ProposerPriority - - for _, v := range validators[1:] { - if v.ProposerPriority.Cmp(min) < 0 { - min = v.ProposerPriority - } - - if v.ProposerPriority.Cmp(max) > 0 { - max = v.ProposerPriority - } - } - - diff := new(big.Int).Sub(max, min) - - if diff.Cmp(big.NewInt(0)) < 0 { - return diff.Neg(diff) - } - - return diff -} - -// isBetterProposer compares provided PrioritizedValidator instances -// and chooses either one with higher ProposerPriority or the one with the smaller address (compared lexicographically). -func isBetterProposer(a, b *PrioritizedValidator) bool { - if b == nil || a.ProposerPriority.Cmp(b.ProposerPriority) > 0 { - return true - } else if a.ProposerPriority == b.ProposerPriority { - return bytes.Compare(a.Metadata.Address.Bytes(), b.Metadata.Address.Bytes()) <= 0 - } - - return false -} diff --git a/consensus/polybft/proposer_calculator_test.go b/consensus/polybft/proposer_calculator_test.go deleted file mode 100644 index bfceaa7..0000000 --- a/consensus/polybft/proposer_calculator_test.go +++ /dev/null @@ -1,712 +0,0 @@ -package polybft - -import ( - "bytes" - "math/big" - "testing" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/types" -) - -func TestProposerCalculator_SetIndex(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E"}, []uint64{10, 100, 1, 50, 30}) - metadata := validators.GetPublicIdentities() - - vs := validators.ToValidatorSet() - - snapshot := NewProposerSnapshot(1, metadata) - - // validate no changes to validator set positions - for i, v := range vs.Accounts() { - assert.Equal(t, metadata[i].Address, v.Address) - } - - proposer, err := snapshot.CalcProposer(0, 1) - require.NoError(t, err) - assert.Equal(t, proposer, metadata[1].Address) - // validate no changes to validator set positions - for i, v := range vs.Accounts() { - assert.Equal(t, metadata[i].Address, v.Address) - } -} - -func TestProposerCalculator_RegularFlow(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E"}, []uint64{1, 2, 3, 4, 5}) - metadata := validators.GetPublicIdentities() - - snapshot := NewProposerSnapshot(0, metadata) - - currProposerAddress, err := snapshot.CalcProposer(0, 0) - require.NoError(t, err) - assert.Equal(t, metadata[4].Address, currProposerAddress) - - proposerAddressR1, err := snapshot.CalcProposer(1, 0) - require.NoError(t, err) - assert.Equal(t, metadata[3].Address, proposerAddressR1) - - proposerAddressR2, err := snapshot.CalcProposer(2, 0) - require.NoError(t, err) - assert.Equal(t, metadata[2].Address, proposerAddressR2) - - proposerAddressR3, err := snapshot.CalcProposer(3, 0) - require.NoError(t, err) - assert.Equal(t, metadata[1].Address, proposerAddressR3) - - proposerAddressR4, err := snapshot.CalcProposer(4, 0) - require.NoError(t, err) - assert.Equal(t, metadata[4].Address, proposerAddressR4) - - proposerAddressR5, err := snapshot.CalcProposer(5, 0) - require.NoError(t, err) - assert.Equal(t, metadata[3].Address, proposerAddressR5) - - proposerAddressR6, err := snapshot.CalcProposer(6, 0) - require.NoError(t, err) - assert.Equal(t, metadata[0].Address, proposerAddressR6) -} - -func TestProposerCalculator_SamePriority(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(5) - require.NoError(t, err) - - // at some point priorities will be the same and bytes address will be compared - vs := validator.NewValidatorSet([]*validator.ValidatorMetadata{ - { - BlsKey: keys[0].PublicKey(), - Address: types.Address{0x1}, - VotingPower: big.NewInt(1), - }, - { - BlsKey: keys[1].PublicKey(), - Address: types.Address{0x2}, - VotingPower: big.NewInt(2), - }, - { - BlsKey: keys[2].PublicKey(), - Address: types.Address{0x3}, - VotingPower: big.NewInt(3), - }, - }, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(0, vs.Accounts()) - - proposerR0, err := snapshot.CalcProposer(0, 0) - require.NoError(t, err) - assert.Equal(t, types.Address{0x3}, proposerR0) - - proposerR1, err := snapshot.CalcProposer(1, 0) - require.NoError(t, err) - assert.Equal(t, types.Address{0x2}, proposerR1) - - proposerR2, err := snapshot.CalcProposer(2, 0) - require.NoError(t, err) - assert.Equal(t, types.Address{0x1}, proposerR2) - - proposerR2, err = snapshot.CalcProposer(2, 0) // call again same round - require.NoError(t, err) - assert.Equal(t, types.Address{0x1}, proposerR2) -} - -func TestProposerCalculator_InversePriorityOrderWithExpectedListOfSelection(t *testing.T) { - t.Parallel() - - const numberOfIteration = 99 - - keys, err := bls.CreateRandomBlsKeys(3) - require.NoError(t, err) - - // priorities are from high to low vp in validator set - vset := validator.NewValidatorSet([]*validator.ValidatorMetadata{ - { - BlsKey: keys[0].PublicKey(), - Address: types.Address{0x1}, - VotingPower: big.NewInt(1000), - }, - { - BlsKey: keys[1].PublicKey(), - Address: types.Address{0x2}, - VotingPower: big.NewInt(300), - }, - { - BlsKey: keys[2].PublicKey(), - Address: types.Address{0x3}, - VotingPower: big.NewInt(330), - }, - }, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(4, vset.Accounts()) - - var proposers = make([]types.Address, numberOfIteration) - - for i := uint64(0); i < numberOfIteration; i++ { - proposers[i], err = snapshot.CalcProposer(i, 4) - require.NoError(t, err) - } - - // list of addresses in order that should be selected - expectedValidatorAddresses := []types.Address{ - {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, - {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, - {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, - {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x1}, {0x3}, {0x2}, {0x1}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, - {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, - {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, - {0x3}, {0x1}, {0x1}, - } - - for i, p := range proposers { - assert.True(t, bytes.Equal(expectedValidatorAddresses[i].Bytes(), p.Bytes())) - } -} - -func TestProposerCalculator_IncrementProposerPrioritySameVotingPower(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(3) - require.NoError(t, err) - - vs := validator.NewValidatorSet([]*validator.ValidatorMetadata{ - { - BlsKey: keys[0].PublicKey(), - Address: types.Address{0x1}, - VotingPower: big.NewInt(1), - }, - { - BlsKey: keys[1].PublicKey(), - Address: types.Address{0x2}, - VotingPower: big.NewInt(1), - }, - { - BlsKey: keys[2].PublicKey(), - Address: types.Address{0x3}, - VotingPower: big.NewInt(1), - }, - }, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(0, vs.Accounts()) - - // when voting power is the same order is by address - currProposerAddress, err := snapshot.CalcProposer(0, 0) - require.NoError(t, err) - assert.Equal(t, types.Address{0x1}, currProposerAddress) - - proposerAddresR1, err := snapshot.CalcProposer(1, 0) - require.NoError(t, err) - assert.Equal(t, types.Address{0x2}, proposerAddresR1) - - proposerAddressR2, err := snapshot.CalcProposer(2, 0) - require.NoError(t, err) - assert.Equal(t, types.Address{0x3}, proposerAddressR2) - - proposerAddressR3, err := snapshot.CalcProposer(3, 0) - require.NoError(t, err) - assert.Equal(t, types.Address{0x1}, proposerAddressR3) - - proposerAddressR4, err := snapshot.CalcProposer(4, 0) - require.NoError(t, err) - assert.Equal(t, types.Address{0x2}, proposerAddressR4) -} - -func TestProposerCalculator_AveragingInIncrementProposerPriorityWithVotingPower(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(3) - require.NoError(t, err) - - // Other than TestAveragingInIncrementProposerPriority this is a more complete test showing - // how each ProposerPriority changes in relation to the validator's voting power respectively. - // average is zero in each round: - vp0 := int64(10) - vp1 := int64(1) - vp2 := int64(1) - total := vp0 + vp1 + vp2 - avg := (vp0 + vp1 + vp2 - total) / 3 - valz := []*validator.ValidatorMetadata{ - { - BlsKey: keys[0].PublicKey(), - Address: types.Address{0x1}, - VotingPower: big.NewInt(vp0), - }, - { - BlsKey: keys[1].PublicKey(), - Address: types.Address{0x2}, - VotingPower: big.NewInt(vp1), - }, - { - BlsKey: keys[2].PublicKey(), - Address: types.Address{0x3}, - VotingPower: big.NewInt(vp2), - }, - } - - tcs := []struct { - wantProposerPriority []int64 - times uint64 - wantProposerIndex int64 - }{ - - 0: { - []int64{ - // Acumm+VotingPower-Avg: - 0 + vp0 - total - avg, // mostest will be subtracted by total voting power (12) - 0 + vp1, - 0 + vp2}, - 1, - 0, - }, - 1: { - []int64{ - (0 + vp0 - total) + vp0 - total - avg, // this will be mostest on 2nd iter, too - (0 + vp1) + vp1, - (0 + vp2) + vp2}, - 2, - 0, - }, // increment twice -> expect average to be subtracted twice - 2: { - []int64{ - 0 + 3*(vp0-total) - avg, // still mostest - 0 + 3*vp1, - 0 + 3*vp2}, - 3, - 0, - }, - 3: { - []int64{ - 0 + 4*(vp0-total), // still mostest - 0 + 4*vp1, - 0 + 4*vp2}, - 4, - 0, - }, - 4: { - []int64{ - 0 + 4*(vp0-total) + vp0, // 4 iters was mostest - 0 + 5*vp1 - total, // now this val is mostest for the 1st time (hence -12==totalVotingPower) - 0 + 5*vp2}, - 5, - 1, - }, - 5: { - []int64{ - 0 + 6*vp0 - 5*total, // mostest again - 0 + 6*vp1 - total, // mostest once up to here - 0 + 6*vp2}, - 6, - 0, - }, - 6: { - []int64{ - 0 + 7*vp0 - 6*total, // in 7 iters this val is mostest 6 times - 0 + 7*vp1 - total, // in 7 iters this val is mostest 1 time - 0 + 7*vp2}, - 7, - 0, - }, - 7: { - []int64{ - 0 + 8*vp0 - 7*total, // mostest again - 0 + 8*vp1 - total, - 0 + 8*vp2}, - 8, - 0, - }, - 8: { - []int64{ - 0 + 9*vp0 - 7*total, - 0 + 9*vp1 - total, - 0 + 9*vp2 - total}, // mostest - 9, - 2, - }, - 9: { - []int64{ - 0 + 10*vp0 - 8*total, // after 10 iters this is mostest again - 0 + 10*vp1 - total, // after 6 iters this val is "mostest" once and not in between - 0 + 10*vp2 - total}, // in between 10 iters this val is "mostest" once - 10, - 0, - }, - 10: { - []int64{ - 0 + 11*vp0 - 9*total, - 0 + 11*vp1 - total, // after 6 iters this val is "mostest" once and not in between - 0 + 11*vp2 - total}, // after 10 iters this val is "mostest" once - 11, - 0, - }, - } - - for i, tc := range tcs { - snap := NewProposerSnapshot(1, valz) - - _, err := incrementProposerPriorityNTimes(snap, tc.times) - require.NoError(t, err) - - address, _ := snap.GetLatestProposer(tc.times-1, 1) - - assert.Equal(t, snap.Validators[tc.wantProposerIndex].Metadata.Address, address, - "test case: %v", - i) - - for valIdx, val := range snap.Validators { - assert.Equal(t, - tc.wantProposerPriority[valIdx], - val.ProposerPriority.Int64(), - "test case: %v, validator: %v", - i, - valIdx) - } - } -} - -func TestProposerCalculator_UpdatesForNewValidatorSet(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(2) - require.NoError(t, err) - - v1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(100)} - v2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(100)} - - accountSet := []*validator.ValidatorMetadata{v1, v2} - vs := validator.NewValidatorSet(accountSet, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(0, vs.Accounts()) - - _, err = snapshot.CalcProposer(1, 0) - require.NoError(t, err) - - // verify that the capacity and length of validators is the same - assert.Equal(t, len(vs.Accounts()), cap(snapshot.Validators)) - // verify that validator priorities are centered - valsCount := int64(len(snapshot.Validators)) - - sum := big.NewInt(0) - for _, val := range snapshot.Validators { - // mind overflow - sum = new(big.Int).Add(sum, val.ProposerPriority) - } - - assert.True(t, sum.Cmp(big.NewInt(valsCount)) < 0 && sum.Cmp(big.NewInt(-valsCount)) > 0, - "expected total priority in (-%d, %d). Got %d", valsCount, valsCount, sum) - - // verify that priorities are scaled - diff := computeMaxMinPriorityDiff(snapshot.Validators) - totalVotingPower := vs.TotalVotingPower() - diffMax := new(big.Int).Mul(priorityWindowSizeFactor, &totalVotingPower) - assert.True(t, diff.Cmp(diffMax) <= 0, "expected priority distance < %d. Got %d", diffMax, diff) -} - -func TestProposerCalculator_GetLatestProposer(t *testing.T) { - t.Parallel() - - const ( - bestIdx = 5 - count = 10 - ) - - validatorSet := validator.NewTestValidators(t, count).GetPublicIdentities() - snapshot := NewProposerSnapshot(0, validatorSet) - snapshot.Validators[bestIdx].ProposerPriority = big.NewInt(1000000) - - // not set - _, err := snapshot.GetLatestProposer(0, 0) - assert.Error(t, err) - - _, err = snapshot.CalcProposer(0, 0) - assert.NoError(t, err) - - // wrong round - _, err = snapshot.GetLatestProposer(1, 0) - assert.Error(t, err) - - // wrong height - _, err = snapshot.GetLatestProposer(0, 1) - assert.Error(t, err) - - // ok - address, err := snapshot.GetLatestProposer(0, 0) - assert.NoError(t, err) - - proposerAddress := validatorSet[bestIdx].Address - assert.Equal(t, proposerAddress, address) -} - -func TestProposerCalculator_UpdateValidatorsSameVpUpdatedAndNewAdded(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(8) - require.NoError(t, err) - - v1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(100)} - v2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(100)} - v3 := &validator.ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(100)} - v4 := &validator.ValidatorMetadata{Address: types.Address{0x4}, BlsKey: keys[3].PublicKey(), VotingPower: big.NewInt(100)} - v5 := &validator.ValidatorMetadata{Address: types.Address{0x5}, BlsKey: keys[4].PublicKey(), VotingPower: big.NewInt(100)} - - vs := validator.NewValidatorSet([]*validator.ValidatorMetadata{v1, v2, v3, v4, v5}, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(0, vs.Accounts()) - - // iterate one cycle should bring back priority to 0 - _, err = incrementProposerPriorityNTimes(snapshot, 5) - require.NoError(t, err) - - for _, v := range snapshot.Validators { - assert.True(t, v.ProposerPriority.Cmp(big.NewInt(0)) == 0) - } - - // updated old validators - u1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(10)} - u2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(10)} - // added new validator - a1 := &validator.ValidatorMetadata{Address: types.Address{0x9}, BlsKey: keys[7].PublicKey(), VotingPower: big.NewInt(100)} - - newAccountSet := []*validator.ValidatorMetadata{u1, u2, a1} - - require.NoError(t, updateValidators(snapshot, newAccountSet)) - assert.Equal(t, 3, len(snapshot.Validators)) - - // removedVp := sum(v3, v4, v5) = 300 - // newVp := sum(u1, u2, a1) = 120 - // sum(removedVp, newVp) = 420; priority(a1) = -1.125*420 = -472 - // scale: difMax = 2 * 120; diff(-475, 0); ratio ~ 2 - // priority(a1) = -472/2 = 236; u1 = 0, u2 = 0 - // center: avg = 236/3 = 79; priority(a1)= 236 - 79 - - // check voting power after update - assert.Equal(t, big.NewInt(10), snapshot.Validators[0].Metadata.VotingPower) - assert.Equal(t, big.NewInt(10), snapshot.Validators[1].Metadata.VotingPower) - assert.Equal(t, big.NewInt(100), snapshot.Validators[2].Metadata.VotingPower) - // newly added validator - assert.Equal(t, big.NewInt(100), snapshot.Validators[2].Metadata.VotingPower) - assert.Equal(t, types.Address{0x9}, snapshot.Validators[2].Metadata.Address) - assert.Equal(t, big.NewInt(-157), snapshot.Validators[2].ProposerPriority) // a1 - // check priority - assert.Equal(t, big.NewInt(79), snapshot.Validators[0].ProposerPriority) // u1 - assert.Equal(t, big.NewInt(79), snapshot.Validators[1].ProposerPriority) // u2 - - _, err = incrementProposerPriorityNTimes(snapshot, 1) - require.NoError(t, err) - - // 79 + 10 - (100+10+10) - assert.Equal(t, big.NewInt(-31), snapshot.Validators[0].ProposerPriority) - // 79 + 10 - assert.Equal(t, big.NewInt(89), snapshot.Validators[1].ProposerPriority) - // -157+100 - assert.Equal(t, big.NewInt(-57), snapshot.Validators[2].ProposerPriority) -} - -func TestProposerCalculator_UpdateValidators(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(4) - require.NoError(t, err) - - v1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(10)} - v2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(20)} - v3 := &validator.ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(30)} - - vs := validator.NewValidatorSet([]*validator.ValidatorMetadata{v1, v2, v3}, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(0, vs.Accounts()) - require.Equal(t, big.NewInt(60), snapshot.GetTotalVotingPower()) - // init priority must be 0 - require.Zero(t, snapshot.Validators[0].ProposerPriority.Int64()) - require.Zero(t, snapshot.Validators[1].ProposerPriority.Int64()) - require.Zero(t, snapshot.Validators[2].ProposerPriority.Int64()) - // vp must be initialized - require.Equal(t, big.NewInt(10), snapshot.Validators[0].Metadata.VotingPower) - require.Equal(t, big.NewInt(20), snapshot.Validators[1].Metadata.VotingPower) - require.Equal(t, big.NewInt(30), snapshot.Validators[2].Metadata.VotingPower) - - // increment once - _, err = incrementProposerPriorityNTimes(snapshot, 1) - require.NoError(t, err) - - // updated - u1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(100)} - u2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(200)} - u3 := &validator.ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(300)} - // added - a1 := &validator.ValidatorMetadata{Address: types.Address{0x4}, BlsKey: keys[3].PublicKey(), VotingPower: big.NewInt(400)} - - require.NoError(t, updateValidators(snapshot, []*validator.ValidatorMetadata{u1, u2, u3, a1})) - - require.Equal(t, 4, len(snapshot.Validators)) - // priorities are from previous iteration - require.Equal(t, big.NewInt(292), snapshot.Validators[0].ProposerPriority) - require.Equal(t, big.NewInt(302), snapshot.Validators[1].ProposerPriority) - require.Equal(t, big.NewInt(252), snapshot.Validators[2].ProposerPriority) - // new added a1 - require.Equal(t, types.Address{0x4}, snapshot.Validators[3].Metadata.Address) - require.Equal(t, big.NewInt(-843), snapshot.Validators[3].ProposerPriority) - // total vp is updated - require.Equal(t, big.NewInt(1000), snapshot.GetTotalVotingPower()) -} - -func TestProposerCalculator_ScaleAfterDelete(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(3) - require.NoError(t, err) - - v1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(10)} - v2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(10)} - v3 := &validator.ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(80000)} - - vs := validator.NewValidatorSet([]*validator.ValidatorMetadata{v1, v2, v3}, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(0, vs.Accounts()) - assert.Equal(t, big.NewInt(80020), snapshot.GetTotalVotingPower()) - - _, err = incrementProposerPriorityNTimes(snapshot, 1) - require.NoError(t, err) - - // priorities are from previous iteration - require.Equal(t, big.NewInt(10), snapshot.Validators[0].ProposerPriority) - require.Equal(t, big.NewInt(10), snapshot.Validators[1].ProposerPriority) - require.Equal(t, big.NewInt(-20), snapshot.Validators[2].ProposerPriority) - - // another increment - proposer, err := incrementProposerPriorityNTimes(snapshot, 4000) - require.NoError(t, err) - // priorities are from previous iteration - assert.Equal(t, types.Address{0x3}, proposer.Metadata.Address) - - // reduce validator voting power from 8k to 1 - u1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(10)} - u2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(10)} - - require.Equal(t, big.NewInt(-40010), snapshot.Validators[0].ProposerPriority) - require.Equal(t, big.NewInt(40010), snapshot.Validators[1].ProposerPriority) - - require.NoError(t, updateValidators(snapshot, []*validator.ValidatorMetadata{u1, u2})) - - // maxdiff = 2*tvp = 40 - // diff(min,max) (-40010, 40010) = 80020 - // ratio := (diff + diffMax - 1) / diffMax; (80020 + 20 - 1)/20 = 2001 - // priority = priority / ratio; u1 = -40010 / 4001 ~ -19; u2 = 40010 / 4001 ~ 19 - require.Equal(t, big.NewInt(-19), snapshot.Validators[0].ProposerPriority) - require.Equal(t, big.NewInt(19), snapshot.Validators[1].ProposerPriority) - require.Equal(t, big.NewInt(20), snapshot.GetTotalVotingPower()) -} - -func TestProposerCalculator_ShiftAfterUpdate(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(3) - require.NoError(t, err) - - v1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(50)} - v2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(80)} - v3 := &validator.ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(100000)} - - vs := validator.NewValidatorSet([]*validator.ValidatorMetadata{v1, v2, v3}, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(0, vs.Accounts()) - assert.Equal(t, big.NewInt(100130), snapshot.GetTotalVotingPower()) - - _, err = incrementProposerPriorityNTimes(snapshot, 4000) - require.NoError(t, err) - - // updates - u1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(5)} - u2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(8)} - - require.NoError(t, updateValidators(snapshot, []*validator.ValidatorMetadata{u1, u2})) - - // maxdiff = 2*tvp = 26 - // diff(min,max) (-260, 19610) = 19870 - // ratio := (diff + diffMax - 1) / diffMax; (19870 + 26 - 1)/26 =765 - // scale priority = priority / ratio; p1 = 0; p2 = 25 - // shift with avg=(25+0)/2=12; p = priority - avg; u1 = -12; u2= 13 - require.Equal(t, big.NewInt(-12), snapshot.Validators[0].ProposerPriority) - require.Equal(t, big.NewInt(13), snapshot.Validators[1].ProposerPriority) - require.Equal(t, big.NewInt(13), snapshot.GetTotalVotingPower()) -} - -func TestProposerCalculator_UpdateValidatorSet(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(3) - require.NoError(t, err) - - v1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(1)} - v2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(8)} - v3 := &validator.ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(15)} - - vs := validator.NewValidatorSet([]*validator.ValidatorMetadata{v1, v2, v3}, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(0, vs.Accounts()) - assert.Equal(t, big.NewInt(24), snapshot.GetTotalVotingPower()) - - _, err = incrementProposerPriorityNTimes(snapshot, 2) - require.NoError(t, err) - - // modified validator - u1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(5)} - // added validator - a1 := &validator.ValidatorMetadata{Address: types.Address{0x4}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(8)} - - require.NoError(t, updateValidators(snapshot, []*validator.ValidatorMetadata{u1, a1})) - // expecting 2 validators with updated voting power and total voting power - require.Equal(t, 2, len(snapshot.Validators)) - require.Equal(t, types.Address{0x1}, snapshot.Validators[0].Metadata.Address) - require.Equal(t, big.NewInt(5), snapshot.Validators[0].Metadata.VotingPower) - require.Equal(t, big.NewInt(11), snapshot.Validators[0].ProposerPriority) - - require.Equal(t, types.Address{0x4}, snapshot.Validators[1].Metadata.Address) - require.Equal(t, big.NewInt(8), snapshot.Validators[1].Metadata.VotingPower) - require.Equal(t, big.NewInt(-10), snapshot.Validators[1].ProposerPriority) - require.Equal(t, big.NewInt(13), snapshot.GetTotalVotingPower()) -} - -func TestProposerCalculator_AddValidator(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(3) - require.NoError(t, err) - - v1 := &validator.ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(3)} - v2 := &validator.ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(1)} - - vs := validator.NewValidatorSet([]*validator.ValidatorMetadata{v1, v2}, hclog.NewNullLogger()) - - snapshot := NewProposerSnapshot(0, vs.Accounts()) - assert.Equal(t, big.NewInt(4), snapshot.GetTotalVotingPower()) - proposer, err := incrementProposerPriorityNTimes(snapshot, 1) - require.NoError(t, err) - require.Equal(t, types.Address{0x1}, proposer.Metadata.Address) - require.Equal(t, big.NewInt(-1), snapshot.Validators[0].ProposerPriority) - require.Equal(t, big.NewInt(1), snapshot.Validators[1].ProposerPriority) - - _, err = incrementProposerPriorityNTimes(snapshot, 1) - require.NoError(t, err) - - require.Equal(t, big.NewInt(-2), snapshot.Validators[0].ProposerPriority) - require.Equal(t, big.NewInt(2), snapshot.Validators[1].ProposerPriority) - - a1 := &validator.ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(8)} - - require.NoError(t, updateValidators(snapshot, []*validator.ValidatorMetadata{v1, v2, a1})) - - // updated vp: 8+3+1 = 12 - // added validator priority = -1.125*8 ~ -13 - // scaling: max(-13, 3) = 16 < 2* 12; no scaling - // centring: avg = (13+3+1)/3=5; v1=-2+5, v2=2+5; u3=-13+5 - require.Equal(t, big.NewInt(3), snapshot.Validators[0].ProposerPriority) - require.Equal(t, big.NewInt(7), snapshot.Validators[1].ProposerPriority) - require.Equal(t, big.NewInt(-8), snapshot.Validators[2].ProposerPriority) -} diff --git a/consensus/polybft/proto/transport.pb.go b/consensus/polybft/proto/transport.pb.go deleted file mode 100644 index 3aaa60f..0000000 --- a/consensus/polybft/proto/transport.pb.go +++ /dev/null @@ -1,144 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.7 -// source: consensus/polybft/proto/transport.proto - -package proto - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type TransportMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` -} - -func (x *TransportMessage) Reset() { - *x = TransportMessage{} - if protoimpl.UnsafeEnabled { - mi := &file_consensus_polybft_proto_transport_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *TransportMessage) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TransportMessage) ProtoMessage() {} - -func (x *TransportMessage) ProtoReflect() protoreflect.Message { - mi := &file_consensus_polybft_proto_transport_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TransportMessage.ProtoReflect.Descriptor instead. -func (*TransportMessage) Descriptor() ([]byte, []int) { - return file_consensus_polybft_proto_transport_proto_rawDescGZIP(), []int{0} -} - -func (x *TransportMessage) GetData() []byte { - if x != nil { - return x.Data - } - return nil -} - -var File_consensus_polybft_proto_transport_proto protoreflect.FileDescriptor - -var file_consensus_polybft_proto_transport_proto_rawDesc = []byte{ - 0x0a, 0x27, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2f, 0x70, 0x6f, 0x6c, 0x79, - 0x62, 0x66, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x31, 0x22, 0x26, 0x0a, - 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x1a, 0x5a, 0x18, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, - 0x73, 0x75, 0x73, 0x2f, 0x70, 0x6f, 0x6c, 0x79, 0x62, 0x66, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_consensus_polybft_proto_transport_proto_rawDescOnce sync.Once - file_consensus_polybft_proto_transport_proto_rawDescData = file_consensus_polybft_proto_transport_proto_rawDesc -) - -func file_consensus_polybft_proto_transport_proto_rawDescGZIP() []byte { - file_consensus_polybft_proto_transport_proto_rawDescOnce.Do(func() { - file_consensus_polybft_proto_transport_proto_rawDescData = protoimpl.X.CompressGZIP(file_consensus_polybft_proto_transport_proto_rawDescData) - }) - return file_consensus_polybft_proto_transport_proto_rawDescData -} - -var file_consensus_polybft_proto_transport_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_consensus_polybft_proto_transport_proto_goTypes = []interface{}{ - (*TransportMessage)(nil), // 0: v1.TransportMessage -} -var file_consensus_polybft_proto_transport_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_consensus_polybft_proto_transport_proto_init() } -func file_consensus_polybft_proto_transport_proto_init() { - if File_consensus_polybft_proto_transport_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_consensus_polybft_proto_transport_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TransportMessage); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_consensus_polybft_proto_transport_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_consensus_polybft_proto_transport_proto_goTypes, - DependencyIndexes: file_consensus_polybft_proto_transport_proto_depIdxs, - MessageInfos: file_consensus_polybft_proto_transport_proto_msgTypes, - }.Build() - File_consensus_polybft_proto_transport_proto = out.File - file_consensus_polybft_proto_transport_proto_rawDesc = nil - file_consensus_polybft_proto_transport_proto_goTypes = nil - file_consensus_polybft_proto_transport_proto_depIdxs = nil -} diff --git a/consensus/polybft/proto/transport.pb.validate.go b/consensus/polybft/proto/transport.pb.validate.go deleted file mode 100644 index b198296..0000000 --- a/consensus/polybft/proto/transport.pb.validate.go +++ /dev/null @@ -1,138 +0,0 @@ -// Code generated by protoc-gen-validate. DO NOT EDIT. -// source: consensus/polybft/proto/transport.proto - -package proto - -import ( - "bytes" - "errors" - "fmt" - "net" - "net/mail" - "net/url" - "regexp" - "sort" - "strings" - "time" - "unicode/utf8" - - "google.golang.org/protobuf/types/known/anypb" -) - -// ensure the imports are used -var ( - _ = bytes.MinRead - _ = errors.New("") - _ = fmt.Print - _ = utf8.UTFMax - _ = (*regexp.Regexp)(nil) - _ = (*strings.Reader)(nil) - _ = net.IPv4len - _ = time.Duration(0) - _ = (*url.URL)(nil) - _ = (*mail.Address)(nil) - _ = anypb.Any{} - _ = sort.Sort -) - -// Validate checks the field values on TransportMessage with the rules defined -// in the proto definition for this message. If any rules are violated, the -// first error encountered is returned, or nil if there are no violations. -func (m *TransportMessage) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on TransportMessage with the rules -// defined in the proto definition for this message. If any rules are -// violated, the result is a list of violation errors wrapped in -// TransportMessageMultiError, or nil if none found. -func (m *TransportMessage) ValidateAll() error { - return m.validate(true) -} - -func (m *TransportMessage) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - // no validation rules for Data - - if len(errors) > 0 { - return TransportMessageMultiError(errors) - } - - return nil -} - -// TransportMessageMultiError is an error wrapping multiple validation errors -// returned by TransportMessage.ValidateAll() if the designated constraints -// aren't met. -type TransportMessageMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m TransportMessageMultiError) Error() string { - var msgs []string - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m TransportMessageMultiError) AllErrors() []error { return m } - -// TransportMessageValidationError is the validation error returned by -// TransportMessage.Validate if the designated constraints aren't met. -type TransportMessageValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e TransportMessageValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e TransportMessageValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e TransportMessageValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e TransportMessageValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e TransportMessageValidationError) ErrorName() string { return "TransportMessageValidationError" } - -// Error satisfies the builtin error interface -func (e TransportMessageValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sTransportMessage.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = TransportMessageValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = TransportMessageValidationError{} diff --git a/consensus/polybft/proto/transport.proto b/consensus/polybft/proto/transport.proto deleted file mode 100644 index 329c105..0000000 --- a/consensus/polybft/proto/transport.proto +++ /dev/null @@ -1,9 +0,0 @@ -syntax = "proto3"; - -package v1; - -option go_package = "/consensus/polybft/proto"; - -message TransportMessage { - bytes data = 1; -} \ No newline at end of file diff --git a/consensus/polybft/runtime_helpers.go b/consensus/polybft/runtime_helpers.go deleted file mode 100644 index d1d419a..0000000 --- a/consensus/polybft/runtime_helpers.go +++ /dev/null @@ -1,50 +0,0 @@ -package polybft - -import ( - "github.com/xgr-network/xgr-node/blockchain" - "github.com/xgr-network/xgr-node/types" -) - -// isEndOfPeriod checks if an end of a period (either it be sprint or epoch) -// is reached with the current block (the parent block of the current fsm iteration) -func isEndOfPeriod(blockNumber, periodSize uint64) bool { - return blockNumber%periodSize == 0 -} - -// getBlockData returns block header and extra -func getBlockData(blockNumber uint64, blockchainBackend blockchainBackend) (*types.Header, *Extra, error) { - blockHeader, found := blockchainBackend.GetHeaderByNumber(blockNumber) - if !found { - return nil, nil, blockchain.ErrNoBlock - } - - blockExtra, err := GetIbftExtra(blockHeader.ExtraData) - if err != nil { - return nil, nil, err - } - - return blockHeader, blockExtra, nil -} - -// isEpochEndingBlock checks if given block is an epoch ending block -func isEpochEndingBlock(blockNumber uint64, extra *Extra, blockchain blockchainBackend) (bool, error) { - if extra.Validators == nil { - // non epoch ending blocks have validator set delta as nil - return false, nil - } - - if !extra.Validators.IsEmpty() { - // if validator set delta is not empty, the validator set was changed in this block - // meaning the epoch changed as well - return true, nil - } - - _, nextBlockExtra, err := getBlockData(blockNumber+1, blockchain) - if err != nil { - return false, err - } - - // validator set delta can be empty (no change in validator set happened) - // so we need to check if their epoch numbers are different - return extra.Checkpoint.EpochNumber != nextBlockExtra.Checkpoint.EpochNumber, nil -} diff --git a/consensus/polybft/runtime_helpers_test.go b/consensus/polybft/runtime_helpers_test.go deleted file mode 100644 index 77f7e5b..0000000 --- a/consensus/polybft/runtime_helpers_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package polybft - -import ( - "testing" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/blockchain" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/types" -) - -func TestHelpers_isEpochEndingBlock_DeltaNotEmpty(t *testing.T) { - t.Parallel() - - validators := validator.NewTestValidators(t, 3).GetPublicIdentities() - bitmap := bitmap.Bitmap{} - bitmap.Set(0) - - delta := &validator.ValidatorSetDelta{ - Added: validators[1:], - Removed: bitmap, - } - - extra := &Extra{Validators: delta, Checkpoint: &CheckpointData{EpochNumber: 2}} - blockNumber := uint64(20) - - isEndOfEpoch, err := isEpochEndingBlock(blockNumber, extra, new(blockchainMock)) - require.NoError(t, err) - require.True(t, isEndOfEpoch) -} - -func TestHelpers_isEpochEndingBlock_NoBlock(t *testing.T) { - t.Parallel() - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(&types.Header{}, false) - - extra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 2}, Validators: &validator.ValidatorSetDelta{}} - blockNumber := uint64(20) - - isEndOfEpoch, err := isEpochEndingBlock(blockNumber, extra, blockchainMock) - require.ErrorIs(t, blockchain.ErrNoBlock, err) - require.False(t, isEndOfEpoch) -} - -func TestHelpers_isEpochEndingBlock_EpochsNotTheSame(t *testing.T) { - t.Parallel() - - blockchainMock := new(blockchainMock) - - nextBlockExtra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 3}, Validators: &validator.ValidatorSetDelta{}} - nextBlock := &types.Header{ - Number: 21, - ExtraData: nextBlockExtra.MarshalRLPTo(nil), - } - - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(nextBlock, true) - - extra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 2}, Validators: &validator.ValidatorSetDelta{}} - blockNumber := uint64(20) - - isEndOfEpoch, err := isEpochEndingBlock(blockNumber, extra, blockchainMock) - require.NoError(t, err) - require.True(t, isEndOfEpoch) -} - -func TestHelpers_isEpochEndingBlock_EpochsAreTheSame(t *testing.T) { - t.Parallel() - - blockchainMock := new(blockchainMock) - - nextBlockExtra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 2}, Validators: &validator.ValidatorSetDelta{}} - nextBlock := &types.Header{ - Number: 16, - ExtraData: nextBlockExtra.MarshalRLPTo(nil), - } - - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(nextBlock, true) - - extra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 2}, Validators: &validator.ValidatorSetDelta{}} - blockNumber := uint64(15) - - isEndOfEpoch, err := isEpochEndingBlock(blockNumber, extra, blockchainMock) - require.NoError(t, err) - require.False(t, isEndOfEpoch) -} diff --git a/consensus/polybft/sc_integration_test.go b/consensus/polybft/sc_integration_test.go deleted file mode 100644 index 3638c26..0000000 --- a/consensus/polybft/sc_integration_test.go +++ /dev/null @@ -1,413 +0,0 @@ -package polybft - -import ( - "math" - "math/big" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/crypto" - "github.com/xgr-network/xgr-node/helper/hex" - "github.com/xgr-network/xgr-node/state" - "github.com/xgr-network/xgr-node/types" -) - -func TestIntegration_PerformExit(t *testing.T) { - t.Parallel() - - const gasLimit = 1000000000000 - - // create validator set and checkpoint mngr - currentValidators := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D"}, []uint64{100, 100, 100, 100}) - accSet := currentValidators.GetPublicIdentities() - cm := checkpointManager{blockchain: &blockchainMock{}} - - deployerAddress := types.Address{76, 76, 1} // account that will deploy contracts - senderAddress := types.Address{1} // account that sends exit/withdraw transactions - receiverAddr := types.Address{6} // account that receive tokens - amount1 := big.NewInt(3) // amount of the first widrawal - amount2 := big.NewInt(2) // amount of the second widrawal - bn256Addr := types.Address{2} // bls contract - stateSenderAddr := types.Address{5} // generic bridge contract on rootchain - - alloc := map[types.Address]*chain.GenesisAccount{ - senderAddress: {Balance: new(big.Int).Add(amount1, amount2)}, // give some ethers to sender - deployerAddress: {Balance: ethgo.Ether(100)}, // give 100 ethers to deployer - contracts.BLSContract: {Code: contractsapi.BLS.DeployedBytecode}, - bn256Addr: {Code: contractsapi.BLS256.DeployedBytecode}, - stateSenderAddr: {Code: contractsapi.StateSender.DeployedBytecode}, - } - transition := newTestTransition(t, alloc) - - getField := func(addr types.Address, abi *abi.ABI, function string, args ...interface{}) []byte { - input, err := abi.GetMethod(function).Encode(args) - require.NoError(t, err) - - result := transition.Call2(deployerAddress, addr, input, big.NewInt(0), gasLimit) - require.True(t, result.Succeeded()) - - return result.ReturnValue - } - - // deploy MockERC20 as root chain ERC 20 token - rootERC20Addr := deployAndInitContract(t, transition, contractsapi.RootERC20.Bytecode, deployerAddress, nil) - - // deploy CheckpointManager - checkpointManagerInit := func() ([]byte, error) { - return (&contractsapi.InitializeCheckpointManagerFn{ - NewBls: contracts.BLSContract, - NewBn256G2: bn256Addr, - NewValidatorSet: accSet.ToAPIBinding(), - ChainID_: big.NewInt(0), - }).EncodeAbi() - } - - checkpointMgrConstructor := &contractsapi.CheckpointManagerConstructorFn{Initiator: deployerAddress} - constructorInput, err := checkpointMgrConstructor.EncodeAbi() - require.NoError(t, err) - - checkpointManagerAddr := deployAndInitContract(t, transition, append(contractsapi.CheckpointManager.Bytecode, constructorInput...), deployerAddress, checkpointManagerInit) - - // deploy ExitHelper - exitHelperInit := func() ([]byte, error) { - return (&contractsapi.InitializeExitHelperFn{NewCheckpointManager: checkpointManagerAddr}).EncodeAbi() - } - exitHelperContractAddress := deployAndInitContract(t, transition, contractsapi.ExitHelper.Bytecode, deployerAddress, exitHelperInit) - - // deploy RootERC20Predicate - rootERC20PredicateInit := func() ([]byte, error) { - return (&contractsapi.InitializeRootERC20PredicateFn{ - NewStateSender: stateSenderAddr, - NewExitHelper: exitHelperContractAddress, - NewChildERC20Predicate: contracts.ChildERC20PredicateContract, - NewChildTokenTemplate: contracts.ChildERC20Contract, - NewNativeTokenRoot: contracts.NativeERC20TokenContract, - }).EncodeAbi() - } - rootERC20PredicateAddr := deployAndInitContract(t, transition, contractsapi.RootERC20Predicate.Bytecode, deployerAddress, rootERC20PredicateInit) - - // validate initialization of CheckpointManager - require.Equal(t, getField(checkpointManagerAddr, contractsapi.CheckpointManager.Abi, "currentCheckpointBlockNumber")[31], uint8(0)) - - accSetHash, err := accSet.Hash() - require.NoError(t, err) - - blockHash := types.Hash{5} - blockNumber := uint64(1) - epochNumber := uint64(1) - blockRound := uint64(1) - - // mint - mintInput, err := (&contractsapi.MintRootERC20Fn{ - To: senderAddress, - Amount: alloc[senderAddress].Balance, - }).EncodeAbi() - require.NoError(t, err) - - result := transition.Call2(deployerAddress, rootERC20Addr, mintInput, nil, gasLimit) - require.NoError(t, result.Err) - - // approve - approveInput, err := (&contractsapi.ApproveRootERC20Fn{ - Spender: rootERC20PredicateAddr, - Amount: alloc[senderAddress].Balance, - }).EncodeAbi() - require.NoError(t, err) - - result = transition.Call2(senderAddress, rootERC20Addr, approveInput, big.NewInt(0), gasLimit) - require.NoError(t, result.Err) - - // deposit - depositInput, err := (&contractsapi.DepositToRootERC20PredicateFn{ - RootToken: rootERC20Addr, - Receiver: receiverAddr, - Amount: new(big.Int).Add(amount1, amount2), - }).EncodeAbi() - require.NoError(t, err) - - // send sync events to childchain so that receiver can obtain tokens - result = transition.Call2(senderAddress, rootERC20PredicateAddr, depositInput, big.NewInt(0), gasLimit) - require.NoError(t, result.Err) - - // simulate withdrawal from childchain to rootchain - widthdrawSig := crypto.Keccak256([]byte("WITHDRAW")) - erc20DataType := abi.MustNewType( - "tuple(bytes32 withdrawSignature, address rootToken, address withdrawer, address receiver, uint256 amount)") - - exitData1, err := erc20DataType.Encode(map[string]interface{}{ - "withdrawSignature": widthdrawSig, - "rootToken": ethgo.Address(rootERC20Addr), - "withdrawer": ethgo.Address(senderAddress), - "receiver": ethgo.Address(receiverAddr), - "amount": amount1, - }) - require.NoError(t, err) - - exitData2, err := erc20DataType.Encode(map[string]interface{}{ - "withdrawSignature": widthdrawSig, - "rootToken": ethgo.Address(rootERC20Addr), - "withdrawer": ethgo.Address(senderAddress), - "receiver": ethgo.Address(receiverAddr), - "amount": amount2, - }) - require.NoError(t, err) - - exits := []*ExitEvent{ - { - L2StateSyncedEvent: &contractsapi.L2StateSyncedEvent{ - ID: big.NewInt(1), - Sender: contracts.ChildERC20PredicateContract, - Receiver: rootERC20PredicateAddr, - Data: exitData1, - }, - }, - { - L2StateSyncedEvent: &contractsapi.L2StateSyncedEvent{ - ID: big.NewInt(2), - Sender: contracts.ChildERC20PredicateContract, - Receiver: rootERC20PredicateAddr, - Data: exitData2, - }, - }, - } - exitTree, err := createExitTree(exits) - require.NoError(t, err) - - eventRoot := exitTree.Hash() - - checkpointData := &CheckpointData{ - BlockRound: blockRound, - EpochNumber: epochNumber, - CurrentValidatorsHash: accSetHash, - NextValidatorsHash: accSetHash, - EventRoot: eventRoot, - } - - checkpointHash, err := checkpointData.Hash( - cm.blockchain.GetChainID(), - blockRound, - blockHash) - require.NoError(t, err) - - i := uint64(0) - bmp := bitmap.Bitmap{} - signatures := bls.Signatures(nil) - - currentValidators.IterAcct(nil, func(v *validator.TestValidator) { - signatures = append(signatures, v.MustSign(checkpointHash[:], signer.DomainCheckpointManager)) - bmp.Set(i) - i++ - }) - - aggSignature, err := signatures.Aggregate().Marshal() - require.NoError(t, err) - - extra := &Extra{ - Checkpoint: checkpointData, - Committed: &Signature{ - AggregatedSignature: aggSignature, - Bitmap: bmp, - }, - } - - // submit a checkpoint - submitCheckpointEncoded, err := cm.abiEncodeCheckpointBlock(blockNumber, blockHash, extra, accSet) - require.NoError(t, err) - - result = transition.Call2(senderAddress, checkpointManagerAddr, submitCheckpointEncoded, big.NewInt(0), gasLimit) - require.NoError(t, result.Err) - require.Equal(t, getField(checkpointManagerAddr, contractsapi.CheckpointManager.Abi, "currentCheckpointBlockNumber")[31], uint8(1)) - - // check that the exit hasn't performed - res := getField(exitHelperContractAddress, contractsapi.ExitHelper.Abi, "processedExits", exits[0].ID) - require.Equal(t, 0, int(res[31])) - - proofExitEvent, err := exits[0].L2StateSyncedEvent.Encode() - require.NoError(t, err) - - proof, err := exitTree.GenerateProof(proofExitEvent) - require.NoError(t, err) - - leafIndex, err := exitTree.LeafIndex(proofExitEvent) - require.NoError(t, err) - - exitFnInput, err := (&contractsapi.ExitExitHelperFn{ - BlockNumber: new(big.Int).SetUint64(blockNumber), - LeafIndex: new(big.Int).SetUint64(leafIndex), - UnhashedLeaf: proofExitEvent, - Proof: proof, - }).EncodeAbi() - require.NoError(t, err) - - result = transition.Call2(senderAddress, exitHelperContractAddress, exitFnInput, big.NewInt(0), gasLimit) - require.NoError(t, result.Err) - - // check that first exit event is processed - res = getField(exitHelperContractAddress, contractsapi.ExitHelper.Abi, "processedExits", exits[0].ID) - require.Equal(t, 1, int(res[31])) - - res = getField(rootERC20Addr, contractsapi.RootERC20.Abi, "balanceOf", receiverAddr) - require.Equal(t, amount1, new(big.Int).SetBytes(res)) -} - -func TestIntegration_CommitEpoch(t *testing.T) { - t.Parallel() - - // init validator sets - validatorSetSize := []int{5, 10, 50, 100} - - intialBalance := uint64(5 * math.Pow(10, 18)) // 5 tokens - reward := uint64(math.Pow(10, 18)) // 1 token - walletAddress := types.StringToAddress("1234889893") - - validatorSets := make([]*validator.TestValidators, len(validatorSetSize), len(validatorSetSize)) - - // create all validator sets which will be used in test - for i, size := range validatorSetSize { - aliases := make([]string, size, size) - vps := make([]uint64, size, size) - - for j := 0; j < size; j++ { - aliases[j] = "v" + strconv.Itoa(j) - vps[j] = intialBalance - } - - validatorSets[i] = validator.NewTestValidatorsWithAliases(t, aliases, vps) - } - - // iterate through the validator set and do the test for each of them - for _, currentValidators := range validatorSets { - accSet := currentValidators.GetPublicIdentities() - - // validator data for polybft config - initValidators := make([]*validator.GenesisValidator, accSet.Len()) - // add contracts to genesis data - alloc := map[types.Address]*chain.GenesisAccount{ - contracts.ValidatorSetContract: { - Code: contractsapi.ValidatorSet.DeployedBytecode, - }, - contracts.RewardPoolContract: { - Code: contractsapi.RewardPool.DeployedBytecode, - }, - contracts.NativeERC20TokenContract: { - Code: contractsapi.NativeERC20.DeployedBytecode, - }, - walletAddress: { - Balance: new(big.Int).SetUint64(intialBalance), - }, - } - - for i, val := range accSet { - // add validator to genesis data - alloc[val.Address] = &chain.GenesisAccount{ - Balance: val.VotingPower, - } - - // create validator data for polybft config - initValidators[i] = &validator.GenesisValidator{ - Address: val.Address, - Stake: val.VotingPower, - BlsKey: hex.EncodeToString(val.BlsKey.Marshal()), - } - } - - polyBFTConfig := PolyBFTConfig{ - InitialValidatorSet: initValidators, - EpochSize: 24 * 60 * 60 / 2, - SprintSize: 5, - EpochReward: reward, - // use 1st account as governance address - Governance: currentValidators.ToValidatorSet().Accounts().GetAddresses()[0], - RewardConfig: &RewardsConfig{ - TokenAddress: contracts.NativeERC20TokenContract, - WalletAddress: walletAddress, - WalletAmount: new(big.Int).SetUint64(intialBalance), - }, - Bridge: &BridgeConfig{ - CustomSupernetManagerAddr: types.StringToAddress("0x12312451"), - }, - } - - transition := newTestTransition(t, alloc) - - // init ValidatorSet - err := initValidatorSet(polyBFTConfig, transition) - require.NoError(t, err) - - // init RewardPool - err = initRewardPool(polyBFTConfig, transition) - require.NoError(t, err) - - // approve reward pool as reward token spender - err = approveRewardPoolAsSpender(polyBFTConfig, transition) - require.NoError(t, err) - - // create input for commit epoch - commitEpoch := createTestCommitEpochInput(t, 1, polyBFTConfig.EpochSize) - input, err := commitEpoch.EncodeAbi() - require.NoError(t, err) - - // call commit epoch - result := transition.Call2(contracts.SystemCaller, contracts.ValidatorSetContract, input, big.NewInt(0), 10000000000) - require.NoError(t, result.Err) - t.Logf("Number of validators %d on commit epoch, Gas used %+v\n", accSet.Len(), result.GasUsed) - - // create input for distribute rewards - distributeRewards := createTestDistributeRewardsInput(t, 1, accSet, polyBFTConfig.EpochSize) - input, err = distributeRewards.EncodeAbi() - require.NoError(t, err) - - // call reward distributor - result = transition.Call2(contracts.SystemCaller, contracts.RewardPoolContract, input, big.NewInt(0), 10000000000) - require.NoError(t, result.Err) - t.Logf("Number of validators %d on reward distribution, Gas used %+v\n", accSet.Len(), result.GasUsed) - - commitEpoch = createTestCommitEpochInput(t, 2, polyBFTConfig.EpochSize) - input, err = commitEpoch.EncodeAbi() - require.NoError(t, err) - - // call commit epoch - result = transition.Call2(contracts.SystemCaller, contracts.ValidatorSetContract, input, big.NewInt(0), 10000000000) - require.NoError(t, result.Err) - t.Logf("Number of validators %d on commit epoch, Gas used %+v\n", accSet.Len(), result.GasUsed) - - distributeRewards = createTestDistributeRewardsInput(t, 2, accSet, polyBFTConfig.EpochSize) - input, err = distributeRewards.EncodeAbi() - require.NoError(t, err) - - // call reward distributor - result = transition.Call2(contracts.SystemCaller, contracts.RewardPoolContract, input, big.NewInt(0), 10000000000) - require.NoError(t, result.Err) - t.Logf("Number of validators %d on reward distribution, Gas used %+v\n", accSet.Len(), result.GasUsed) - } -} - -func deployAndInitContract(t *testing.T, transition *state.Transition, bytecode []byte, sender types.Address, - initCallback func() ([]byte, error)) types.Address { - t.Helper() - - deployResult := transition.Create2(sender, bytecode, big.NewInt(0), 1e9) - assert.NoError(t, deployResult.Err) - - if initCallback != nil { - initInput, err := initCallback() - require.NoError(t, err) - - result := transition.Call2(sender, deployResult.Address, initInput, big.NewInt(0), 1e9) - require.NoError(t, result.Err) - } - - return deployResult.Address -} diff --git a/consensus/polybft/signer/utils.go b/consensus/polybft/signer/utils.go deleted file mode 100644 index 80efefe..0000000 --- a/consensus/polybft/signer/utils.go +++ /dev/null @@ -1,60 +0,0 @@ -package signer - -import ( - "bytes" - "math/big" - - "github.com/umbracle/ethgo/abi" - - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/crypto" - "github.com/xgr-network/xgr-node/types" -) - -var ( - addressABIType = abi.MustNewType("address") - uint256ABIType = abi.MustNewType("uint256") -) - -const ( - DomainValidatorSetString = "DOMAIN_CHILD_VALIDATOR_SET" - DomainCheckpointManagerString = "DOMAIN_CHECKPOINT_MANAGER" - DomainCommonSigningString = "DOMAIN_COMMON_SIGNING" - DomainStateReceiverString = "DOMAIN_STATE_RECEIVER" -) - -var ( - // domain used to map hash to G1 used by (child) validator set - DomainValidatorSet = crypto.Keccak256([]byte(DomainValidatorSetString)) - - // domain used to map hash to G1 used by child checkpoint manager - DomainCheckpointManager = crypto.Keccak256([]byte(DomainCheckpointManagerString)) - - DomainCommonSigning = crypto.Keccak256([]byte(DomainCommonSigningString)) - DomainStateReceiver = crypto.Keccak256([]byte(DomainStateReceiverString)) -) - -// MakeKOSKSignature creates KOSK signature which prevents rogue attack -func MakeKOSKSignature(privateKey *bls.PrivateKey, address types.Address, - chainID int64, domain []byte, supernetManagerAddr types.Address) (*bls.Signature, error) { - spenderABI, err := addressABIType.Encode(address) - if err != nil { - return nil, err - } - - supernetManagerABI, err := addressABIType.Encode(supernetManagerAddr) - if err != nil { - return nil, err - } - - chainIDABI, err := uint256ABIType.Encode(big.NewInt(chainID)) - if err != nil { - return nil, err - } - - // ethgo pads address to 32 bytes, but solidity doesn't (keeps it 20 bytes) - // that's why we are skipping first 12 bytes - message := bytes.Join([][]byte{spenderABI[12:], supernetManagerABI[12:], chainIDABI}, nil) - - return privateKey.Sign(message, domain) -} diff --git a/consensus/polybft/signer/utils_test.go b/consensus/polybft/signer/utils_test.go deleted file mode 100644 index f99fd45..0000000 --- a/consensus/polybft/signer/utils_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package signer - -import ( - "encoding/hex" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/types" -) - -func Test_MakeKOSKSignature(t *testing.T) { - t.Parallel() - - expected := "03441ebc4bbca37664455b293cfc371ccf9a4f21e40c19a3a25beee77bf6e97d16cdd264c188a01d415ee99865f52567a402044a1c13aa6915a1237a41d35e9e" - bytes, _ := hex.DecodeString("3139343634393730313533353434353137333331343333303931343932303731313035313730303336303738373134363131303435323837383335373237343933383834303135343336383231") - - pk, err := bls.UnmarshalPrivateKey(bytes) - require.NoError(t, err) - - supernetManagerAddr := types.StringToAddress("0x1010101") - address := types.BytesToAddress((pk.PublicKey().Marshal())[:types.AddressLength]) - - signature, err := MakeKOSKSignature(pk, address, 10, DomainValidatorSet, supernetManagerAddr) - require.NoError(t, err) - - signatureBytes, err := signature.Marshal() - require.NoError(t, err) - - assert.Equal(t, expected, hex.EncodeToString(signatureBytes)) - - signature, err = MakeKOSKSignature(pk, address, 100, DomainValidatorSet, supernetManagerAddr) - require.NoError(t, err) - - signatureBytes, err = signature.Marshal() - require.NoError(t, err) - - assert.NotEqual(t, expected, hex.EncodeToString(signatureBytes)) -} diff --git a/consensus/polybft/stake_manager.go b/consensus/polybft/stake_manager.go deleted file mode 100644 index 46c2ab9..0000000 --- a/consensus/polybft/stake_manager.go +++ /dev/null @@ -1,526 +0,0 @@ -package polybft - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "math/big" - "sort" - "strings" - - "github.com/hashicorp/go-hclog" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/helper/hex" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" -) - -var ( - bigZero = big.NewInt(0) - validatorTypeABI = abi.MustNewType("tuple(uint256[4] blsKey, uint256 stake, bool isWhitelisted, bool isActive)") -) - -// StakeManager interface provides functions for handling stake change of validators -// and updating validator set based on changed stake -type StakeManager interface { - EventSubscriber - PostBlock(req *PostBlockRequest) error - UpdateValidatorSet(epoch uint64, currentValidatorSet validator.AccountSet) (*validator.ValidatorSetDelta, error) -} - -var _ StakeManager = (*dummyStakeManager)(nil) - -// dummyStakeManager is a dummy implementation of StakeManager interface -// used only for unit testing -type dummyStakeManager struct{} - -func (d *dummyStakeManager) PostBlock(req *PostBlockRequest) error { return nil } -func (d *dummyStakeManager) UpdateValidatorSet(epoch uint64, - currentValidatorSet validator.AccountSet) (*validator.ValidatorSetDelta, error) { - return &validator.ValidatorSetDelta{}, nil -} - -// EventSubscriber implementation -func (d *dummyStakeManager) GetLogFilters() map[types.Address][]types.Hash { - return make(map[types.Address][]types.Hash) -} -func (d *dummyStakeManager) ProcessLog(header *types.Header, log *ethgo.Log, dbTx *bolt.Tx) error { - return nil -} - -var _ StakeManager = (*stakeManager)(nil) - -// stakeManager saves transfer events that happened in each block -// and calculates updated validator set based on changed stake -type stakeManager struct { - logger hclog.Logger - state *State - rootChainRelayer txrelayer.TxRelayer - key ethgo.Key - supernetManagerContract types.Address - validatorSetContract types.Address - maxValidatorSetSize int - polybftBackend polybftBackend -} - -// newStakeManager returns a new instance of stake manager -func newStakeManager( - logger hclog.Logger, - state *State, - rootchainRelayer txrelayer.TxRelayer, - key ethgo.Key, - validatorSetAddr, supernetManagerAddr types.Address, - blockchain blockchainBackend, - polybftBackend polybftBackend, - maxValidatorSetSize int, - dbTx *bolt.Tx, -) (*stakeManager, error) { - sm := &stakeManager{ - logger: logger, - state: state, - rootChainRelayer: rootchainRelayer, - key: key, - supernetManagerContract: supernetManagerAddr, - validatorSetContract: validatorSetAddr, - maxValidatorSetSize: maxValidatorSetSize, - polybftBackend: polybftBackend, - } - - if err := sm.init(blockchain, dbTx); err != nil { - return nil, err - } - - return sm, nil -} - -// PostBlock is called on every insert of finalized block (either from consensus or syncer) -// It will update the fullValidatorSet in db to the current block number -// Note that EventSubscriber - AddLog will get all the transfer events that happened in block -func (s *stakeManager) PostBlock(req *PostBlockRequest) error { - fullValidatorSet, err := s.getOrInitValidatorSet(req.DBTx) - if err != nil { - return err - } - - blockNumber := req.FullBlock.Block.Number() - - s.logger.Debug("Stake manager on post block", - "block", blockNumber, - "last saved", fullValidatorSet.BlockNumber, - "last updated", fullValidatorSet.UpdatedAtBlockNumber) - - // we should save new state even if number of events is zero - // because otherwise next time we will process more blocks - fullValidatorSet.EpochID = req.Epoch - fullValidatorSet.BlockNumber = blockNumber - - return s.state.StakeStore.insertFullValidatorSet(fullValidatorSet, req.DBTx) -} - -func (s *stakeManager) init(blockchain blockchainBackend, dbTx *bolt.Tx) error { - currentHeader := blockchain.CurrentHeader() - currentBlockNumber := currentHeader.Number - - validatorSet, err := s.getOrInitValidatorSet(dbTx) - if err != nil { - return err - } - - // early return if current block is already processed - if validatorSet.BlockNumber == currentBlockNumber { - return nil - } - - // retrieve epoch needed for state - epochID, err := getEpochID(blockchain, currentHeader) - if err != nil { - return err - } - - s.logger.Debug("Stake manager on post block", - "block", currentBlockNumber, - "last saved", validatorSet.BlockNumber, - "last updated", validatorSet.UpdatedAtBlockNumber) - - // we will use eventsGetter to update the fullValidatorSet if - // for any reason, we don't have the correct state - eventsGetter := &eventsGetter[*contractsapi.TransferEvent]{ - receiptsGetter: receiptsGetter{ - blockchain: blockchain, - }, - isValidLogFn: func(l *types.Log) bool { - return l.Address == s.validatorSetContract - }, - parseEventFn: func(h *types.Header, l *ethgo.Log) (*contractsapi.TransferEvent, bool, error) { - var transferEvent contractsapi.TransferEvent - doesMatch, err := transferEvent.ParseLog(l) - - return &transferEvent, doesMatch, err - }, - } - - transferEvents, err := eventsGetter.getEventsFromBlocksRange(validatorSet.BlockNumber+1, currentBlockNumber) - if err != nil { - return err - } - - if err := s.updateWithReceipts(&validatorSet, transferEvents, currentBlockNumber); err != nil { - return err - } - - // we should save new state even if number of events is zero - // because otherwise next time we will process more blocks - validatorSet.EpochID = epochID - validatorSet.BlockNumber = currentBlockNumber - - return s.state.StakeStore.insertFullValidatorSet(validatorSet, dbTx) -} - -func (s *stakeManager) getOrInitValidatorSet(dbTx *bolt.Tx) (validatorSetState, error) { - validatorSet, err := s.state.StakeStore.getFullValidatorSet(dbTx) - if err != nil { - if !errors.Is(err, errNoFullValidatorSet) { - return validatorSetState{}, err - } - - validators, err := s.polybftBackend.GetValidatorsWithTx(0, nil, dbTx) - if err != nil { - return validatorSetState{}, err - } - - validatorSet = validatorSetState{ - BlockNumber: 0, - EpochID: 0, - UpdatedAtBlockNumber: 0, - Validators: newValidatorStakeMap(validators), - } - - if err = s.state.StakeStore.insertFullValidatorSet(validatorSet, dbTx); err != nil { - return validatorSetState{}, err - } - } - - return validatorSet, nil -} - -func (s *stakeManager) updateWithReceipts( - fullValidatorSet *validatorSetState, - transferEvents []*contractsapi.TransferEvent, - blockNumber uint64) error { - if len(transferEvents) == 0 { - return nil - } - - for _, event := range transferEvents { - if event.IsStake() { - s.logger.Debug("Stake transfer event", "to", event.To, "value", event.Value) - - // then this amount was minted To validator address - fullValidatorSet.Validators.addStake(event.To, event.Value) - } else if event.IsUnstake() { - s.logger.Debug("Unstake transfer event", "from", event.From, "value", event.Value) - - // then this amount was burned From validator address - fullValidatorSet.Validators.removeStake(event.From, event.Value) - } else { - // this should not happen, but lets log it if it does - s.logger.Warn("Found a transfer event that represents neither stake nor unstake") - } - } - - for addr, data := range fullValidatorSet.Validators { - if data.BlsKey == nil { - blsKey, err := s.getBlsKey(data.Address) - if err != nil { - s.logger.Warn("Could not get info for new validator", - "block", blockNumber, "address", addr) - } - - data.BlsKey = blsKey - } - - data.IsActive = data.VotingPower.Cmp(bigZero) > 0 - } - - // mark on which block validator set has been updated - fullValidatorSet.UpdatedAtBlockNumber = blockNumber - - s.logger.Debug("Full validator set after", "block", blockNumber, "data", fullValidatorSet.Validators) - - return nil -} - -// UpdateValidatorSet returns an updated validator set -// based on stake change (transfer) events from ValidatorSet contract -func (s *stakeManager) UpdateValidatorSet( - epoch uint64, oldValidatorSet validator.AccountSet) (*validator.ValidatorSetDelta, error) { - s.logger.Info("Calculating validators set update...", "epoch", epoch) - - fullValidatorSet, err := s.state.StakeStore.getFullValidatorSet(nil) - if err != nil { - return nil, fmt.Errorf("failed to get full validators set. Epoch: %d. Error: %w", epoch, err) - } - - // stake map that holds stakes for all validators - stakeMap := fullValidatorSet.Validators - - // slice of all validator set - newValidatorSet := stakeMap.getSorted(s.maxValidatorSetSize) - // set of all addresses that will be in next validator set - addressesSet := make(map[types.Address]struct{}, len(newValidatorSet)) - - for _, si := range newValidatorSet { - addressesSet[si.Address] = struct{}{} - } - - removedBitmap := bitmap.Bitmap{} - updatedValidators := validator.AccountSet{} - addedValidators := validator.AccountSet{} - oldActiveMap := make(map[types.Address]*validator.ValidatorMetadata) - - for i, validator := range oldValidatorSet { - oldActiveMap[validator.Address] = validator - // remove existing validators from validator set if they did not make it to the set - if _, exists := addressesSet[validator.Address]; !exists { - removedBitmap.Set(uint64(i)) - } - } - - for _, newValidator := range newValidatorSet { - // check if its already in existing validator set - if oldValidator, exists := oldActiveMap[newValidator.Address]; exists { - if oldValidator.VotingPower.Cmp(newValidator.VotingPower) != 0 { - updatedValidators = append(updatedValidators, newValidator) - } - } else { - if newValidator.BlsKey == nil { - newValidator.BlsKey, err = s.getBlsKey(newValidator.Address) - if err != nil { - return nil, fmt.Errorf("could not retrieve validator data. Address: %v. Error: %w", - newValidator.Address, err) - } - } - - addedValidators = append(addedValidators, newValidator) - } - } - - s.logger.Info("Calculating validators set update finished.", "epoch", epoch) - - delta := &validator.ValidatorSetDelta{ - Added: addedValidators, - Updated: updatedValidators, - Removed: removedBitmap, - } - - if s.logger.IsDebug() { - newValidatorSet, err := oldValidatorSet.Copy().ApplyDelta(delta) - if err != nil { - return nil, err - } - - s.logger.Debug("New validator set", "validatorSet", newValidatorSet) - } - - return delta, nil -} - -// getBlsKey returns bls key for validator from the supernet contract -func (s *stakeManager) getBlsKey(address types.Address) (*bls.PublicKey, error) { - getValidatorFn := &contractsapi.GetValidatorCustomSupernetManagerFn{ - Validator_: address, - } - - encoded, err := getValidatorFn.EncodeAbi() - if err != nil { - return nil, err - } - - response, err := s.rootChainRelayer.Call( - s.key.Address(), - ethgo.Address(s.supernetManagerContract), - encoded) - if err != nil { - return nil, fmt.Errorf("failed to invoke validators function on the supernet manager: %w", err) - } - - byteResponse, err := hex.DecodeHex(response) - if err != nil { - return nil, fmt.Errorf("unable to decode hex response, %w", err) - } - - decoded, err := validatorTypeABI.Decode(byteResponse) - if err != nil { - return nil, err - } - - output, ok := decoded.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("could not convert decoded outputs to map") - } - - blsKey, ok := output["blsKey"].([4]*big.Int) - if !ok { - return nil, fmt.Errorf("failed to decode blskey") - } - - pubKey, err := bls.UnmarshalPublicKeyFromBigInt(blsKey) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal BLS public key: %w", err) - } - - return pubKey, nil -} - -// EventSubscriber implementation - -// GetLogFilters returns a map of log filters for getting desired events, -// where the key is the address of contract that emits desired events, -// and the value is a slice of signatures of events we want to get. -// This function is the implementation of EventSubscriber interface -func (s *stakeManager) GetLogFilters() map[types.Address][]types.Hash { - var transferEvent contractsapi.TransferEvent - - return map[types.Address][]types.Hash{ - s.validatorSetContract: {types.Hash(transferEvent.Sig())}, - } -} - -// ProcessLog is the implementation of EventSubscriber interface, -// used to handle a log defined in GetLogFilters, provided by event provider -func (s *stakeManager) ProcessLog(header *types.Header, log *ethgo.Log, dbTx *bolt.Tx) error { - var transferEvent contractsapi.TransferEvent - - doesMatch, err := transferEvent.ParseLog(log) - if err != nil { - return err - } - - if !doesMatch { - return nil - } - - fullValidatorSet, err := s.getOrInitValidatorSet(dbTx) - if err != nil { - return err - } - - if err := s.updateWithReceipts(&fullValidatorSet, - []*contractsapi.TransferEvent{&transferEvent}, header.Number); err != nil { - return err - } - - return s.state.StakeStore.insertFullValidatorSet(fullValidatorSet, dbTx) -} - -type validatorSetState struct { - BlockNumber uint64 `json:"block"` - EpochID uint64 `json:"epoch"` - UpdatedAtBlockNumber uint64 `json:"updated_at_block"` - Validators validatorStakeMap `json:"validators"` -} - -func (vs validatorSetState) Marshal() ([]byte, error) { - return json.Marshal(vs) -} - -func (vs *validatorSetState) Unmarshal(b []byte) error { - return json.Unmarshal(b, vs) -} - -// validatorStakeMap holds ValidatorMetadata for each validator address -type validatorStakeMap map[types.Address]*validator.ValidatorMetadata - -// newValidatorStakeMap returns a new instance of validatorStakeMap -func newValidatorStakeMap(validatorSet validator.AccountSet) validatorStakeMap { - stakeMap := make(validatorStakeMap, len(validatorSet)) - - for _, v := range validatorSet { - stakeMap[v.Address] = v.Copy() - } - - return stakeMap -} - -// addStake adds given amount to a validator defined by address -func (sc *validatorStakeMap) addStake(address types.Address, amount *big.Int) { - if metadata, exists := (*sc)[address]; exists { - metadata.VotingPower.Add(metadata.VotingPower, amount) - metadata.IsActive = metadata.VotingPower.Cmp(bigZero) > 0 - } else { - (*sc)[address] = &validator.ValidatorMetadata{ - VotingPower: new(big.Int).Set(amount), - Address: address, - IsActive: amount.Cmp(bigZero) > 0, - } - } -} - -// removeStake removes given amount from validator defined by address -func (sc *validatorStakeMap) removeStake(address types.Address, amount *big.Int) { - stakeData := (*sc)[address] - stakeData.VotingPower.Sub(stakeData.VotingPower, amount) - stakeData.IsActive = stakeData.VotingPower.Cmp(bigZero) > 0 -} - -// getSorted returns validators (*ValidatorMetadata) in sorted order -func (sc validatorStakeMap) getSorted(maxValidatorSetSize int) validator.AccountSet { - activeValidators := make(validator.AccountSet, 0, len(sc)) - - for _, v := range sc { - if v.VotingPower.Cmp(bigZero) > 0 { - activeValidators = append(activeValidators, v) - } - } - - sort.Slice(activeValidators, func(i, j int) bool { - v1, v2 := activeValidators[i], activeValidators[j] - - switch v1.VotingPower.Cmp(v2.VotingPower) { - case 1: - return true - case 0: - return bytes.Compare(v1.Address[:], v2.Address[:]) < 0 - default: - return false - } - }) - - if len(activeValidators) <= maxValidatorSetSize { - return activeValidators - } - - return activeValidators[:maxValidatorSetSize] -} - -func (sc validatorStakeMap) String() string { - var sb strings.Builder - - for _, x := range sc.getSorted(len(sc)) { - bls := "" - if x.BlsKey != nil { - bls = hex.EncodeToString(x.BlsKey.Marshal()) - } - - sb.WriteString(fmt.Sprintf("%s:%s:%s:%t\n", - x.Address, x.VotingPower, bls, x.IsActive)) - } - - return sb.String() -} - -func getEpochID(blockchain blockchainBackend, header *types.Header) (uint64, error) { - provider, err := blockchain.GetStateProviderForBlock(header) - if err != nil { - return 0, err - } - - return blockchain.GetSystemState(provider).GetEpoch() -} diff --git a/consensus/polybft/stake_manager_fuzz_test.go b/consensus/polybft/stake_manager_fuzz_test.go deleted file mode 100644 index 0f2e61a..0000000 --- a/consensus/polybft/stake_manager_fuzz_test.go +++ /dev/null @@ -1,248 +0,0 @@ -package polybft - -import ( - "encoding/json" - "errors" - "fmt" - "math/big" - "reflect" - "testing" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/types" -) - -type epochIDValidatorsF struct { - EpochID uint64 - Validators []*validator.ValidatorMetadata -} - -type postBlockStructF struct { - EpochID uint64 - ValidatorID uint64 - BlockID uint64 - StakeValue uint64 -} - -type updateValidatorSetF struct { - EpochID uint64 - Index uint64 - VotingPower int64 -} - -func FuzzTestStakeManagerPostBlock(f *testing.F) { - var ( - allAliases = []string{"A", "B", "C", "D", "E", "F"} - initialSetAliases = []string{"A", "B", "C", "D", "E"} - validators = validator.NewTestValidatorsWithAliases(f, allAliases) - state = newTestState(f) - ) - - seeds := []postBlockStructF{ - { - EpochID: 0, - ValidatorID: 1, - BlockID: 1, - StakeValue: 30, - }, - { - EpochID: 5, - ValidatorID: 30, - BlockID: 4, - StakeValue: 60, - }, - { - EpochID: 1, - ValidatorID: 42, - BlockID: 11, - StakeValue: 70, - }, - { - EpochID: 7, - ValidatorID: 1, - BlockID: 2, - StakeValue: 10, - }, - } - - for _, seed := range seeds { - data, err := json.Marshal(seed) - if err != nil { - return - } - - f.Add(data) - } - - f.Fuzz(func(t *testing.T, input []byte) { - t.Parallel() - - var data postBlockStructF - if err := json.Unmarshal(input, &data); err != nil { - t.Skip(err) - } - - if err := ValidateStruct(data); err != nil { - t.Skip(err) - } - - if data.ValidatorID > uint64(len(initialSetAliases)-1) { - t.Skip() - } - - validatorSetAddr := types.StringToAddress("0x0001") - - bcMock := new(blockchainMock) - for i := 0; i < int(data.BlockID); i++ { - bcMock.On("CurrentHeader").Return(&types.Header{Number: 0}) - bcMock.On("GetHeaderByNumber", mock.Anything).Return(&types.Header{Hash: types.Hash{6, 4}}, true).Once() - bcMock.On("GetReceiptsByHash", mock.Anything).Return([]*types.Receipt{{}}, error(nil)).Once() - } - - // insert initial full validator set - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(validators.GetPublicIdentities(initialSetAliases...)), - }, nil)) - - stakeManager, err := newStakeManager( - hclog.NewNullLogger(), - state, - nil, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), - validatorSetAddr, - types.StringToAddress("0x0002"), - bcMock, - nil, - 5, - nil, - ) - require.NoError(t, err) - - header := &types.Header{Number: data.BlockID} - require.NoError(t, stakeManager.ProcessLog(header, convertLog(createTestLogForTransferEvent( - t, - validatorSetAddr, - validators.GetValidator(initialSetAliases[data.ValidatorID]).Address(), - types.ZeroAddress, - data.StakeValue, - )), nil)) - - require.NoError(t, stakeManager.PostBlock(&PostBlockRequest{ - FullBlock: &types.FullBlock{Block: &types.Block{Header: &types.Header{Number: data.BlockID}}}, - Epoch: data.EpochID, - })) - }) -} - -func FuzzTestStakeManagerUpdateValidatorSet(f *testing.F) { - var ( - aliases = []string{"A", "B", "C", "D", "E"} - stakes = []uint64{10, 10, 10, 10, 10} - ) - - validators := validator.NewTestValidatorsWithAliases(f, aliases, stakes) - state := newTestState(f) - - bcMock := new(blockchainMock) - bcMock.On("CurrentHeader").Return(&types.Header{Number: 0}) - - err := state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(validators.GetPublicIdentities())}, nil) - require.NoError(f, err) - - stakeManager, err := newStakeManager( - hclog.NewNullLogger(), - state, - nil, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), - types.StringToAddress("0x0001"), types.StringToAddress("0x0002"), - bcMock, - nil, - 10, - nil, - ) - require.NoError(f, err) - - seeds := []updateValidatorSetF{ - { - EpochID: 0, - Index: 1, - VotingPower: 30, - }, - { - EpochID: 1, - Index: 4, - VotingPower: 1, - }, - { - EpochID: 2, - Index: 3, - VotingPower: -2, - }, - } - - for _, seed := range seeds { - data, err := json.Marshal(seed) - if err != nil { - return - } - - f.Add(data) - } - - f.Fuzz(func(t *testing.T, input []byte) { - var data updateValidatorSetF - if err := json.Unmarshal(input, &data); err != nil { - t.Skip(err) - } - - if err := ValidateStruct(data); err != nil { - t.Skip(err) - } - - if data.Index > uint64(len(aliases)-1) { - t.Skip() - } - - err := state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(validators.GetPublicIdentities())}, nil) - require.NoError(t, err) - - _, err = stakeManager.UpdateValidatorSet(data.EpochID, validators.GetPublicIdentities(aliases[data.Index:]...)) - require.NoError(t, err) - - fullValidatorSet := validators.GetPublicIdentities().Copy() - validatorToUpdate := fullValidatorSet[data.Index] - validatorToUpdate.VotingPower = big.NewInt(data.VotingPower) - - _, err = stakeManager.UpdateValidatorSet(data.EpochID, validators.GetPublicIdentities()) - require.NoError(t, err) - }) -} - -func ValidateStruct(s interface{}) (err error) { - structType := reflect.TypeOf(s) - if structType.Kind() != reflect.Struct { - return errors.New("input param should be a struct") - } - - structVal := reflect.ValueOf(s) - fieldNum := structVal.NumField() - - for i := 0; i < fieldNum; i++ { - field := structVal.Field(i) - fieldName := structType.Field(i).Name - - isSet := field.IsValid() && !field.IsZero() - - if !isSet { - err = fmt.Errorf("%w%s is not set; ", err, fieldName) - } - } - - return err -} diff --git a/consensus/polybft/stake_manager_test.go b/consensus/polybft/stake_manager_test.go deleted file mode 100644 index 8b78a6b..0000000 --- a/consensus/polybft/stake_manager_test.go +++ /dev/null @@ -1,580 +0,0 @@ -package polybft - -import ( - "math/big" - "testing" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - "github.com/umbracle/ethgo/jsonrpc" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/helper/hex" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" -) - -func TestStakeManager_PostBlock(t *testing.T) { - t.Parallel() - - var ( - allAliases = []string{"A", "B", "C", "D", "E", "F"} - initialSetAliases = []string{"A", "B", "C", "D", "E"} - epoch = uint64(1) - block = uint64(10) - newStake = uint64(100) - firstValidator = uint64(0) - secondValidator = uint64(1) - validatorSetAddr = types.StringToAddress("0x0001") - ) - - t.Run("PostBlock - unstake to zero", func(t *testing.T) { - t.Parallel() - - state := newTestState(t) - - bcMock := new(blockchainMock) - bcMock.On("CurrentHeader").Return(&types.Header{Number: block - 1}, true).Once() - - validators := validator.NewTestValidatorsWithAliases(t, allAliases) - - // insert initial full validator set - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(validators.GetPublicIdentities(initialSetAliases...)), - BlockNumber: block - 1, - }, nil)) - - stakeManager, err := newStakeManager( - hclog.NewNullLogger(), - state, - nil, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), - validatorSetAddr, types.StringToAddress("0x0002"), - bcMock, - nil, - 5, - nil, - ) - require.NoError(t, err) - - header := &types.Header{Number: block} - - require.NoError(t, stakeManager.ProcessLog(header, convertLog(createTestLogForTransferEvent( - t, - validatorSetAddr, - validators.GetValidator(initialSetAliases[firstValidator]).Address(), - types.ZeroAddress, - 1, // initial validator stake was 1 - )), nil)) - - req := &PostBlockRequest{ - FullBlock: &types.FullBlock{Block: &types.Block{Header: header}}, - Epoch: epoch, - } - - require.NoError(t, stakeManager.PostBlock(req)) - - fullValidatorSet, err := state.StakeStore.getFullValidatorSet(nil) - require.NoError(t, err) - var firstValidatorMeta *validator.ValidatorMetadata - firstValidatorMeta = nil - for _, validator := range fullValidatorSet.Validators { - if validator.Address.String() == validators.GetValidator(initialSetAliases[firstValidator]).Address().String() { - firstValidatorMeta = validator - } - } - require.NotNil(t, firstValidatorMeta) - require.Equal(t, bigZero, firstValidatorMeta.VotingPower) - require.False(t, firstValidatorMeta.IsActive) - }) - t.Run("PostBlock - add stake to one validator", func(t *testing.T) { - t.Parallel() - - state := newTestState(t) - - bcMock := new(blockchainMock) - bcMock.On("CurrentHeader").Return(&types.Header{Number: block - 1}, true).Once() - - validators := validator.NewTestValidatorsWithAliases(t, allAliases) - - // insert initial full validator set - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(validators.GetPublicIdentities(initialSetAliases...)), - BlockNumber: block - 1, - }, nil)) - - stakeManager, err := newStakeManager( - hclog.NewNullLogger(), - state, - nil, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), - types.StringToAddress("0x0001"), types.StringToAddress("0x0002"), - bcMock, - nil, - 5, - nil, - ) - require.NoError(t, err) - - header := &types.Header{Number: block} - require.NoError(t, stakeManager.ProcessLog(header, convertLog(createTestLogForTransferEvent( - t, - validatorSetAddr, - types.ZeroAddress, - validators.GetValidator(initialSetAliases[secondValidator]).Address(), - 250, - )), nil)) - - req := &PostBlockRequest{ - FullBlock: &types.FullBlock{Block: &types.Block{Header: header}}, - Epoch: epoch, - } - - require.NoError(t, stakeManager.PostBlock(req)) - - fullValidatorSet, err := state.StakeStore.getFullValidatorSet(nil) - require.NoError(t, err) - var firstValidator *validator.ValidatorMetadata - firstValidator = nil - for _, validator := range fullValidatorSet.Validators { - if validator.Address.String() == validators.GetValidator(initialSetAliases[secondValidator]).Address().String() { - firstValidator = validator - } - } - require.NotNil(t, firstValidator) - require.Equal(t, big.NewInt(251), firstValidator.VotingPower) // 250 + initial 1 - require.True(t, firstValidator.IsActive) - }) - - t.Run("PostBlock - add validator and stake", func(t *testing.T) { - t.Parallel() - - state := newTestState(t) - - validators := validator.NewTestValidatorsWithAliases(t, allAliases, []uint64{1, 2, 3, 4, 5, 6}) - - txRelayerMock := newDummyStakeTxRelayer(t, func() *validator.ValidatorMetadata { - return validators.GetValidator("F").ValidatorMetadata() - }) - - // just mock the call however, the dummy relayer should do its magic - txRelayerMock.On("Call", mock.Anything, mock.Anything, mock.Anything). - Return(nil, error(nil)) - - bcMock := new(blockchainMock) - bcMock.On("CurrentHeader").Return(&types.Header{Number: block - 1}, true).Once() - - // insert initial full validator set - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(validators.GetPublicIdentities(initialSetAliases...)), - BlockNumber: block - 1, - }, nil)) - - stakeManager, err := newStakeManager( - hclog.NewNullLogger(), - state, - txRelayerMock, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), - types.StringToAddress("0x0001"), types.StringToAddress("0x0002"), - bcMock, - nil, - 5, - nil, - ) - require.NoError(t, err) - - header := &types.Header{Number: block} - - for i := 0; i < len(allAliases); i++ { - require.NoError(t, stakeManager.ProcessLog(header, convertLog(createTestLogForTransferEvent( - t, - validatorSetAddr, - types.ZeroAddress, - validators.GetValidator(allAliases[i]).Address(), - newStake, - )), nil)) - } - - req := &PostBlockRequest{ - FullBlock: &types.FullBlock{Block: &types.Block{Header: header}}, - Epoch: epoch, - } - - require.NoError(t, stakeManager.PostBlock(req)) - - fullValidatorSet, err := state.StakeStore.getFullValidatorSet(nil) - require.NoError(t, err) - require.Len(t, fullValidatorSet.Validators, len(allAliases)) - - validatorsCount := validators.ToValidatorSet().Len() - for i, v := range fullValidatorSet.Validators.getSorted(validatorsCount) { - require.Equal(t, newStake+uint64(validatorsCount)-uint64(i)-1, v.VotingPower.Uint64()) - } - }) -} - -func TestStakeManager_UpdateValidatorSet(t *testing.T) { - var ( - aliases = []string{"A", "B", "C", "D", "E"} - stakes = []uint64{10, 10, 10, 10, 10} - epoch = uint64(1) - ) - - validators := validator.NewTestValidatorsWithAliases(t, aliases, stakes) - state := newTestState(t) - - bcMock := new(blockchainMock) - bcMock.On("CurrentHeader").Return(&types.Header{Number: 0}, true).Once() - - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(validators.ToValidatorSet().Accounts()), - }, nil)) - - stakeManager, err := newStakeManager( - hclog.NewNullLogger(), - state, - nil, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), - types.StringToAddress("0x0001"), types.StringToAddress("0x0002"), - bcMock, - nil, - 10, - nil, - ) - require.NoError(t, err) - - t.Run("UpdateValidatorSet - only update", func(t *testing.T) { - fullValidatorSet := validators.GetPublicIdentities().Copy() - validatorToUpdate := fullValidatorSet[0] - validatorToUpdate.VotingPower = big.NewInt(11) - - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(fullValidatorSet), - }, nil)) - - updateDelta, err := stakeManager.UpdateValidatorSet(epoch, validators.GetPublicIdentities()) - require.NoError(t, err) - require.Len(t, updateDelta.Added, 0) - require.Len(t, updateDelta.Updated, 1) - require.Len(t, updateDelta.Removed, 0) - require.Equal(t, updateDelta.Updated[0].Address, validatorToUpdate.Address) - require.Equal(t, updateDelta.Updated[0].VotingPower.Uint64(), validatorToUpdate.VotingPower.Uint64()) - }) - - t.Run("UpdateValidatorSet - one unstake", func(t *testing.T) { - fullValidatorSet := validators.GetPublicIdentities(aliases[1:]...) - - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(fullValidatorSet), - }, nil)) - - updateDelta, err := stakeManager.UpdateValidatorSet(epoch+1, validators.GetPublicIdentities()) - require.NoError(t, err) - require.Len(t, updateDelta.Added, 0) - require.Len(t, updateDelta.Updated, 0) - require.Len(t, updateDelta.Removed, 1) - }) - - t.Run("UpdateValidatorSet - one new validator", func(t *testing.T) { - addedValidator := validators.GetValidator("A") - - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(validators.GetPublicIdentities()), - }, nil)) - - updateDelta, err := stakeManager.UpdateValidatorSet(epoch+2, - validators.GetPublicIdentities(aliases[1:]...)) - require.NoError(t, err) - require.Len(t, updateDelta.Added, 1) - require.Len(t, updateDelta.Updated, 0) - require.Len(t, updateDelta.Removed, 0) - require.Equal(t, addedValidator.Address(), updateDelta.Added[0].Address) - require.Equal(t, addedValidator.VotingPower, updateDelta.Added[0].VotingPower.Uint64()) - }) - t.Run("UpdateValidatorSet - remove some stake", func(t *testing.T) { - fullValidatorSet := validators.GetPublicIdentities().Copy() - validatorToUpdate := fullValidatorSet[2] - validatorToUpdate.VotingPower = big.NewInt(5) - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(fullValidatorSet), - }, nil)) - - updateDelta, err := stakeManager.UpdateValidatorSet(epoch+3, validators.GetPublicIdentities()) - require.NoError(t, err) - require.Len(t, updateDelta.Added, 0) - require.Len(t, updateDelta.Updated, 1) - require.Len(t, updateDelta.Removed, 0) - require.Equal(t, updateDelta.Updated[0].Address, validatorToUpdate.Address) - require.Equal(t, updateDelta.Updated[0].VotingPower.Uint64(), validatorToUpdate.VotingPower.Uint64()) - }) - t.Run("UpdateValidatorSet - remove entire stake", func(t *testing.T) { - fullValidatorSet := validators.GetPublicIdentities().Copy() - validatorToUpdate := fullValidatorSet[3] - validatorToUpdate.VotingPower = bigZero - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(fullValidatorSet), - }, nil)) - - updateDelta, err := stakeManager.UpdateValidatorSet(epoch+4, validators.GetPublicIdentities()) - require.NoError(t, err) - require.Len(t, updateDelta.Added, 0) - require.Len(t, updateDelta.Updated, 0) - require.Len(t, updateDelta.Removed, 1) - }) - t.Run("UpdateValidatorSet - voting power negative", func(t *testing.T) { - fullValidatorSet := validators.GetPublicIdentities().Copy() - validatorsToUpdate := fullValidatorSet[4] - validatorsToUpdate.VotingPower = bigZero - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(fullValidatorSet), - }, nil)) - - updateDelta, err := stakeManager.UpdateValidatorSet(epoch+5, validators.GetPublicIdentities()) - require.NoError(t, err) - require.Len(t, updateDelta.Added, 0) - require.Len(t, updateDelta.Updated, 0) - require.Len(t, updateDelta.Removed, 1) - }) - - t.Run("UpdateValidatorSet - max validator set size reached", func(t *testing.T) { - // because we now have 5 validators, and the new validator has more stake - stakeManager.maxValidatorSetSize = 4 - - fullValidatorSet := validators.GetPublicIdentities().Copy() - validatorToAdd := fullValidatorSet[0] - validatorToAdd.VotingPower = big.NewInt(11) - - require.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - Validators: newValidatorStakeMap(fullValidatorSet), - }, nil)) - - updateDelta, err := stakeManager.UpdateValidatorSet(epoch+6, - validators.GetPublicIdentities(aliases[1:]...)) - - require.NoError(t, err) - require.Len(t, updateDelta.Added, 1) - require.Len(t, updateDelta.Updated, 0) - require.Len(t, updateDelta.Removed, 1) - require.Equal(t, validatorToAdd.Address, updateDelta.Added[0].Address) - require.Equal(t, validatorToAdd.VotingPower.Uint64(), updateDelta.Added[0].VotingPower.Uint64()) - }) -} - -func TestStakeCounter_ShouldBeDeterministic(t *testing.T) { - t.Parallel() - - const timesToExecute = 100 - - stakes := [][]uint64{ - {103, 102, 101, 51, 50, 30, 10}, - {100, 100, 100, 50, 50, 30, 10}, - {103, 102, 101, 51, 50, 30, 10}, - {100, 100, 100, 50, 50, 30, 10}, - } - maxValidatorSetSizes := []int{1000, 1000, 5, 6} - - for ind, stake := range stakes { - maxValidatorSetSize := maxValidatorSetSizes[ind] - - aliases := []string{"A", "B", "C", "D", "E", "F", "G"} - validators := validator.NewTestValidatorsWithAliases(t, aliases, stake) - - test := func() []*validator.ValidatorMetadata { - stakeCounter := newValidatorStakeMap(validators.GetPublicIdentities("A", "B", "C", "D", "E")) - - return stakeCounter.getSorted(maxValidatorSetSize) - } - - initialSlice := test() - - // stake counter and stake map should always be deterministic - for i := 0; i < timesToExecute; i++ { - currentSlice := test() - - require.Len(t, currentSlice, len(initialSlice)) - - for i, si := range currentSlice { - initialSi := initialSlice[i] - require.Equal(t, si.Address, initialSi.Address) - require.Equal(t, si.VotingPower.Uint64(), initialSi.VotingPower.Uint64()) - } - } - } -} - -func TestStakeManager_UpdateOnInit(t *testing.T) { - t.Parallel() - - var ( - allAliases = []string{"A", "B", "C", "D", "E", "F"} - validatorSetAddr = types.StringToAddress("0xf001") - epochID = uint64(120) - ) - - success := types.ReceiptSuccess - contractProvider := &stateProvider{} - header1Hash := types.StringToHash("0x99aa") - header2Hash := types.StringToHash("0xffee") - header3Hash := types.StringToHash("0xeeff") - header4Hash := types.StringToHash("0xaaff") - currentHeader := &types.Header{Number: 4} - validators := validator.NewTestValidatorsWithAliases(t, allAliases) - accountSet := validators.GetPublicIdentities(allAliases...) - addresses := accountSet.GetAddresses() - state := newTestState(t) - - sysStateMock := &systemStateMock{} - sysStateMock.On("GetEpoch").Return(epochID, nil).Once() - - polyBackendMock := new(polybftBackendMock) - polyBackendMock.On("GetValidatorsWithTx", uint64(0), []*types.Header(nil), mock.Anything).Return(accountSet, nil).Once() - - bcMock := new(blockchainMock) - bcMock.On("GetStateProviderForBlock", currentHeader).Return(contractProvider, nil).Once() - bcMock.On("GetSystemState", contractProvider).Return(sysStateMock, nil).Once() - bcMock.On("CurrentHeader", mock.Anything).Return(currentHeader, true).Once() - bcMock.On("GetHeaderByNumber", uint64(1)).Return(&types.Header{Number: 1, Hash: header1Hash}, true).Once() - bcMock.On("GetHeaderByNumber", uint64(2)).Return(&types.Header{Number: 2, Hash: header2Hash}, true).Once() - bcMock.On("GetHeaderByNumber", uint64(3)).Return(&types.Header{Number: 3, Hash: header3Hash}, true).Once() - bcMock.On("GetHeaderByNumber", uint64(4)).Return(&types.Header{Number: 4, Hash: header4Hash}, true).Once() - bcMock.On("GetReceiptsByHash", header1Hash).Return([]*types.Receipt(nil), nil).Once() - bcMock.On("GetReceiptsByHash", header2Hash).Return([]*types.Receipt{ - { - Status: &success, - Logs: []*types.Log{ - createTestLogForTransferEvent( - t, - validatorSetAddr, - types.ZeroAddress, - addresses[len(addresses)-2], - 4, - ), - }, - }, - }, nil).Once() - bcMock.On("GetReceiptsByHash", header3Hash).Return([]*types.Receipt{ - { - Status: &success, - Logs: []*types.Log{ - createTestLogForTransferEvent( - t, - validatorSetAddr, - types.ZeroAddress, - addresses[len(addresses)-1], - 6, - ), - }, - }, - }, nil).Once() - bcMock.On("GetReceiptsByHash", header4Hash).Return([]*types.Receipt{{}}, nil).Once() - - _, err := newStakeManager( - hclog.NewNullLogger(), - state, - nil, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), - validatorSetAddr, types.StringToAddress("0x0002"), - bcMock, - polyBackendMock, - 5, - nil, - ) - require.NoError(t, err) - - bcMock.AssertExpectations(t) - sysStateMock.AssertExpectations(t) - - fullValidatorSet, err := state.StakeStore.getFullValidatorSet(nil) - require.NoError(t, err) - - require.Equal(t, uint64(4), fullValidatorSet.BlockNumber) - require.Equal(t, uint64(4), fullValidatorSet.UpdatedAtBlockNumber) - require.Equal(t, epochID, fullValidatorSet.EpochID) - - for _, x := range fullValidatorSet.Validators { - if x.Address == addresses[len(addresses)-1] { - require.Equal(t, new(big.Int).SetUint64(7), x.VotingPower) - } else if x.Address == addresses[len(addresses)-2] { - require.Equal(t, new(big.Int).SetUint64(5), x.VotingPower) - } else { - require.Equal(t, new(big.Int).SetUint64(1), x.VotingPower) - } - } -} - -func createTestLogForTransferEvent(t *testing.T, validatorSet, from, to types.Address, stake uint64) *types.Log { - t.Helper() - - var transferEvent contractsapi.TransferEvent - - topics := make([]types.Hash, 3) - topics[0] = types.Hash(transferEvent.Sig()) - topics[1] = types.BytesToHash(from.Bytes()) - topics[2] = types.BytesToHash(to.Bytes()) - encodedData, err := abi.MustNewType("uint256").Encode(new(big.Int).SetUint64(stake)) - require.NoError(t, err) - - return &types.Log{ - Address: validatorSet, - Topics: topics, - Data: encodedData, - } -} - -var _ txrelayer.TxRelayer = (*dummyStakeTxRelayer)(nil) - -type dummyStakeTxRelayer struct { - mock.Mock - callback func() *validator.ValidatorMetadata - t *testing.T -} - -func newDummyStakeTxRelayer(t *testing.T, callback func() *validator.ValidatorMetadata) *dummyStakeTxRelayer { - t.Helper() - - return &dummyStakeTxRelayer{ - t: t, - callback: callback, - } -} - -func (d *dummyStakeTxRelayer) Call(from ethgo.Address, to ethgo.Address, input []byte) (string, error) { - args := d.Called(from, to, input) - - if d.callback != nil { - validatorMetaData := d.callback() - encoded, err := validatorTypeABI.Encode(map[string]interface{}{ - "blsKey": validatorMetaData.BlsKey.ToBigInt(), - "stake": validatorMetaData.VotingPower, - "isWhitelisted": true, - "isActive": true, - }) - - require.NoError(d.t, err) - - return hex.EncodeToHex(encoded), nil - } - - return args.String(0), args.Error(1) -} - -func (d *dummyStakeTxRelayer) SendTransaction(transaction *ethgo.Transaction, key ethgo.Key) (*ethgo.Receipt, error) { - args := d.Called(transaction, key) - - return args.Get(0).(*ethgo.Receipt), args.Error(1) //nolint:forcetypeassert -} - -// SendTransactionLocal sends non-signed transaction (this is only for testing purposes) -func (d *dummyStakeTxRelayer) SendTransactionLocal(txn *ethgo.Transaction) (*ethgo.Receipt, error) { - args := d.Called(txn) - - return args.Get(0).(*ethgo.Receipt), args.Error(1) //nolint:forcetypeassert -} - -func (d *dummyStakeTxRelayer) Client() *jsonrpc.Client { - return nil -} diff --git a/consensus/polybft/state.go b/consensus/polybft/state.go deleted file mode 100644 index d78d091..0000000 --- a/consensus/polybft/state.go +++ /dev/null @@ -1,184 +0,0 @@ -package polybft - -import ( - "fmt" - - "github.com/hashicorp/go-hclog" - "github.com/xgr-network/xgr-node/helper/common" - bolt "go.etcd.io/bbolt" -) - -var ( - edgeEventsLastProcessedBlockBucket = []byte("EdgeEventsLastProcessedBlock") - edgeEventsLastProcessedBlockKey = []byte("EdgeEventsLastProcessedBlockKey") -) - -// MessageSignature encapsulates sender identifier and its signature -type MessageSignature struct { - // Signer of the vote - From string - // Signature of the message - Signature []byte -} - -// TransportMessage represents the payload which is gossiped across the network -type TransportMessage struct { - // Hash is encoded data - Hash []byte - // Message signature - Signature []byte - // From is the address of the message signer - From string - // Number of epoch - EpochNumber uint64 -} - -// State represents a persistence layer which persists consensus data off-chain -type State struct { - db *bolt.DB - close chan struct{} - - StateSyncStore *StateSyncStore - CheckpointStore *CheckpointStore - EpochStore *EpochStore - ProposerSnapshotStore *ProposerSnapshotStore - StakeStore *StakeStore -} - -// newState creates new instance of State -func newState(path string, logger hclog.Logger, closeCh chan struct{}) (*State, error) { - db, err := bolt.Open(path, 0666, nil) - if err != nil { - return nil, err - } - - s := &State{ - db: db, - close: closeCh, - StateSyncStore: &StateSyncStore{db: db}, - CheckpointStore: &CheckpointStore{db: db}, - EpochStore: &EpochStore{db: db}, - ProposerSnapshotStore: &ProposerSnapshotStore{db: db}, - StakeStore: &StakeStore{db: db}, - } - - if err = s.initStorages(); err != nil { - return nil, err - } - - return s, nil -} - -// initStorages initializes data storages -func (s *State) initStorages() error { - // init the buckets - return s.db.Update(func(tx *bolt.Tx) error { - if err := s.StateSyncStore.initialize(tx); err != nil { - return err - } - if err := s.CheckpointStore.initialize(tx); err != nil { - return err - } - if err := s.EpochStore.initialize(tx); err != nil { - return err - } - if err := s.ProposerSnapshotStore.initialize(tx); err != nil { - return err - } - if err := s.StakeStore.initialize(tx); err != nil { - return err - } - - _, err := tx.CreateBucketIfNotExists(edgeEventsLastProcessedBlockBucket) - if err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(edgeEventsLastProcessedBlockBucket), err) - } - - lastProcessedBlock, err := s.getLastProcessedEventsBlock(tx) - if err != nil { - return fmt.Errorf("failed to get last processed block: %w", err) - } - - if lastProcessedBlock == 0 { - // we do this for already existing chains, which just updated their Edge binary, - // to not get events from scratch, but start from what other stores have - lastSaved, err := s.CheckpointStore.getLastSaved(tx) - if err != nil { - return fmt.Errorf("could not initialize last processed block bucket: %w", err) - } - - if lastSaved > 0 { - return s.insertLastProcessedEventsBlock(lastSaved, tx) - } - } - - return nil - }) -} - -// insertLastProcessedEventsBlock inserts the last processed block for events on Edge -func (s *State) insertLastProcessedEventsBlock(block uint64, dbTx *bolt.Tx) error { - insertFn := func(tx *bolt.Tx) error { - return tx.Bucket(edgeEventsLastProcessedBlockBucket).Put( - edgeEventsLastProcessedBlockKey, common.EncodeUint64ToBytes(block)) - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return insertFn(tx) - }) - } - - return insertFn(dbTx) -} - -// getLastProcessedEventsBlock gets the last processed block for events on Edge -func (s *State) getLastProcessedEventsBlock(dbTx *bolt.Tx) (uint64, error) { - var ( - lastProcessed uint64 - err error - ) - - getFn := func(tx *bolt.Tx) { - value := tx.Bucket(edgeEventsLastProcessedBlockBucket).Get(edgeEventsLastProcessedBlockKey) - if value != nil { - lastProcessed = common.EncodeBytesToUint64(value) - } - } - - if dbTx == nil { - err = s.db.View(func(tx *bolt.Tx) error { - getFn(tx) - - return nil - }) - } else { - getFn(dbTx) - } - - return lastProcessed, err -} - -// beginDBTransaction creates and begins a transaction on BoltDB -// Note that transaction needs to be manually rollback or committed -func (s *State) beginDBTransaction(isWriteTx bool) (*bolt.Tx, error) { - return s.db.Begin(isWriteTx) -} - -// bucketStats returns stats for the given bucket in db -func bucketStats(bucketName []byte, db *bolt.DB) (*bolt.BucketStats, error) { - var stats *bolt.BucketStats - - err := db.View(func(tx *bolt.Tx) error { - s := tx.Bucket(bucketName).Stats() - stats = &s - - return nil - }) - - if err != nil { - return nil, fmt.Errorf("cannot check bucket stats. Bucket name=%s: %w", string(bucketName), err) - } - - return stats, nil -} diff --git a/consensus/polybft/state_event_getter.go b/consensus/polybft/state_event_getter.go deleted file mode 100644 index 2f223ff..0000000 --- a/consensus/polybft/state_event_getter.go +++ /dev/null @@ -1,256 +0,0 @@ -package polybft - -import ( - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/blockchain" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" -) - -// EventSubscriber specifies functions needed for a component to subscribe to eventProvider -type EventSubscriber interface { - // GetLogFilters returns a map of log filters for getting desired events, - // where the key is the address of contract that emits desired events, - // and the value is a slice of signatures of events we want to get. - GetLogFilters() map[types.Address][]types.Hash - - // ProcessLog is used to handle a log defined in GetLogFilters, provided by event provider - ProcessLog(header *types.Header, log *ethgo.Log, dbTx *bolt.Tx) error -} - -// EventProvider represents an event provider in a blockchain system -// that returns desired events to subscribers from finalized blocks -// Note that this provider needs to be called manually on each block finalization. -// The EventProvider struct has the following fields: -// - blockchain: A blockchainBackend type that represents the blockchain backend used by the event provider. -// - subscribers: A map[string]eventSubscriber that stores the subscribers of the event provider. -// - allFilters: A map[types.Address]map[types.Hash][]string that stores the filters for event logs. -type EventProvider struct { - receiptsGetter - - subscriberIDCounter uint64 - - blockchain blockchainBackend - - subscribers map[uint64]EventSubscriber - allFilters map[types.Address]map[types.Hash][]uint64 -} - -// NewEventProvider returns a new instance of eventProvider -func NewEventProvider(blockchain blockchainBackend) *EventProvider { - return &EventProvider{ - receiptsGetter: receiptsGetter{ - blockchain: blockchain, - }, - subscribers: make(map[uint64]EventSubscriber, 0), - allFilters: make(map[types.Address]map[types.Hash][]uint64, 0), - } -} - -// Subscribe subscribes given EventSubscriber to desired logs (events) -func (e *EventProvider) Subscribe(subscriber EventSubscriber) { - e.subscriberIDCounter++ - subscriberID := e.subscriberIDCounter - e.subscribers[subscriberID] = subscriber - - for address, filters := range subscriber.GetLogFilters() { - existingAddressFilters, exist := e.allFilters[address] - if !exist { - existingAddressFilters = make(map[types.Hash][]uint64, 0) - e.allFilters[address] = existingAddressFilters - } - - for _, f := range filters { - existingAddressFilters[f] = append(existingAddressFilters[f], subscriberID) - } - } -} - -// GetEventsFromBlocks gets all desired logs (events) for each subscriber in given block range -// -// Inputs: -// - lastProcessedBlock - last finalized block that was processed for desired events -// - latestBlock - latest finalized block -// - dbTx - database transaction under which events are gathered -// -// Returns: -// - nil - if getting events finished successfully -// - error - if a block or its receipts could not be retrieved from blockchain -func (e *EventProvider) GetEventsFromBlocks(lastProcessedBlock uint64, - latestBlock *types.FullBlock, - dbTx *bolt.Tx) error { - if err := e.getEventsFromBlocksRange(lastProcessedBlock+1, latestBlock.Block.Number()-1, dbTx); err != nil { - return err - } - - return e.getEventsFromReceipts(latestBlock.Block.Header, latestBlock.Receipts, dbTx) -} - -// getEventsFromBlocksRange gets all desired logs (events) for each subscriber in given block range -// -// Inputs: -// - from - block from which log (event) retrieval should start -// - to - last block from which log (event) retrieval should be done -// - dbTx - database transaction under which events are gathered -// -// Returns: -// - nil - if getting events finished successfully -// - error - if a block or its receipts could not be retrieved from blockchain -func (e *EventProvider) getEventsFromBlocksRange(from, to uint64, dbTx *bolt.Tx) error { - receiptsHandler := func(header *types.Header, receipts []*types.Receipt) error { - return e.getEventsFromReceipts(header, receipts, dbTx) - } - - return e.receiptsGetter.getReceiptsFromBlocksRange(from, to, receiptsHandler) -} - -// getEventsFromReceipts gets all desired logs (events) for each subscriber from given block receipts -// -// Inputs: -// - blockHeader - header of block from whose receipts the function will retrieve logs (events) -// - receipts - given block receipts from which the function will retrieve logs (events) -// - dbTx - database transaction under which events are gathered -// -// Returns: -// - nil - if getting events finished successfully -// - error - if a subscriber for a certain log (event) returns an error on log (event) handling -func (e *EventProvider) getEventsFromReceipts(blockHeader *types.Header, - receipts []*types.Receipt, - dbTx *bolt.Tx) error { - for _, receipt := range receipts { - if receipt.Status == nil || *receipt.Status != types.ReceiptSuccess { - continue - } - - for _, log := range receipt.Logs { - logFilters, isRelevantLog := e.allFilters[log.Address] - if !isRelevantLog { - continue - } - - for logFilter, subscribers := range logFilters { - if log.Topics[0] == logFilter { - convertedLog := convertLog(log) - for _, subscriber := range subscribers { - if err := e.subscribers[subscriber].ProcessLog(blockHeader, convertedLog, dbTx); err != nil { - return err - } - } - } - } - } - } - - return nil -} - -// eventsGetter is a struct for getting missed and current events -// of specified type from specified blocks -type eventsGetter[T contractsapi.EventAbi] struct { - receiptsGetter - // parseEventFn is a plugin function used to parse the event from transaction log - parseEventFn func(*types.Header, *ethgo.Log) (T, bool, error) - // isValidLogFn is a plugin function that validates the log - // for example: if it was sent from the desired address - isValidLogFn func(*types.Log) bool -} - -// getFromBlocks gets events of specified type from specified blocks -// and saves them using the provided saveEventsFn -func (e *eventsGetter[T]) getFromBlocks(lastProcessedBlock uint64, - currentBlock *types.FullBlock) ([]T, error) { - allEvents, err := e.getEventsFromBlocksRange(lastProcessedBlock+1, currentBlock.Block.Number()-1) - if err != nil { - return nil, err - } - - currentEvents, err := e.getEventsFromReceipts(currentBlock.Block.Header, currentBlock.Receipts) - if err != nil { - return nil, err - } - - allEvents = append(allEvents, currentEvents...) - - return allEvents, nil -} - -// getEventsFromBlocksRange gets events of specified type from all the blocks specified [from, to] -func (e *eventsGetter[T]) getEventsFromBlocksRange(from, to uint64) ([]T, error) { - var allEvents []T - - receiptsHandler := func(header *types.Header, receipts []*types.Receipt) error { - events, err := e.getEventsFromReceipts(header, receipts) - if err != nil { - return err - } - - allEvents = append(allEvents, events...) - - return nil - } - - if err := e.receiptsGetter.getReceiptsFromBlocksRange(from, to, receiptsHandler); err != nil { - return nil, err - } - - return allEvents, nil -} - -// getEventsFromReceipts returns events of specified type from block transaction receipts -func (e *eventsGetter[T]) getEventsFromReceipts(blockHeader *types.Header, - receipts []*types.Receipt) ([]T, error) { - var events []T - - for _, receipt := range receipts { - if receipt.Status == nil || *receipt.Status != types.ReceiptSuccess { - continue - } - - for _, log := range receipt.Logs { - if e.isValidLogFn != nil && !e.isValidLogFn(log) { - continue - } - - event, doesMatch, err := e.parseEventFn(blockHeader, convertLog(log)) - if err != nil { - return nil, err - } - - if !doesMatch { - continue - } - - events = append(events, event) - } - } - - return events, nil -} - -type receiptsGetter struct { - // blockchain is an abstraction of blockchain that provides necessary functions - // for querying blockchain data (blocks, receipts, etc.) - blockchain blockchainBackend -} - -func (r *receiptsGetter) getReceiptsFromBlocksRange(from, to uint64, - receiptsHandler func(*types.Header, []*types.Receipt) error) error { - for i := from; i <= to; i++ { - blockHeader, found := r.blockchain.GetHeaderByNumber(i) - if !found { - return blockchain.ErrNoBlock - } - - receipts, err := r.blockchain.GetReceiptsByHash(blockHeader.Hash) - if err != nil { - return err - } - - if err := receiptsHandler(blockHeader, receipts); err != nil { - return err - } - } - - return nil -} diff --git a/consensus/polybft/state_event_getter_test.go b/consensus/polybft/state_event_getter_test.go deleted file mode 100644 index ad0505d..0000000 --- a/consensus/polybft/state_event_getter_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package polybft - -import ( - "testing" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/types" -) - -func TestEventDBInsertRetry_GetEvents(t *testing.T) { - receipt := &types.Receipt{ - Logs: []*types.Log{ - createTestLogForTransferEvent(t, contracts.ValidatorSetContract, types.ZeroAddress, types.ZeroAddress, 10), - }, - } - receipt.SetStatus(types.ReceiptSuccess) - - backend := new(blockchainMock) - backend.On("GetHeaderByNumber", mock.Anything).Return(&types.Header{ - Hash: types.BytesToHash([]byte{0, 1, 2, 3}), - }, true) - backend.On("GetReceiptsByHash", mock.Anything).Return([]*types.Receipt{receipt}, nil) - - retryManager := &eventsGetter[*contractsapi.TransferEvent]{ - receiptsGetter: receiptsGetter{ - blockchain: backend, - }, - isValidLogFn: func(l *types.Log) bool { - return l.Address == contracts.ValidatorSetContract - }, - parseEventFn: func(h *types.Header, l *ethgo.Log) (*contractsapi.TransferEvent, bool, error) { - var e contractsapi.TransferEvent - doesMatch, err := e.ParseLog(l) - - return &e, doesMatch, err - }, - } - - events, err := retryManager.getFromBlocks(0, &types.FullBlock{ - Block: &types.Block{Header: &types.Header{Number: 2}}, - Receipts: []*types.Receipt{}, - }) - - require.NoError(t, err) - require.Len(t, events, 1) -} diff --git a/consensus/polybft/state_store_checkpoint.go b/consensus/polybft/state_store_checkpoint.go deleted file mode 100644 index 964e00e..0000000 --- a/consensus/polybft/state_store_checkpoint.go +++ /dev/null @@ -1,245 +0,0 @@ -package polybft - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "sort" - - "github.com/umbracle/ethgo" - bolt "go.etcd.io/bbolt" - - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/types" -) - -var ( - // bucket to store exit contract events - exitEventsBucket = []byte("exitEvent") - exitEventToEpochLookupBucket = []byte("exitIdToEpochLookup") - exitEventLastProcessedBlockBucket = []byte("lastProcessedBlock") - - lastProcessedBlockKey = []byte("lastProcessedBlock") - errNoLastSavedEntry = errors.New("there is no last saved block in last saved bucket") -) - -type exitEventNotFoundError struct { - exitID uint64 - epoch uint64 -} - -func (e *exitEventNotFoundError) Error() string { - return fmt.Sprintf("could not find any exit event that has an id: %v and epoch: %v", e.exitID, e.epoch) -} - -// ExitEvent is an event emitted by Exit contract -type ExitEvent struct { - *contractsapi.L2StateSyncedEvent - // EpochNumber is the epoch number in which exit event was added - EpochNumber uint64 `abi:"-"` - // BlockNumber is the block in which exit event was added - BlockNumber uint64 `abi:"-"` -} - -/* -Bolt DB schema: - -exit events/ -|--> (id+epoch+blockNumber) -> *ExitEvent (json marshalled) -|--> (exitEventID) -> epochNumber -|--> (lastProcessedBlockKey) -> block number -*/ -type CheckpointStore struct { - db *bolt.DB -} - -// initialize creates necessary buckets in DB if they don't already exist -func (s *CheckpointStore) initialize(tx *bolt.Tx) error { - if _, err := tx.CreateBucketIfNotExists(exitEventsBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(exitEventsBucket), err) - } - - if _, err := tx.CreateBucketIfNotExists(exitEventToEpochLookupBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(exitEventToEpochLookupBucket), err) - } - - if _, err := tx.CreateBucketIfNotExists(exitEventLastProcessedBlockBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(exitEventLastProcessedBlockBucket), err) - } - - return tx.Bucket(exitEventLastProcessedBlockBucket).Put(lastProcessedBlockKey, common.EncodeUint64ToBytes(0)) -} - -// insertExitEventWithTx inserts an exit event to db -func (s *CheckpointStore) insertExitEvent(exitEvent *ExitEvent, dbTx *bolt.Tx) error { - insertFn := func(tx *bolt.Tx) error { - raw, err := json.Marshal(exitEvent) - if err != nil { - return err - } - - epochBytes := common.EncodeUint64ToBytes(exitEvent.EpochNumber) - exitIDBytes := common.EncodeUint64ToBytes(exitEvent.ID.Uint64()) - - err = tx.Bucket(exitEventsBucket).Put(bytes.Join([][]byte{epochBytes, - exitIDBytes, common.EncodeUint64ToBytes(exitEvent.BlockNumber)}, nil), raw) - if err != nil { - return err - } - - return tx.Bucket(exitEventToEpochLookupBucket).Put(exitIDBytes, epochBytes) - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return insertFn(tx) - }) - } - - return insertFn(dbTx) -} - -// getExitEvent returns exit event with given id, which happened in given epoch and given block number -func (s *CheckpointStore) getExitEvent(exitEventID uint64) (*ExitEvent, error) { - var exitEvent *ExitEvent - - err := s.db.View(func(tx *bolt.Tx) error { - exitEventBucket := tx.Bucket(exitEventsBucket) - lookupBucket := tx.Bucket(exitEventToEpochLookupBucket) - - exitIDBytes := common.EncodeUint64ToBytes(exitEventID) - - epochBytes := lookupBucket.Get(exitIDBytes) - if epochBytes == nil { - return fmt.Errorf("could not find any exit event that has an id: %v. Its epoch was not found in lookup table", - exitEventID) - } - - key := bytes.Join([][]byte{epochBytes, exitIDBytes}, nil) - k, v := exitEventBucket.Cursor().Seek(key) - - if bytes.HasPrefix(k, key) == false || v == nil { - return &exitEventNotFoundError{ - exitID: exitEventID, - epoch: common.EncodeBytesToUint64(epochBytes), - } - } - - return json.Unmarshal(v, &exitEvent) - }) - - return exitEvent, err -} - -// getExitEventsByEpoch returns all exit events that happened in the given epoch -func (s *CheckpointStore) getExitEventsByEpoch(epoch uint64) ([]*ExitEvent, error) { - return s.getExitEvents(epoch, func(exitEvent *ExitEvent) bool { - return exitEvent.EpochNumber == epoch - }) -} - -// getExitEventsForProof returns all exit events that happened in and prior to the given checkpoint block number -// with respect to the epoch in which block is added -func (s *CheckpointStore) getExitEventsForProof(epoch, checkpointBlock uint64) ([]*ExitEvent, error) { - return s.getExitEvents(epoch, func(exitEvent *ExitEvent) bool { - return exitEvent.EpochNumber == epoch && exitEvent.BlockNumber <= checkpointBlock - }) -} - -// getExitEvents returns exit events for given epoch and provided filter -func (s *CheckpointStore) getExitEvents(epoch uint64, filter func(exitEvent *ExitEvent) bool) ([]*ExitEvent, error) { - var events []*ExitEvent - - err := s.db.View(func(tx *bolt.Tx) error { - c := tx.Bucket(exitEventsBucket).Cursor() - prefix := common.EncodeUint64ToBytes(epoch) - - for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() { - var event *ExitEvent - if err := json.Unmarshal(v, &event); err != nil { - return err - } - - if filter(event) { - events = append(events, event) - } - } - - return nil - }) - - // enforce sequential order - sort.Slice(events, func(i, j int) bool { - return events[i].ID.Cmp(events[j].ID) < 0 - }) - - return events, err -} - -// updateLastSaved saves the last block processed for exit events -func (s *CheckpointStore) getLastSaved(dbTx *bolt.Tx) (uint64, error) { - var ( - lastSavedBlock uint64 - err error - ) - - getFn := func(tx *bolt.Tx) error { - v := tx.Bucket(exitEventLastProcessedBlockBucket).Get(lastProcessedBlockKey) - if v == nil { - return errNoLastSavedEntry - } - - lastSavedBlock = common.EncodeBytesToUint64(v) - - return nil - } - - if dbTx == nil { - err = s.db.View(func(tx *bolt.Tx) error { - return getFn(tx) - }) - } else { - err = getFn(dbTx) - } - - return lastSavedBlock, err -} - -// decodeExitEvent tries to decode exit event from the provided log -func decodeExitEvent(log *ethgo.Log, epoch, block uint64) (*ExitEvent, error) { - var l2StateSyncedEvent contractsapi.L2StateSyncedEvent - - doesMatch, err := l2StateSyncedEvent.ParseLog(log) - if err != nil { - return nil, err - } - - if !doesMatch { - return nil, nil - } - - return &ExitEvent{ - L2StateSyncedEvent: &l2StateSyncedEvent, - EpochNumber: epoch, - BlockNumber: block, - }, nil -} - -// convertLog converts types.Log to ethgo.Log -func convertLog(log *types.Log) *ethgo.Log { - l := ðgo.Log{ - Address: ethgo.Address(log.Address), - Data: make([]byte, len(log.Data)), - Topics: make([]ethgo.Hash, len(log.Topics)), - } - - copy(l.Data, log.Data) - - for i, topic := range log.Topics { - l.Topics[i] = ethgo.Hash(topic) - } - - return l -} diff --git a/consensus/polybft/state_store_checkpoint_test.go b/consensus/polybft/state_store_checkpoint_test.go deleted file mode 100644 index 5edbf82..0000000 --- a/consensus/polybft/state_store_checkpoint_test.go +++ /dev/null @@ -1,206 +0,0 @@ -package polybft - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" -) - -func TestState_Insert_And_Get_ExitEvents_PerEpoch(t *testing.T) { - const ( - numOfEpochs = 11 - numOfBlocksPerEpoch = 10 - numOfEventsPerBlock = 11 - ) - - state := newTestState(t) - insertTestExitEvents(t, state, numOfEpochs, numOfBlocksPerEpoch, numOfEventsPerBlock) - - t.Run("Get events for existing epoch", func(t *testing.T) { - events, err := state.CheckpointStore.getExitEventsByEpoch(1) - - assert.NoError(t, err) - assert.Len(t, events, numOfBlocksPerEpoch*numOfEventsPerBlock) - }) - - t.Run("Get events for non-existing epoch", func(t *testing.T) { - events, err := state.CheckpointStore.getExitEventsByEpoch(12) - - assert.NoError(t, err) - assert.Len(t, events, 0) - }) -} - -func TestState_Insert_And_Get_ExitEvents_ForProof(t *testing.T) { - const ( - numOfEpochs = 11 - numOfBlocksPerEpoch = 10 - numOfEventsPerBlock = 10 - ) - - state := newTestState(t) - insertTestExitEvents(t, state, numOfEpochs, numOfBlocksPerEpoch, numOfEventsPerBlock) - - var cases = []struct { - epoch uint64 - checkpointBlockNumber uint64 - expectedNumberOfEvents int - }{ - {1, 1, 10}, - {1, 2, 20}, - {1, 8, 80}, - {2, 12, 20}, - {2, 14, 40}, - {3, 26, 60}, - {4, 38, 80}, - {11, 105, 50}, - } - - for _, c := range cases { - events, err := state.CheckpointStore.getExitEventsForProof(c.epoch, c.checkpointBlockNumber) - - assert.NoError(t, err) - assert.Len(t, events, c.expectedNumberOfEvents) - } -} - -func TestState_Insert_And_Get_ExitEvents_ForProof_NoEvents(t *testing.T) { - t.Parallel() - - state := newTestState(t) - insertTestExitEvents(t, state, 1, 10, 1) - - events, err := state.CheckpointStore.getExitEventsForProof(2, 11) - - assert.NoError(t, err) - assert.Nil(t, events) -} - -func TestState_NoEpochForExitEventInLookup(t *testing.T) { - t.Parallel() - - const ( - exitToTest = uint64(11) - epochToMatch = uint64(2) - blockNumberToMatch = uint64(12) - ) - - state := newTestState(t) - insertTestExitEvents(t, state, 3, 10, 1) - - exitEventFromDB, err := state.CheckpointStore.getExitEvent(exitToTest) - require.NoError(t, err) - require.Equal(t, exitToTest, exitEventFromDB.ID.Uint64()) - require.Equal(t, epochToMatch, exitEventFromDB.EpochNumber) - require.Equal(t, blockNumberToMatch, exitEventFromDB.BlockNumber) - - // simulate invalid case (for some reason lookup table doesn't have epoch for given exit) - err = state.db.Update(func(tx *bolt.Tx) error { - return tx.Bucket(exitEventToEpochLookupBucket).Delete(common.EncodeUint64ToBytes(exitEventFromDB.ID.Uint64())) - }) - - require.NoError(t, err) - - _, err = state.CheckpointStore.getExitEvent(exitToTest) - require.ErrorContains(t, err, "epoch was not found in lookup table") -} - -func TestState_decodeExitEvent(t *testing.T) { - t.Parallel() - - const ( - exitID = 1 - epoch = 1 - blockNumber = 10 - ) - - state := newTestState(t) - - var exitEvent contractsapi.L2StateSyncedEvent - - topics := make([]ethgo.Hash, 4) - topics[0] = exitEvent.Sig() - topics[1] = ethgo.BytesToHash([]byte{exitID}) - topics[2] = ethgo.BytesToHash(ethgo.HexToAddress("0x1111").Bytes()) - topics[3] = ethgo.BytesToHash(ethgo.HexToAddress("0x2222").Bytes()) - personType := abi.MustNewType("tuple(string firstName, string lastName)") - encodedData, err := personType.Encode(map[string]string{"firstName": "John", "lastName": "Doe"}) - require.NoError(t, err) - - log := ðgo.Log{ - Address: ethgo.ZeroAddress, - Topics: topics, - Data: encodedData, - } - - event, err := decodeExitEvent(log, epoch, blockNumber) - require.NoError(t, err) - require.Equal(t, uint64(exitID), event.ID.Uint64()) - require.Equal(t, uint64(epoch), event.EpochNumber) - require.Equal(t, uint64(blockNumber), event.BlockNumber) - - require.NoError(t, state.CheckpointStore.insertExitEvent(event, nil)) -} - -func TestState_decodeExitEvent_NotAnExitEvent(t *testing.T) { - t.Parallel() - - var stateSyncedEvent contractsapi.StateSyncedEvent - - topics := make([]ethgo.Hash, 4) - topics[0] = stateSyncedEvent.Sig() - - log := ðgo.Log{ - Address: ethgo.ZeroAddress, - Topics: topics, - } - - event, err := decodeExitEvent(log, 1, 1) - require.NoError(t, err) - require.Nil(t, event) -} - -func insertTestExitEvents(t *testing.T, state *State, - numOfEpochs, numOfBlocksPerEpoch, numOfEventsPerBlock int) []*ExitEvent { - t.Helper() - - var ( - index = uint64(0) - block = uint64(1) - exitEvents = make([]*ExitEvent, numOfEpochs*numOfBlocksPerEpoch*numOfEventsPerBlock) - ) - - for i := uint64(1); i <= uint64(numOfEpochs); i++ { - for j := 1; j <= numOfBlocksPerEpoch; j++ { - for k := 1; k <= numOfEventsPerBlock; k++ { - exitEvents[index] = - &ExitEvent{ - L2StateSyncedEvent: &contractsapi.L2StateSyncedEvent{ - ID: new(big.Int).SetUint64(index), - Sender: types.ZeroAddress, - Receiver: types.ZeroAddress, - Data: generateRandomBytes(t), - }, - EpochNumber: i, - BlockNumber: block, - } - index++ - } - block++ - } - } - - for _, ee := range exitEvents { - require.NoError(t, state.CheckpointStore.insertExitEvent(ee, nil)) - } - - return exitEvents -} diff --git a/consensus/polybft/state_store_epoch.go b/consensus/polybft/state_store_epoch.go deleted file mode 100644 index 416890b..0000000 --- a/consensus/polybft/state_store_epoch.go +++ /dev/null @@ -1,289 +0,0 @@ -package polybft - -import ( - "encoding/json" - "fmt" - - "github.com/xgr-network/xgr-node/helper/common" - bolt "go.etcd.io/bbolt" -) - -const ( - // validatorSnapshotLimit defines a maximum number of validator snapshots - // that can be stored in cache (both memory and db) - validatorSnapshotLimit = 100 - // numberOfSnapshotsToLeaveInMemory defines a number of validator snapshots to leave in memory - numberOfSnapshotsToLeaveInMemory = 12 - // numberOfSnapshotsToLeaveInDB defines a number of validator snapshots to leave in db - numberOfSnapshotsToLeaveInDB = 20 -) - -var ( - // bucket to store epochs and all its nested buckets (message votes and message pool events) - epochsBucket = []byte("epochs") - - // bucket to store validator snapshots - validatorSnapshotsBucket = []byte("validatorSnapshots") -) - -/* -Bolt DB schema: - -epochs/ -|--> epochNumber - |--> hash -> []*MessageSignatures (json marshalled) - -epochs/ -|--> epochNumber -> []*TransferEvent (json marshalled) - -validatorSnapshots/ -|--> epochNumber -> *AccountSet (json marshalled) -*/ - -type EpochStore struct { - db *bolt.DB -} - -// initialize creates necessary buckets in DB if they don't already exist -func (s *EpochStore) initialize(tx *bolt.Tx) error { - if _, err := tx.CreateBucketIfNotExists(epochsBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(epochsBucket), err) - } - - if _, err := tx.CreateBucketIfNotExists(validatorSnapshotsBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(validatorSnapshotsBucket), err) - } - - return nil -} - -// insertValidatorSnapshot inserts a validator snapshot for the given block to its bucket in db -func (s *EpochStore) insertValidatorSnapshot(validatorSnapshot *validatorSnapshot, dbTx *bolt.Tx) error { - insertFn := func(tx *bolt.Tx) error { - raw, err := json.Marshal(validatorSnapshot) - if err != nil { - return err - } - - return tx.Bucket(validatorSnapshotsBucket).Put(common.EncodeUint64ToBytes(validatorSnapshot.Epoch), raw) - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return insertFn(tx) - }) - } - - return insertFn(dbTx) -} - -// getValidatorSnapshot queries the validator snapshot for given block from db -func (s *EpochStore) getValidatorSnapshot(epoch uint64) (*validatorSnapshot, error) { - var validatorSnapshot *validatorSnapshot - - err := s.db.View(func(tx *bolt.Tx) error { - v := tx.Bucket(validatorSnapshotsBucket).Get(common.EncodeUint64ToBytes(epoch)) - if v != nil { - return json.Unmarshal(v, &validatorSnapshot) - } - - return nil - }) - - return validatorSnapshot, err -} - -// getLastSnapshot returns the last snapshot saved in db -// since they are stored by epoch number (uint64), they are sequentially stored, -// so the latest epoch will be the last snapshot in db -func (s *EpochStore) getLastSnapshot(dbTx *bolt.Tx) (*validatorSnapshot, error) { - var ( - snapshot *validatorSnapshot - err error - ) - - getFn := func(tx *bolt.Tx) error { - c := tx.Bucket(validatorSnapshotsBucket).Cursor() - k, v := c.Last() - - if k == nil { - // we have no snapshots in db - return nil - } - - return json.Unmarshal(v, &snapshot) - } - - if dbTx == nil { - err = s.db.View(func(tx *bolt.Tx) error { - return getFn(tx) - }) - } else { - err = getFn(dbTx) - } - - return snapshot, err -} - -// insertEpoch inserts a new epoch to db with its meta data -func (s *EpochStore) insertEpoch(epoch uint64, dbTx *bolt.Tx) error { - insertFn := func(tx *bolt.Tx) error { - epochBucket, err := tx.Bucket(epochsBucket).CreateBucketIfNotExists(common.EncodeUint64ToBytes(epoch)) - if err != nil { - return err - } - - _, err = epochBucket.CreateBucketIfNotExists(messageVotesBucket) - if err != nil { - return err - } - - return err - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return insertFn(tx) - }) - } - - return insertFn(dbTx) -} - -// isEpochInserted checks if given epoch is present in db -func (s *EpochStore) isEpochInserted(epoch uint64) bool { - return s.db.View(func(tx *bolt.Tx) error { - _, err := getEpochBucket(tx, epoch) - - return err - }) == nil -} - -// getEpochBucket returns bucket from db associated with given epoch -func getEpochBucket(tx *bolt.Tx, epoch uint64) (*bolt.Bucket, error) { - epochBucket := tx.Bucket(epochsBucket).Bucket(common.EncodeUint64ToBytes(epoch)) - if epochBucket == nil { - return nil, fmt.Errorf("could not find bucket for epoch: %v", epoch) - } - - return epochBucket, nil -} - -// cleanEpochsFromDB cleans epoch buckets from db -func (s *EpochStore) cleanEpochsFromDB(dbTx *bolt.Tx) error { - cleanFn := func(tx *bolt.Tx) error { - if err := tx.DeleteBucket(epochsBucket); err != nil { - return err - } - - _, err := tx.CreateBucket(epochsBucket) - - return err - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return cleanFn(tx) - }) - } - - return cleanFn(dbTx) -} - -// cleanValidatorSnapshotsFromDB cleans the validator snapshots bucket if a limit is reached, -// but it leaves the latest (n) number of snapshots -func (s *EpochStore) cleanValidatorSnapshotsFromDB(epoch uint64, dbTx *bolt.Tx) error { - cleanFn := func(tx *bolt.Tx) error { - bucket := tx.Bucket(validatorSnapshotsBucket) - - // paired list - keys := make([][]byte, 0) - values := make([][]byte, 0) - - for i := 0; i < numberOfSnapshotsToLeaveInDB; i++ { // exclude the last inserted we already appended - key := common.EncodeUint64ToBytes(epoch) - value := bucket.Get(key) - - if value == nil { - continue - } - - keys = append(keys, key) - values = append(values, value) - epoch-- - } - - // removing an entire bucket is much faster than removing all keys - // look at thread https://github.com/boltdb/bolt/issues/667 - err := tx.DeleteBucket(validatorSnapshotsBucket) - if err != nil { - return err - } - - bucket, err = tx.CreateBucket(validatorSnapshotsBucket) - if err != nil { - return err - } - - // we start the loop in reverse so that the oldest of snapshots get inserted first in db - for i := len(keys) - 1; i >= 0; i-- { - if err := bucket.Put(keys[i], values[i]); err != nil { - return err - } - } - - return nil - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return cleanFn(tx) - }) - } - - return cleanFn(dbTx) -} - -// removeAllValidatorSnapshots drops a validator snapshot bucket and re-creates it in bolt database -func (s *EpochStore) removeAllValidatorSnapshots() error { - return s.db.Update(func(tx *bolt.Tx) error { - // removing an entire bucket is much faster than removing all keys - // look at thread https://github.com/boltdb/bolt/issues/667 - err := tx.DeleteBucket(validatorSnapshotsBucket) - if err != nil { - return err - } - - _, err = tx.CreateBucket(validatorSnapshotsBucket) - if err != nil { - return err - } - - return nil - }) -} - -// epochsDBStats returns stats of epochs bucket in db -func (s *EpochStore) epochsDBStats() (*bolt.BucketStats, error) { - return bucketStats(epochsBucket, s.db) -} - -// validatorSnapshotsDBStats returns stats of validators snapshot bucket in db -func (s *EpochStore) validatorSnapshotsDBStats() (*bolt.BucketStats, error) { - return bucketStats(validatorSnapshotsBucket, s.db) -} - -// getNestedBucketInEpoch returns a nested (child) bucket from db associated with given epoch -func getNestedBucketInEpoch(tx *bolt.Tx, epoch uint64, bucketKey []byte) (*bolt.Bucket, error) { - epochBucket, err := getEpochBucket(tx, epoch) - if err != nil { - return nil, err - } - - bucket := epochBucket.Bucket(bucketKey) - if bucket == nil { - return nil, fmt.Errorf("could not find %v bucket for epoch: %v", string(bucketKey), epoch) - } - - return bucket, nil -} diff --git a/consensus/polybft/state_store_epoch_test.go b/consensus/polybft/state_store_epoch_test.go deleted file mode 100644 index 7ad8206..0000000 --- a/consensus/polybft/state_store_epoch_test.go +++ /dev/null @@ -1,219 +0,0 @@ -package polybft - -import ( - "fmt" - "sync" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/types" -) - -func TestState_insertAndGetValidatorSnapshot(t *testing.T) { - t.Parallel() - - const ( - epoch = uint64(1) - epochEndingBlock = uint64(100) - ) - - state := newTestState(t) - keys, err := bls.CreateRandomBlsKeys(3) - - require.NoError(t, err) - - snapshot := validator.AccountSet{ - &validator.ValidatorMetadata{Address: types.BytesToAddress([]byte{0x18}), BlsKey: keys[0].PublicKey()}, - &validator.ValidatorMetadata{Address: types.BytesToAddress([]byte{0x23}), BlsKey: keys[1].PublicKey()}, - &validator.ValidatorMetadata{Address: types.BytesToAddress([]byte{0x37}), BlsKey: keys[2].PublicKey()}, - } - - assert.NoError(t, state.EpochStore.insertValidatorSnapshot( - &validatorSnapshot{epoch, epochEndingBlock, snapshot}, nil)) - - snapshotFromDB, err := state.EpochStore.getValidatorSnapshot(epoch) - - assert.NoError(t, err) - assert.Equal(t, snapshot.Len(), snapshotFromDB.Snapshot.Len()) - assert.Equal(t, epoch, snapshotFromDB.Epoch) - assert.Equal(t, epochEndingBlock, snapshotFromDB.EpochEndingBlock) - - for i, v := range snapshot { - assert.Equal(t, v.Address, snapshotFromDB.Snapshot[i].Address) - assert.Equal(t, v.BlsKey, snapshotFromDB.Snapshot[i].BlsKey) - } -} - -func TestState_cleanValidatorSnapshotsFromDb(t *testing.T) { - t.Parallel() - - fixedEpochSize := uint64(10) - state := newTestState(t) - keys, err := bls.CreateRandomBlsKeys(3) - require.NoError(t, err) - - snapshot := validator.AccountSet{ - &validator.ValidatorMetadata{Address: types.BytesToAddress([]byte{0x18}), BlsKey: keys[0].PublicKey()}, - &validator.ValidatorMetadata{Address: types.BytesToAddress([]byte{0x23}), BlsKey: keys[1].PublicKey()}, - &validator.ValidatorMetadata{Address: types.BytesToAddress([]byte{0x37}), BlsKey: keys[2].PublicKey()}, - } - - var epoch uint64 - // add a couple of more snapshots above limit just to make sure we reached it - for i := 1; i <= validatorSnapshotLimit+2; i++ { - epoch = uint64(i) - assert.NoError(t, state.EpochStore.insertValidatorSnapshot( - &validatorSnapshot{epoch, epoch * fixedEpochSize, snapshot}, nil)) - } - - snapshotFromDB, err := state.EpochStore.getValidatorSnapshot(epoch) - - assert.NoError(t, err) - assert.Equal(t, snapshot.Len(), snapshotFromDB.Snapshot.Len()) - assert.Equal(t, epoch, snapshotFromDB.Epoch) - assert.Equal(t, epoch*fixedEpochSize, snapshotFromDB.EpochEndingBlock) - - for i, v := range snapshot { - assert.Equal(t, v.Address, snapshotFromDB.Snapshot[i].Address) - assert.Equal(t, v.BlsKey, snapshotFromDB.Snapshot[i].BlsKey) - } - - assert.NoError(t, state.EpochStore.cleanValidatorSnapshotsFromDB(epoch, nil)) - - // test that last (numberOfSnapshotsToLeaveInDb) of snapshots are left in db after cleanup - validatorSnapshotsBucketStats, err := state.EpochStore.validatorSnapshotsDBStats() - require.NoError(t, err) - - assert.Equal(t, numberOfSnapshotsToLeaveInDB, validatorSnapshotsBucketStats.KeyN) - - for i := 0; i < numberOfSnapshotsToLeaveInDB; i++ { - snapshotFromDB, err = state.EpochStore.getValidatorSnapshot(epoch) - assert.NoError(t, err) - assert.NotNil(t, snapshotFromDB) - epoch-- - } -} - -func TestState_InsertVoteConcurrent(t *testing.T) { - t.Parallel() - - state := newTestState(t) - epoch := uint64(1) - assert.NoError(t, state.EpochStore.insertEpoch(epoch, nil)) - - hash := []byte{1, 2} - - var wg sync.WaitGroup - - for i := 0; i < 100; i++ { - wg.Add(1) - - go func(i int) { - defer wg.Done() - - _, _ = state.StateSyncStore.insertMessageVote(epoch, hash, &MessageSignature{ - From: fmt.Sprintf("NODE_%d", i), - Signature: []byte{1, 2}, - }, nil) - }(i) - } - - wg.Wait() - - signatures, err := state.StateSyncStore.getMessageVotes(epoch, hash) - assert.NoError(t, err) - assert.Len(t, signatures, 100) -} - -func TestState_Insert_And_Cleanup(t *testing.T) { - t.Parallel() - - state := newTestState(t) - hash1 := []byte{1, 2} - - for i := uint64(1); i <= 500; i++ { - epoch := i - err := state.EpochStore.insertEpoch(epoch, nil) - - assert.NoError(t, err) - - _, _ = state.StateSyncStore.insertMessageVote(epoch, hash1, &MessageSignature{ - From: "NODE_1", - Signature: []byte{1, 2}, - }, nil) - } - - stats, err := state.EpochStore.epochsDBStats() - require.NoError(t, err) - - // BucketN returns number of all buckets inside root bucket (including nested buckets) + the root itself - // Since we inserted 500 epochs we expect to have 1000 buckets inside epochs root bucket - // (500 buckets for epochs + each epoch has 1 nested bucket for message votes) - assert.Equal(t, 1000, stats.BucketN-1) - - assert.NoError(t, state.EpochStore.cleanEpochsFromDB(nil)) - - stats, err = state.EpochStore.epochsDBStats() - require.NoError(t, err) - - assert.Equal(t, 0, stats.BucketN-1) - - // there should be no votes for given epoch since we cleaned the db - votes, _ := state.StateSyncStore.getMessageVotes(1, hash1) - assert.Nil(t, votes) - - for i := uint64(501); i <= 1000; i++ { - epoch := i - err := state.EpochStore.insertEpoch(epoch, nil) - assert.NoError(t, err) - - _, _ = state.StateSyncStore.insertMessageVote(epoch, hash1, &MessageSignature{ - From: "NODE_1", - Signature: []byte{1, 2}, - }, nil) - } - - stats, err = state.EpochStore.epochsDBStats() - require.NoError(t, err) - - assert.Equal(t, 1000, stats.BucketN-1) - - votes, _ = state.StateSyncStore.getMessageVotes(1000, hash1) - assert.Equal(t, 1, len(votes)) -} - -func TestState_getLastSnapshot(t *testing.T) { - t.Parallel() - - const ( - lastEpoch = uint64(10) - fixedEpochSize = uint64(10) - numberOfValidators = 3 - ) - - state := newTestState(t) - - for i := uint64(1); i <= lastEpoch; i++ { - keys, err := bls.CreateRandomBlsKeys(numberOfValidators) - - require.NoError(t, err) - - var snapshot validator.AccountSet - for j := 0; j < numberOfValidators; j++ { - snapshot = append(snapshot, &validator.ValidatorMetadata{Address: types.BytesToAddress(generateRandomBytes(t)), BlsKey: keys[j].PublicKey()}) - } - - require.NoError(t, state.EpochStore.insertValidatorSnapshot( - &validatorSnapshot{i, i * fixedEpochSize, snapshot}, nil)) - } - - snapshotFromDB, err := state.EpochStore.getLastSnapshot(nil) - - assert.NoError(t, err) - assert.Equal(t, numberOfValidators, snapshotFromDB.Snapshot.Len()) - assert.Equal(t, lastEpoch, snapshotFromDB.Epoch) - assert.Equal(t, lastEpoch*fixedEpochSize, snapshotFromDB.EpochEndingBlock) -} diff --git a/consensus/polybft/state_store_proposer_snapshot.go b/consensus/polybft/state_store_proposer_snapshot.go deleted file mode 100644 index abcae84..0000000 --- a/consensus/polybft/state_store_proposer_snapshot.go +++ /dev/null @@ -1,82 +0,0 @@ -package polybft - -import ( - "encoding/json" - "fmt" - - bolt "go.etcd.io/bbolt" -) - -/* -Bolt DB schema: - -proposer snapshot/ -|--> proposerSnapshotKey - only current one snapshot is preserved -> *ProposerSnapshot (json marshalled) -*/ -var ( - // bucket to store proposer calculator snapshot - proposerSnapshotBucket = []byte("proposerSnapshot") - // proposerSnapshotKey is a static key which is used to save latest proposer snapshot. - // (there will always be one object in bucket) - proposerSnapshotKey = []byte("proposerSnapshotKey") -) - -type ProposerSnapshotStore struct { - db *bolt.DB -} - -// initialize creates necessary buckets in DB if they don't already exist -func (s *ProposerSnapshotStore) initialize(tx *bolt.Tx) error { - if _, err := tx.CreateBucketIfNotExists(proposerSnapshotBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(validatorSnapshotsBucket), err) - } - - return nil -} - -// getProposerSnapshot gets latest proposer snapshot -func (s *ProposerSnapshotStore) getProposerSnapshot(dbTx *bolt.Tx) (*ProposerSnapshot, error) { - var ( - snapshot *ProposerSnapshot - err error - ) - - getFn := func(tx *bolt.Tx) error { - value := tx.Bucket(proposerSnapshotBucket).Get(proposerSnapshotKey) - if value == nil { - return nil - } - - return json.Unmarshal(value, &snapshot) - } - - if dbTx == nil { - err = s.db.View(func(tx *bolt.Tx) error { - return getFn(tx) - }) - } else { - err = getFn(dbTx) - } - - return snapshot, err -} - -// writeProposerSnapshot writes proposer snapshot -func (s *ProposerSnapshotStore) writeProposerSnapshot(snapshot *ProposerSnapshot, dbTx *bolt.Tx) error { - insertFn := func(tx *bolt.Tx) error { - raw, err := json.Marshal(snapshot) - if err != nil { - return err - } - - return tx.Bucket(proposerSnapshotBucket).Put(proposerSnapshotKey, raw) - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return insertFn(tx) - }) - } - - return insertFn(dbTx) -} diff --git a/consensus/polybft/state_store_proposer_snapshot_test.go b/consensus/polybft/state_store_proposer_snapshot_test.go deleted file mode 100644 index e8657f2..0000000 --- a/consensus/polybft/state_store_proposer_snapshot_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package polybft - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestState_getProposerSnapshot_writeProposerSnapshot(t *testing.T) { - t.Parallel() - - const ( - height = uint64(100) - round = uint64(5) - ) - - state := newTestState(t) - - snap, err := state.ProposerSnapshotStore.getProposerSnapshot(nil) - require.NoError(t, err) - require.Nil(t, snap) - - newSnapshot := &ProposerSnapshot{Height: height, Round: round} - require.NoError(t, state.ProposerSnapshotStore.writeProposerSnapshot(newSnapshot, nil)) - - snap, err = state.ProposerSnapshotStore.getProposerSnapshot(nil) - require.NoError(t, err) - require.Equal(t, newSnapshot, snap) -} diff --git a/consensus/polybft/state_store_stake.go b/consensus/polybft/state_store_stake.go deleted file mode 100644 index 1734b63..0000000 --- a/consensus/polybft/state_store_stake.go +++ /dev/null @@ -1,81 +0,0 @@ -package polybft - -import ( - "errors" - "fmt" - - bolt "go.etcd.io/bbolt" -) - -var ( - // bucket to store full validator set - validatorSetBucket = []byte("fullValidatorSetBucket") - // key of the full validator set in bucket - fullValidatorSetKey = []byte("fullValidatorSet") - // error returned if full validator set does not exists in db - errNoFullValidatorSet = errors.New("full validator set not in db") -) - -type StakeStore struct { - db *bolt.DB -} - -// initialize creates necessary buckets in DB if they don't already exist -func (s *StakeStore) initialize(tx *bolt.Tx) error { - if _, err := tx.CreateBucketIfNotExists(validatorSetBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(epochsBucket), err) - } - - return nil -} - -// insertFullValidatorSet inserts full validator set to its bucket (or updates it if exists) -// If the passed tx is already open (not nil), it will use it to insert full validator set -// If the passed tx is not open (it is nil), it will open a new transaction on db and insert full validator set -func (s *StakeStore) insertFullValidatorSet(fullValidatorSet validatorSetState, dbTx *bolt.Tx) error { - insertFn := func(tx *bolt.Tx) error { - raw, err := fullValidatorSet.Marshal() - if err != nil { - return err - } - - return tx.Bucket(validatorSetBucket).Put(fullValidatorSetKey, raw) - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return insertFn(tx) - }) - } - - return insertFn(dbTx) -} - -// getFullValidatorSet returns full validator set from its bucket if exists -// If the passed tx is already open (not nil), it will use it to get full validator set -// If the passed tx is not open (it is nil), it will open a new transaction on db and get full validator set -func (s *StakeStore) getFullValidatorSet(dbTx *bolt.Tx) (validatorSetState, error) { - var ( - fullValidatorSet validatorSetState - err error - ) - - getFn := func(tx *bolt.Tx) error { - raw := tx.Bucket(validatorSetBucket).Get(fullValidatorSetKey) - if raw == nil { - return errNoFullValidatorSet - } - - return fullValidatorSet.Unmarshal(raw) - } - - if dbTx == nil { - err = s.db.View(func(tx *bolt.Tx) error { - return getFn(tx) - }) - } else { - err = getFn(dbTx) - } - - return fullValidatorSet, err -} diff --git a/consensus/polybft/state_store_stake_test.go b/consensus/polybft/state_store_stake_test.go deleted file mode 100644 index c3fd813..0000000 --- a/consensus/polybft/state_store_stake_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package polybft - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" -) - -func TestState_Insert_And_Get_FullValidatorSet(t *testing.T) { - state := newTestState(t) - - t.Run("No full validator set", func(t *testing.T) { - _, err := state.StakeStore.getFullValidatorSet(nil) - - require.ErrorIs(t, err, errNoFullValidatorSet) - }) - - t.Run("Insert validator set", func(t *testing.T) { - validators := validator.NewTestValidators(t, 5).GetPublicIdentities() - - assert.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - BlockNumber: 100, - EpochID: 10, - Validators: newValidatorStakeMap(validators), - }, nil)) - - fullValidatorSet, err := state.StakeStore.getFullValidatorSet(nil) - require.NoError(t, err) - assert.Equal(t, uint64(100), fullValidatorSet.BlockNumber) - assert.Equal(t, uint64(10), fullValidatorSet.EpochID) - assert.Len(t, fullValidatorSet.Validators, len(validators)) - }) - - t.Run("Update validator set", func(t *testing.T) { - validators := validator.NewTestValidators(t, 10).GetPublicIdentities() - - assert.NoError(t, state.StakeStore.insertFullValidatorSet(validatorSetState{ - BlockNumber: 40, - EpochID: 4, - Validators: newValidatorStakeMap(validators), - }, nil)) - - fullValidatorSet, err := state.StakeStore.getFullValidatorSet(nil) - require.NoError(t, err) - assert.Len(t, fullValidatorSet.Validators, len(validators)) - assert.Equal(t, uint64(40), fullValidatorSet.BlockNumber) - assert.Equal(t, uint64(4), fullValidatorSet.EpochID) - }) -} diff --git a/consensus/polybft/state_store_state_sync.go b/consensus/polybft/state_store_state_sync.go deleted file mode 100644 index d6b0422..0000000 --- a/consensus/polybft/state_store_state_sync.go +++ /dev/null @@ -1,442 +0,0 @@ -package polybft - -import ( - "encoding/json" - "errors" - "fmt" - - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/helper/common" - bolt "go.etcd.io/bbolt" -) - -var ( - // bucket to store rootchain bridge events - stateSyncEventsBucket = []byte("stateSyncEvents") - // bucket to store commitments - commitmentsBucket = []byte("commitments") - // bucket to store state sync proofs - stateSyncProofsBucket = []byte("stateSyncProofs") - // bucket to store message votes (signatures) - messageVotesBucket = []byte("votes") - // bucket to store all state sync relayer events - stateSyncRelayerEventsBucket = []byte("relayerEvents") - - // errNotEnoughStateSyncs error message - errNotEnoughStateSyncs = errors.New("there is either a gap or not enough sync events") - // errCommitmentNotBuilt error message - errCommitmentNotBuilt = errors.New("there is no built commitment to register") - // errNoCommitmentForStateSync error message - errNoCommitmentForStateSync = errors.New("no commitment found for given state sync event") -) - -/* -Bolt DB schema: - -state sync events/ -|--> stateSyncEvent.Id -> *StateSyncEvent (json marshalled) - -commitments/ -|--> commitment.Message.ToIndex -> *CommitmentMessageSigned (json marshalled) - -stateSyncProofs/ -|--> stateSyncProof.StateSync.Id -> *StateSyncProof (json marshalled) - -relayerEvents/ -|--> StateSyncRelayerEventData.EventID -> *StateSyncRelayerEventData (json marshalled) -*/ - -type StateSyncStore struct { - db *bolt.DB -} - -// initialize creates necessary buckets in DB if they don't already exist -func (s *StateSyncStore) initialize(tx *bolt.Tx) error { - if _, err := tx.CreateBucketIfNotExists(stateSyncEventsBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(stateSyncEventsBucket), err) - } - - if _, err := tx.CreateBucketIfNotExists(commitmentsBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(commitmentsBucket), err) - } - - if _, err := tx.CreateBucketIfNotExists(stateSyncProofsBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(stateSyncProofsBucket), err) - } - - if _, err := tx.CreateBucketIfNotExists(stateSyncRelayerEventsBucket); err != nil { - return fmt.Errorf("failed to create bucket=%s: %w", string(stateSyncRelayerEventsBucket), err) - } - - return nil -} - -// insertStateSyncEvent inserts a new state sync event to state event bucket in db -func (s *StateSyncStore) insertStateSyncEvent(event *contractsapi.StateSyncedEvent) error { - return s.db.Update(func(tx *bolt.Tx) error { - raw, err := json.Marshal(event) - if err != nil { - return err - } - - bucket := tx.Bucket(stateSyncEventsBucket) - - return bucket.Put(common.EncodeUint64ToBytes(event.ID.Uint64()), raw) - }) -} - -// removeStateSyncEventsAndProofs removes state sync events and their proofs from the buckets in db -func (s *StateSyncStore) removeStateSyncEventsAndProofs(stateSyncEventIDs []uint64) error { - return s.db.Update(func(tx *bolt.Tx) error { - eventsBucket := tx.Bucket(stateSyncEventsBucket) - proofsBucket := tx.Bucket(stateSyncProofsBucket) - - for _, stateSyncEventID := range stateSyncEventIDs { - stateSyncEventIDKey := common.EncodeUint64ToBytes(stateSyncEventID) - - if err := eventsBucket.Delete(stateSyncEventIDKey); err != nil { - return fmt.Errorf("failed to remove state sync event (ID=%d): %w", stateSyncEventID, err) - } - - if err := proofsBucket.Delete(stateSyncEventIDKey); err != nil { - return fmt.Errorf("failed to remove state sync event proof (ID=%d): %w", stateSyncEventID, err) - } - } - - return nil - }) -} - -// list iterates through all events in events bucket in db, un-marshals them, and returns as array -func (s *StateSyncStore) list() ([]*contractsapi.StateSyncedEvent, error) { - events := []*contractsapi.StateSyncedEvent{} - - err := s.db.View(func(tx *bolt.Tx) error { - return tx.Bucket(stateSyncEventsBucket).ForEach(func(k, v []byte) error { - var event *contractsapi.StateSyncedEvent - if err := json.Unmarshal(v, &event); err != nil { - return err - } - events = append(events, event) - - return nil - }) - }) - if err != nil { - return nil, err - } - - return events, nil -} - -// getStateSyncEventsForCommitment returns state sync events for commitment -func (s *StateSyncStore) getStateSyncEventsForCommitment( - fromIndex, toIndex uint64, dbTx *bolt.Tx) ([]*contractsapi.StateSyncedEvent, error) { - var ( - events []*contractsapi.StateSyncedEvent - err error - ) - - getFn := func(tx *bolt.Tx) error { - bucket := tx.Bucket(stateSyncEventsBucket) - for i := fromIndex; i <= toIndex; i++ { - v := bucket.Get(common.EncodeUint64ToBytes(i)) - if v == nil { - return errNotEnoughStateSyncs - } - - var event *contractsapi.StateSyncedEvent - if err := json.Unmarshal(v, &event); err != nil { - return err - } - - events = append(events, event) - } - - return nil - } - - if dbTx == nil { - err = s.db.View(func(tx *bolt.Tx) error { - return getFn(tx) - }) - } else { - err = getFn(dbTx) - } - - return events, err -} - -// getCommitmentForStateSync returns the commitment that contains given state sync event if it exists -func (s *StateSyncStore) getCommitmentForStateSync(stateSyncID uint64) (*CommitmentMessageSigned, error) { - var commitment *CommitmentMessageSigned - - err := s.db.View(func(tx *bolt.Tx) error { - c := tx.Bucket(commitmentsBucket).Cursor() - - k, v := c.Seek(common.EncodeUint64ToBytes(stateSyncID)) - if k == nil { - return errNoCommitmentForStateSync - } - - if err := json.Unmarshal(v, &commitment); err != nil { - return err - } - - if !commitment.ContainsStateSync(stateSyncID) { - return errNoCommitmentForStateSync - } - - return nil - }) - - return commitment, err -} - -// insertCommitmentMessage inserts signed commitment to db -func (s *StateSyncStore) insertCommitmentMessage(commitment *CommitmentMessageSigned, - dbTx *bolt.Tx) error { - insertFn := func(tx *bolt.Tx) error { - raw, err := json.Marshal(commitment) - if err != nil { - return err - } - - if err := tx.Bucket(commitmentsBucket).Put( - common.EncodeUint64ToBytes(commitment.Message.EndID.Uint64()), raw); err != nil { - return err - } - - return nil - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return insertFn(tx) - }) - } - - return insertFn(dbTx) -} - -// getCommitmentMessage queries the signed commitment from the db -func (s *StateSyncStore) getCommitmentMessage(toIndex uint64) (*CommitmentMessageSigned, error) { - var commitment *CommitmentMessageSigned - - err := s.db.View(func(tx *bolt.Tx) error { - raw := tx.Bucket(commitmentsBucket).Get(common.EncodeUint64ToBytes(toIndex)) - if raw == nil { - return nil - } - - return json.Unmarshal(raw, &commitment) - }) - - return commitment, err -} - -// insertMessageVote inserts given vote to signatures bucket of given epoch -func (s *StateSyncStore) insertMessageVote(epoch uint64, key []byte, - vote *MessageSignature, dbTx *bolt.Tx) (int, error) { - var ( - numOfSignatures int - err error - ) - - insertFn := func(tx *bolt.Tx) error { - signatures, err := s.getMessageVotesLocked(tx, epoch, key) - if err != nil { - return err - } - - // check if the signature has already being included - for _, sigs := range signatures { - if sigs.From == vote.From { - return nil - } - } - - if signatures == nil { - signatures = []*MessageSignature{vote} - } else { - signatures = append(signatures, vote) - } - - raw, err := json.Marshal(signatures) - if err != nil { - return err - } - - bucket, err := getNestedBucketInEpoch(tx, epoch, messageVotesBucket) - if err != nil { - return err - } - - numOfSignatures = len(signatures) - - return bucket.Put(key, raw) - } - - if dbTx == nil { - err = s.db.Update(func(tx *bolt.Tx) error { - return insertFn(tx) - }) - } else { - err = insertFn(dbTx) - } - - return numOfSignatures, err -} - -// getMessageVotes gets all signatures from db associated with given epoch and hash -func (s *StateSyncStore) getMessageVotes(epoch uint64, hash []byte) ([]*MessageSignature, error) { - var signatures []*MessageSignature - - err := s.db.View(func(tx *bolt.Tx) error { - res, err := s.getMessageVotesLocked(tx, epoch, hash) - if err != nil { - return err - } - signatures = res - - return nil - }) - - if err != nil { - return nil, err - } - - return signatures, nil -} - -// getMessageVotesLocked gets all signatures from db associated with given epoch and hash -func (s *StateSyncStore) getMessageVotesLocked(tx *bolt.Tx, epoch uint64, - hash []byte) ([]*MessageSignature, error) { - bucket, err := getNestedBucketInEpoch(tx, epoch, messageVotesBucket) - if err != nil { - return nil, err - } - - v := bucket.Get(hash) - if v == nil { - return nil, nil - } - - var signatures []*MessageSignature - if err := json.Unmarshal(v, &signatures); err != nil { - return nil, err - } - - return signatures, nil -} - -// insertStateSyncProofs inserts the provided state sync proofs to db -func (s *StateSyncStore) insertStateSyncProofs(stateSyncProof []*StateSyncProof, dbTx *bolt.Tx) error { - insertFn := func(tx *bolt.Tx) error { - bucket := tx.Bucket(stateSyncProofsBucket) - - for _, ssp := range stateSyncProof { - raw, err := json.Marshal(ssp) - if err != nil { - return err - } - - if err := bucket.Put(common.EncodeUint64ToBytes(ssp.StateSync.ID.Uint64()), raw); err != nil { - return err - } - } - - return nil - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return insertFn(tx) - }) - } - - return insertFn(dbTx) -} - -// getStateSyncProof gets state sync proof that are not executed -func (s *StateSyncStore) getStateSyncProof(stateSyncID uint64) (*StateSyncProof, error) { - var ssp *StateSyncProof - - err := s.db.View(func(tx *bolt.Tx) error { - if v := tx.Bucket(stateSyncProofsBucket).Get(common.EncodeUint64ToBytes(stateSyncID)); v != nil { - if err := json.Unmarshal(v, &ssp); err != nil { - return err - } - } - - return nil - }) - - return ssp, err -} - -// updateStateSyncRelayerEvents updates/remove desired events -func (s *StateSyncStore) updateStateSyncRelayerEvents( - events []*StateSyncRelayerEventData, removeIDs []uint64, dbTx *bolt.Tx) error { - updateFn := func(tx *bolt.Tx) error { - relayerEventsBucket := tx.Bucket(stateSyncRelayerEventsBucket) - - for _, evnt := range events { - raw, err := json.Marshal(evnt) - if err != nil { - return err - } - - key := common.EncodeUint64ToBytes(evnt.EventID) - - if err := relayerEventsBucket.Put(key, raw); err != nil { - return err - } - } - - for _, stateSyncEventID := range removeIDs { - stateSyncEventIDKey := common.EncodeUint64ToBytes(stateSyncEventID) - - if err := relayerEventsBucket.Delete(stateSyncEventIDKey); err != nil { - return fmt.Errorf("failed to remove state sync relayer event (ID=%d): %w", stateSyncEventID, err) - } - } - - return nil - } - - if dbTx == nil { - return s.db.Update(func(tx *bolt.Tx) error { - return updateFn(tx) - }) - } - - return updateFn(dbTx) -} - -// getAllAvailableEvents retrieves all StateSyncRelayerEventData that should be sent as a transactions -func (s *StateSyncStore) getAllAvailableEvents(limit int) (result []*StateSyncRelayerEventData, err error) { - if err = s.db.View(func(tx *bolt.Tx) error { - cursor := tx.Bucket(stateSyncRelayerEventsBucket).Cursor() - - for k, v := cursor.First(); k != nil; k, v = cursor.Next() { - var event *StateSyncRelayerEventData - - if err := json.Unmarshal(v, &event); err != nil { - return err - } - - result = append(result, event) - - if limit > 0 && len(result) >= limit { - break - } - } - - return nil - }); err != nil { - return nil, err - } - - return result, nil -} diff --git a/consensus/polybft/state_store_state_sync_test.go b/consensus/polybft/state_store_state_sync_test.go deleted file mode 100644 index 599eb53..0000000 --- a/consensus/polybft/state_store_state_sync_test.go +++ /dev/null @@ -1,355 +0,0 @@ -package polybft - -import ( - "bytes" - "fmt" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/merkle-tree" - "github.com/xgr-network/xgr-node/types" - "go.etcd.io/bbolt" -) - -func TestState_InsertEvent(t *testing.T) { - t.Parallel() - - state := newTestState(t) - event1 := &contractsapi.StateSyncedEvent{ - ID: big.NewInt(0), - Sender: types.Address{}, - Receiver: types.Address{}, - Data: []byte{}, - } - - err := state.StateSyncStore.insertStateSyncEvent(event1) - assert.NoError(t, err) - - events, err := state.StateSyncStore.list() - assert.NoError(t, err) - assert.Len(t, events, 1) -} - -func TestState_Insert_And_Get_MessageVotes(t *testing.T) { - t.Parallel() - - state := newTestState(t) - epoch := uint64(1) - assert.NoError(t, state.EpochStore.insertEpoch(epoch, nil)) - - hash := []byte{1, 2} - _, err := state.StateSyncStore.insertMessageVote(1, hash, &MessageSignature{ - From: "NODE_1", - Signature: []byte{1, 2}, - }, nil) - - assert.NoError(t, err) - - votes, err := state.StateSyncStore.getMessageVotes(epoch, hash) - assert.NoError(t, err) - assert.Equal(t, 1, len(votes)) - assert.Equal(t, "NODE_1", votes[0].From) - assert.True(t, bytes.Equal([]byte{1, 2}, votes[0].Signature)) -} - -func TestState_getStateSyncEventsForCommitment_NotEnoughEvents(t *testing.T) { - t.Parallel() - - state := newTestState(t) - - for i := 0; i < maxCommitmentSize-2; i++ { - assert.NoError(t, state.StateSyncStore.insertStateSyncEvent(&contractsapi.StateSyncedEvent{ - ID: big.NewInt(int64(i)), - Data: []byte{1, 2}, - })) - } - - _, err := state.StateSyncStore.getStateSyncEventsForCommitment(0, maxCommitmentSize-1, nil) - assert.ErrorIs(t, err, errNotEnoughStateSyncs) -} - -func TestState_getStateSyncEventsForCommitment(t *testing.T) { - t.Parallel() - - state := newTestState(t) - - for i := 0; i < maxCommitmentSize; i++ { - assert.NoError(t, state.StateSyncStore.insertStateSyncEvent(&contractsapi.StateSyncedEvent{ - ID: big.NewInt(int64(i)), - Data: []byte{1, 2}, - })) - } - - t.Run("Return all - forced. Enough events", func(t *testing.T) { - t.Parallel() - - events, err := state.StateSyncStore.getStateSyncEventsForCommitment(0, maxCommitmentSize-1, nil) - require.NoError(t, err) - require.Equal(t, maxCommitmentSize, len(events)) - }) - - t.Run("Return all - forced. Not enough events", func(t *testing.T) { - t.Parallel() - - _, err := state.StateSyncStore.getStateSyncEventsForCommitment(0, maxCommitmentSize+1, nil) - require.ErrorIs(t, err, errNotEnoughStateSyncs) - }) - - t.Run("Return all you can. Enough events", func(t *testing.T) { - t.Parallel() - - events, err := state.StateSyncStore.getStateSyncEventsForCommitment(0, maxCommitmentSize-1, nil) - assert.NoError(t, err) - assert.Equal(t, maxCommitmentSize, len(events)) - }) - - t.Run("Return all you can. Not enough events", func(t *testing.T) { - t.Parallel() - - events, err := state.StateSyncStore.getStateSyncEventsForCommitment(0, maxCommitmentSize+1, nil) - assert.ErrorIs(t, err, errNotEnoughStateSyncs) - assert.Equal(t, maxCommitmentSize, len(events)) - }) -} - -func TestState_insertCommitmentMessage(t *testing.T) { - t.Parallel() - - commitment := createTestCommitmentMessage(t, 11) - - state := newTestState(t) - assert.NoError(t, state.StateSyncStore.insertCommitmentMessage(commitment, nil)) - - commitmentFromDB, err := state.StateSyncStore.getCommitmentMessage(commitment.Message.EndID.Uint64()) - - assert.NoError(t, err) - assert.NotNil(t, commitmentFromDB) - assert.Equal(t, commitment, commitmentFromDB) -} - -func TestState_StateSync_insertAndGetStateSyncProof(t *testing.T) { - t.Parallel() - - state := newTestState(t) - commitment := createTestCommitmentMessage(t, 0) - require.NoError(t, state.StateSyncStore.insertCommitmentMessage(commitment, nil)) - - insertTestStateSyncProofs(t, state, 10) - - proofFromDB, err := state.StateSyncStore.getStateSyncProof(1) - - assert.NoError(t, err) - assert.Equal(t, uint64(1), proofFromDB.StateSync.ID.Uint64()) - assert.NotNil(t, proofFromDB.Proof) -} - -func TestState_getCommitmentForStateSync(t *testing.T) { - const ( - numOfCommitments = 10 - ) - - state := newTestState(t) - - insertTestCommitments(t, state, numOfCommitments) - - var cases = []struct { - stateSyncID uint64 - hasCommitment bool - }{ - {1, true}, - {10, true}, - {11, true}, - {7, true}, - {999, false}, - {121, false}, - {99, true}, - {101, true}, - {111, false}, - {75, true}, - {5, true}, - {102, true}, - {211, false}, - {21, true}, - {30, true}, - {81, true}, - {90, true}, - } - - for _, c := range cases { - commitment, err := state.StateSyncStore.getCommitmentForStateSync(c.stateSyncID) - - if c.hasCommitment { - require.NoError(t, err, fmt.Sprintf("state sync %v", c.stateSyncID)) - require.Equal(t, c.hasCommitment, commitment.ContainsStateSync(c.stateSyncID)) - } else { - require.ErrorIs(t, errNoCommitmentForStateSync, err) - } - } -} - -func TestState_GetNestedBucketInEpoch(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - epochNumber uint64 - bucketName []byte - errMsg string - }{ - { - name: "Not existing inner bucket", - epochNumber: 3, - bucketName: []byte("Foo"), - errMsg: "could not find Foo bucket for epoch: 3", - }, - { - name: "Happy path", - epochNumber: 5, - bucketName: messageVotesBucket, - errMsg: "", - }, - } - - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - - var ( - nestedBucket *bbolt.Bucket - err error - ) - - s := newTestState(t) - require.NoError(t, s.EpochStore.insertEpoch(c.epochNumber, nil)) - err = s.db.View(func(tx *bbolt.Tx) error { - nestedBucket, err = getNestedBucketInEpoch(tx, c.epochNumber, c.bucketName) - - return err - }) - if c.errMsg != "" { - require.ErrorContains(t, err, c.errMsg) - require.Nil(t, nestedBucket) - } else { - require.NoError(t, err) - require.NotNil(t, nestedBucket) - } - }) - } -} - -func createTestCommitmentMessage(t *testing.T, fromIndex uint64) *CommitmentMessageSigned { - t.Helper() - - tree, err := merkle.NewMerkleTree([][]byte{ - {0, 1}, - {2, 3}, - {4, 5}, - }) - - require.NoError(t, err) - - msg := &contractsapi.StateSyncCommitment{ - Root: tree.Hash(), - StartID: big.NewInt(int64(fromIndex)), - EndID: big.NewInt(int64(fromIndex + maxCommitmentSize - 1)), - } - - return &CommitmentMessageSigned{ - Message: msg, - AggSignature: Signature{}, - } -} - -func insertTestCommitments(t *testing.T, state *State, numberOfCommitments uint64) { - t.Helper() - - for i := uint64(0); i <= numberOfCommitments; i++ { - commitment := createTestCommitmentMessage(t, i*maxCommitmentSize+1) - require.NoError(t, state.StateSyncStore.insertCommitmentMessage(commitment, nil)) - } -} - -func insertTestStateSyncProofs(t *testing.T, state *State, numberOfProofs int64) { - t.Helper() - - ssProofs := make([]*StateSyncProof, numberOfProofs) - - for i := int64(0); i < numberOfProofs; i++ { - proofs := &StateSyncProof{ - Proof: []types.Hash{types.BytesToHash(generateRandomBytes(t))}, - StateSync: createTestStateSync(i), - } - ssProofs[i] = proofs - } - - require.NoError(t, state.StateSyncStore.insertStateSyncProofs(ssProofs, nil)) -} - -func createTestStateSync(index int64) *contractsapi.StateSyncedEvent { - return &contractsapi.StateSyncedEvent{ - ID: big.NewInt(index), - Sender: types.ZeroAddress, - Receiver: types.ZeroAddress, - Data: []byte{0, 1}, - } -} - -func TestState_StateSync_StateSyncRelayerDataAndEvents(t *testing.T) { - t.Parallel() - - state := newTestState(t) - - // update - require.NoError(t, state.StateSyncStore.updateStateSyncRelayerEvents([]*StateSyncRelayerEventData{ - {EventID: 2}, - {EventID: 4}, - {EventID: 7, SentStatus: true, BlockNumber: 100}, - }, []uint64{}, nil)) - - // get available events - events, err := state.StateSyncStore.getAllAvailableEvents(0) - - require.NoError(t, err) - require.Len(t, events, 3) - require.Equal(t, uint64(2), events[0].EventID) - require.Equal(t, uint64(4), events[1].EventID) - require.Equal(t, uint64(7), events[2].EventID) - - // update again - require.NoError(t, state.StateSyncStore.updateStateSyncRelayerEvents( - []*StateSyncRelayerEventData{ - {EventID: 10}, - {EventID: 12}, - {EventID: 11}, - }, - []uint64{4, 7}, - nil, - )) - - // get available events - events, err = state.StateSyncStore.getAllAvailableEvents(1000) - - require.NoError(t, err) - require.Len(t, events, 4) - require.Equal(t, uint64(2), events[0].EventID) - require.Equal(t, uint64(10), events[1].EventID) - require.Equal(t, false, events[1].SentStatus) - require.Equal(t, uint64(11), events[2].EventID) - require.Equal(t, uint64(12), events[3].EventID) - - events[1].SentStatus = true - require.NoError(t, state.StateSyncStore.updateStateSyncRelayerEvents(events[1:2], []uint64{2}, nil)) - - // get available events with limit - events, err = state.StateSyncStore.getAllAvailableEvents(2) - - require.NoError(t, err) - require.Len(t, events, 2) - require.Equal(t, uint64(10), events[0].EventID) - require.Equal(t, true, events[0].SentStatus) - require.Equal(t, uint64(11), events[1].EventID) -} diff --git a/consensus/polybft/state_sync_commitment.go b/consensus/polybft/state_sync_commitment.go deleted file mode 100644 index 932975c..0000000 --- a/consensus/polybft/state_sync_commitment.go +++ /dev/null @@ -1,202 +0,0 @@ -package polybft - -import ( - "bytes" - "errors" - "fmt" - - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/crypto" - "github.com/xgr-network/xgr-node/merkle-tree" - "github.com/xgr-network/xgr-node/state/runtime/precompiled" - "github.com/xgr-network/xgr-node/types" -) - -const ( - stTypeBridgeCommitment = "commitment" - stTypeEndEpoch = "end-epoch" -) - -// PendingCommitment holds merkle trie of bridge transactions accompanied by epoch number -type PendingCommitment struct { - *contractsapi.StateSyncCommitment - MerkleTree *merkle.MerkleTree - Epoch uint64 -} - -// NewPendingCommitment creates a new commitment object -func NewPendingCommitment(epoch uint64, stateSyncEvents []*contractsapi.StateSyncedEvent) (*PendingCommitment, error) { - tree, err := createMerkleTree(stateSyncEvents) - if err != nil { - return nil, err - } - - return &PendingCommitment{ - MerkleTree: tree, - Epoch: epoch, - StateSyncCommitment: &contractsapi.StateSyncCommitment{ - StartID: stateSyncEvents[0].ID, - EndID: stateSyncEvents[len(stateSyncEvents)-1].ID, - Root: tree.Hash(), - }, - }, nil -} - -// Hash calculates hash value for commitment object. -func (cm *PendingCommitment) Hash() (types.Hash, error) { - data, err := cm.StateSyncCommitment.EncodeAbi() - if err != nil { - return types.Hash{}, err - } - - return crypto.Keccak256Hash(data), nil -} - -var _ contractsapi.StateTransactionInput = &CommitmentMessageSigned{} - -// CommitmentMessageSigned encapsulates commitment message with aggregated signatures -type CommitmentMessageSigned struct { - Message *contractsapi.StateSyncCommitment - AggSignature Signature - PublicKeys [][]byte -} - -// Hash calculates hash value for commitment object. -func (cm *CommitmentMessageSigned) Hash() (types.Hash, error) { - data, err := cm.Message.EncodeAbi() - if err != nil { - return types.Hash{}, err - } - - return crypto.Keccak256Hash(data), nil -} - -// VerifyStateSyncProof validates given state sync proof -// against merkle tree root hash contained in the CommitmentMessage -func (cm *CommitmentMessageSigned) VerifyStateSyncProof(proof []types.Hash, - stateSync *contractsapi.StateSyncedEvent) error { - if stateSync == nil { - return errors.New("no state sync event") - } - - if stateSync.ID.Uint64() < cm.Message.StartID.Uint64() || - stateSync.ID.Uint64() > cm.Message.EndID.Uint64() { - return errors.New("invalid state sync ID") - } - - hash, err := stateSync.EncodeAbi() - if err != nil { - return err - } - - return merkle.VerifyProof(stateSync.ID.Uint64()-cm.Message.StartID.Uint64(), - hash, proof, cm.Message.Root) -} - -// ContainsStateSync checks if commitment contains given state sync event -func (cm *CommitmentMessageSigned) ContainsStateSync(stateSyncID uint64) bool { - return cm.Message.StartID.Uint64() <= stateSyncID && cm.Message.EndID.Uint64() >= stateSyncID -} - -// EncodeAbi contains logic for encoding arbitrary data into ABI format -func (cm *CommitmentMessageSigned) EncodeAbi() ([]byte, error) { - blsVerificationPart, err := precompiled.BlsVerificationABIType.Encode( - [2]interface{}{cm.PublicKeys, cm.AggSignature.Bitmap}) - if err != nil { - return nil, err - } - - commit := &contractsapi.CommitStateReceiverFn{ - Commitment: cm.Message, - Signature: cm.AggSignature.AggregatedSignature, - Bitmap: blsVerificationPart, - } - - return commit.EncodeAbi() -} - -// DecodeAbi contains logic for decoding given ABI data -func (cm *CommitmentMessageSigned) DecodeAbi(txData []byte) error { - if len(txData) < abiMethodIDLength { - return fmt.Errorf("invalid commitment data, len = %d", len(txData)) - } - - commit := contractsapi.CommitStateReceiverFn{} - - err := commit.DecodeAbi(txData) - if err != nil { - return err - } - - decoded, err := precompiled.BlsVerificationABIType.Decode(commit.Bitmap) - if err != nil { - return err - } - - blsMap, isOk := decoded.(map[string]interface{}) - if !isOk { - return fmt.Errorf("invalid commitment data. Bls verification part not in correct format") - } - - publicKeys, isOk := blsMap["0"].([][]byte) - if !isOk { - return fmt.Errorf("invalid commitment data. Could not find public keys part") - } - - bitmap, isOk := blsMap["1"].([]byte) - if !isOk { - return fmt.Errorf("invalid commitment data. Could not find bitmap part") - } - - *cm = CommitmentMessageSigned{ - Message: commit.Commitment, - AggSignature: Signature{ - AggregatedSignature: commit.Signature, - Bitmap: bitmap, - }, - PublicKeys: publicKeys, - } - - return nil -} - -// getCommitmentMessageSignedTx returns a CommitmentMessageSigned object from a commit state transaction -func getCommitmentMessageSignedTx(txs []*types.Transaction) (*CommitmentMessageSigned, error) { - var commitFn contractsapi.CommitStateReceiverFn - for _, tx := range txs { - // skip non state CommitmentMessageSigned transactions - if tx.Type != types.StateTx || - len(tx.Input) < abiMethodIDLength || - !bytes.Equal(tx.Input[:abiMethodIDLength], commitFn.Sig()) { - continue - } - - obj := &CommitmentMessageSigned{} - - if err := obj.DecodeAbi(tx.Input); err != nil { - return nil, fmt.Errorf("get commitment message signed tx error: %w", err) - } - - return obj, nil - } - - return nil, nil -} - -// createMerkleTree creates a merkle tree from provided state sync events -// if only one state sync event is provided, a second, empty leaf will be added to merkle tree -// so that we can have a commitment with a single state sync event -func createMerkleTree(stateSyncEvents []*contractsapi.StateSyncedEvent) (*merkle.MerkleTree, error) { - stateSyncData := make([][]byte, len(stateSyncEvents)) - - for i, sse := range stateSyncEvents { - data, err := sse.EncodeAbi() - if err != nil { - return nil, err - } - - stateSyncData[i] = data - } - - return merkle.NewMerkleTree(stateSyncData) -} diff --git a/consensus/polybft/state_sync_commitment_test.go b/consensus/polybft/state_sync_commitment_test.go deleted file mode 100644 index ea0bec9..0000000 --- a/consensus/polybft/state_sync_commitment_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package polybft - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/types" -) - -func TestCommitmentMessage_Hash(t *testing.T) { - t.Parallel() - - const ( - eventsCount = 10 - ) - - stateSyncEvents := generateStateSyncEvents(t, eventsCount, 0) - - trie1, err := createMerkleTree(stateSyncEvents) - require.NoError(t, err) - - trie2, err := createMerkleTree(stateSyncEvents[0 : len(stateSyncEvents)-1]) - require.NoError(t, err) - - commitmentMessage1 := newTestCommitmentSigned(t, trie1.Hash(), 2, 8) - commitmentMessage2 := newTestCommitmentSigned(t, trie1.Hash(), 2, 8) - commitmentMessage3 := newTestCommitmentSigned(t, trie1.Hash(), 6, 10) - commitmentMessage4 := newTestCommitmentSigned(t, trie2.Hash(), 2, 8) - - hash1, err := commitmentMessage1.Hash() - require.NoError(t, err) - hash2, err := commitmentMessage2.Hash() - require.NoError(t, err) - hash3, err := commitmentMessage3.Hash() - require.NoError(t, err) - hash4, err := commitmentMessage4.Hash() - require.NoError(t, err) - - require.Equal(t, hash1, hash2) - require.NotEqual(t, hash1, hash3) - require.NotEqual(t, hash1, hash4) - require.NotEqual(t, hash3, hash4) -} - -func TestCommitmentMessage_ToRegisterCommitmentInputData(t *testing.T) { - t.Parallel() - - const epoch, eventsCount = uint64(100), 11 - pendingCommitment, _, _ := buildCommitmentAndStateSyncs(t, eventsCount, epoch, uint64(2)) - expectedSignedCommitmentMsg := &CommitmentMessageSigned{ - Message: pendingCommitment.StateSyncCommitment, - AggSignature: Signature{ - Bitmap: []byte{5, 1}, - AggregatedSignature: []byte{1, 1}, - }, - PublicKeys: [][]byte{{0, 1}, {2, 3}, {4, 5}}, - } - inputData, err := expectedSignedCommitmentMsg.EncodeAbi() - require.NoError(t, err) - require.NotEmpty(t, inputData) - - var actualSignedCommitmentMsg CommitmentMessageSigned - - require.NoError(t, actualSignedCommitmentMsg.DecodeAbi(inputData)) - require.NoError(t, err) - require.Equal(t, *expectedSignedCommitmentMsg.Message, *actualSignedCommitmentMsg.Message) - require.Equal(t, expectedSignedCommitmentMsg.AggSignature, actualSignedCommitmentMsg.AggSignature) -} - -func TestCommitmentMessage_VerifyProof(t *testing.T) { - t.Parallel() - - const epoch, eventsCount = uint64(100), 11 - commitment, commitmentSigned, stateSyncs := buildCommitmentAndStateSyncs(t, eventsCount, epoch, 0) - require.Equal(t, uint64(10), commitment.EndID.Sub(commitment.EndID, commitment.StartID).Uint64()) - - for _, stateSync := range stateSyncs { - leaf, err := stateSync.EncodeAbi() - require.NoError(t, err) - - proof, err := commitment.MerkleTree.GenerateProof(leaf) - require.NoError(t, err) - - execute := &contractsapi.ExecuteStateReceiverFn{ - Proof: proof, - Obj: (*contractsapi.StateSync)(stateSync), - } - - inputData, err := execute.EncodeAbi() - require.NoError(t, err) - - executionStateSync := &contractsapi.ExecuteStateReceiverFn{} - require.NoError(t, executionStateSync.DecodeAbi(inputData)) - require.Equal(t, stateSync.ID.Uint64(), executionStateSync.Obj.ID.Uint64()) - require.Equal(t, stateSync.Sender, executionStateSync.Obj.Sender) - require.Equal(t, stateSync.Receiver, executionStateSync.Obj.Receiver) - require.Equal(t, stateSync.Data, executionStateSync.Obj.Data) - require.Equal(t, proof, executionStateSync.Proof) - - err = commitmentSigned.VerifyStateSyncProof(executionStateSync.Proof, - (*contractsapi.StateSyncedEvent)(executionStateSync.Obj)) - require.NoError(t, err) - } -} - -func TestCommitmentMessage_VerifyProof_NoStateSyncsInCommitment(t *testing.T) { - t.Parallel() - - commitment := &CommitmentMessageSigned{Message: &contractsapi.StateSyncCommitment{StartID: big.NewInt(1), EndID: big.NewInt(10)}} - err := commitment.VerifyStateSyncProof([]types.Hash{}, nil) - assert.ErrorContains(t, err, "no state sync event") -} - -func TestCommitmentMessage_VerifyProof_StateSyncHashNotEqualToProof(t *testing.T) { - t.Parallel() - - const ( - fromIndex = 0 - toIndex = 4 - ) - - stateSyncs := generateStateSyncEvents(t, 5, 0) - tree, err := createMerkleTree(stateSyncs) - require.NoError(t, err) - - leaf, err := stateSyncs[0].EncodeAbi() - require.NoError(t, err) - - proof, err := tree.GenerateProof(leaf) - require.NoError(t, err) - - commitment := &CommitmentMessageSigned{ - Message: &contractsapi.StateSyncCommitment{ - StartID: big.NewInt(fromIndex), - EndID: big.NewInt(toIndex), - Root: tree.Hash(), - }, - } - - assert.ErrorContains(t, commitment.VerifyStateSyncProof(proof, stateSyncs[4]), "not a member of merkle tree") -} - -func newTestCommitmentSigned(t *testing.T, root types.Hash, startID, endID int64) *CommitmentMessageSigned { - t.Helper() - - return &CommitmentMessageSigned{ - Message: &contractsapi.StateSyncCommitment{ - StartID: big.NewInt(startID), - EndID: big.NewInt(endID), - Root: root, - }, - AggSignature: Signature{}, - PublicKeys: [][]byte{}, - } -} - -func buildCommitmentAndStateSyncs(t *testing.T, stateSyncsCount int, - epoch, startIdx uint64) (*PendingCommitment, *CommitmentMessageSigned, []*contractsapi.StateSyncedEvent) { - t.Helper() - - stateSyncEvents := generateStateSyncEvents(t, stateSyncsCount, startIdx) - commitment, err := NewPendingCommitment(epoch, stateSyncEvents) - require.NoError(t, err) - - commitmentSigned := &CommitmentMessageSigned{ - Message: commitment.StateSyncCommitment, - AggSignature: Signature{ - AggregatedSignature: []byte{}, - Bitmap: []byte{}, - }, - PublicKeys: [][]byte{}, - } - - return commitment, commitmentSigned, stateSyncEvents -} diff --git a/consensus/polybft/state_sync_manager.go b/consensus/polybft/state_sync_manager.go deleted file mode 100644 index c60a692..0000000 --- a/consensus/polybft/state_sync_manager.go +++ /dev/null @@ -1,651 +0,0 @@ -package polybft - -import ( - "context" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "path" - "sync" - "time" - - "github.com/hashicorp/go-hclog" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/umbracle/ethgo" - bolt "go.etcd.io/bbolt" - "google.golang.org/protobuf/proto" - - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - polybftProto "github.com/xgr-network/xgr-node/consensus/polybft/proto" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/tracker" - "github.com/xgr-network/xgr-node/types" -) - -type Runtime interface { - IsActiveValidator() bool -} - -type StateSyncProof struct { - Proof []types.Hash - StateSync *contractsapi.StateSyncedEvent -} - -// StateSyncManager is an interface that defines functions for state sync workflow -type StateSyncManager interface { - EventSubscriber - Init() error - Close() - Commitment(blockNumber uint64) (*CommitmentMessageSigned, error) - GetStateSyncProof(stateSyncID uint64) (types.Proof, error) - PostBlock(req *PostBlockRequest) error - PostEpoch(req *PostEpochRequest) error -} - -var _ StateSyncManager = (*dummyStateSyncManager)(nil) - -// dummyStateSyncManager is used when bridge is not enabled -type dummyStateSyncManager struct{} - -func (d *dummyStateSyncManager) Init() error { return nil } -func (d *dummyStateSyncManager) Close() {} -func (d *dummyStateSyncManager) Commitment(blockNumber uint64) (*CommitmentMessageSigned, error) { - return nil, nil -} -func (d *dummyStateSyncManager) PostBlock(req *PostBlockRequest) error { return nil } -func (d *dummyStateSyncManager) PostEpoch(req *PostEpochRequest) error { return nil } -func (d *dummyStateSyncManager) GetStateSyncProof(stateSyncID uint64) (types.Proof, error) { - return types.Proof{}, nil -} - -// EventSubscriber implementation -func (d *dummyStateSyncManager) GetLogFilters() map[types.Address][]types.Hash { - return make(map[types.Address][]types.Hash) -} -func (d *dummyStateSyncManager) ProcessLog(header *types.Header, - log *ethgo.Log, dbTx *bolt.Tx) error { - return nil -} - -// stateSyncConfig holds the configuration data of state sync manager -type stateSyncConfig struct { - stateSenderAddr types.Address - stateSenderStartBlock uint64 - jsonrpcAddr string - dataDir string - topic topic - key *wallet.Key - maxCommitmentSize uint64 - numBlockConfirmations uint64 - blockTrackerPollInterval time.Duration -} - -var _ StateSyncManager = (*stateSyncManager)(nil) - -// stateSyncManager is a struct that manages the workflow of -// saving and querying state sync events, and creating, and submitting new commitments -type stateSyncManager struct { - logger hclog.Logger - state *State - - config *stateSyncConfig - closeCh chan struct{} - - // per epoch fields - lock sync.RWMutex - pendingCommitments []*PendingCommitment - validatorSet validator.ValidatorSet - epoch uint64 - nextCommittedIndex uint64 - - runtime Runtime -} - -// topic is an interface for p2p message gossiping -type topic interface { - Publish(obj proto.Message) error - Subscribe(handler func(obj interface{}, from peer.ID)) error -} - -// newStateSyncManager creates a new instance of state sync manager -func newStateSyncManager(logger hclog.Logger, state *State, config *stateSyncConfig, - runtime Runtime) *stateSyncManager { - return &stateSyncManager{ - logger: logger, - state: state, - config: config, - closeCh: make(chan struct{}), - runtime: runtime, - } -} - -// Init subscribes to bridge topics (getting votes) and start the event tracker routine -func (s *stateSyncManager) Init() error { - if err := s.initTracker(); err != nil { - return fmt.Errorf("failed to init event tracker. Error: %w", err) - } - - if err := s.initTransport(); err != nil { - return fmt.Errorf("failed to initialize state sync transport layer. Error: %w", err) - } - - return nil -} - -func (s *stateSyncManager) Close() { - close(s.closeCh) -} - -// initTracker starts a new event tracker (to receive new state sync events) -func (s *stateSyncManager) initTracker() error { - ctx, cancelFn := context.WithCancel(context.Background()) - - evtTracker := tracker.NewEventTracker( - path.Join(s.config.dataDir, "/deposit.db"), - s.config.jsonrpcAddr, - ethgo.Address(s.config.stateSenderAddr), - s, - s.config.numBlockConfirmations, - s.config.stateSenderStartBlock, - s.logger, - s.config.blockTrackerPollInterval) - - go func() { - <-s.closeCh - cancelFn() - }() - - return evtTracker.Start(ctx) -} - -// initTransport subscribes to bridge topics (getting votes for commitments) -func (s *stateSyncManager) initTransport() error { - return s.config.topic.Subscribe(func(obj interface{}, _ peer.ID) { - if !s.runtime.IsActiveValidator() { - // don't save votes if not a validator - return - } - - msg, ok := obj.(*polybftProto.TransportMessage) - if !ok { - s.logger.Warn("failed to deliver vote, invalid msg", "obj", obj) - - return - } - - var transportMsg *TransportMessage - - if err := json.Unmarshal(msg.Data, &transportMsg); err != nil { - s.logger.Warn("failed to deliver vote", "error", err) - - return - } - - if err := s.saveVote(transportMsg); err != nil { - s.logger.Warn("failed to deliver vote", "error", err) - } - }) -} - -// saveVote saves the gotten vote to boltDb for later quorum check and signature aggregation -func (s *stateSyncManager) saveVote(msg *TransportMessage) error { - s.lock.RLock() - epoch := s.epoch - valSet := s.validatorSet - s.lock.RUnlock() - - if valSet == nil || msg.EpochNumber != epoch { - // Epoch metadata is undefined or received a message for the irrelevant epoch - return nil - } - - if err := s.verifyVoteSignature(valSet, types.StringToAddress(msg.From), msg.Signature, msg.Hash); err != nil { - return fmt.Errorf("error verifying vote signature: %w", err) - } - - msgVote := &MessageSignature{ - From: msg.From, - Signature: msg.Signature, - } - - numSignatures, err := s.state.StateSyncStore.insertMessageVote(msg.EpochNumber, msg.Hash, msgVote, nil) - if err != nil { - return fmt.Errorf("error inserting message vote: %w", err) - } - - s.logger.Info( - "deliver message", - "hash", hex.EncodeToString(msg.Hash), - "sender", msg.From, - "signatures", numSignatures, - ) - - return nil -} - -// Verifies signature of the message against the public key of the signer and checks if the signer is a validator -func (s *stateSyncManager) verifyVoteSignature(valSet validator.ValidatorSet, signerAddr types.Address, - signature []byte, hash []byte) error { - validator := valSet.Accounts().GetValidatorMetadata(signerAddr) - if validator == nil { - return fmt.Errorf("unable to resolve validator %s", signerAddr) - } - - unmarshaledSignature, err := bls.UnmarshalSignature(signature) - if err != nil { - return fmt.Errorf("failed to unmarshal signature from signer %s, %w", signerAddr.String(), err) - } - - if !unmarshaledSignature.Verify(validator.BlsKey, hash, signer.DomainStateReceiver) { - return fmt.Errorf("incorrect signature from %s", signerAddr) - } - - return nil -} - -// AddLog saves the received log from event tracker if it matches a state sync event ABI -func (s *stateSyncManager) AddLog(eventLog *ethgo.Log) error { - event := &contractsapi.StateSyncedEvent{} - - doesMatch, err := event.ParseLog(eventLog) - if !doesMatch { - return nil - } - - s.logger.Info( - "Add State sync event", - "block", eventLog.BlockNumber, - "hash", eventLog.TransactionHash, - "index", eventLog.LogIndex, - ) - - if err != nil { - s.logger.Error("could not decode state sync event", "err", err) - - return err - } - - if err := s.state.StateSyncStore.insertStateSyncEvent(event); err != nil { - s.logger.Error("could not save state sync event to boltDb", "err", err) - - return err - } - - if err := s.buildCommitment(nil); err != nil { - // we don't return an error here. If state sync event is inserted in db, - // we will just try to build a commitment on next block or next event arrival - s.logger.Error("could not build a commitment on arrival of new state sync", "err", err, "stateSyncID", event.ID) - } - - return nil -} - -// Commitment returns a commitment to be submitted if there is a pending commitment with quorum -func (s *stateSyncManager) Commitment(blockNumber uint64) (*CommitmentMessageSigned, error) { - s.lock.RLock() - defer s.lock.RUnlock() - - var largestCommitment *CommitmentMessageSigned - - // we start from the end, since last pending commitment is the largest one - for i := len(s.pendingCommitments) - 1; i >= 0; i-- { - commitment := s.pendingCommitments[i] - aggregatedSignature, publicKeys, err := s.getAggSignatureForCommitmentMessage(blockNumber, commitment) - - if err != nil { - if errors.Is(err, errQuorumNotReached) { - // a valid case, commitment has no quorum, we should not return an error - s.logger.Debug("can not submit a commitment, quorum not reached", - "from", commitment.StartID.Uint64(), - "to", commitment.EndID.Uint64()) - - continue - } - - return nil, err - } - - largestCommitment = &CommitmentMessageSigned{ - Message: commitment.StateSyncCommitment, - AggSignature: aggregatedSignature, - PublicKeys: publicKeys, - } - - break - } - - return largestCommitment, nil -} - -// getAggSignatureForCommitmentMessage checks if pending commitment has quorum, -// and if it does, aggregates the signatures -func (s *stateSyncManager) getAggSignatureForCommitmentMessage(blockNumber uint64, - commitment *PendingCommitment) (Signature, [][]byte, error) { - validatorSet := s.validatorSet - - validatorAddrToIndex := make(map[string]int, validatorSet.Len()) - validatorsMetadata := validatorSet.Accounts() - - for i, validator := range validatorsMetadata { - validatorAddrToIndex[validator.Address.String()] = i - } - - commitmentHash, err := commitment.Hash() - if err != nil { - return Signature{}, nil, err - } - - // get all the votes from the database for this commitment - votes, err := s.state.StateSyncStore.getMessageVotes(commitment.Epoch, commitmentHash.Bytes()) - if err != nil { - return Signature{}, nil, err - } - - var signatures bls.Signatures - - publicKeys := make([][]byte, 0) - bmap := bitmap.Bitmap{} - signers := make(map[types.Address]struct{}, 0) - - for _, vote := range votes { - index, exists := validatorAddrToIndex[vote.From] - if !exists { - continue // don't count this vote, because it does not belong to validator - } - - signature, err := bls.UnmarshalSignature(vote.Signature) - if err != nil { - return Signature{}, nil, err - } - - bmap.Set(uint64(index)) - - signatures = append(signatures, signature) - publicKeys = append(publicKeys, validatorsMetadata[index].BlsKey.Marshal()) - signers[types.StringToAddress(vote.From)] = struct{}{} - } - - if !validatorSet.HasQuorum(blockNumber, signers) { - return Signature{}, nil, errQuorumNotReached - } - - aggregatedSignature, err := signatures.Aggregate().Marshal() - if err != nil { - return Signature{}, nil, err - } - - result := Signature{ - AggregatedSignature: aggregatedSignature, - Bitmap: bmap, - } - - return result, publicKeys, nil -} - -// PostEpoch notifies the state sync manager that an epoch has changed, -// so that it can discard any previous epoch commitments, and build a new one (since validator set changed) -func (s *stateSyncManager) PostEpoch(req *PostEpochRequest) error { - s.lock.Lock() - - s.pendingCommitments = nil - s.validatorSet = req.ValidatorSet - s.epoch = req.NewEpochID - - // build a new commitment at the end of the epoch - nextCommittedIndex, err := req.SystemState.GetNextCommittedIndex() - if err != nil { - s.lock.Unlock() - - return err - } - - s.nextCommittedIndex = nextCommittedIndex - - s.lock.Unlock() - - return s.buildCommitment(req.DBTx) -} - -// PostBlock notifies state sync manager that a block was finalized, -// so that it can build state sync proofs if a block has a commitment submission transaction. -// Additionally, it will remove any processed state sync events and their proofs from the store. -func (s *stateSyncManager) PostBlock(req *PostBlockRequest) error { - commitment, err := getCommitmentMessageSignedTx(req.FullBlock.Block.Transactions) - if err != nil { - return err - } - - // no commitment message -> this is not end of sprint block - if commitment == nil { - return nil - } - - if err := s.state.StateSyncStore.insertCommitmentMessage(commitment, req.DBTx); err != nil { - return fmt.Errorf("insert commitment message error: %w", err) - } - - if err := s.buildProofs(commitment.Message, req.DBTx); err != nil { - return fmt.Errorf("build commitment proofs error: %w", err) - } - - s.lock.Lock() - defer s.lock.Unlock() - // update the nextCommittedIndex since a commitment was submitted - s.nextCommittedIndex = commitment.Message.EndID.Uint64() + 1 - // commitment was submitted, so discard what we have in memory, so we can build a new one - s.pendingCommitments = nil - - return nil -} - -// GetStateSyncProof returns the proof for the state sync -func (s *stateSyncManager) GetStateSyncProof(stateSyncID uint64) (types.Proof, error) { - stateSyncProof, err := s.state.StateSyncStore.getStateSyncProof(stateSyncID) - if err != nil { - return types.Proof{}, fmt.Errorf("cannot get state sync proof for StateSync id %d: %w", stateSyncID, err) - } - - if stateSyncProof == nil { - // check if we might've missed a commitment. if it is so, we didn't build proofs for it while syncing - // if we are all synced up, commitment will be saved through PostBlock, but we won't have proofs, - // so we will build them now and save them to db so that we have proofs for missed commitment - commitment, err := s.state.StateSyncStore.getCommitmentForStateSync(stateSyncID) - if err != nil { - return types.Proof{}, fmt.Errorf("cannot find commitment for StateSync id %d: %w", stateSyncID, err) - } - - if err := s.buildProofs(commitment.Message, nil); err != nil { - return types.Proof{}, fmt.Errorf("cannot build proofs for commitment for StateSync id %d: %w", stateSyncID, err) - } - - stateSyncProof, err = s.state.StateSyncStore.getStateSyncProof(stateSyncID) - if err != nil { - return types.Proof{}, fmt.Errorf("cannot get state sync proof for StateSync id %d: %w", stateSyncID, err) - } - } - - return types.Proof{ - Data: stateSyncProof.Proof, - Metadata: map[string]interface{}{ - "StateSync": stateSyncProof.StateSync, - }, - }, nil -} - -// buildProofs builds state sync proofs for the submitted commitment and saves them in boltDb for later execution -func (s *stateSyncManager) buildProofs(commitmentMsg *contractsapi.StateSyncCommitment, - dbTx *bolt.Tx) error { - from := commitmentMsg.StartID.Uint64() - to := commitmentMsg.EndID.Uint64() - - s.logger.Debug( - "[buildProofs] Building proofs for commitment...", - "fromIndex", from, - "toIndex", to, - ) - - events, err := s.state.StateSyncStore.getStateSyncEventsForCommitment(from, to, dbTx) - if err != nil { - return fmt.Errorf("failed to get state sync events for commitment to build proofs. Error: %w", err) - } - - tree, err := createMerkleTree(events) - if err != nil { - return fmt.Errorf("could not create merkle tree. error: %w", err) - } - - stateSyncProofs := make([]*StateSyncProof, len(events)) - - for i, event := range events { - leaf, err := event.EncodeAbi() - if err != nil { - return fmt.Errorf("could not encode state sync event. error: %w", err) - } - - p, err := tree.GenerateProof(leaf) - if err != nil { - return fmt.Errorf("error generating proof for event: %v. error: %w", event.ID, err) - } - - stateSyncProofs[i] = &StateSyncProof{ - Proof: p, - StateSync: event, - } - } - - s.logger.Debug( - "[buildProofs] Building proofs for commitment finished.", - "fromIndex", from, - "toIndex", to, - ) - - return s.state.StateSyncStore.insertStateSyncProofs(stateSyncProofs, dbTx) -} - -// buildCommitment builds a new commitment, signs it and gossips its vote for it -func (s *stateSyncManager) buildCommitment(dbTx *bolt.Tx) error { - if !s.runtime.IsActiveValidator() { - // don't build commitment if not a validator - return nil - } - - s.lock.Lock() - defer s.lock.Unlock() - - stateSyncEvents, err := s.state.StateSyncStore.getStateSyncEventsForCommitment(s.nextCommittedIndex, - s.nextCommittedIndex+s.config.maxCommitmentSize-1, dbTx) - if err != nil && !errors.Is(err, errNotEnoughStateSyncs) { - return fmt.Errorf("failed to get state sync events for commitment. Error: %w", err) - } - - if len(stateSyncEvents) == 0 { - // there are no state sync events - return nil - } - - if len(s.pendingCommitments) > 0 && - s.pendingCommitments[len(s.pendingCommitments)-1].StartID.Cmp(stateSyncEvents[len(stateSyncEvents)-1].ID) >= 0 { - // already built a commitment of this size which is pending to be submitted - return nil - } - - commitment, err := NewPendingCommitment(s.epoch, stateSyncEvents) - if err != nil { - return err - } - - hash, err := commitment.Hash() - if err != nil { - return fmt.Errorf("failed to generate hash for commitment. Error: %w", err) - } - - hashBytes := hash.Bytes() - - signature, err := s.config.key.SignWithDomain(hashBytes, signer.DomainStateReceiver) - if err != nil { - return fmt.Errorf("failed to sign commitment message. Error: %w", err) - } - - sig := &MessageSignature{ - From: s.config.key.String(), - Signature: signature, - } - - if _, err = s.state.StateSyncStore.insertMessageVote(s.epoch, hashBytes, sig, dbTx); err != nil { - return fmt.Errorf( - "failed to insert signature for hash=%v to the state. Error: %w", - hex.EncodeToString(hashBytes), - err, - ) - } - - // gossip message - s.multicast(&TransportMessage{ - Hash: hashBytes, - Signature: signature, - From: s.config.key.String(), - EpochNumber: s.epoch, - }) - - s.logger.Debug( - "[buildCommitment] Built commitment", - "from", commitment.StartID.Uint64(), - "to", commitment.EndID.Uint64(), - ) - - s.pendingCommitments = append(s.pendingCommitments, commitment) - - return nil -} - -// multicast publishes given message to the rest of the network -func (s *stateSyncManager) multicast(msg interface{}) { - data, err := json.Marshal(msg) - if err != nil { - s.logger.Warn("failed to marshal bridge message", "err", err) - - return - } - - err = s.config.topic.Publish(&polybftProto.TransportMessage{Data: data}) - if err != nil { - s.logger.Warn("failed to gossip bridge message", "err", err) - } -} - -// EventSubscriber implementation - -// GetLogFilters returns a map of log filters for getting desired events, -// where the key is the address of contract that emits desired events, -// and the value is a slice of signatures of events we want to get. -// This function is the implementation of EventSubscriber interface -func (s *stateSyncManager) GetLogFilters() map[types.Address][]types.Hash { - var stateSyncResultEvent contractsapi.StateSyncResultEvent - - return map[types.Address][]types.Hash{ - contracts.StateReceiverContract: {types.Hash(stateSyncResultEvent.Sig())}, - } -} - -// ProcessLog is the implementation of EventSubscriber interface, -// used to handle a log defined in GetLogFilters, provided by event provider -func (s *stateSyncManager) ProcessLog(header *types.Header, log *ethgo.Log, dbTx *bolt.Tx) error { - var stateSyncResultEvent contractsapi.StateSyncResultEvent - - doesMatch, err := stateSyncResultEvent.ParseLog(log) - if err != nil { - return err - } - - if !doesMatch { - return nil - } - - return s.state.StateSyncStore.removeStateSyncEventsAndProofs([]uint64{stateSyncResultEvent.Counter.Uint64()}) -} diff --git a/consensus/polybft/state_sync_manager_test.go b/consensus/polybft/state_sync_manager_test.go deleted file mode 100644 index 1721ba5..0000000 --- a/consensus/polybft/state_sync_manager_test.go +++ /dev/null @@ -1,702 +0,0 @@ -package polybft - -import ( - "math/big" - "math/rand" - "os" - "testing" - "time" - - "github.com/hashicorp/go-hclog" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - "github.com/umbracle/ethgo/testutil" - "google.golang.org/protobuf/proto" - - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/merkle-tree" - "github.com/xgr-network/xgr-node/types" -) - -func newTestStateSyncManager(t *testing.T, key *validator.TestValidator, runtime Runtime) *stateSyncManager { - t.Helper() - - tmpDir, err := os.MkdirTemp("/tmp", "test-data-dir-state-sync") - require.NoError(t, err) - - state := newTestState(t) - require.NoError(t, state.EpochStore.insertEpoch(0, nil)) - - topic := &mockTopic{} - - s := newStateSyncManager(hclog.NewNullLogger(), state, - &stateSyncConfig{ - stateSenderAddr: types.Address{}, - jsonrpcAddr: "", - dataDir: tmpDir, - topic: topic, - key: key.Key(), - maxCommitmentSize: maxCommitmentSize, - }, runtime) - - t.Cleanup(func() { - os.RemoveAll(tmpDir) - }) - - return s -} - -func TestStateSyncManager_PostEpoch_BuildCommitment(t *testing.T) { - t.Parallel() - - vals := validator.NewTestValidators(t, 5) - - t.Run("When node is validator", func(t *testing.T) { - t.Parallel() - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - - // there are no state syncs - require.NoError(t, s.buildCommitment(nil)) - require.Nil(t, s.pendingCommitments) - - stateSyncs10 := generateStateSyncEvents(t, 10, 0) - - // add 5 state syncs starting in index 0, it will generate one smaller commitment - for i := 0; i < 5; i++ { - require.NoError(t, s.state.StateSyncStore.insertStateSyncEvent(stateSyncs10[i])) - } - - require.NoError(t, s.buildCommitment(nil)) - require.Len(t, s.pendingCommitments, 1) - require.Equal(t, uint64(0), s.pendingCommitments[0].StartID.Uint64()) - require.Equal(t, uint64(4), s.pendingCommitments[0].EndID.Uint64()) - require.Equal(t, uint64(0), s.pendingCommitments[0].Epoch) - - // add the next 5 state syncs, at that point, so that it generates a larger commitment - for i := 5; i < 10; i++ { - require.NoError(t, s.state.StateSyncStore.insertStateSyncEvent(stateSyncs10[i])) - } - - require.NoError(t, s.buildCommitment(nil)) - require.Len(t, s.pendingCommitments, 2) - require.Equal(t, uint64(0), s.pendingCommitments[1].StartID.Uint64()) - require.Equal(t, uint64(9), s.pendingCommitments[1].EndID.Uint64()) - require.Equal(t, uint64(0), s.pendingCommitments[1].Epoch) - - // the message was sent - require.NotNil(t, s.config.topic.(*mockTopic).consume()) //nolint - }) - - t.Run("When node is not validator", func(t *testing.T) { - t.Parallel() - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: false}) - - stateSyncs10 := generateStateSyncEvents(t, 10, 0) - - // add 5 state syncs starting in index 0, they will be saved to db - for i := 0; i < 5; i++ { - require.NoError(t, s.state.StateSyncStore.insertStateSyncEvent(stateSyncs10[i])) - } - - // I am not a validator so no commitments should be built - require.NoError(t, s.buildCommitment(nil)) - require.Len(t, s.pendingCommitments, 0) - }) -} - -func TestStateSyncManager_MessagePool(t *testing.T) { - t.Parallel() - - vals := validator.NewTestValidators(t, 5) - - t.Run("Old epoch", func(t *testing.T) { - t.Parallel() - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - - s.epoch = 1 - msg := &TransportMessage{ - EpochNumber: 0, - } - - err := s.saveVote(msg) - require.NoError(t, err) - }) - - t.Run("Sender is not a validator", func(t *testing.T) { - t.Parallel() - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - s.validatorSet = vals.ToValidatorSet() - - badVal := validator.NewTestValidator(t, "a", 0) - msg, err := newMockMsg().sign(badVal, signer.DomainStateReceiver) - require.NoError(t, err) - - require.Error(t, s.saveVote(msg)) - }) - - t.Run("Invalid epoch", func(t *testing.T) { - t.Parallel() - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - s.validatorSet = vals.ToValidatorSet() - - val := newMockMsg() - msg, err := val.sign(vals.GetValidator("0"), signer.DomainStateReceiver) - require.NoError(t, err) - - msg.EpochNumber = 1 - - require.NoError(t, s.saveVote(msg)) - - // no votes for the current epoch - votes, err := s.state.StateSyncStore.getMessageVotes(0, msg.Hash) - require.NoError(t, err) - require.Len(t, votes, 0) - - // returns an error for the invalid epoch - _, err = s.state.StateSyncStore.getMessageVotes(1, msg.Hash) - require.Error(t, err) - }) - - t.Run("Sender and signature mismatch", func(t *testing.T) { - t.Parallel() - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - s.validatorSet = vals.ToValidatorSet() - - // validator signs the msg in behalf of another validator - val := newMockMsg() - msg, err := val.sign(vals.GetValidator("0"), signer.DomainStateReceiver) - require.NoError(t, err) - - msg.From = vals.GetValidator("1").Address().String() - require.Error(t, s.saveVote(msg)) - - // non validator signs the msg in behalf of a validator - badVal := validator.NewTestValidator(t, "a", 0) - msg, err = newMockMsg().sign(badVal, signer.DomainStateReceiver) - require.NoError(t, err) - - msg.From = vals.GetValidator("1").Address().String() - require.Error(t, s.saveVote(msg)) - }) - - t.Run("Sender votes", func(t *testing.T) { - t.Parallel() - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - s.validatorSet = vals.ToValidatorSet() - - msg := newMockMsg() - val1signed, err := msg.sign(vals.GetValidator("1"), signer.DomainStateReceiver) - require.NoError(t, err) - - val2signed, err := msg.sign(vals.GetValidator("2"), signer.DomainStateReceiver) - require.NoError(t, err) - - // vote with validator 1 - require.NoError(t, s.saveVote(val1signed)) - - votes, err := s.state.StateSyncStore.getMessageVotes(0, msg.hash) - require.NoError(t, err) - require.Len(t, votes, 1) - - // vote with validator 1 again (the votes do not increase) - require.NoError(t, s.saveVote(val1signed)) - votes, _ = s.state.StateSyncStore.getMessageVotes(0, msg.hash) - require.Len(t, votes, 1) - - // vote with validator 2 - require.NoError(t, s.saveVote(val2signed)) - votes, _ = s.state.StateSyncStore.getMessageVotes(0, msg.hash) - require.Len(t, votes, 2) - }) -} - -func TestStateSyncManager_BuildCommitment(t *testing.T) { - vals := validator.NewTestValidators(t, 5) - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - s.validatorSet = vals.ToValidatorSet() - - // commitment is empty - commitment, err := s.Commitment(1) - require.NoError(t, err) - require.Nil(t, commitment) - - tree, err := merkle.NewMerkleTree([][]byte{{0x1}}) - require.NoError(t, err) - - s.pendingCommitments = []*PendingCommitment{ - { - MerkleTree: tree, - StateSyncCommitment: &contractsapi.StateSyncCommitment{ - Root: tree.Hash(), - StartID: big.NewInt(0), - EndID: big.NewInt(1), - }, - }, - } - - hash, err := s.pendingCommitments[0].Hash() - require.NoError(t, err) - - msg := newMockMsg().WithHash(hash.Bytes()) - - // validators 0 and 1 vote for the proposal, there is not enough - // voting power for the proposal - signedMsg1, err := msg.sign(vals.GetValidator("0"), signer.DomainStateReceiver) - require.NoError(t, err) - - signedMsg2, err := msg.sign(vals.GetValidator("1"), signer.DomainStateReceiver) - require.NoError(t, err) - - require.NoError(t, s.saveVote(signedMsg1)) - require.NoError(t, s.saveVote(signedMsg2)) - - commitment, err = s.Commitment(1) - require.NoError(t, err) // there is no error if quorum is not met, since its a valid case - require.Nil(t, commitment) - - // validator 2 and 3 vote for the proposal, there is enough voting power now - - signedMsg1, err = msg.sign(vals.GetValidator("2"), signer.DomainStateReceiver) - require.NoError(t, err) - - signedMsg2, err = msg.sign(vals.GetValidator("3"), signer.DomainStateReceiver) - require.NoError(t, err) - - require.NoError(t, s.saveVote(signedMsg1)) - require.NoError(t, s.saveVote(signedMsg2)) - - commitment, err = s.Commitment(1) - require.NoError(t, err) - require.NotNil(t, commitment) -} - -func TestStateSyncerManager_BuildProofs(t *testing.T) { - vals := validator.NewTestValidators(t, 5) - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - - for _, evnt := range generateStateSyncEvents(t, 20, 0) { - require.NoError(t, s.state.StateSyncStore.insertStateSyncEvent(evnt)) - } - - require.NoError(t, s.buildCommitment(nil)) - require.Len(t, s.pendingCommitments, 1) - - mockMsg := &CommitmentMessageSigned{ - Message: &contractsapi.StateSyncCommitment{ - StartID: s.pendingCommitments[0].StartID, - EndID: s.pendingCommitments[0].EndID, - }, - } - - txData, err := mockMsg.EncodeAbi() - require.NoError(t, err) - - tx := createStateTransactionWithData(1, types.Address{}, txData) - - req := &PostBlockRequest{ - FullBlock: &types.FullBlock{ - Block: &types.Block{ - Transactions: []*types.Transaction{tx}, - }, - }, - } - - require.NoError(t, s.PostBlock(req)) - require.Equal(t, mockMsg.Message.EndID.Uint64()+1, s.nextCommittedIndex) - - for i := uint64(0); i < 10; i++ { - proof, err := s.state.StateSyncStore.getStateSyncProof(i) - require.NoError(t, err) - require.NotNil(t, proof) - } -} - -func TestStateSyncerManager_RemoveProcessedEventsAndProofs(t *testing.T) { - const stateSyncEventsCount = 5 - - vals := validator.NewTestValidators(t, 5) - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - stateSyncEvents := generateStateSyncEvents(t, stateSyncEventsCount, 0) - - for _, event := range stateSyncEvents { - require.NoError(t, s.state.StateSyncStore.insertStateSyncEvent(event)) - } - - require.NoError(t, s.buildProofs(&contractsapi.StateSyncCommitment{ - StartID: stateSyncEvents[0].ID, - EndID: stateSyncEvents[len(stateSyncEvents)-1].ID, - }, nil)) - - stateSyncEventsBefore, err := s.state.StateSyncStore.list() - require.NoError(t, err) - require.Equal(t, stateSyncEventsCount, len(stateSyncEventsBefore)) - - for _, event := range stateSyncEvents { - proof, err := s.state.StateSyncStore.getStateSyncProof(event.ID.Uint64()) - require.NoError(t, err) - require.NotNil(t, proof) - } - - for _, event := range stateSyncEvents { - eventLog := createTestLogForStateSyncResultEvent(t, event.ID.Uint64()) - require.NoError(t, s.ProcessLog(&types.Header{Number: 10}, convertLog(eventLog), nil)) - } - - // all state sync events and their proofs should be removed from the store - stateSyncEventsAfter, err := s.state.StateSyncStore.list() - require.NoError(t, err) - require.Equal(t, 0, len(stateSyncEventsAfter)) - - for _, event := range stateSyncEvents { - proof, err := s.state.StateSyncStore.getStateSyncProof(event.ID.Uint64()) - require.NoError(t, err) - require.Nil(t, proof) - } -} - -func TestStateSyncerManager_AddLog_BuildCommitments(t *testing.T) { - t.Parallel() - - vals := validator.NewTestValidators(t, 5) - - t.Run("Node is a validator", func(t *testing.T) { - t.Parallel() - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - - // empty log which is not an state sync - require.NoError(t, s.AddLog(ðgo.Log{})) - stateSyncs, err := s.state.StateSyncStore.list() - - require.NoError(t, err) - require.Len(t, stateSyncs, 0) - - var stateSyncedEvent contractsapi.StateSyncedEvent - - stateSyncEventID := stateSyncedEvent.Sig() - - // log with the state sync topic but incorrect content - require.Error(t, s.AddLog(ðgo.Log{Topics: []ethgo.Hash{stateSyncEventID}})) - stateSyncs, err = s.state.StateSyncStore.list() - - require.NoError(t, err) - require.Len(t, stateSyncs, 0) - - // correct event log - data, err := abi.MustNewType("tuple(string a)").Encode([]string{"data"}) - require.NoError(t, err) - - goodLog := ðgo.Log{ - Topics: []ethgo.Hash{ - stateSyncEventID, - ethgo.BytesToHash([]byte{0x0}), // state sync index 0 - ethgo.ZeroHash, - ethgo.ZeroHash, - }, - Data: data, - } - - require.NoError(t, s.AddLog(goodLog)) - - stateSyncs, err = s.state.StateSyncStore.getStateSyncEventsForCommitment(0, 0, nil) - require.NoError(t, err) - require.Len(t, stateSyncs, 1) - require.Len(t, s.pendingCommitments, 1) - require.Equal(t, uint64(0), s.pendingCommitments[0].StartID.Uint64()) - require.Equal(t, uint64(0), s.pendingCommitments[0].EndID.Uint64()) - - // add one more log to have a minimum commitment - goodLog2 := goodLog.Copy() - goodLog2.Topics[1] = ethgo.BytesToHash([]byte{0x1}) // state sync index 1 - require.NoError(t, s.AddLog(goodLog2)) - - require.Len(t, s.pendingCommitments, 2) - require.Equal(t, uint64(0), s.pendingCommitments[1].StartID.Uint64()) - require.Equal(t, uint64(1), s.pendingCommitments[1].EndID.Uint64()) - - // add two more logs to have larger commitments - goodLog3 := goodLog.Copy() - goodLog3.Topics[1] = ethgo.BytesToHash([]byte{0x2}) // state sync index 2 - require.NoError(t, s.AddLog(goodLog3)) - - goodLog4 := goodLog.Copy() - goodLog4.Topics[1] = ethgo.BytesToHash([]byte{0x3}) // state sync index 3 - require.NoError(t, s.AddLog(goodLog4)) - - require.Len(t, s.pendingCommitments, 4) - require.Equal(t, uint64(0), s.pendingCommitments[3].StartID.Uint64()) - require.Equal(t, uint64(3), s.pendingCommitments[3].EndID.Uint64()) - }) - - t.Run("Node is not a validator", func(t *testing.T) { - t.Parallel() - - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: false}) - - // correct event log - data, err := abi.MustNewType("tuple(string a)").Encode([]string{"data"}) - require.NoError(t, err) - - var stateSyncedEvent contractsapi.StateSyncedEvent - - goodLog := ðgo.Log{ - Topics: []ethgo.Hash{ - stateSyncedEvent.Sig(), - ethgo.BytesToHash([]byte{0x0}), // state sync index 0 - ethgo.ZeroHash, - ethgo.ZeroHash, - }, - Data: data, - } - - require.NoError(t, s.AddLog(goodLog)) - - // node should have inserted given state sync event, but it shouldn't build any commitment - stateSyncs, err := s.state.StateSyncStore.getStateSyncEventsForCommitment(0, 0, nil) - require.NoError(t, err) - require.Len(t, stateSyncs, 1) - require.Equal(t, uint64(0), stateSyncs[0].ID.Uint64()) - require.Len(t, s.pendingCommitments, 0) - }) -} - -func TestStateSyncerManager_EventTracker_Sync(t *testing.T) { - t.Parallel() - - vals := validator.NewTestValidators(t, 5) - s := newTestStateSyncManager(t, vals.GetValidator("0"), &mockRuntime{isActiveValidator: true}) - - server := testutil.DeployTestServer(t, nil) - - // Deploy contract - contractReceipt, err := server.SendTxn(ðgo.Transaction{ - Input: contractsapi.StateSender.Bytecode, - }) - require.NoError(t, err) - - // Create contract function call payload - encodedSyncStateData, err := (&contractsapi.SyncStateStateSenderFn{ - Receiver: types.BytesToAddress(server.Account(0).Bytes()), - Data: []byte{}, - }).EncodeAbi() - require.NoError(t, err) - - // prefill with 10 events - for i := 0; i < 10; i++ { - receipt, err := server.SendTxn(ðgo.Transaction{ - To: &contractReceipt.ContractAddress, - Input: encodedSyncStateData, - }) - require.NoError(t, err) - require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) - } - - s.config.stateSenderAddr = types.Address(contractReceipt.ContractAddress) - s.config.jsonrpcAddr = server.HTTPAddr() - - require.NoError(t, s.initTracker()) - - time.Sleep(2 * time.Second) - - events, err := s.state.StateSyncStore.getStateSyncEventsForCommitment(1, 10, nil) - require.NoError(t, err) - require.Len(t, events, 10) -} - -func TestStateSyncManager_Close(t *testing.T) { - t.Parallel() - - mgr := newTestStateSyncManager(t, validator.NewTestValidator(t, "A", 100), &mockRuntime{isActiveValidator: true}) - require.NotPanics(t, func() { mgr.Close() }) -} - -func TestStateSyncManager_GetProofs(t *testing.T) { - t.Parallel() - - const ( - stateSyncID = uint64(5) - ) - - state := newTestState(t) - insertTestStateSyncProofs(t, state, maxCommitmentSize) - - stateSyncManager := &stateSyncManager{state: state} - - proof, err := stateSyncManager.GetStateSyncProof(stateSyncID) - require.NoError(t, err) - - stateSync, ok := (proof.Metadata["StateSync"]).(*contractsapi.StateSyncedEvent) - require.True(t, ok) - require.Equal(t, stateSyncID, stateSync.ID.Uint64()) - require.NotEmpty(t, proof.Data) -} - -func TestStateSyncManager_GetProofs_NoProof_NoCommitment(t *testing.T) { - t.Parallel() - - const ( - stateSyncID = uint64(5) - ) - - stateSyncManager := &stateSyncManager{state: newTestState(t)} - - _, err := stateSyncManager.GetStateSyncProof(stateSyncID) - require.ErrorContains(t, err, "cannot find commitment for StateSync id") -} - -func TestStateSyncManager_GetProofs_NoProof_HasCommitment_NoStateSyncs(t *testing.T) { - t.Parallel() - - const ( - stateSyncID = uint64(5) - ) - - state := newTestState(t) - require.NoError(t, state.StateSyncStore.insertCommitmentMessage(createTestCommitmentMessage(t, 1), nil)) - - stateSyncManager := &stateSyncManager{state: state, logger: hclog.NewNullLogger()} - - _, err := stateSyncManager.GetStateSyncProof(stateSyncID) - require.ErrorContains(t, err, "failed to get state sync events for commitment to build proofs") -} - -func TestStateSyncManager_GetProofs_NoProof_BuildProofs(t *testing.T) { - t.Parallel() - - const ( - stateSyncID = uint64(5) - fromIndex = 1 - ) - - state := newTestState(t) - stateSyncs := generateStateSyncEvents(t, maxCommitmentSize, fromIndex) - - tree, err := createMerkleTree(stateSyncs) - require.NoError(t, err) - - commitment := &CommitmentMessageSigned{ - Message: &contractsapi.StateSyncCommitment{ - StartID: big.NewInt(fromIndex), - EndID: big.NewInt(maxCommitmentSize), - Root: tree.Hash(), - }, - } - - for _, sse := range stateSyncs { - require.NoError(t, state.StateSyncStore.insertStateSyncEvent(sse)) - } - - require.NoError(t, state.StateSyncStore.insertCommitmentMessage(commitment, nil)) - - stateSyncManager := &stateSyncManager{state: state, logger: hclog.NewNullLogger()} - - proof, err := stateSyncManager.GetStateSyncProof(stateSyncID) - require.NoError(t, err) - - stateSync, ok := (proof.Metadata["StateSync"]).(*contractsapi.StateSyncedEvent) - require.True(t, ok) - require.Equal(t, stateSyncID, stateSync.ID.Uint64()) - require.NotEmpty(t, proof.Data) - - require.NoError(t, commitment.VerifyStateSyncProof(proof.Data, stateSync)) -} - -func createTestLogForStateSyncResultEvent(t *testing.T, stateSyncEventID uint64) *types.Log { - t.Helper() - - var stateSyncResultEvent contractsapi.StateSyncResultEvent - - topics := make([]types.Hash, 3) - topics[0] = types.Hash(stateSyncResultEvent.Sig()) - topics[1] = types.BytesToHash(common.EncodeUint64ToBytes(stateSyncEventID)) - topics[2] = types.BytesToHash(common.EncodeUint64ToBytes(1)) // Status = true - someType := abi.MustNewType("tuple(string field1, string field2)") - encodedData, err := someType.Encode(map[string]string{"field1": "value1", "field2": "value2"}) - require.NoError(t, err) - - return &types.Log{ - Address: contracts.StateReceiverContract, - Topics: topics, - Data: encodedData, - } -} - -type mockTopic struct { - published proto.Message -} - -func (m *mockTopic) consume() proto.Message { - msg := m.published - - if m.published != nil { - m.published = nil - } - - return msg -} - -func (m *mockTopic) Publish(obj proto.Message) error { - m.published = obj - - return nil -} - -func (m *mockTopic) Subscribe(handler func(obj interface{}, from peer.ID)) error { - return nil -} - -type mockMsg struct { - hash []byte - epoch uint64 -} - -func newMockMsg() *mockMsg { - hash := make([]byte, 32) - rand.Read(hash) - - return &mockMsg{hash: hash} -} - -func (m *mockMsg) WithHash(hash []byte) *mockMsg { - m.hash = hash - - return m -} - -func (m *mockMsg) sign(val *validator.TestValidator, domain []byte) (*TransportMessage, error) { - signature, err := val.MustSign(m.hash, domain).Marshal() - if err != nil { - return nil, err - } - - return &TransportMessage{ - Hash: m.hash, - Signature: signature, - From: val.Address().String(), - EpochNumber: m.epoch, - }, nil -} - -type mockRuntime struct { - isActiveValidator bool -} - -func (m *mockRuntime) IsActiveValidator() bool { - return m.isActiveValidator -} diff --git a/consensus/polybft/state_sync_relayer.go b/consensus/polybft/state_sync_relayer.go deleted file mode 100644 index 863eb6b..0000000 --- a/consensus/polybft/state_sync_relayer.go +++ /dev/null @@ -1,347 +0,0 @@ -package polybft - -import ( - "encoding/json" - "errors" - "fmt" - "net" - "strings" - - "github.com/hashicorp/go-hclog" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/txrelayer" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" -) - -const ( - // defaultMaxBlocksToWaitForResend specifies how many blocks should be wait - // in order to try again to send transaction - defaultMaxBlocksToWaitForResend = uint64(30) - // defaultMaxAttemptsToSend specifies how many sending retries for one transaction - defaultMaxAttemptsToSend = uint64(15) - // defaultMaxEventsPerBatch specifies maximum events per one batchExecute tx - defaultMaxEventsPerBatch = uint64(10) -) - -var ( - errFailedToExecuteStateSync = errors.New("failed to execute state sync") - errUnknownStateSyncRelayerEvent = errors.New("unknown event") - - commitmentEventSignature = new(contractsapi.NewCommitmentEvent).Sig() - stateSyncResultEventSignature = new(contractsapi.StateSyncResultEvent).Sig() -) - -// StateSyncRelayer is an interface that defines functions for state sync relayer -type StateSyncRelayer interface { - EventSubscriber - PostBlock(req *PostBlockRequest) error - Init() error - Close() -} - -// stateSyncProofRetriever is an interface that exposes function for retrieving state sync proof -type stateSyncProofRetriever interface { - GetStateSyncProof(stateSyncID uint64) (types.Proof, error) -} - -var _ StateSyncRelayer = (*dummyStateSyncRelayer)(nil) - -// dummyStateSyncRelayer is a dummy implementation of a StateSyncRelayer -type dummyStateSyncRelayer struct{} - -func (d *dummyStateSyncRelayer) PostBlock(req *PostBlockRequest) error { return nil } - -func (d *dummyStateSyncRelayer) Init() error { return nil } -func (d *dummyStateSyncRelayer) Close() {} - -// EventSubscriber implementation -func (d *dummyStateSyncRelayer) GetLogFilters() map[types.Address][]types.Hash { - return make(map[types.Address][]types.Hash) -} -func (d *dummyStateSyncRelayer) ProcessLog(header *types.Header, log *ethgo.Log, dbTx *bolt.Tx) error { - return nil -} - -var _ StateSyncRelayer = (*stateSyncRelayerImpl)(nil) - -// StateSyncRelayerEventData keeps information about an event -type StateSyncRelayerEventData struct { - EventID uint64 `json:"eventID"` - CountTries uint64 `json:"countTries"` - BlockNumber uint64 `json:"blockNumber"` // block when state sync is sent - SentStatus bool `json:"sentStatus"` -} - -func (ed StateSyncRelayerEventData) String() string { - return fmt.Sprintf("%d", ed.EventID) -} - -type stateSyncRelayerConfig struct { - maxBlocksToWaitForResend uint64 - maxAttemptsToSend uint64 - maxEventsPerBatch uint64 -} - -type stateSyncRelayerImpl struct { - txRelayer txrelayer.TxRelayer - key ethgo.Key - proofRetriever stateSyncProofRetriever - state *StateSyncStore - logger hclog.Logger - blockchain blockchainBackend - - notifyCh chan struct{} - closeCh chan struct{} - - config *stateSyncRelayerConfig -} - -func NewStateSyncRelayer( - txRelayer txrelayer.TxRelayer, - stateReceiverAddr types.Address, - state *StateSyncStore, - store stateSyncProofRetriever, - blockchain blockchainBackend, - key ethgo.Key, - config *stateSyncRelayerConfig, - logger hclog.Logger, -) *stateSyncRelayerImpl { - if config == nil { - config = &stateSyncRelayerConfig{ - maxBlocksToWaitForResend: defaultMaxBlocksToWaitForResend, - maxAttemptsToSend: defaultMaxAttemptsToSend, - maxEventsPerBatch: defaultMaxEventsPerBatch, - } - } - - return &stateSyncRelayerImpl{ - txRelayer: txRelayer, - key: key, - proofRetriever: store, - state: state, - closeCh: make(chan struct{}), - notifyCh: make(chan struct{}, 1), - blockchain: blockchain, - config: config, - logger: logger, - } -} - -func (ssr *stateSyncRelayerImpl) Init() error { - // start consumer - go func() { - for { - select { - case <-ssr.closeCh: - return - case <-ssr.notifyCh: - ssr.processEvents() - } - } - }() - - return nil -} - -func (ssr *stateSyncRelayerImpl) Close() { - close(ssr.closeCh) -} - -func (ssr *stateSyncRelayerImpl) PostBlock(req *PostBlockRequest) error { - select { - case ssr.notifyCh <- struct{}{}: - default: - } - - return nil -} - -func (ssr *stateSyncRelayerImpl) processEvents() { - // we need twice as batch size because events from first batch are possible already sent maxAttemptsToSend times - events, err := ssr.state.getAllAvailableEvents(int(ssr.config.maxEventsPerBatch) * 2) - if err != nil { - ssr.logger.Error("retrieving events failed", "err", err) - - return - } - - removedEventIDs := []uint64{} - sendingEvents := []*StateSyncRelayerEventData{} - currentBlockNumber := ssr.blockchain.CurrentHeader().Number - - // check already processed events - for _, evnt := range events { - // quit if we are still waiting for some old event confirmation (there is no paralelization right now!) - if evnt.SentStatus && evnt.BlockNumber+ssr.config.maxBlocksToWaitForResend > currentBlockNumber { - return - } - - // remove event if it is processed too many times - if evnt.CountTries+1 > ssr.config.maxAttemptsToSend { - removedEventIDs = append(removedEventIDs, evnt.EventID) - } else { - evnt.CountTries++ - evnt.BlockNumber = currentBlockNumber - evnt.SentStatus = true - - sendingEvents = append(sendingEvents, evnt) - if len(sendingEvents) == int(ssr.config.maxEventsPerBatch) { - break - } - } - } - - // update state only if needed - if len(sendingEvents)+len(removedEventIDs) > 0 { - ssr.logger.Info("sending events", "events", sendingEvents, "removed", removedEventIDs) - - if err := ssr.state.updateStateSyncRelayerEvents(sendingEvents, removedEventIDs, nil); err != nil { - ssr.logger.Error("updating events failed", - "events", sendingEvents, "removed", removedEventIDs, "err", err) - - return - } - } - - // send tx only if needed - if len(sendingEvents) > 0 { - if err := ssr.sendTx(sendingEvents); err != nil { - ssr.logger.Error("failed to send tx", "block", currentBlockNumber, "events", sendingEvents, "err", err) - } else { - ssr.logger.Info("tx has been successfully sent", "block", currentBlockNumber, "events", sendingEvents) - } - } -} - -func (ssr *stateSyncRelayerImpl) sendTx(events []*StateSyncRelayerEventData) error { - proofs := make([][]types.Hash, len(events)) - objs := make([]*contractsapi.StateSync, len(events)) - - for i, event := range events { - proof, err := ssr.proofRetriever.GetStateSyncProof(event.EventID) - if err != nil { - return fmt.Errorf("failed to get proof for %d: %w", event.EventID, err) - } - - // since state sync event is a map in the jsonrpc response, - // to not have custom logic of converting the map to state sync event - // json encoding is used, since it manages to successfully unmarshal the - // event from the marshaled map - raw, err := json.Marshal(proof.Metadata["StateSync"]) - if err != nil { - return fmt.Errorf("failed to marshal event %d: %w", event.EventID, err) - } - - if err = json.Unmarshal(raw, &objs[i]); err != nil { - return fmt.Errorf("failed to unmarshal event %d: %w", event.EventID, err) - } - - proofs[i] = proof.Data - } - - input, err := (&contractsapi.BatchExecuteStateReceiverFn{ - Proofs: proofs, - Objs: objs, - }).EncodeAbi() - if err != nil { - return err - } - - // send batchExecute state sync - _, err = ssr.txRelayer.SendTransaction(ðgo.Transaction{ - From: ssr.key.Address(), - To: (*ethgo.Address)(&contracts.StateReceiverContract), - Gas: types.StateTransactionGasLimit, - Input: input, - }, ssr.key) - - return err -} - -// EventSubscriber implementation - -// GetLogFilters returns a map of log filters for getting desired events, -// where the key is the address of contract that emits desired events, -// and the value is a slice of signatures of events we want to get. -// This function is the implementation of EventSubscriber interface -func (ssr *stateSyncRelayerImpl) GetLogFilters() map[types.Address][]types.Hash { - var ( - stateSyncResultEvent contractsapi.StateSyncResultEvent - newCommitmentEvent contractsapi.NewCommitmentEvent - ) - - return map[types.Address][]types.Hash{ - contracts.StateReceiverContract: { - types.Hash(stateSyncResultEvent.Sig()), - types.Hash(newCommitmentEvent.Sig()), - }, - } -} - -// ProcessLog is the implementation of EventSubscriber interface, -// used to handle a log defined in GetLogFilters, provided by event provider -func (ssr *stateSyncRelayerImpl) ProcessLog(header *types.Header, log *ethgo.Log, dbTx *bolt.Tx) error { - var ( - commitEvent contractsapi.NewCommitmentEvent - stateSyncResultEvent contractsapi.StateSyncResultEvent - ) - - switch log.Topics[0] { - case commitmentEventSignature: - _, err := commitEvent.ParseLog(log) - if err != nil { - return err - } - - firstID, lastID := commitEvent.StartID.Uint64(), commitEvent.EndID.Uint64() - newEvents := make([]*StateSyncRelayerEventData, lastID-firstID+1) - - for eventID := firstID; eventID <= lastID; eventID++ { - newEvents[eventID-firstID] = &StateSyncRelayerEventData{EventID: eventID} - } - - ssr.logger.Info("new events has been arrived", "block", header.Number, "events", newEvents) - - return ssr.state.updateStateSyncRelayerEvents(newEvents, nil, dbTx) - - case stateSyncResultEventSignature: - _, err := stateSyncResultEvent.ParseLog(log) - if err != nil { - return err - } - - eventID := stateSyncResultEvent.Counter.Uint64() - - if stateSyncResultEvent.Status { - ssr.logger.Info("event has been processed", "block", header.Number, "event", eventID) - - return ssr.state.updateStateSyncRelayerEvents(nil, []uint64{eventID}, dbTx) - } - - ssr.logger.Info("event has been failed to process", "block", header.Number, - "event", eventID, "reason", string(stateSyncResultEvent.Message)) - - return nil - - default: - return errUnknownStateSyncRelayerEvent - } -} - -func getStateSyncTxRelayer(rpcEndpoint string, logger hclog.Logger) (txrelayer.TxRelayer, error) { - if rpcEndpoint == "" || strings.Contains(rpcEndpoint, "0.0.0.0") { - _, port, err := net.SplitHostPort(rpcEndpoint) - if err == nil { - rpcEndpoint = fmt.Sprintf("http://%s:%s", "127.0.0.1", port) - } else { - rpcEndpoint = txrelayer.DefaultRPCAddress - } - } - - return txrelayer.NewTxRelayer( - txrelayer.WithIPAddress(rpcEndpoint), txrelayer.WithNumRetries(-1), - txrelayer.WithWriter(logger.StandardWriter(&hclog.StandardLoggerOptions{}))) -} diff --git a/consensus/polybft/state_sync_relayer_test.go b/consensus/polybft/state_sync_relayer_test.go deleted file mode 100644 index 11ecee4..0000000 --- a/consensus/polybft/state_sync_relayer_test.go +++ /dev/null @@ -1,203 +0,0 @@ -package polybft - -import ( - "errors" - "math/big" - "testing" - "time" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/types" -) - -func TestStateSyncRelayer_FullWorkflow(t *testing.T) { - t.Parallel() - - testKey := createTestKey(t) - stateSyncAddr := types.StringToAddress("0x56563") - - resultLogs := []*types.Log{ - createTestLogForStateSyncResultEvent(t, 1), createTestLogForStateSyncResultEvent(t, 2), - createTestLogForStateSyncResultEvent(t, 3), createTestLogForStateSyncResultEvent(t, 4), - createTestLogForStateSyncResultEvent(t, 5), - } - commitmentLogs := []*types.Log{ - createTestLogForNewCommitmentEvent(t, stateSyncAddr, 1, 1, types.StringToHash("0x2")), - createTestLogForNewCommitmentEvent(t, stateSyncAddr, 2, 3, types.StringToHash("0x2")), - createTestLogForNewCommitmentEvent(t, stateSyncAddr, 4, 5, types.StringToHash("0x2")), - } - - headers := []*types.Header{ - {Number: 2}, {Number: 3}, {Number: 4}, {Number: 5}, {Number: 5}, - } - - proofMock := &mockStateSyncProofRetriever{ - fn: func(stateSyncID uint64) (types.Proof, error) { - return types.Proof{ - Data: []types.Hash{types.StringToHash("0x1122334455")}, - Metadata: map[string]interface{}{ - "StateSync": map[string]interface{}{ - "ID": stateSyncID, - "Sender": types.StringToAddress("0xffee"), - "Receiver": types.StringToAddress("0xeeff"), - "Data": nil, - }, - }, - }, nil - }, - } - blockhainMock := &blockchainMock{} - dummyTxRelayer := newDummyStakeTxRelayer(t, nil) - state := newTestState(t) - - stateSyncRelayer := NewStateSyncRelayer( - dummyTxRelayer, - stateSyncAddr, - state.StateSyncStore, - proofMock, - blockhainMock, - testKey, - &stateSyncRelayerConfig{ - maxAttemptsToSend: 6, - maxBlocksToWaitForResend: 1, - maxEventsPerBatch: 1, - }, - hclog.Default(), - ) - - for _, h := range headers { - blockhainMock.On("CurrentHeader").Return(h).Once() - } - - // send first two events without errors - dummyTxRelayer.On("SendTransaction", mock.Anything, testKey).Return((*ethgo.Receipt)(nil), nil).Times(2) - // fail 3rd time - dummyTxRelayer.On("SendTransaction", mock.Anything, testKey).Return( - (*ethgo.Receipt)(nil), errors.New("e")).Once() - // send 3 events all at once at the end - dummyTxRelayer.On("SendTransaction", mock.Anything, testKey).Return((*ethgo.Receipt)(nil), nil).Once() - - require.NoError(t, stateSyncRelayer.Init()) - - // post 1st block - require.NoError(t, stateSyncRelayer.ProcessLog(headers[0], convertLog(commitmentLogs[0]), nil)) - require.NoError(t, stateSyncRelayer.ProcessLog(headers[0], convertLog(commitmentLogs[1]), nil)) - require.NoError(t, stateSyncRelayer.PostBlock(&PostBlockRequest{})) - - time.Sleep(time.Second * 2) // wait for some time - - events, err := state.StateSyncStore.getAllAvailableEvents(0) - - require.NoError(t, err) - require.Len(t, events, 3) - require.Equal(t, uint64(1), events[0].EventID) - require.True(t, events[0].SentStatus) - require.False(t, events[1].SentStatus) - require.False(t, events[2].SentStatus) - - // post 2nd block - require.NoError(t, stateSyncRelayer.ProcessLog(headers[1], convertLog(resultLogs[0]), nil)) - require.NoError(t, stateSyncRelayer.ProcessLog(headers[1], convertLog(commitmentLogs[2]), nil)) - require.NoError(t, stateSyncRelayer.PostBlock(&PostBlockRequest{})) - - time.Sleep(time.Second * 2) // wait for some time - - events, err = state.StateSyncStore.getAllAvailableEvents(0) - - require.NoError(t, err) - require.Len(t, events, 4) - require.True(t, events[0].SentStatus) - require.Equal(t, uint64(2), events[0].EventID) - require.False(t, events[1].SentStatus) - require.False(t, events[2].SentStatus) - - // post 3rd block - require.NoError(t, stateSyncRelayer.ProcessLog(headers[2], convertLog(resultLogs[1]), nil)) - require.NoError(t, stateSyncRelayer.PostBlock(&PostBlockRequest{})) - - time.Sleep(time.Second * 2) // wait for some time - - events, err = state.StateSyncStore.getAllAvailableEvents(0) - - require.NoError(t, err) - require.Len(t, events, 3) - require.True(t, events[0].SentStatus) - require.Equal(t, uint64(3), events[0].EventID) - require.False(t, events[1].SentStatus) - - // post 4th block - will not provide result, so one more SendTransaction will be triggered - stateSyncRelayer.config.maxEventsPerBatch = 3 // send all 3 left events at once - - require.NoError(t, stateSyncRelayer.PostBlock(&PostBlockRequest{})) - - time.Sleep(time.Second * 2) // wait for some time - - events, err = state.StateSyncStore.getAllAvailableEvents(0) - - require.NoError(t, err) - require.Len(t, events, 3) - require.True(t, events[0].SentStatus && events[1].SentStatus && events[2].SentStatus) - - // post 5th block - require.NoError(t, stateSyncRelayer.ProcessLog(headers[4], convertLog(resultLogs[2]), nil)) - require.NoError(t, stateSyncRelayer.ProcessLog(headers[4], convertLog(resultLogs[3]), nil)) - require.NoError(t, stateSyncRelayer.ProcessLog(headers[4], convertLog(resultLogs[4]), nil)) - require.NoError(t, stateSyncRelayer.PostBlock(&PostBlockRequest{})) - - time.Sleep(time.Second * 2) // wait for some time - - events, err = state.StateSyncStore.getAllAvailableEvents(0) - - require.NoError(t, err) - require.Len(t, events, 0) - - stateSyncRelayer.Close() - time.Sleep(time.Second) - - blockhainMock.AssertExpectations(t) - dummyTxRelayer.AssertExpectations(t) -} - -type mockStateSyncProofRetriever struct { - fn func(uint64) (types.Proof, error) -} - -func (m *mockStateSyncProofRetriever) GetStateSyncProof(stateSyncID uint64) (types.Proof, error) { - if m.fn != nil { - return m.fn(stateSyncID) - } - - return types.Proof{}, nil -} - -func createTestLogForNewCommitmentEvent( - t *testing.T, - stateSyncAddr types.Address, - startEventID uint64, - endEventID uint64, - rootHash types.Hash) *types.Log { - t.Helper() - - var evnt contractsapi.NewCommitmentEvent - - encodedData1, err := abi.MustNewType("uint256").Encode(new(big.Int).SetUint64(startEventID)) - require.NoError(t, err) - - encodedData2, err := abi.MustNewType("uint256").Encode(new(big.Int).SetUint64(endEventID)) - require.NoError(t, err) - - return &types.Log{ - Address: stateSyncAddr, - Topics: []types.Hash{ - types.Hash(evnt.Sig()), - types.BytesToHash(encodedData1), - types.BytesToHash(encodedData2), - }, - Data: rootHash[:], - } -} diff --git a/consensus/polybft/state_transaction.go b/consensus/polybft/state_transaction.go deleted file mode 100644 index 48ff64e..0000000 --- a/consensus/polybft/state_transaction.go +++ /dev/null @@ -1,44 +0,0 @@ -package polybft - -import ( - "bytes" - "fmt" - - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" -) - -const abiMethodIDLength = 4 - -func decodeStateTransaction(txData []byte) (contractsapi.StateTransactionInput, error) { - if len(txData) < abiMethodIDLength { - return nil, fmt.Errorf("state transactions have input") - } - - sig := txData[:abiMethodIDLength] - - var ( - commitFn contractsapi.CommitStateReceiverFn - commitEpochFn contractsapi.CommitEpochValidatorSetFn - distributeRewardsFn contractsapi.DistributeRewardForRewardPoolFn - obj contractsapi.StateTransactionInput - ) - - if bytes.Equal(sig, commitFn.Sig()) { - // bridge commitment - obj = &CommitmentMessageSigned{} - } else if bytes.Equal(sig, commitEpochFn.Sig()) { - // commit epoch - obj = &contractsapi.CommitEpochValidatorSetFn{} - } else if bytes.Equal(sig, distributeRewardsFn.Sig()) { - // distribute rewards - obj = &contractsapi.DistributeRewardForRewardPoolFn{} - } else { - return nil, fmt.Errorf("unknown state transaction") - } - - if err := obj.DecodeAbi(txData); err != nil { - return nil, err - } - - return obj, nil -} diff --git a/consensus/polybft/state_transaction_test.go b/consensus/polybft/state_transaction_test.go deleted file mode 100644 index 4e6ca9e..0000000 --- a/consensus/polybft/state_transaction_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package polybft - -import ( - "encoding/hex" - "math/big" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo/abi" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/types" -) - -func TestStateTransaction_Signature(t *testing.T) { - t.Parallel() - - cases := []struct { - m *abi.Method - sig string - }{ - { - contractsapi.ValidatorSet.Abi.GetMethod("commitEpoch"), - "0f50287c", - }, - } - for _, c := range cases { - sig := hex.EncodeToString(c.m.ID()) - require.Equal(t, c.sig, sig) - } -} - -func TestStateTransaction_Encoding(t *testing.T) { - t.Parallel() - - cases := []contractsapi.StateTransactionInput{ - &contractsapi.CommitEpochValidatorSetFn{ - ID: big.NewInt(1), - Epoch: &contractsapi.Epoch{ - StartBlock: big.NewInt(1), - EndBlock: big.NewInt(10), - EpochRoot: types.Hash{}, - }, - }, - } - - for _, c := range cases { - res, err := c.EncodeAbi() - - require.NoError(t, err) - - // use reflection to create another type and decode - val := reflect.New(reflect.TypeOf(c).Elem()).Interface() - obj, ok := val.(contractsapi.StateTransactionInput) - assert.True(t, ok) - - err = obj.DecodeAbi(res) - require.NoError(t, err) - - require.Equal(t, obj, c) - } -} diff --git a/consensus/polybft/stats.go b/consensus/polybft/stats.go deleted file mode 100644 index bd3d254..0000000 --- a/consensus/polybft/stats.go +++ /dev/null @@ -1,210 +0,0 @@ -package polybft - -import ( - "math/big" - "time" - - "github.com/armon/go-metrics" - "github.com/hashicorp/go-hclog" - "github.com/prometheus/client_golang/prometheus" - "github.com/umbracle/ethgo" - "github.com/xgr-network/xgr-node/txrelayer" -) - -// startStatsReleasing starts the process that releases BoltDB stats into prometheus periodically. -func (s *State) startStatsReleasing() { - const ( - statUpdatePeriod = 10 * time.Second - dbSubsystem = "db" - txSubsystem = "tx" - namespace = "polybft_state" - ) - - // Grab the initial stats. - prev := s.db.Stats() - - // Initialize ticker in order to send stats once a statUpdatePeriod - ticker := time.NewTicker(statUpdatePeriod) - - // Stop ticker (stats releasing basically) when receiving the closing signal - go func() { - <-s.close - ticker.Stop() - }() - - for range ticker.C { - // Grab the current stats and diff them. - stats := s.db.Stats() - diff := stats.Sub(&prev) - - // Freelist stats - { - // Update total number of free pages on the freelist - metrics.SetGauge( - []string{prometheus.BuildFQName(namespace, dbSubsystem, "freelist_free_pages")}, - float32(diff.FreePageN), - ) - - // Update total number of pending pages on the freelist - metrics.SetGauge( - []string{prometheus.BuildFQName(namespace, dbSubsystem, "freelist_pending_pages")}, - float32(diff.PendingPageN), - ) - - // Update total bytes allocated in free pages - metrics.SetGauge( - []string{prometheus.BuildFQName(namespace, dbSubsystem, "freelist_free_page_allocated_bytes")}, - float32(diff.FreeAlloc), - ) - - // Update total bytes used by the freelist - metrics.SetGauge( - []string{prometheus.BuildFQName(namespace, dbSubsystem, "freelist_in_use_bytes")}, - float32(diff.FreelistInuse), - ) - } - - // Transaction stats - { - // Update total number of started read transactions - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, dbSubsystem, "read_tx_total")}, - float32(diff.TxN), - ) - - // Update number of currently open read transactions - metrics.SetGauge( - []string{prometheus.BuildFQName(namespace, dbSubsystem, "open_read_tx")}, - float32(diff.OpenTxN), - ) - } - - // Global, ongoing stats - { - // Update number of page allocations - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "pages_allocated_total")}, - float32(diff.TxStats.PageCount), - ) - - // Update total bytes allocated - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "pages_allocated_bytes_total")}, - float32(diff.TxStats.PageAlloc), - ) - - // Update number of cursors created - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "cursors_total")}, - float32(diff.TxStats.CursorCount), - ) - - // Update number of node allocations - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_allocated_total")}, - float32(diff.TxStats.NodeCount), - ) - - // Update number of node dereferences - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_dereferenced_total")}, - float32(diff.TxStats.NodeDeref), - ) - - // Update number of node rebalances - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "node_rebalances_total")}, - float32(diff.TxStats.Rebalance), - ) - - // Update total time spent rebalancing - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "node_rebalance_seconds_total")}, - float32(diff.TxStats.RebalanceTime.Seconds()), - ) - - // Update number of nodes split - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_split_total")}, - float32(diff.TxStats.Split), - ) - - // Update number of nodes spilled - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_spilled_total")}, - float32(diff.TxStats.Spill), - ) - - // Update total time spent spilling - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_spilled_seconds_total")}, - float32(diff.TxStats.SpillTime.Seconds()), - ) - - // Update number of writes performed - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "writes_total")}, - float32(diff.TxStats.Write), - ) - - // Update total time spent writing to disk - metrics.IncrCounter( - []string{prometheus.BuildFQName(namespace, txSubsystem, "write_seconds_total")}, - float32(diff.TxStats.WriteTime), - ) - } - - // Save stats for the next loop. - prev = stats - } -} - -// publishRootchainMetrics publishes rootchain related metrics -func (p *Polybft) publishRootchainMetrics(logger hclog.Logger) { - interval := p.config.MetricsInterval - validatorAddr := p.key.Address() - bridgeCfg := p.consensusConfig.Bridge - - // zero means metrics are disabled - if interval <= 0 { - return - } - - relayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(bridgeCfg.JSONRPCEndpoint)) - if err != nil { - logger.Error("failed to connect to the rootchain node", "err", err, "JSON RPC", bridgeCfg.JSONRPCEndpoint) - - return - } - - gweiPerWei := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)) // 10^9 - - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for { - select { - case <-p.closeCh: - return - case <-ticker.C: - // rootchain validator balance - balance, err := relayer.Client().Eth().GetBalance(p.key.Address(), ethgo.Latest) - if err != nil { - logger.Error("failed to query eth_getBalance", "err", err) - } else { - balanceInGwei := new(big.Float).Quo(new(big.Float).SetInt(balance), gweiPerWei) - balanceInGweiFloat, _ := balanceInGwei.Float32() - - metrics.SetGauge([]string{"bridge", "validator_root_balance_gwei", validatorAddr.String()}, balanceInGweiFloat) - } - - // rootchain current checkpoint block - checkpointBlock, err := getCurrentCheckpointBlock(relayer, bridgeCfg.CheckpointManagerAddr) - if err != nil { - logger.Error("failed to query latest checkpoint block", "err", err) - } else { - metrics.SetGauge([]string{"bridge", "checkpoint_block_number"}, float32(checkpointBlock)) - } - } - } -} diff --git a/consensus/polybft/system_state.go b/consensus/polybft/system_state.go deleted file mode 100644 index aa7e102..0000000 --- a/consensus/polybft/system_state.go +++ /dev/null @@ -1,83 +0,0 @@ -package polybft - -import ( - "fmt" - "math/big" - - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/contract" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/types" -) - -// ValidatorInfo is data transfer object which holds validator information, -// provided by smart contract -type ValidatorInfo struct { - Address ethgo.Address `json:"address"` - Stake *big.Int `json:"stake"` - WithdrawableRewards *big.Int `json:"withdrawableRewards"` - IsActive bool `json:"isActive"` - IsWhitelisted bool `json:"isWhitelisted"` -} - -// SystemState is an interface to interact with the consensus system contracts in the chain -type SystemState interface { - // GetEpoch retrieves current epoch number from the smart contract - GetEpoch() (uint64, error) - // GetNextCommittedIndex retrieves next committed bridge state sync index - GetNextCommittedIndex() (uint64, error) -} - -var _ SystemState = &SystemStateImpl{} - -// SystemStateImpl is implementation of SystemState interface -type SystemStateImpl struct { - validatorContract *contract.Contract - sidechainBridgeContract *contract.Contract -} - -// NewSystemState initializes new instance of systemState which abstracts smart contracts functions -func NewSystemState(valSetAddr types.Address, stateRcvAddr types.Address, provider contract.Provider) *SystemStateImpl { - s := &SystemStateImpl{} - s.validatorContract = contract.NewContract( - ethgo.Address(valSetAddr), - contractsapi.ValidatorSet.Abi, contract.WithProvider(provider), - ) - s.sidechainBridgeContract = contract.NewContract( - ethgo.Address(stateRcvAddr), - contractsapi.StateReceiver.Abi, - contract.WithProvider(provider), - ) - - return s -} - -// GetEpoch retrieves current epoch number from the smart contract -func (s *SystemStateImpl) GetEpoch() (uint64, error) { - rawResult, err := s.validatorContract.Call("currentEpochId", ethgo.Latest) - if err != nil { - return 0, err - } - - epochNumber, isOk := rawResult["0"].(*big.Int) - if !isOk { - return 0, fmt.Errorf("failed to decode epoch") - } - - return epochNumber.Uint64(), nil -} - -// GetNextCommittedIndex retrieves next committed bridge state sync index -func (s *SystemStateImpl) GetNextCommittedIndex() (uint64, error) { - rawResult, err := s.sidechainBridgeContract.Call("lastCommittedId", ethgo.Latest) - if err != nil { - return 0, err - } - - nextCommittedIndex, isOk := rawResult["0"].(*big.Int) - if !isOk { - return 0, fmt.Errorf("failed to decode next committed index") - } - - return nextCommittedIndex.Uint64() + 1, nil -} diff --git a/consensus/polybft/system_state_test.go b/consensus/polybft/system_state_test.go deleted file mode 100644 index 31e4f52..0000000 --- a/consensus/polybft/system_state_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package polybft - -import ( - "encoding/hex" - "math/big" - "testing" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" - "github.com/umbracle/ethgo/contract" - "github.com/umbracle/ethgo/testutil" - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/contracts" - "github.com/xgr-network/xgr-node/state" - itrie "github.com/xgr-network/xgr-node/state/immutable-trie" - "github.com/xgr-network/xgr-node/types" -) - -func TestSystemState_GetNextCommittedIndex(t *testing.T) { - t.Parallel() - - var sideChainBridgeABI, _ = abi.NewMethod( - "function setNextCommittedIndex(uint256 _index) public payable", - ) - - cc := &testutil.Contract{} - cc.AddCallback(func() string { - return ` - uint256 public lastCommittedId; - - function setNextCommittedIndex(uint256 _index) public payable { - lastCommittedId = _index; - }` - }) - - solcContract, err := cc.Compile() - require.NoError(t, err) - - bin, err := hex.DecodeString(solcContract.Bin) - require.NoError(t, err) - - transition := newTestTransition(t, nil) - - // deploy a contract - result := transition.Create2(types.Address{}, bin, big.NewInt(0), 1000000000) - assert.NoError(t, result.Err) - - provider := &stateProvider{ - transition: transition, - } - - systemState := NewSystemState(contracts.ValidatorSetContract, result.Address, provider) - - expectedNextCommittedIndex := uint64(45) - input, err := sideChainBridgeABI.Encode([1]interface{}{expectedNextCommittedIndex}) - assert.NoError(t, err) - - _, err = provider.Call(ethgo.Address(result.Address), input, &contract.CallOpts{}) - assert.NoError(t, err) - - nextCommittedIndex, err := systemState.GetNextCommittedIndex() - assert.NoError(t, err) - assert.Equal(t, expectedNextCommittedIndex+1, nextCommittedIndex) -} - -func TestSystemState_GetEpoch(t *testing.T) { - t.Parallel() - - setEpochMethod, err := abi.NewMethod("function setEpoch(uint256 _epochId) public payable") - require.NoError(t, err) - - cc := &testutil.Contract{} - cc.AddCallback(func() string { - return ` - uint256 public currentEpochId; - - function setEpoch(uint256 _epochId) public payable { - currentEpochId = _epochId; - } - ` - }) - - solcContract, err := cc.Compile() - require.NoError(t, err) - - bin, err := hex.DecodeString(solcContract.Bin) - require.NoError(t, err) - - transition := newTestTransition(t, nil) - - // deploy a contract - result := transition.Create2(types.Address{}, bin, big.NewInt(0), 1000000000) - assert.NoError(t, result.Err) - - provider := &stateProvider{ - transition: transition, - } - - systemState := NewSystemState(result.Address, contracts.StateReceiverContract, provider) - - expectedEpoch := uint64(50) - input, err := setEpochMethod.Encode([1]interface{}{expectedEpoch}) - require.NoError(t, err) - - _, err = provider.Call(ethgo.Address(result.Address), input, &contract.CallOpts{}) - require.NoError(t, err) - - epoch, err := systemState.GetEpoch() - require.NoError(t, err) - require.Equal(t, expectedEpoch, epoch) -} - -func TestStateProvider_Txn_NotSupported(t *testing.T) { - t.Parallel() - - transition := newTestTransition(t, nil) - - provider := &stateProvider{ - transition: transition, - } - - _, err := provider.Txn(ethgo.ZeroAddress, createTestKey(t), []byte{0x1}) - require.ErrorIs(t, err, errSendTxnUnsupported) -} - -func newTestTransition(t *testing.T, alloc map[types.Address]*chain.GenesisAccount) *state.Transition { - t.Helper() - - st := itrie.NewState(itrie.NewMemoryStorage()) - - ex := state.NewExecutor(&chain.Params{ - Forks: chain.AllForksEnabled, - BurnContract: map[uint64]types.Address{ - 0: types.ZeroAddress, - }, - }, st, hclog.NewNullLogger()) - - rootHash, err := ex.WriteGenesis(alloc, types.Hash{}) - require.NoError(t, err) - - ex.GetHash = func(h *types.Header) state.GetHashByNumber { - return func(i uint64) types.Hash { - return rootHash - } - } - - transition, err := ex.BeginTxn( - rootHash, - &types.Header{}, - types.ZeroAddress, - ) - assert.NoError(t, err) - - return transition -} diff --git a/consensus/polybft/transport.go b/consensus/polybft/transport.go deleted file mode 100644 index 6ef662f..0000000 --- a/consensus/polybft/transport.go +++ /dev/null @@ -1,65 +0,0 @@ -package polybft - -import ( - "fmt" - - ibftProto "github.com/0xPolygon/go-ibft/messages/proto" - "github.com/libp2p/go-libp2p/core/peer" - polybftProto "github.com/xgr-network/xgr-node/consensus/polybft/proto" - "github.com/xgr-network/xgr-node/types" -) - -// BridgeTransport is an abstraction of network layer for a bridge -type BridgeTransport interface { - Multicast(msg interface{}) -} - -// subscribeToIbftTopic subscribes to ibft topic -func (p *Polybft) subscribeToIbftTopic() error { - return p.consensusTopic.Subscribe(func(obj interface{}, _ peer.ID) { - if !p.runtime.IsActiveValidator() { - return - } - - msg, ok := obj.(*ibftProto.Message) - if !ok { - p.logger.Error("consensus engine: invalid type assertion for message request") - - return - } - - p.ibft.AddMessage(msg) - - p.logger.Debug( - "validator message received", - "type", msg.Type.String(), - "height", msg.GetView().Height, - "round", msg.GetView().Round, - "addr", types.BytesToAddress(msg.From).String(), - ) - }) -} - -// createTopics create all topics for a PolyBft instance -func (p *Polybft) createTopics() (err error) { - if p.consensusConfig.IsBridgeEnabled() { - p.bridgeTopic, err = p.config.Network.NewTopic(bridgeProto, &polybftProto.TransportMessage{}) - if err != nil { - return fmt.Errorf("failed to create bridge topic: %w", err) - } - } - - p.consensusTopic, err = p.config.Network.NewTopic(pbftProto, &ibftProto.Message{}) - if err != nil { - return fmt.Errorf("failed to create consensus topic: %w", err) - } - - return nil -} - -// Multicast is implementation of core.Transport interface -func (p *Polybft) Multicast(msg *ibftProto.Message) { - if err := p.consensusTopic.Publish(msg); err != nil { - p.logger.Warn("failed to multicast consensus message", "error", err) - } -} diff --git a/consensus/polybft/validator/genesis_validator.go b/consensus/polybft/validator/genesis_validator.go deleted file mode 100644 index 155c983..0000000 --- a/consensus/polybft/validator/genesis_validator.go +++ /dev/null @@ -1,83 +0,0 @@ -package validator - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "math/big" - - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/types" -) - -// GenesisValidator represents public information about validator accounts which are the part of genesis -type GenesisValidator struct { - Address types.Address - BlsKey string - Stake *big.Int - MultiAddr string -} - -type genesisValidatorRaw struct { - Address types.Address `json:"address"` - BlsKey string `json:"blsKey"` - Stake *string `json:"stake"` - MultiAddr string `json:"multiAddr"` -} - -func (v *GenesisValidator) MarshalJSON() ([]byte, error) { - raw := &genesisValidatorRaw{Address: v.Address, BlsKey: v.BlsKey, MultiAddr: v.MultiAddr} - raw.Stake = common.EncodeBigInt(v.Stake) - - return json.Marshal(raw) -} - -func (v *GenesisValidator) UnmarshalJSON(data []byte) (err error) { - var raw genesisValidatorRaw - - if err = json.Unmarshal(data, &raw); err != nil { - return err - } - - v.Address = raw.Address - v.BlsKey = raw.BlsKey - v.MultiAddr = raw.MultiAddr - - v.Stake, err = common.ParseUint256orHex(raw.Stake) - - return err -} - -// UnmarshalBLSPublicKey unmarshals the hex encoded BLS public key -func (v *GenesisValidator) UnmarshalBLSPublicKey() (*bls.PublicKey, error) { - decoded, err := hex.DecodeString(v.BlsKey) - if err != nil { - return nil, err - } - - return bls.UnmarshalPublicKey(decoded) -} - -// ToValidatorMetadata creates ValidatorMetadata instance -func (v *GenesisValidator) ToValidatorMetadata() (*ValidatorMetadata, error) { - blsKey, err := v.UnmarshalBLSPublicKey() - if err != nil { - return nil, err - } - - metadata := &ValidatorMetadata{ - Address: v.Address, - BlsKey: blsKey, - VotingPower: new(big.Int).Set(v.Stake), - IsActive: true, - } - - return metadata, nil -} - -// String implements fmt.Stringer interface -func (v *GenesisValidator) String() string { - return fmt.Sprintf("Address=%s; Stake=%d; P2P Multi addr=%s; BLS Key=%s;", - v.Address, v.Stake, v.MultiAddr, v.BlsKey) -} diff --git a/consensus/polybft/validator/test_helpers.go b/consensus/polybft/validator/test_helpers.go deleted file mode 100644 index e2c3507..0000000 --- a/consensus/polybft/validator/test_helpers.go +++ /dev/null @@ -1,243 +0,0 @@ -package validator - -import ( - "encoding/hex" - "fmt" - "math/big" - "sort" - "strconv" - "testing" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/consensus/polybft/wallet" - "github.com/xgr-network/xgr-node/types" -) - -type TestValidators struct { - Validators map[string]*TestValidator -} - -func NewTestValidators(tb testing.TB, validatorsCount int) *TestValidators { - tb.Helper() - - aliases := make([]string, validatorsCount) - for i := 0; i < validatorsCount; i++ { - aliases[i] = strconv.Itoa(i) - } - - return NewTestValidatorsWithAliases(tb, aliases) -} - -func NewTestValidatorsWithAliases(tb testing.TB, aliases []string, votingPowers ...[]uint64) *TestValidators { - tb.Helper() - - validators := map[string]*TestValidator{} - - for i, alias := range aliases { - votingPower := uint64(1) - if len(votingPowers) == 1 { - votingPower = votingPowers[0][i] - } - - validators[alias] = NewTestValidator(tb, alias, votingPower) - } - - return &TestValidators{Validators: validators} -} - -func (v *TestValidators) Create(t *testing.T, alias string, votingPower uint64) { - t.Helper() - - if _, ok := v.Validators[alias]; !ok { - v.Validators[alias] = NewTestValidator(t, alias, votingPower) - } -} - -func (v *TestValidators) IterAcct(aliases []string, handle func(t *TestValidator)) { - if len(aliases) == 0 { - // loop over the whole set - for k := range v.Validators { - aliases = append(aliases, k) - } - // sort the names since they get queried randomly - sort.Strings(aliases) - } - - for _, alias := range aliases { - handle(v.GetValidator(alias)) - } -} - -func (v *TestValidators) GetParamValidators(aliases ...string) (res []*GenesisValidator) { - v.IterAcct(aliases, func(t *TestValidator) { - res = append(res, t.ParamsValidator()) - }) - - return -} - -func (v *TestValidators) GetValidators(aliases ...string) (res []*TestValidator) { - v.IterAcct(aliases, func(t *TestValidator) { - res = append(res, t) - }) - - return -} - -func (v *TestValidators) GetPublicIdentities(aliases ...string) (res AccountSet) { - v.IterAcct(aliases, func(t *TestValidator) { - res = append(res, t.ValidatorMetadata()) - }) - - return -} - -func (v *TestValidators) GetPrivateIdentities(aliases ...string) (res []*wallet.Account) { - v.IterAcct(aliases, func(t *TestValidator) { - res = append(res, t.Account) - }) - - return -} - -func (v *TestValidators) GetValidator(alias string) *TestValidator { - vv, ok := v.Validators[alias] - if !ok { - panic(fmt.Sprintf("Validator %s does not exist", alias)) //nolint:gocritic - } - - return vv -} - -func (v *TestValidators) ToValidatorSet() ValidatorSet { - return NewValidatorSet(v.GetPublicIdentities(), hclog.NewNullLogger()) -} - -func (v *TestValidators) UpdateVotingPowers(votingPowersMap map[string]uint64) AccountSet { - if len(votingPowersMap) == 0 { - return AccountSet{} - } - - aliases := []string{} - for alias := range votingPowersMap { - aliases = append(aliases, alias) - } - - v.IterAcct(aliases, func(t *TestValidator) { - t.VotingPower = votingPowersMap[t.Alias] - }) - - return v.GetPublicIdentities(aliases...) -} - -type TestValidator struct { - Alias string - Account *wallet.Account - VotingPower uint64 -} - -func NewTestValidator(tb testing.TB, alias string, votingPower uint64) *TestValidator { - tb.Helper() - - return &TestValidator{ - Alias: alias, - VotingPower: votingPower, - Account: generateTestAccount(tb), - } -} - -func (v *TestValidator) Address() types.Address { - return types.Address(v.Account.Ecdsa.Address()) -} - -func (v *TestValidator) Key() *wallet.Key { - return wallet.NewKey(v.Account) -} - -func (v *TestValidator) ParamsValidator() *GenesisValidator { - bls := v.Account.Bls.PublicKey().Marshal() - - return &GenesisValidator{ - Address: v.Address(), - BlsKey: hex.EncodeToString(bls), - Stake: big.NewInt(1000), - } -} - -func (v *TestValidator) ValidatorMetadata() *ValidatorMetadata { - return &ValidatorMetadata{ - Address: types.Address(v.Account.Ecdsa.Address()), - BlsKey: v.Account.Bls.PublicKey(), - VotingPower: new(big.Int).SetUint64(v.VotingPower), - } -} - -func (v *TestValidator) MustSign(hash, domain []byte) *bls.Signature { - signature, err := v.Account.Bls.Sign(hash, domain) - if err != nil { - panic(fmt.Sprintf("BUG: failed to sign: %v", err)) //nolint:gocritic - } - - return signature -} - -func generateTestAccount(tb testing.TB) *wallet.Account { - tb.Helper() - - acc, err := wallet.GenerateAccount() - require.NoError(tb, err) - - return acc -} - -// CreateValidatorSetDelta calculates ValidatorSetDelta based on the provided old and new validator sets -func CreateValidatorSetDelta(oldValidatorSet, newValidatorSet AccountSet) (*ValidatorSetDelta, error) { - var addedValidators, updatedValidators AccountSet - - oldValidatorSetMap := make(map[types.Address]*ValidatorMetadata) - removedValidators := map[types.Address]int{} - - for i, validator := range oldValidatorSet { - if (validator.Address != types.Address{}) { - removedValidators[validator.Address] = i - oldValidatorSetMap[validator.Address] = validator - } - } - - for _, newValidator := range newValidatorSet { - // Check if the validator is among both old and new validator set - oldValidator, validatorExists := oldValidatorSetMap[newValidator.Address] - if validatorExists { - if !oldValidator.EqualAddressAndBlsKey(newValidator) { - return nil, fmt.Errorf("validator '%s' found in both old and new validator set, but its BLS keys differ", - newValidator.Address.String()) - } - - // If it is, then discard it from removed validators... - delete(removedValidators, newValidator.Address) - - if !oldValidator.Equals(newValidator) { - updatedValidators = append(updatedValidators, newValidator) - } - } else { - // ...otherwise it is added - addedValidators = append(addedValidators, newValidator) - } - } - - removedValsBitmap := bitmap.Bitmap{} - for _, i := range removedValidators { - removedValsBitmap.Set(uint64(i)) - } - - delta := &ValidatorSetDelta{ - Added: addedValidators, - Updated: updatedValidators, - Removed: removedValsBitmap, - } - - return delta, nil -} diff --git a/consensus/polybft/validator/validator_metadata.go b/consensus/polybft/validator/validator_metadata.go deleted file mode 100644 index 0dda1b2..0000000 --- a/consensus/polybft/validator/validator_metadata.go +++ /dev/null @@ -1,360 +0,0 @@ -package validator - -import ( - "bytes" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "math/big" - "reflect" - - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/contractsapi" - "github.com/xgr-network/xgr-node/crypto" - - "github.com/umbracle/ethgo/abi" - "github.com/umbracle/fastrlp" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/types" -) - -var accountSetABIType = abi.MustNewType(`tuple(tuple(address _address, uint256[4] blsKey, uint256 votingPower)[])`) - -// ValidatorMetadata represents a validator metadata (its public identity) -type ValidatorMetadata struct { - Address types.Address - BlsKey *bls.PublicKey - VotingPower *big.Int - IsActive bool -} - -// Equals checks ValidatorMetadata equality -func (v *ValidatorMetadata) Equals(b *ValidatorMetadata) bool { - if b == nil { - return false - } - - return v.EqualAddressAndBlsKey(b) && v.VotingPower.Cmp(b.VotingPower) == 0 && v.IsActive == b.IsActive -} - -// EqualAddressAndBlsKey checks ValidatorMetadata equality against Address and BlsKey fields -func (v *ValidatorMetadata) EqualAddressAndBlsKey(b *ValidatorMetadata) bool { - if b == nil { - return false - } - - return v.Address == b.Address && reflect.DeepEqual(v.BlsKey, b.BlsKey) -} - -// Copy returns a deep copy of ValidatorMetadata -func (v *ValidatorMetadata) Copy() *ValidatorMetadata { - copiedBlsKey := v.BlsKey.Marshal() - blsKey, _ := bls.UnmarshalPublicKey(copiedBlsKey) - - return &ValidatorMetadata{ - Address: types.BytesToAddress(v.Address[:]), - BlsKey: blsKey, - VotingPower: new(big.Int).Set(v.VotingPower), - IsActive: v.IsActive, - } -} - -// MarshalRLPWith marshals ValidatorMetadata to the RLP format -func (v *ValidatorMetadata) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { - vv := ar.NewArray() - // Address - vv.Set(ar.NewBytes(v.Address.Bytes())) - // BlsKey - vv.Set(ar.NewCopyBytes(v.BlsKey.Marshal())) - // VotingPower - vv.Set(ar.NewBigInt(v.VotingPower)) - // IsActive - vv.Set(ar.NewBool(v.IsActive)) - - return vv -} - -// UnmarshalRLPWith unmarshals ValidatorMetadata from the RLP format -func (v *ValidatorMetadata) UnmarshalRLPWith(val *fastrlp.Value) error { - elems, err := val.GetElems() - if err != nil { - return err - } - - if num := len(elems); num != 4 { - return fmt.Errorf("incorrect elements count to decode validator account, expected 4 but found %d", num) - } - - // Address - addressRaw, err := elems[0].GetBytes(nil) - if err != nil { - return fmt.Errorf("expected 'Address' field encoded as bytes. Error: %w", err) - } - - v.Address = types.BytesToAddress(addressRaw) - - // BlsKey - blsKeyRaw, err := elems[1].GetBytes(nil) - if err != nil { - return fmt.Errorf("expected 'BlsKey' encoded as bytes: %w", err) - } - - blsKey, err := bls.UnmarshalPublicKey(blsKeyRaw) - if err != nil { - return fmt.Errorf("failed to unmarshal BLS public key: %w", err) - } - - v.BlsKey = blsKey - - // VotingPower - votingPower := new(big.Int) - if err = elems[2].GetBigInt(votingPower); err != nil { - return fmt.Errorf("expected 'VotingPower' encoded as big int: %w", err) - } - - v.VotingPower = new(big.Int).Set(votingPower) - - // IsActive - isActive, err := elems[3].GetBool() - if err != nil { - return fmt.Errorf("expected 'IsActive' encoded as bool: %w", err) - } - - v.IsActive = isActive - - return nil -} - -// fmt.Stringer implementation -func (v *ValidatorMetadata) String() string { - return fmt.Sprintf("Address=%v; Is Active=%v; Voting Power=%d; BLS Key=%v;", - v.Address.String(), v.IsActive, v.VotingPower, hex.EncodeToString(v.BlsKey.Marshal())) -} - -// AccountSet is a type alias for slice of ValidatorMetadata instances -type AccountSet []*ValidatorMetadata - -// Equals compares checks if two AccountSet instances are equal (ordering is important) -func (as AccountSet) Equals(other AccountSet) bool { - if len(as) != len(other) { - return false - } - - for i := range as { - if !as[i].Equals(other[i]) { - return false - } - } - - return true -} - -// fmt.Stringer implementation -func (as AccountSet) String() string { - var buf bytes.Buffer - for _, v := range as { - buf.WriteString(fmt.Sprintf("%s\n", v.String())) - } - - return buf.String() -} - -// GetAddresses aggregates addresses for given AccountSet -func (as AccountSet) GetAddresses() []types.Address { - res := make([]types.Address, 0, len(as)) - for _, account := range as { - res = append(res, account.Address) - } - - return res -} - -// GetAddresses aggregates addresses as map for given AccountSet -func (as AccountSet) GetAddressesAsSet() map[types.Address]struct{} { - res := make(map[types.Address]struct{}, len(as)) - for _, account := range as { - res[account.Address] = struct{}{} - } - - return res -} - -// GetBlsKeys aggregates public BLS keys for given AccountSet -func (as AccountSet) GetBlsKeys() []*bls.PublicKey { - res := make([]*bls.PublicKey, 0, len(as)) - for _, account := range as { - res = append(res, account.BlsKey) - } - - return res -} - -// Len returns length of AccountSet -func (as AccountSet) Len() int { - return len(as) -} - -// ContainsNodeID checks whether ValidatorMetadata with given nodeID is present in the AccountSet -func (as AccountSet) ContainsNodeID(nodeID string) bool { - for _, validator := range as { - if validator.Address.String() == nodeID { - return true - } - } - - return false -} - -// ContainsAddress checks whether ValidatorMetadata with given address is present in the AccountSet -func (as AccountSet) ContainsAddress(address types.Address) bool { - return as.Index(address) != -1 -} - -// Index returns index of the given ValidatorMetadata, identified by address within the AccountSet. -// If given ValidatorMetadata is not present, it returns -1. -func (as AccountSet) Index(addr types.Address) int { - for indx, validator := range as { - if validator.Address == addr { - return indx - } - } - - return -1 -} - -// Copy returns deep copy of AccountSet -func (as AccountSet) Copy() AccountSet { - copiedAccs := make([]*ValidatorMetadata, as.Len()) - for i, acc := range as { - copiedAccs[i] = acc.Copy() - } - - return AccountSet(copiedAccs) -} - -// Hash returns hash value of the AccountSet -func (as AccountSet) Hash() (types.Hash, error) { - abiEncoded, err := accountSetABIType.Encode([]interface{}{as.ToAPIBinding()}) - if err != nil { - return types.ZeroHash, err - } - - return types.BytesToHash(crypto.Keccak256(abiEncoded)), nil -} - -// ToAPIBinding converts AccountSet to slice of contract api stubs to be encoded -func (as AccountSet) ToAPIBinding() []*contractsapi.Validator { - apiBinding := make([]*contractsapi.Validator, len(as)) - for i, v := range as { - apiBinding[i] = &contractsapi.Validator{ - Address: v.Address, - BlsKey: v.BlsKey.ToBigInt(), - VotingPower: new(big.Int).Set(v.VotingPower), - } - } - - return apiBinding -} - -// GetValidatorMetadata tries to retrieve validator account metadata by given address from the account set. -// It returns nil if such account is not found. -func (as AccountSet) GetValidatorMetadata(address types.Address) *ValidatorMetadata { - i := as.Index(address) - if i == -1 { - return nil - } - - return as[i] -} - -// GetFilteredValidators returns filtered validators based on provided bitmap. -// Filtered validators will contain validators whose index corresponds -// to the position in bitmap which has value set to 1. -func (as AccountSet) GetFilteredValidators(bitmap bitmap.Bitmap) (AccountSet, error) { - var filteredValidators AccountSet - if len(as) == 0 { - return filteredValidators, nil - } - - if bitmap.Len() > uint64(len(as)) { - for i := len(as); i < int(bitmap.Len()); i++ { - if bitmap.IsSet(uint64(i)) { - return filteredValidators, errors.New("invalid bitmap filter provided") - } - } - } - - for i, validator := range as { - if bitmap.IsSet(uint64(i)) { - filteredValidators = append(filteredValidators, validator) - } - } - - return filteredValidators, nil -} - -// ApplyDelta receives ValidatorSetDelta and applies it to the values from the current AccountSet -// (removes the ones marked for deletion and adds the one which are being added by delta) -// Function returns new AccountSet with old and new data merged. AccountSet is immutable! -func (as AccountSet) ApplyDelta(validatorsDelta *ValidatorSetDelta) (AccountSet, error) { - if validatorsDelta == nil || validatorsDelta.IsEmpty() { - return as.Copy(), nil - } - - // Figure out which validators from the existing set are not marked for deletion. - // Those should be kept in the snapshot. - validators := make(AccountSet, 0) - - for i, validator := range as { - // If a validator is not in the Removed set, or it is in the Removed set - // but it exists in the Added set as well (which should never happen), - // the validator should remain in the validator set. - if !validatorsDelta.Removed.IsSet(uint64(i)) || - validatorsDelta.Added.ContainsAddress(validator.Address) { - validators = append(validators, validator) - } - } - - // Append added validators - for _, addedValidator := range validatorsDelta.Added { - if validators.ContainsAddress(addedValidator.Address) { - return nil, fmt.Errorf("validator %v is already present in the validators snapshot", addedValidator.Address.String()) - } - - validators = append(validators, addedValidator) - } - - // Handle updated validators (find them in the validators slice and insert to appropriate index) - for _, updatedValidator := range validatorsDelta.Updated { - validatorIndex := validators.Index(updatedValidator.Address) - if validatorIndex == -1 { - return nil, fmt.Errorf("incorrect delta provided: validator %s is marked as updated but not found in the validators", - updatedValidator.Address) - } - - validators[validatorIndex] = updatedValidator - } - - return validators, nil -} - -// Marshal marshals AccountSet to JSON -func (as AccountSet) Marshal() ([]byte, error) { - return json.Marshal(as) -} - -// Unmarshal unmarshals AccountSet from JSON -func (as *AccountSet) Unmarshal(b []byte) error { - return json.Unmarshal(b, as) -} - -// GetTotalVotingPower calculates sum of voting power for each validator in the AccountSet -func (as *AccountSet) GetTotalVotingPower() *big.Int { - totalVotingPower := big.NewInt(0) - for _, v := range *as { - totalVotingPower = totalVotingPower.Add(totalVotingPower, v.VotingPower) - } - - return totalVotingPower -} diff --git a/consensus/polybft/validator/validator_metadata_test.go b/consensus/polybft/validator/validator_metadata_test.go deleted file mode 100644 index 0a6bad7..0000000 --- a/consensus/polybft/validator/validator_metadata_test.go +++ /dev/null @@ -1,281 +0,0 @@ -package validator - -import ( - "crypto/rand" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" - "github.com/xgr-network/xgr-node/types" -) - -// generateRandomBytes generates byte array with random data of 32 bytes length -func generateRandomBytes(t *testing.T) (result []byte) { - t.Helper() - - result = make([]byte, types.HashLength) - _, err := rand.Reader.Read(result) - require.NoError(t, err, "Cannot generate random byte array content.") - - return -} - -func TestValidatorMetadata_Equals(t *testing.T) { - t.Parallel() - - v := NewTestValidator(t, "A", 10) - validatorAcc := v.ValidatorMetadata() - // proper validator metadata instance doesn't equal to nil - require.False(t, validatorAcc.Equals(nil)) - // same instances of validator metadata are equal - require.True(t, validatorAcc.Equals(v.ValidatorMetadata())) - - // update voting power => validator metadata instances aren't equal - validatorAcc.VotingPower = new(big.Int).SetInt64(50) - require.False(t, validatorAcc.Equals(v.ValidatorMetadata())) -} - -func TestValidatorMetadata_EqualAddressAndBlsKey(t *testing.T) { - t.Parallel() - - v := NewTestValidator(t, "A", 10) - validatorAcc := v.ValidatorMetadata() - // proper validator metadata instance doesn't equal to nil - require.False(t, validatorAcc.EqualAddressAndBlsKey(nil)) - // same instances of validator metadata are equal - require.True(t, validatorAcc.EqualAddressAndBlsKey(v.ValidatorMetadata())) - - // update voting power => validator metadata instances aren't equal - validatorAcc.Address = types.BytesToAddress(generateRandomBytes(t)) - require.False(t, validatorAcc.EqualAddressAndBlsKey(v.ValidatorMetadata())) -} - -func TestAccountSet_GetAddresses(t *testing.T) { - t.Parallel() - - address1, address2, address3 := types.Address{4, 3}, types.Address{68, 123}, types.Address{168, 123} - ac := AccountSet{ - &ValidatorMetadata{Address: address1}, - &ValidatorMetadata{Address: address2}, - &ValidatorMetadata{Address: address3}, - } - rs := ac.GetAddresses() - assert.Len(t, rs, 3) - assert.Equal(t, address1, rs[0]) - assert.Equal(t, address2, rs[1]) - assert.Equal(t, address3, rs[2]) -} - -func TestAccountSet_GetBlsKeys(t *testing.T) { - t.Parallel() - - keys, err := bls.CreateRandomBlsKeys(3) - assert.NoError(t, err) - - key1, key2, key3 := keys[0], keys[1], keys[2] - ac := AccountSet{ - &ValidatorMetadata{BlsKey: key1.PublicKey()}, - &ValidatorMetadata{BlsKey: key2.PublicKey()}, - &ValidatorMetadata{BlsKey: key3.PublicKey()}, - } - rs := ac.GetBlsKeys() - assert.Len(t, rs, 3) - assert.Equal(t, key1.PublicKey(), rs[0]) - assert.Equal(t, key2.PublicKey(), rs[1]) - assert.Equal(t, key3.PublicKey(), rs[2]) -} - -func TestAccountSet_IndexContainsAddressesAndContainsNodeId(t *testing.T) { - t.Parallel() - - const count = 10 - - dummy := types.Address{2, 3, 4} - validators := NewTestValidators(t, count).GetPublicIdentities() - addresses := [count]types.Address{} - - for i, validator := range validators { - addresses[i] = validator.Address - } - - for i, a := range addresses { - assert.Equal(t, i, validators.Index(a)) - assert.True(t, validators.ContainsAddress(a)) - assert.True(t, validators.ContainsNodeID(a.String())) - } - - assert.Equal(t, -1, validators.Index(dummy)) - assert.False(t, validators.ContainsAddress(dummy)) - assert.False(t, validators.ContainsNodeID(dummy.String())) -} - -func TestAccountSet_Len(t *testing.T) { - t.Parallel() - - const count = 10 - - ac := AccountSet{} - - for i := 0; i < count; i++ { - ac = append(ac, &ValidatorMetadata{}) - assert.Equal(t, i+1, ac.Len()) - } -} - -func TestAccountSet_ApplyDelta(t *testing.T) { - t.Parallel() - - type Step struct { - added []string - updated map[string]uint64 - removed []uint64 - expected map[string]uint64 - errMsg string - } - - cases := []struct { - name string - steps []*Step - }{ - { - name: "Basic", - steps: []*Step{ - { - []string{"A", "B", "C", "D"}, - nil, - nil, - map[string]uint64{"A": 1, "B": 1, "C": 1, "D": 1}, - "", - }, - { - // add two new validators and remove 3 (one does not exists) - // update voting powers to subset of validators - // (two of them added in the previous step and one added in the current one) - []string{"E", "F"}, - map[string]uint64{"A": 30, "D": 10, "E": 5}, - []uint64{1, 2, 5}, - map[string]uint64{"A": 30, "D": 10, "E": 5, "F": 1}, - "", - }, - }, - }, - { - name: "AddRemoveSameValidator", - steps: []*Step{ - { - []string{"A"}, - nil, - []uint64{0}, - map[string]uint64{"A": 1}, - "", - }, - }, - }, - { - name: "AddSameValidatorTwice", - steps: []*Step{ - { - []string{"A", "A"}, - nil, - nil, - nil, - "is already present in the validators snapshot", - }, - }, - }, - { - name: "UpdateNonExistingValidator", - steps: []*Step{ - { - nil, - map[string]uint64{"B": 5}, - nil, - nil, - "incorrect delta provided: validator", - }, - }, - }, - } - - for _, cc := range cases { - cc := cc - t.Run(cc.name, func(t *testing.T) { - t.Parallel() - - snapshot := AccountSet{} - // Add a couple of validators to the snapshot => validators are present in the snapshot after applying such delta - vals := NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - - for _, step := range cc.steps { - addedValidators := AccountSet{} - if step.added != nil { - addedValidators = vals.GetPublicIdentities(step.added...) - } - delta := &ValidatorSetDelta{ - Added: addedValidators, - Removed: bitmap.Bitmap{}, - } - for _, i := range step.removed { - delta.Removed.Set(i) - } - - // update voting powers - delta.Updated = vals.UpdateVotingPowers(step.updated) - - // apply delta - var err error - snapshot, err = snapshot.ApplyDelta(delta) - if step.errMsg != "" { - require.ErrorContains(t, err, step.errMsg) - require.Nil(t, snapshot) - - return - } - require.NoError(t, err) - - // validate validator set - require.Equal(t, len(step.expected), snapshot.Len()) - for validatorAlias, votingPower := range step.expected { - v := vals.GetValidator(validatorAlias).ValidatorMetadata() - require.True(t, snapshot.ContainsAddress(v.Address), "validator '%s' not found in snapshot", validatorAlias) - require.Equal(t, new(big.Int).SetUint64(votingPower), v.VotingPower) - } - } - }) - } -} - -func TestAccountSet_ApplyEmptyDelta(t *testing.T) { - t.Parallel() - - v := NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - validatorAccs := v.GetPublicIdentities() - validators, err := validatorAccs.ApplyDelta(nil) - require.NoError(t, err) - require.Equal(t, validatorAccs, validators) -} - -func TestAccountSet_Hash(t *testing.T) { - t.Parallel() - - t.Run("Hash non-empty account set", func(t *testing.T) { - t.Parallel() - - v := NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - hash, err := v.GetPublicIdentities().Hash() - require.NoError(t, err) - require.NotEqual(t, types.ZeroHash, hash) - }) - - t.Run("Hash empty account set", func(t *testing.T) { - t.Parallel() - - empty := AccountSet{} - hash, err := empty.Hash() - require.NoError(t, err) - require.NotEqual(t, types.ZeroHash, hash) - }) -} diff --git a/consensus/polybft/validator/validator_set.go b/consensus/polybft/validator/validator_set.go deleted file mode 100644 index d0abcf4..0000000 --- a/consensus/polybft/validator/validator_set.go +++ /dev/null @@ -1,121 +0,0 @@ -package validator - -import ( - "math/big" - - "github.com/hashicorp/go-hclog" - "github.com/xgr-network/xgr-node/chain" - "github.com/xgr-network/xgr-node/forkmanager" - "github.com/xgr-network/xgr-node/helper/common" - "github.com/xgr-network/xgr-node/types" -) - -// ValidatorSet interface of the current validator set -type ValidatorSet interface { - // Includes check if given address is among the current validator set - Includes(address types.Address) bool - - // Len returns the size of the validator set - Len() int - - // Accounts returns the list of the ValidatorMetadata - Accounts() AccountSet - - // HasQuorum checks if submitted signers have reached quorum - HasQuorum(blockNumber uint64, signers map[types.Address]struct{}) bool - - // GetVotingPowers retrieves map: string(address) -> vp - GetVotingPowers() map[string]*big.Int -} - -var _ ValidatorSet = (*validatorSet)(nil) - -type validatorSet struct { - // validators represents current list of validators - validators AccountSet - - // votingPowerMap represents voting powers per validator address - votingPowerMap map[types.Address]*big.Int - - // totalVotingPower is sum of active validator set - totalVotingPower *big.Int - - // logger instance - logger hclog.Logger -} - -// NewValidatorSet creates a new validator set. -func NewValidatorSet(valz AccountSet, logger hclog.Logger) *validatorSet { - votingPowerMap := make(map[types.Address]*big.Int, len(valz)) - for _, val := range valz { - votingPowerMap[val.Address] = val.VotingPower - } - - return &validatorSet{ - validators: valz, - votingPowerMap: votingPowerMap, - totalVotingPower: valz.GetTotalVotingPower(), - logger: logger.Named("validator_set"), - } -} - -// HasQuorum determines if there is quorum of enough signers reached, -// based on its voting power and quorum size -func (vs validatorSet) HasQuorum(blockNumber uint64, signers map[types.Address]struct{}) bool { - aggregateVotingPower := big.NewInt(0) - - for address := range signers { - if votingPower := vs.votingPowerMap[address]; votingPower != nil { - _ = aggregateVotingPower.Add(aggregateVotingPower, votingPower) - } - } - - quorumSize := getQuorumSize(blockNumber, vs.totalVotingPower) - hasQuorum := aggregateVotingPower.Cmp(quorumSize) >= 0 - - vs.logger.Debug("HasQuorum", - "signers", len(signers), - "signers voting power", aggregateVotingPower, - "hasQuorum", hasQuorum) - - return hasQuorum -} - -func (vs validatorSet) GetVotingPowers() map[string]*big.Int { - result := make(map[string]*big.Int, vs.Len()) - - for address, vp := range vs.votingPowerMap { - result[types.AddressToString(address)] = vp - } - - return result -} - -func (vs validatorSet) Accounts() AccountSet { - return vs.validators -} - -func (vs validatorSet) Includes(address types.Address) bool { - return vs.validators.ContainsAddress(address) -} - -func (vs validatorSet) Len() int { - return vs.validators.Len() -} - -func (vs validatorSet) TotalVotingPower() big.Int { - return *vs.totalVotingPower -} - -// getQuorumSize calculates quorum size as 2/3 super-majority of provided total voting power -func getQuorumSize(blockNumber uint64, totalVotingPower *big.Int) *big.Int { - quorum := new(big.Int) - quorum.Mul(totalVotingPower, big.NewInt(2)) - - if forkmanager.GetInstance().IsForkEnabled(chain.QuorumCalcAlignment, blockNumber) { - // this will floor the 2 * totalVotingPower / 3 and add one to it - return quorum.Div(quorum, big.NewInt(3)).Add(quorum, big.NewInt(1)) - } - - return common.BigIntDivCeil(quorum, big.NewInt(3)) -} diff --git a/consensus/polybft/validator/validator_set_delta.go b/consensus/polybft/validator/validator_set_delta.go deleted file mode 100644 index 2c48871..0000000 --- a/consensus/polybft/validator/validator_set_delta.go +++ /dev/null @@ -1,144 +0,0 @@ -package validator - -import ( - "bytes" - "fmt" - - "github.com/umbracle/fastrlp" - "github.com/xgr-network/xgr-node/consensus/polybft/bitmap" -) - -// ValidatorSetDelta holds information about added and removed validators compared to the previous epoch -type ValidatorSetDelta struct { - // Added is the slice of added validators - Added AccountSet - // Updated is the slice of updated valiadtors - Updated AccountSet - // Removed is a bitmap of the validators removed from the set - Removed bitmap.Bitmap -} - -// Equals checks validator set delta equality -func (d *ValidatorSetDelta) Equals(other *ValidatorSetDelta) bool { - if other == nil { - return false - } - - return d.Added.Equals(other.Added) && - d.Updated.Equals(other.Updated) && - bytes.Equal(d.Removed, other.Removed) -} - -// MarshalRLPWith marshals ValidatorSetDelta to RLP format -func (d *ValidatorSetDelta) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { - vv := ar.NewArray() - addedValidatorsRaw := ar.NewArray() - updatedValidatorsRaw := ar.NewArray() - - for _, validatorAccount := range d.Added { - addedValidatorsRaw.Set(validatorAccount.MarshalRLPWith(ar)) - } - - for _, validatorAccount := range d.Updated { - updatedValidatorsRaw.Set(validatorAccount.MarshalRLPWith(ar)) - } - - vv.Set(addedValidatorsRaw) // added - vv.Set(updatedValidatorsRaw) // updated - vv.Set(ar.NewCopyBytes(d.Removed)) // removed - - return vv -} - -// UnmarshalRLPWith unmarshals ValidatorSetDelta from RLP format -func (d *ValidatorSetDelta) UnmarshalRLPWith(v *fastrlp.Value) error { - elems, err := v.GetElems() - if err != nil { - return err - } - - if len(elems) == 0 { - return nil - } else if num := len(elems); num != 3 { - return fmt.Errorf("incorrect elements count to decode validator set delta, expected 3 but found %d", num) - } - - // Validators (added) - { - validatorsRaw, err := elems[0].GetElems() - if err != nil { - return fmt.Errorf("array expected for added validators") - } - - d.Added, err = unmarshalValidators(validatorsRaw) - if err != nil { - return err - } - } - - // Validators (updated) - { - validatorsRaw, err := elems[1].GetElems() - if err != nil { - return fmt.Errorf("array expected for updated validators") - } - - d.Updated, err = unmarshalValidators(validatorsRaw) - if err != nil { - return err - } - } - - // Bitmap (removed) - { - dst, err := elems[2].GetBytes(nil) - if err != nil { - return err - } - - d.Removed = bitmap.Bitmap(dst) - } - - return nil -} - -// unmarshalValidators unmarshals RLP encoded validators and returns AccountSet instance -func unmarshalValidators(validatorsRaw []*fastrlp.Value) (AccountSet, error) { - if len(validatorsRaw) == 0 { - return nil, nil - } - - validators := make(AccountSet, len(validatorsRaw)) - - for i, validatorRaw := range validatorsRaw { - acc := &ValidatorMetadata{} - if err := acc.UnmarshalRLPWith(validatorRaw); err != nil { - return nil, err - } - - validators[i] = acc - } - - return validators, nil -} - -// IsEmpty returns indication whether delta is empty (namely added, updated slices and removed bitmap are empty) -func (d *ValidatorSetDelta) IsEmpty() bool { - return len(d.Added) == 0 && - len(d.Updated) == 0 && - d.Removed.Len() == 0 -} - -// Copy creates deep copy of ValidatorSetDelta -func (d *ValidatorSetDelta) Copy() *ValidatorSetDelta { - added := d.Added.Copy() - removed := make([]byte, len(d.Removed)) - copy(removed, d.Removed) - - return &ValidatorSetDelta{Added: added, Removed: removed} -} - -// fmt.Stringer interface implementation -func (d *ValidatorSetDelta) String() string { - return fmt.Sprintf("Added: \n%v Removed: %v\n Updated: \n%v", d.Added, d.Removed, d.Updated) -} diff --git a/consensus/polybft/validator/validator_set_delta_test.go b/consensus/polybft/validator/validator_set_delta_test.go deleted file mode 100644 index d1c5aaa..0000000 --- a/consensus/polybft/validator/validator_set_delta_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package validator - -import ( - "crypto/rand" - "math/big" - "testing" - - "github.com/stretchr/testify/require" - "github.com/umbracle/fastrlp" - "github.com/xgr-network/xgr-node/bls" -) - -func TestExtra_CreateValidatorSetDelta_BlsDiffer(t *testing.T) { - t.Parallel() - - vals := NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"}) - oldValidatorSet := vals.GetPublicIdentities("A", "B", "C", "D") - - // change the public bls key of 'B' - newValidatorSet := vals.GetPublicIdentities("B", "E", "F") - privateKey, err := bls.GenerateBlsKey() - require.NoError(t, err) - - newValidatorSet[0].BlsKey = privateKey.PublicKey() - - _, err = CreateValidatorSetDelta(oldValidatorSet, newValidatorSet) - require.Error(t, err) -} - -func TestValidatorSetDelta_Copy(t *testing.T) { - t.Parallel() - - const ( - originalValidatorsCount = 10 - addedValidatorsCount = 2 - ) - - oldValidatorSet := NewTestValidators(t, originalValidatorsCount).GetPublicIdentities() - newValidatorSet := oldValidatorSet[:len(oldValidatorSet)-2] - originalDelta, err := CreateValidatorSetDelta(oldValidatorSet, newValidatorSet) - require.NoError(t, err) - require.NotNil(t, originalDelta) - require.Empty(t, originalDelta.Added) - - copiedDelta := originalDelta.Copy() - require.NotNil(t, copiedDelta) - require.NotSame(t, originalDelta, copiedDelta) - require.NotEqual(t, originalDelta, copiedDelta) - require.Empty(t, copiedDelta.Added) - require.Equal(t, copiedDelta.Removed.Len(), originalDelta.Removed.Len()) - - newValidators := NewTestValidators(t, addedValidatorsCount).GetPublicIdentities() - copiedDelta.Added = append(copiedDelta.Added, newValidators...) - require.Empty(t, originalDelta.Added) - require.Len(t, copiedDelta.Added, addedValidatorsCount) - require.Equal(t, copiedDelta.Removed.Len(), originalDelta.Removed.Len()) -} - -func TestValidatorSetDelta_UnmarshalRLPWith_NegativeCases(t *testing.T) { - t.Parallel() - - t.Run("Incorrect RLP value type provided", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - delta := &ValidatorSetDelta{} - require.ErrorContains(t, delta.UnmarshalRLPWith(ar.NewNull()), "value is not of type array") - }) - - t.Run("Empty RLP array provided", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - delta := &ValidatorSetDelta{} - require.NoError(t, delta.UnmarshalRLPWith(ar.NewArray())) - }) - - t.Run("Incorrect RLP array size", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - deltaMarshalled := ar.NewArray() - deltaMarshalled.Set(ar.NewBytes([]byte{0x59})) - deltaMarshalled.Set(ar.NewBytes([]byte{0x33})) - deltaMarshalled.Set(ar.NewBytes([]byte{0x26})) - deltaMarshalled.Set(ar.NewBytes([]byte{0x74})) - delta := &ValidatorSetDelta{} - require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "incorrect elements count to decode validator set delta, expected 3 but found 4") - }) - - t.Run("Incorrect RLP value type for Added field", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - deltaMarshalled := ar.NewArray() - deltaMarshalled.Set(ar.NewBytes([]byte{0x59})) - deltaMarshalled.Set(ar.NewBytes([]byte{0x33})) - deltaMarshalled.Set(ar.NewBytes([]byte{0x27})) - delta := &ValidatorSetDelta{} - require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "array expected for added validators") - }) - - t.Run("Incorrect RLP value type for ValidatorMetadata in Added field", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - deltaMarshalled := ar.NewArray() - addedArray := ar.NewArray() - addedArray.Set(ar.NewNull()) - deltaMarshalled.Set(addedArray) - deltaMarshalled.Set(ar.NewNullArray()) - deltaMarshalled.Set(ar.NewNull()) - delta := &ValidatorSetDelta{} - require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "value is not of type array") - }) - - t.Run("Incorrect RLP value type for Removed field", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - deltaMarshalled := ar.NewArray() - addedValidators := NewTestValidators(t, 3).GetPublicIdentities() - addedArray := ar.NewArray() - updatedArray := ar.NewArray() - for _, validator := range addedValidators { - addedArray.Set(validator.MarshalRLPWith(ar)) - } - for _, validator := range addedValidators { - votingPower, err := rand.Int(rand.Reader, big.NewInt(100)) - require.NoError(t, err) - - validator.VotingPower = new(big.Int).Set(votingPower) - updatedArray.Set(validator.MarshalRLPWith(ar)) - } - deltaMarshalled.Set(addedArray) - deltaMarshalled.Set(updatedArray) - deltaMarshalled.Set(ar.NewNull()) - delta := &ValidatorSetDelta{} - require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "value is not of type bytes") - }) - - t.Run("Incorrect RLP value type for Updated field", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - deltaMarshalled := ar.NewArray() - deltaMarshalled.Set(ar.NewArray()) - deltaMarshalled.Set(ar.NewBytes([]byte{0x33})) - deltaMarshalled.Set(ar.NewNull()) - delta := &ValidatorSetDelta{} - require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "array expected for updated validators") - }) - - t.Run("Incorrect RLP value type for ValidatorMetadata in Updated field", func(t *testing.T) { - t.Parallel() - - ar := &fastrlp.Arena{} - deltaMarshalled := ar.NewArray() - updatedArray := ar.NewArray() - updatedArray.Set(ar.NewNull()) - deltaMarshalled.Set(ar.NewArray()) - deltaMarshalled.Set(updatedArray) - deltaMarshalled.Set(ar.NewNull()) - delta := &ValidatorSetDelta{} - require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "value is not of type array") - }) -} - -func TestExtra_CreateValidatorSetDelta_Cases(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - oldSet []string - newSet []string - added []string - updated []string - removed []uint64 - }{ - { - "Simple", - []string{"A", "B", "C", "E", "F"}, - []string{"B", "E", "H"}, - []string{"H"}, - []string{"B", "E"}, - []uint64{0, 2, 4}, - }, - } - - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - - vals := NewTestValidatorsWithAliases(t, []string{}) - - for _, name := range c.oldSet { - vals.Create(t, name, 1) - } - for _, name := range c.newSet { - vals.Create(t, name, 1) - } - - oldValidatorSet := vals.GetPublicIdentities(c.oldSet...) - // update voting power to random value - maxVotingPower := big.NewInt(100) - for _, name := range c.updated { - v := vals.GetValidator(name) - vp, err := rand.Int(rand.Reader, maxVotingPower) - require.NoError(t, err) - // make sure generated voting power is different than the original one - v.VotingPower += vp.Uint64() + 1 - } - newValidatorSet := vals.GetPublicIdentities(c.newSet...) - - delta, err := CreateValidatorSetDelta(oldValidatorSet, newValidatorSet) - require.NoError(t, err) - - // added validators - require.Len(t, delta.Added, len(c.added)) - for i, name := range c.added { - require.Equal(t, delta.Added[i].Address, vals.GetValidator(name).Address()) - } - - // removed validators - for _, i := range c.removed { - require.True(t, delta.Removed.IsSet(i)) - } - - // updated validators - require.Len(t, delta.Updated, len(c.updated)) - for i, name := range c.updated { - require.Equal(t, delta.Updated[i].Address, vals.GetValidator(name).Address()) - } - }) - } -} diff --git a/consensus/polybft/validator/validator_set_test.go b/consensus/polybft/validator/validator_set_test.go deleted file mode 100644 index 6dad5bc..0000000 --- a/consensus/polybft/validator/validator_set_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package validator - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/types" -) - -func TestValidatorSet_HasQuorum(t *testing.T) { - t.Parallel() - - // enough signers for quorum (2/3 super-majority of validators are signers) - validators := NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F", "G"}) - vs := validators.ToValidatorSet() - - signers := make(map[types.Address]struct{}) - - validators.IterAcct([]string{"A", "B", "C", "D", "E"}, func(v *TestValidator) { - signers[v.Address()] = struct{}{} - }) - - require.True(t, vs.HasQuorum(1, signers)) - - // not enough signers for quorum (less than 2/3 super-majority of validators are signers) - signers = make(map[types.Address]struct{}) - - validators.IterAcct([]string{"A", "B", "C", "D"}, func(v *TestValidator) { - signers[v.Address()] = struct{}{} - }) - require.False(t, vs.HasQuorum(1, signers)) -} - -func TestValidatorSet_getQuorumSize(t *testing.T) { - t.Parallel() - - cases := []struct { - totalVotingPower int64 - expectedQuorumSize int64 - }{ - {10, 7}, - {12, 8}, - {13, 9}, - {50, 34}, - {100, 67}, - } - - for _, c := range cases { - quorumSize := getQuorumSize(1, big.NewInt(c.totalVotingPower)) - require.Equal(t, c.expectedQuorumSize, quorumSize.Int64()) - } -} diff --git a/consensus/polybft/validators_snapshot.go b/consensus/polybft/validators_snapshot.go deleted file mode 100644 index 2f7735d..0000000 --- a/consensus/polybft/validators_snapshot.go +++ /dev/null @@ -1,358 +0,0 @@ -package polybft - -import ( - "errors" - "fmt" - "sync" - - "github.com/hashicorp/go-hclog" - "github.com/xgr-network/xgr-node/blockchain" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/types" - bolt "go.etcd.io/bbolt" -) - -type validatorSnapshot struct { - Epoch uint64 `json:"epoch"` - EpochEndingBlock uint64 `json:"epochEndingBlock"` - Snapshot validator.AccountSet `json:"snapshot"` -} - -func (vs *validatorSnapshot) copy() *validatorSnapshot { - copiedAccountSet := vs.Snapshot.Copy() - - return &validatorSnapshot{ - Epoch: vs.Epoch, - EpochEndingBlock: vs.EpochEndingBlock, - Snapshot: copiedAccountSet, - } -} - -type validatorsSnapshotCache struct { - snapshots map[uint64]*validatorSnapshot - state *State - blockchain blockchainBackend - lock sync.Mutex - logger hclog.Logger -} - -// newValidatorsSnapshotCache initializes a new instance of validatorsSnapshotCache -func newValidatorsSnapshotCache( - logger hclog.Logger, state *State, blockchain blockchainBackend, -) *validatorsSnapshotCache { - return &validatorsSnapshotCache{ - snapshots: map[uint64]*validatorSnapshot{}, - state: state, - blockchain: blockchain, - logger: logger.Named("validators_snapshot"), - } -} - -// GetSnapshot tries to retrieve the most recent cached snapshot (if any) and -// applies pending validator set deltas to it. -// Otherwise, it builds a snapshot from scratch and applies pending validator set deltas. -func (v *validatorsSnapshotCache) GetSnapshot( - blockNumber uint64, parents []*types.Header, dbTx *bolt.Tx) (validator.AccountSet, error) { - tx := dbTx - isPassedTxNil := dbTx == nil - - if isPassedTxNil { - // if no tx is passed, we need to open one, because, - // GetSnapshot is called from multiple routines (new sequence, fsm, OnBlockInserted, etc) - // and what can happen is that one routine takes its lock, and without a global tx, - // a deadlock can happen between that routine and OnBlockInserted, because it to - // is calling GetSnapshot, but it also opens a global db tx, resulting in a deadlock - // between one routine waiting to get a transaction on db, and OnBlockInserted waiting - // to get the validatorsSnapshotCache lock - t, err := v.state.beginDBTransaction(true) - if err != nil { - return nil, err - } - - tx = t - defer tx.Rollback() //nolint:errcheck - } - - v.lock.Lock() - defer func() { - v.lock.Unlock() - }() - - _, extra, err := getBlockData(blockNumber, v.blockchain) - if err != nil { - return nil, err - } - - isEpochEndingBlock, err := isEpochEndingBlock(blockNumber, extra, v.blockchain) - if err != nil && !errors.Is(err, blockchain.ErrNoBlock) { - // if there is no block after given block, we assume its not epoch ending block - // but, it's a regular use case, and we should not stop the snapshot calculation - // because there are cases we need the snapshot for the latest block in chain - return nil, err - } - - epochToGetSnapshot := extra.Checkpoint.EpochNumber - if !isEpochEndingBlock { - epochToGetSnapshot-- - } - - v.logger.Trace("Retrieving snapshot started...", "Block", blockNumber, "Epoch", epochToGetSnapshot) - - latestValidatorSnapshot, err := v.getLastCachedSnapshot(epochToGetSnapshot, tx) - if err != nil { - return nil, err - } - - if latestValidatorSnapshot != nil && latestValidatorSnapshot.Epoch == epochToGetSnapshot { - // we have snapshot for required block (epoch) in cache - return latestValidatorSnapshot.Snapshot, nil - } - - if latestValidatorSnapshot == nil { - // Haven't managed to retrieve snapshot for any epoch from the cache. - // Build snapshot from the scratch, by applying delta from the genesis block. - genesisBlockSnapshot, err := v.computeSnapshot(nil, 0, parents) - if err != nil { - return nil, fmt.Errorf("failed to compute snapshot for epoch 0: %w", err) - } - - err = v.storeSnapshot(genesisBlockSnapshot, tx) - if err != nil { - return nil, fmt.Errorf("failed to store validators snapshot for epoch 0: %w", err) - } - - latestValidatorSnapshot = genesisBlockSnapshot - - v.logger.Trace("Built validators snapshot for genesis block") - } - - deltasCount := 0 - - v.logger.Trace("Applying deltas started...", "LatestSnapshotEpoch", latestValidatorSnapshot.Epoch) - - // Create the snapshot for the desired block (epoch) by incrementally applying deltas to the latest stored snapshot - for latestValidatorSnapshot.Epoch < epochToGetSnapshot { - nextEpochEndBlockNumber, err := v.getNextEpochEndingBlock(latestValidatorSnapshot.EpochEndingBlock) - if err != nil { - return nil, fmt.Errorf("failed to get the epoch ending block for epoch: %d. Error: %w", - latestValidatorSnapshot.Epoch+1, err) - } - - v.logger.Trace("Applying delta", "epochEndBlock", nextEpochEndBlockNumber) - - intermediateSnapshot, err := v.computeSnapshot(latestValidatorSnapshot, nextEpochEndBlockNumber, parents) - if err != nil { - return nil, fmt.Errorf("failed to compute snapshot for epoch %d: %w", latestValidatorSnapshot.Epoch+1, err) - } - - latestValidatorSnapshot = intermediateSnapshot - if err = v.storeSnapshot(latestValidatorSnapshot, tx); err != nil { - return nil, fmt.Errorf("failed to store validators snapshot for epoch %d: %w", latestValidatorSnapshot.Epoch, err) - } - - deltasCount++ - } - - v.logger.Trace( - fmt.Sprintf("Applied %d delta(s) to the validators snapshot", deltasCount), - "Epoch", latestValidatorSnapshot.Epoch, - ) - - if err := v.cleanup(tx); err != nil { - // error on cleanup should not block or fail any action - v.logger.Error("could not clean validator snapshots from cache and db", "err", err) - } - - if isPassedTxNil { - if err := tx.Commit(); err != nil { - return nil, err - } - } - - return latestValidatorSnapshot.Snapshot, nil -} - -// computeSnapshot gets desired block header by block number, extracts its extra and applies given delta to the snapshot -func (v *validatorsSnapshotCache) computeSnapshot( - existingSnapshot *validatorSnapshot, - nextEpochEndBlockNumber uint64, - parents []*types.Header, -) (*validatorSnapshot, error) { - var header *types.Header - - v.logger.Trace("Compute snapshot started...", "BlockNumber", nextEpochEndBlockNumber) - - if len(parents) > 0 { - for i := len(parents) - 1; i >= 0; i-- { - parentHeader := parents[i] - if parentHeader.Number == nextEpochEndBlockNumber { - v.logger.Trace("Compute snapshot. Found header in parents", "Header", parentHeader.Number) - header = parentHeader - - break - } - } - } - - if header == nil { - var ok bool - - header, ok = v.blockchain.GetHeaderByNumber(nextEpochEndBlockNumber) - if !ok { - return nil, fmt.Errorf("unknown block. Block number=%v", nextEpochEndBlockNumber) - } - } - - extra, err := GetIbftExtra(header.ExtraData) - if err != nil { - return nil, fmt.Errorf("failed to decode extra from the block#%d: %w", header.Number, err) - } - - var ( - snapshot validator.AccountSet - snapshotEpoch uint64 - ) - - if existingSnapshot == nil { - snapshot = validator.AccountSet{} - } else { - snapshot = existingSnapshot.Snapshot - snapshotEpoch = existingSnapshot.Epoch + 1 - } - - snapshot, err = snapshot.ApplyDelta(extra.Validators) - if err != nil { - return nil, fmt.Errorf("failed to apply delta to the validators snapshot, block#%d: %w", header.Number, err) - } - - if len(snapshot) == 0 { - return nil, fmt.Errorf("validator snapshot is empty for block: %d", header.Number) - } - - v.logger.Debug("Computed snapshot", - "blockNumber", nextEpochEndBlockNumber, - "snapshot", snapshot.String(), - "delta", extra.Validators) - - return &validatorSnapshot{ - Epoch: snapshotEpoch, - EpochEndingBlock: nextEpochEndBlockNumber, - Snapshot: snapshot, - }, nil -} - -// storeSnapshot stores given snapshot to the in-memory cache and database -func (v *validatorsSnapshotCache) storeSnapshot(snapshot *validatorSnapshot, dbTx *bolt.Tx) error { - copySnap := snapshot.copy() - v.snapshots[copySnap.Epoch] = copySnap - - if err := v.state.EpochStore.insertValidatorSnapshot(copySnap, dbTx); err != nil { - return fmt.Errorf("failed to insert validator snapshot for epoch %d to the database: %w", copySnap.Epoch, err) - } - - v.logger.Trace("Store snapshot", "Snapshots", v.snapshots) - - return nil -} - -// Cleanup cleans the validators cache in memory and db -func (v *validatorsSnapshotCache) cleanup(dbTx *bolt.Tx) error { - if len(v.snapshots) >= validatorSnapshotLimit { - latestEpoch := uint64(0) - - for e := range v.snapshots { - if e > latestEpoch { - latestEpoch = e - } - } - - startEpoch := latestEpoch - cache := make(map[uint64]*validatorSnapshot, numberOfSnapshotsToLeaveInMemory) - - for i := 0; i < numberOfSnapshotsToLeaveInMemory; i++ { - if snapshot, exists := v.snapshots[startEpoch]; exists { - cache[startEpoch] = snapshot - } - - startEpoch-- - } - - v.snapshots = cache - - return v.state.EpochStore.cleanValidatorSnapshotsFromDB(latestEpoch, dbTx) - } - - return nil -} - -// getLastCachedSnapshot gets the latest snapshot cached -// If it doesn't have snapshot cached for desired epoch, it will return the latest one it has -func (v *validatorsSnapshotCache) getLastCachedSnapshot(currentEpoch uint64, - dbTx *bolt.Tx) (*validatorSnapshot, error) { - cachedSnapshot := v.snapshots[currentEpoch] - if cachedSnapshot != nil { - return cachedSnapshot, nil - } - - // if we do not have a snapshot in memory for given epoch, we will get the latest one we have - for ; currentEpoch >= 0; currentEpoch-- { - cachedSnapshot = v.snapshots[currentEpoch] - if cachedSnapshot != nil { - v.logger.Trace("Found snapshot in memory cache", "Epoch", currentEpoch) - - break - } - - if currentEpoch == 0 { // prevent uint64 underflow - break - } - } - - dbSnapshot, err := v.state.EpochStore.getLastSnapshot(dbTx) - if err != nil { - return nil, err - } - - if dbSnapshot != nil { - // if we do not have any snapshot in memory, or db snapshot is newer than the one in memory - // return the one from db - if cachedSnapshot == nil || dbSnapshot.Epoch > cachedSnapshot.Epoch { - cachedSnapshot = dbSnapshot - // save it in cache as well, since it doesn't exist - v.snapshots[dbSnapshot.Epoch] = dbSnapshot.copy() - } - } - - return cachedSnapshot, nil -} - -// getNextEpochEndingBlock gets the epoch ending block of a newer epoch -// It start checking the blocks from the provided epoch ending block of the previous epoch -func (v *validatorsSnapshotCache) getNextEpochEndingBlock(latestEpochEndingBlock uint64) (uint64, error) { - blockNumber := latestEpochEndingBlock + 1 // get next block - - _, extra, err := getBlockData(blockNumber, v.blockchain) - if err != nil { - return 0, err - } - - startEpoch := extra.Checkpoint.EpochNumber - epoch := startEpoch - - for startEpoch == epoch { - blockNumber++ - - _, extra, err = getBlockData(blockNumber, v.blockchain) - if err != nil { - if errors.Is(err, blockchain.ErrNoBlock) { - return blockNumber - 1, nil - } - - return 0, err - } - - epoch = extra.Checkpoint.EpochNumber - } - - return blockNumber - 1, nil -} diff --git a/consensus/polybft/validators_snapshot_test.go b/consensus/polybft/validators_snapshot_test.go deleted file mode 100644 index eb1aec0..0000000 --- a/consensus/polybft/validators_snapshot_test.go +++ /dev/null @@ -1,316 +0,0 @@ -package polybft - -import ( - "fmt" - "testing" - - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/consensus/polybft/validator" - "github.com/xgr-network/xgr-node/types" -) - -func TestValidatorsSnapshotCache_GetSnapshot_Build(t *testing.T) { - t.Parallel() - assertions := require.New(t) - - const ( - totalValidators = 10 - validatorSetSize = 5 - epochSize = uint64(10) - ) - - allValidators := validator.NewTestValidators(t, totalValidators).GetPublicIdentities() - - var oddValidators, evenValidators validator.AccountSet - - for i := 0; i < totalValidators; i++ { - if i%2 == 0 { - evenValidators = append(evenValidators, allValidators[i]) - } else { - oddValidators = append(oddValidators, allValidators[i]) - } - } - - headersMap := &testHeadersMap{headersByNumber: make(map[uint64]*types.Header)} - - createHeaders(t, headersMap, 0, epochSize-1, 1, nil, allValidators[:validatorSetSize]) - createHeaders(t, headersMap, epochSize, 2*epochSize-1, 2, allValidators[:validatorSetSize], allValidators[validatorSetSize:]) - createHeaders(t, headersMap, 2*epochSize, 3*epochSize-1, 3, allValidators[validatorSetSize:], oddValidators) - createHeaders(t, headersMap, 3*epochSize, 4*epochSize-1, 4, oddValidators, evenValidators) - - var cases = []struct { - blockNumber uint64 - expectedSnapshot validator.AccountSet - validatorsOverlap bool - parents []*types.Header - }{ - {4, allValidators[:validatorSetSize], false, nil}, - {1 * epochSize, allValidators[validatorSetSize:], false, nil}, - {13, allValidators[validatorSetSize:], false, nil}, - {27, oddValidators, true, nil}, - {36, evenValidators, true, nil}, - {4, allValidators[:validatorSetSize], false, headersMap.getHeaders()}, - {13, allValidators[validatorSetSize:], false, headersMap.getHeaders()}, - {27, oddValidators, true, headersMap.getHeaders()}, - {36, evenValidators, true, headersMap.getHeaders()}, - } - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) - - testValidatorsCache := &testValidatorsCache{ - validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), - } - - for _, c := range cases { - snapshot, err := testValidatorsCache.GetSnapshot(c.blockNumber, c.parents, nil) - - assertions.NoError(err) - assertions.Len(snapshot, c.expectedSnapshot.Len()) - - if c.validatorsOverlap { - for _, validator := range c.expectedSnapshot { - // Order of validators is not preserved, because there are overlapping between validators set. - // In that case, at the beginning of the set are the ones preserved from the previous validator set. - // Newly validators are added to the end after the one from previous validator set. - assertions.True(snapshot.ContainsAddress(validator.Address)) - } - } else { - assertions.Equal(c.expectedSnapshot, snapshot) - } - - assertions.NoError(testValidatorsCache.cleanValidatorsCache()) - - if c.parents != nil { - blockchainMock.AssertNotCalled(t, "GetHeaderByNumber") - } - } -} - -func TestValidatorsSnapshotCache_GetSnapshot_FetchFromCache(t *testing.T) { - t.Parallel() - require := require.New(t) - - const ( - totalValidators = 10 - validatorSetSize = 5 - ) - - allValidators := validator.NewTestValidators(t, totalValidators).GetPublicIdentities() - epochOneValidators := validator.AccountSet{allValidators[0], allValidators[len(allValidators)-1]} - epochTwoValidators := allValidators[1 : len(allValidators)-2] - - headersMap := &testHeadersMap{headersByNumber: make(map[uint64]*types.Header)} - createHeaders(t, headersMap, 0, 9, 1, nil, allValidators) - createHeaders(t, headersMap, 10, 19, 2, allValidators, epochOneValidators) - createHeaders(t, headersMap, 20, 29, 3, epochOneValidators, epochTwoValidators) - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) - - testValidatorsCache := &testValidatorsCache{ - validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), - } - - require.NoError(testValidatorsCache.storeSnapshot(&validatorSnapshot{1, 10, epochOneValidators}, nil)) - require.NoError(testValidatorsCache.storeSnapshot(&validatorSnapshot{2, 20, epochTwoValidators}, nil)) - - // Fetch snapshot from in memory cache - snapshot, err := testValidatorsCache.GetSnapshot(10, nil, nil) - require.NoError(err) - require.Equal(epochOneValidators, snapshot) - - // Invalidate in memory cache - testValidatorsCache.snapshots = map[uint64]*validatorSnapshot{} - require.NoError(testValidatorsCache.state.EpochStore.removeAllValidatorSnapshots()) - // Fetch snapshot from database - snapshot, err = testValidatorsCache.GetSnapshot(10, nil, nil) - require.NoError(err) - require.Equal(epochOneValidators, snapshot) - - snapshot, err = testValidatorsCache.GetSnapshot(20, nil, nil) - require.NoError(err) - require.Equal(epochTwoValidators, snapshot) -} - -func TestValidatorsSnapshotCache_Cleanup(t *testing.T) { - t.Parallel() - require := require.New(t) - - blockchainMock := new(blockchainMock) - cache := &testValidatorsCache{ - validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), - } - snapshot := validator.NewTestValidators(t, 3).GetPublicIdentities() - maxEpoch := uint64(0) - - for i := uint64(0); i < validatorSnapshotLimit; i++ { - require.NoError(cache.storeSnapshot(&validatorSnapshot{i, i * 10, snapshot}, nil)) - maxEpoch++ - } - - require.NoError(cache.cleanup(nil)) - - // assertions for remaining snapshots in the in memory cache - require.Len(cache.snapshots, numberOfSnapshotsToLeaveInMemory) - - currentEpoch := maxEpoch - - for i := 0; i < numberOfSnapshotsToLeaveInMemory; i++ { - currentEpoch-- - currentSnapshot, snapExists := cache.snapshots[currentEpoch] - require.True(snapExists, fmt.Sprintf("failed to fetch in memory snapshot for epoch %d", currentEpoch)) - require.Equal(snapshot, currentSnapshot.Snapshot, fmt.Sprintf("snapshots for epoch %d are not equal", currentEpoch)) - } - - stats, err := cache.state.EpochStore.validatorSnapshotsDBStats() - require.NoError(err) - - // assertions for remaining snapshots in database - require.Equal(stats.KeyN, numberOfSnapshotsToLeaveInDB) - - currentEpoch = maxEpoch - - for i := 0; i < numberOfSnapshotsToLeaveInDB; i++ { - currentEpoch-- - currentSnapshot, err := cache.state.EpochStore.getValidatorSnapshot(currentEpoch) - require.NoError(err, fmt.Sprintf("failed to fetch database snapshot for epoch %d", currentEpoch)) - require.Equal(snapshot, currentSnapshot.Snapshot, fmt.Sprintf("snapshots for epoch %d are not equal", currentEpoch)) - } -} - -func TestValidatorsSnapshotCache_ComputeSnapshot_UnknownBlock(t *testing.T) { - t.Parallel() - assertions := assert.New(t) - - const ( - totalValidators = 15 - validatorSetSize = totalValidators / 2 - epochSize = uint64(10) - ) - - allValidators := validator.NewTestValidators(t, totalValidators).GetPublicIdentities() - headersMap := &testHeadersMap{} - headersMap.addHeader(createValidatorDeltaHeader(t, 0, 0, nil, allValidators[:validatorSetSize])) - headersMap.addHeader(createValidatorDeltaHeader(t, 1*epochSize, 1, allValidators[:validatorSetSize], allValidators[validatorSetSize:])) - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) - - testValidatorsCache := &testValidatorsCache{ - validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), - } - - snapshot, err := testValidatorsCache.computeSnapshot(nil, 5*epochSize, nil) - assertions.Nil(snapshot) - assertions.ErrorContains(err, "unknown block. Block number=50") -} - -func TestValidatorsSnapshotCache_ComputeSnapshot_IncorrectExtra(t *testing.T) { - t.Parallel() - assertions := assert.New(t) - - const ( - totalValidators = 6 - validatorSetSize = totalValidators / 2 - epochSize = uint64(10) - ) - - allValidators := validator.NewTestValidators(t, totalValidators).GetPublicIdentities() - headersMap := &testHeadersMap{} - invalidHeader := createValidatorDeltaHeader(t, 1*epochSize, 1, allValidators[:validatorSetSize], allValidators[validatorSetSize:]) - invalidHeader.ExtraData = []byte{0x2, 0x7} - headersMap.addHeader(invalidHeader) - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) - - testValidatorsCache := &testValidatorsCache{ - validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), - } - - snapshot, err := testValidatorsCache.computeSnapshot(nil, 1*epochSize, nil) - assertions.Nil(snapshot) - assertions.ErrorContains(err, "failed to decode extra from the block#10: wrong extra size: 2") -} - -func TestValidatorsSnapshotCache_ComputeSnapshot_ApplyDeltaFail(t *testing.T) { - t.Parallel() - assertions := assert.New(t) - - const ( - totalValidators = 6 - validatorSetSize = totalValidators / 2 - epochSize = uint64(10) - ) - - allValidators := validator.NewTestValidators(t, totalValidators).GetPublicIdentities() - headersMap := &testHeadersMap{} - headersMap.addHeader(createValidatorDeltaHeader(t, 0, 0, nil, allValidators[:validatorSetSize])) - headersMap.addHeader(createValidatorDeltaHeader(t, 1*epochSize, 1, nil, allValidators[:validatorSetSize])) - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) - - testValidatorsCache := &testValidatorsCache{ - validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), - } - - snapshot, err := testValidatorsCache.computeSnapshot(&validatorSnapshot{0, 0, allValidators}, 1*epochSize, nil) - assertions.Nil(snapshot) - assertions.ErrorContains(err, "failed to apply delta to the validators snapshot, block#10") -} - -func TestValidatorsSnapshotCache_Empty(t *testing.T) { - t.Parallel() - - headersMap := &testHeadersMap{headersByNumber: make(map[uint64]*types.Header)} - - createHeaders(t, headersMap, 0, 1, 1, nil, nil) - - blockchainMock := new(blockchainMock) - blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) - - testValidatorsCache := &testValidatorsCache{ - validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), - } - - _, err := testValidatorsCache.GetSnapshot(1, nil, nil) - assert.ErrorContains(t, err, "validator snapshot is empty for block") -} - -func createHeaders(t *testing.T, headersMap *testHeadersMap, - fromBlock, toBlock, epoch uint64, oldValidators, newValidators validator.AccountSet) { - t.Helper() - - headersMap.addHeader(createValidatorDeltaHeader(t, fromBlock, epoch-1, oldValidators, newValidators)) - - for i := fromBlock + 1; i <= toBlock; i++ { - headersMap.addHeader(createValidatorDeltaHeader(t, i, epoch, nil, nil)) - } -} - -func createValidatorDeltaHeader(t *testing.T, blockNumber, epoch uint64, oldValidatorSet, newValidatorSet validator.AccountSet) *types.Header { - t.Helper() - - delta, _ := validator.CreateValidatorSetDelta(oldValidatorSet, newValidatorSet) - extra := &Extra{Validators: delta, Checkpoint: &CheckpointData{EpochNumber: epoch}} - - return &types.Header{ - Number: blockNumber, - ExtraData: extra.MarshalRLPTo(nil), - } -} - -type testValidatorsCache struct { - *validatorsSnapshotCache -} - -func (c *testValidatorsCache) cleanValidatorsCache() error { - c.snapshots = make(map[uint64]*validatorSnapshot) - - return c.state.EpochStore.removeAllValidatorSnapshots() -} diff --git a/consensus/polybft/wallet/account.go b/consensus/polybft/wallet/account.go deleted file mode 100644 index dcd7c31..0000000 --- a/consensus/polybft/wallet/account.go +++ /dev/null @@ -1,123 +0,0 @@ -package wallet - -import ( - "crypto/ecdsa" - "encoding/hex" - "fmt" - - "github.com/umbracle/ethgo/wallet" - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/secrets" - "github.com/xgr-network/xgr-node/types" -) - -// Account is an account for key signatures -type Account struct { - Ecdsa *wallet.Key - Bls *bls.PrivateKey -} - -// GenerateAccount generates a new random account -func GenerateAccount() (*Account, error) { - key, err := wallet.GenerateKey() - if err != nil { - return nil, fmt.Errorf("cannot generate key. error: %w", err) - } - - blsKey, err := bls.GenerateBlsKey() - if err != nil { - return nil, fmt.Errorf("cannot generate bls key. error: %w", err) - } - - return &Account{ - Ecdsa: key, - Bls: blsKey, - }, nil -} - -// NewAccountFromSecret creates new account by using provided secretsManager -func NewAccountFromSecret(secretsManager secrets.SecretsManager) (*Account, error) { - ecdsaKey, err := GetEcdsaFromSecret(secretsManager) - if err != nil { - return nil, err - } - - blsKey, err := GetBlsFromSecret(secretsManager) - if err != nil { - return nil, err - } - - return &Account{Ecdsa: ecdsaKey, Bls: blsKey}, nil -} - -// GetEcdsaFromSecret retrieves validator(ECDSA) key by using provided secretsManager -func GetEcdsaFromSecret(secretsManager secrets.SecretsManager) (*wallet.Key, error) { - encodedKey, err := secretsManager.GetSecret(secrets.ValidatorKey) - if err != nil { - return nil, fmt.Errorf("failed to retrieve ecdsa key: %w", err) - } - - ecdsaRaw, err := hex.DecodeString(string(encodedKey)) - if err != nil { - return nil, fmt.Errorf("failed to retrieve ecdsa key: %w", err) - } - - key, err := wallet.NewWalletFromPrivKey(ecdsaRaw) - if err != nil { - return nil, fmt.Errorf("failed to retrieve ecdsa key: %w", err) - } - - return key, nil -} - -// GetBlsFromSecret retrieves BLS key by using provided secretsManager -func GetBlsFromSecret(secretsManager secrets.SecretsManager) (*bls.PrivateKey, error) { - encodedKey, err := secretsManager.GetSecret(secrets.ValidatorBLSKey) - if err != nil { - return nil, fmt.Errorf("failed to retrieve bls key: %w", err) - } - - blsKey, err := bls.UnmarshalPrivateKey(encodedKey) - if err != nil { - return nil, fmt.Errorf("failed to retrieve bls key: %w", err) - } - - return blsKey, nil -} - -// Save persists ECDSA and BLS private keys to the SecretsManager -func (a *Account) Save(secretsManager secrets.SecretsManager) (err error) { - var ( - ecdsaRaw []byte - blsRaw []byte - ) - - // get serialized ecdsa private key - if ecdsaRaw, err = a.Ecdsa.MarshallPrivateKey(); err != nil { - return err - } - - if err = secretsManager.SetSecret(secrets.ValidatorKey, []byte(hex.EncodeToString(ecdsaRaw))); err != nil { - return err - } - - // get serialized bls private key - if blsRaw, err = a.Bls.Marshal(); err != nil { - return err - } - - return secretsManager.SetSecret(secrets.ValidatorBLSKey, blsRaw) -} - -func (a *Account) GetEcdsaPrivateKey() (*ecdsa.PrivateKey, error) { - ecdsaRaw, err := a.Ecdsa.MarshallPrivateKey() - if err != nil { - return nil, err - } - - return wallet.ParsePrivateKey(ecdsaRaw) -} - -func (a Account) Address() types.Address { - return types.Address(a.Ecdsa.Address()) -} diff --git a/consensus/polybft/wallet/account_test.go b/consensus/polybft/wallet/account_test.go deleted file mode 100644 index 0cd416c..0000000 --- a/consensus/polybft/wallet/account_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package wallet - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xgr-network/xgr-node/secrets" -) - -func TestAccount(t *testing.T) { - t.Parallel() - - secretsManager := newSecretsManagerMock() - - key := generateTestAccount(t) - pubKeyMarshalled := key.Bls.PublicKey().Marshal() - privKeyMarshalled, err := key.Bls.Marshal() - require.NoError(t, err) - - require.NoError(t, key.Save(secretsManager)) - - key1, err := NewAccountFromSecret(secretsManager) - require.NoError(t, err) - - pubKeyMarshalled1 := key1.Bls.PublicKey().Marshal() - privKeyMarshalled1, err := key1.Bls.Marshal() - require.NoError(t, err) - - assert.Equal(t, key.Ecdsa.Address(), key1.Ecdsa.Address()) - assert.Equal(t, pubKeyMarshalled, pubKeyMarshalled1) - assert.Equal(t, privKeyMarshalled, privKeyMarshalled1) -} - -func newSecretsManagerMock() secrets.SecretsManager { - return &secretsManagerMock{cache: make(map[string][]byte)} -} - -func generateTestAccount(t *testing.T) *Account { - t.Helper() - - acc, err := GenerateAccount() - require.NoError(t, err) - - return acc -} - -type secretsManagerMock struct { - cache map[string][]byte -} - -func (sm *secretsManagerMock) Setup() error { - return nil -} - -func (sm *secretsManagerMock) GetSecret(name string) ([]byte, error) { - value, exists := sm.cache[name] - if !exists { - return nil, fmt.Errorf("secret does not exists for %s", name) - } - - return value, nil -} - -func (sm *secretsManagerMock) SetSecret(name string, value []byte) error { - sm.cache[name] = value - - return nil -} - -func (sm *secretsManagerMock) HasSecret(name string) bool { - _, exists := sm.cache[name] - - return exists -} - -func (sm *secretsManagerMock) RemoveSecret(name string) error { - delete(sm.cache, name) - - return nil -} diff --git a/consensus/polybft/wallet/key.go b/consensus/polybft/wallet/key.go deleted file mode 100644 index e574ae3..0000000 --- a/consensus/polybft/wallet/key.go +++ /dev/null @@ -1,87 +0,0 @@ -package wallet - -import ( - "fmt" - - "github.com/0xPolygon/go-ibft/messages/proto" - "github.com/umbracle/ethgo" - protobuf "google.golang.org/protobuf/proto" - - "github.com/xgr-network/xgr-node/consensus/polybft/signer" - "github.com/xgr-network/xgr-node/crypto" - "github.com/xgr-network/xgr-node/types" -) - -type Key struct { - raw *Account -} - -func NewKey(raw *Account) *Key { - return &Key{ - raw: raw, - } -} - -// String returns hex encoded ECDSA address -func (k *Key) String() string { - return k.raw.Ecdsa.Address().String() -} - -// Address returns ECDSA address -func (k *Key) Address() ethgo.Address { - return k.raw.Ecdsa.Address() -} - -// Sign signs the provided digest with BLS key -// Used only to sign transactions -func (k *Key) Sign(digest []byte) ([]byte, error) { - return k.SignWithDomain(digest, signer.DomainCommonSigning) -} - -// SignWithDomain signs the provided digest with BLS key and provided domain -func (k *Key) SignWithDomain(digest, domain []byte) ([]byte, error) { - signature, err := k.raw.Bls.Sign(digest, domain) - if err != nil { - return nil, err - } - - return signature.Marshal() -} - -// SignIBFTMessage signs the IBFT consensus message with ECDSA key -func (k *Key) SignIBFTMessage(msg *proto.Message) (*proto.Message, error) { - msgRaw, err := protobuf.Marshal(msg) - if err != nil { - return nil, fmt.Errorf("cannot marshal message: %w", err) - } - - if msg.Signature, err = k.raw.Ecdsa.Sign(crypto.Keccak256(msgRaw)); err != nil { - return nil, fmt.Errorf("cannot create message signature: %w", err) - } - - return msg, nil -} - -// RecoverAddressFromSignature calculates keccak256 hash of provided rawContent -// and recovers signer address from given signature and hash -func RecoverAddressFromSignature(sig, rawContent []byte) (types.Address, error) { - pub, err := crypto.RecoverPubkey(sig, crypto.Keccak256(rawContent)) - if err != nil { - return types.Address{}, fmt.Errorf("cannot recover address from signature: %w", err) - } - - return crypto.PubKeyToAddress(pub), nil -} - -// ECDSASigner implements ethgo.Key interface and it is used for signing using provided ECDSA key -type ECDSASigner struct { - *Key -} - -func NewEcdsaSigner(ecdsaKey *Key) *ECDSASigner { - return &ECDSASigner{Key: ecdsaKey} -} - -func (k *ECDSASigner) Sign(b []byte) ([]byte, error) { - return k.raw.Ecdsa.Sign(b) -} diff --git a/consensus/polybft/wallet/key_test.go b/consensus/polybft/wallet/key_test.go deleted file mode 100644 index 0647f93..0000000 --- a/consensus/polybft/wallet/key_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package wallet - -import ( - "testing" - - "github.com/0xPolygon/go-ibft/messages/proto" - "github.com/stretchr/testify/require" - - "github.com/xgr-network/xgr-node/bls" - "github.com/xgr-network/xgr-node/consensus/polybft/signer" -) - -func Test_RecoverAddressFromSignature(t *testing.T) { - t.Parallel() - - for _, account := range []*Account{generateTestAccount(t), generateTestAccount(t), generateTestAccount(t)} { - key := NewKey(account) - msgNoSig := &proto.Message{ - From: key.Address().Bytes(), - Type: proto.MessageType_COMMIT, - Payload: &proto.Message_CommitData{}, - } - - msg, err := key.SignIBFTMessage(msgNoSig) - require.NoError(t, err) - - payload, err := msgNoSig.PayloadNoSig() - require.NoError(t, err) - - address, err := RecoverAddressFromSignature(msg.Signature, payload) - require.NoError(t, err) - require.Equal(t, key.Address().Bytes(), address.Bytes()) - } -} - -func Test_Sign(t *testing.T) { - t.Parallel() - - msg := []byte("some message") - - for _, account := range []*Account{generateTestAccount(t), generateTestAccount(t)} { - key := NewKey(account) - ser, err := key.SignWithDomain(msg, signer.DomainCheckpointManager) - - require.NoError(t, err) - - sig, err := bls.UnmarshalSignature(ser) - require.NoError(t, err) - - require.True(t, sig.Verify(key.raw.Bls.PublicKey(), msg, signer.DomainCheckpointManager)) - } -} - -func Test_String(t *testing.T) { - t.Parallel() - - for _, account := range []*Account{generateTestAccount(t), generateTestAccount(t), generateTestAccount(t)} { - key := NewKey(account) - require.Equal(t, key.Address().String(), key.String()) - } -} diff --git a/consensus/utils.go b/consensus/utils.go index ef76764..45a88a8 100644 --- a/consensus/utils.go +++ b/consensus/utils.go @@ -25,8 +25,10 @@ func BuildBlock(params BuildBlockParams) *types.Block { if len(params.Receipts) == 0 { header.ReceiptsRoot = types.EmptyRootHash + header.LogsBloom = types.Bloom{} } else { header.ReceiptsRoot = buildroot.CalculateReceiptsRoot(params.Receipts) + header.LogsBloom = types.CreateBloom(params.Receipts) } header.Sha3Uncles = types.EmptyUncleHash diff --git a/contracts/abis/json.go b/contracts/abis/json.go index 88cb4b0..ccb61c3 100644 --- a/contracts/abis/json.go +++ b/contracts/abis/json.go @@ -4,19 +4,99 @@ const StakingJSONABI = `[ { "inputs": [ { - "internalType": "uint256", - "name": "minNumValidators", - "type": "uint256" + "internalType": "uint64", + "name": "_minNumValidators", + "type": "uint64" }, { - "internalType": "uint256", - "name": "maxNumValidators", - "type": "uint256" + "internalType": "uint64", + "name": "_maxNumValidators", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "_epochSize", + "type": "uint64" } ], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "AlreadyInRequestedState", + "type": "error" + }, + { + "inputs": [], + "name": "DelegationPoolClosed", + "type": "error" + }, + { + "inputs": [], + "name": "DelegationPoolMaxExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "DelegatorLimitExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientStakeAfterWithdraw", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCommissionBps", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidEpochSize", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMinDelegatorStake", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidValidatorTarget", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidWithdrawAmount", + "type": "error" + }, + { + "inputs": [], + "name": "JoinStakeTooLow", + "type": "error" + }, + { + "inputs": [], + "name": "MustDeactivateBeforeUnstake", + "type": "error" + }, + { + "inputs": [], + "name": "NoStake", + "type": "error" + }, + { + "inputs": [], + "name": "UnstakeTooEarly", + "type": "error" + }, + { + "inputs": [], + "name": "ValidatorNotFound", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -56,65 +136,128 @@ const StakingJSONABI = `[ "type": "event" }, { - "inputs": [], - "name": "VALIDATOR_THRESHOLD", - "outputs": [ + "anonymous": false, + "inputs": [ { - "internalType": "uint128", - "name": "", - "type": "uint128" + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "active", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" } ], - "stateMutability": "view", - "type": "function" + "name": "ValidatorActivationChanged", + "type": "event" }, { + "anonymous": false, "inputs": [ { + "indexed": true, "internalType": "address", - "name": "", + "name": "validator", "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "delegationEnabled", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxTotalDelegatedStake", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "minDelegatorStake", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "commissionBps", + "type": "uint16" } ], - "name": "_addressToBLSPublicKey", - "outputs": [ + "name": "ValidatorPoolConfigUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ { - "internalType": "bytes", - "name": "", - "type": "bytes" + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" } ], - "stateMutability": "view", - "type": "function" + "name": "ValidatorRemoved", + "type": "event" }, { + "anonymous": false, "inputs": [ { + "indexed": true, "internalType": "address", - "name": "", + "name": "account", "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" } ], - "name": "_addressToIsValidator", + "name": "Withdrawn", + "type": "event" + }, + { + "inputs": [], + "name": "DELEGATOR_MIN_STAKE", "outputs": [ { - "internalType": "bool", + "internalType": "uint256", "name": "", - "type": "bool" + "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "MAX_DELEGATORS_PER_VALIDATOR", + "outputs": [ { - "internalType": "address", + "internalType": "uint256", "name": "", - "type": "address" + "type": "uint256" } ], - "name": "_addressToStakedAmount", + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_STAKE", "outputs": [ { "internalType": "uint256", @@ -126,14 +269,21 @@ const StakingJSONABI = `[ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "VALIDATOR_MIN_SELF_STAKE", + "outputs": [ { - "internalType": "address", + "internalType": "uint256", "name": "", - "type": "address" + "type": "uint256" } ], - "name": "_addressToValidatorIndex", + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VALIDATOR_THRESHOLD", "outputs": [ { "internalType": "uint256", @@ -146,7 +296,7 @@ const StakingJSONABI = `[ }, { "inputs": [], - "name": "_maximumNumValidators", + "name": "VALIDATOR_THRESHOLD_TOTAL", "outputs": [ { "internalType": "uint256", @@ -158,8 +308,14 @@ const StakingJSONABI = `[ "type": "function" }, { - "inputs": [], - "name": "_minimumNumValidators", + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "accountStake", "outputs": [ { "internalType": "uint256", @@ -171,8 +327,14 @@ const StakingJSONABI = `[ "type": "function" }, { - "inputs": [], - "name": "_stakedAmount", + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "deactivationEffectiveAtBlock", "outputs": [ { "internalType": "uint256", @@ -186,19 +348,64 @@ const StakingJSONABI = `[ { "inputs": [ { - "internalType": "uint256", + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "delegate", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "delegationEnabled", + "outputs": [ + { + "internalType": "bool", "name": "", - "type": "uint256" + "type": "bool" } ], - "name": "_validators", + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochSize", "outputs": [ { - "internalType": "address", + "internalType": "uint64", "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", "type": "address" } ], + "name": "isValidatorThresholdMet", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, @@ -206,11 +413,11 @@ const StakingJSONABI = `[ "inputs": [ { "internalType": "address", - "name": "addr", + "name": "account", "type": "address" } ], - "name": "accountStake", + "name": "joinEffectiveAtBlock", "outputs": [ { "internalType": "uint256", @@ -225,16 +432,16 @@ const StakingJSONABI = `[ "inputs": [ { "internalType": "address", - "name": "addr", + "name": "account", "type": "address" } ], - "name": "isValidator", + "name": "joinedAtBlock", "outputs": [ { - "internalType": "bool", + "internalType": "uint256", "name": "", - "type": "bool" + "type": "uint256" } ], "stateMutability": "view", @@ -242,12 +449,12 @@ const StakingJSONABI = `[ }, { "inputs": [], - "name": "maximumNumValidators", + "name": "maxNumValidators", "outputs": [ { - "internalType": "uint256", + "internalType": "uint64", "name": "", - "type": "uint256" + "type": "uint64" } ], "stateMutability": "view", @@ -255,12 +462,12 @@ const StakingJSONABI = `[ }, { "inputs": [], - "name": "minimumNumValidators", + "name": "minNumValidators", "outputs": [ { - "internalType": "uint256", + "internalType": "uint64", "name": "", - "type": "uint256" + "type": "uint64" } ], "stateMutability": "view", @@ -279,6 +486,65 @@ const StakingJSONABI = `[ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "active_", + "type": "bool" + } + ], + "name": "setActive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "internalType": "bool", + "name": "active_", + "type": "bool" + } + ], + "name": "setDelegationActive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "delegationEnabled", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "maxTotalDelegatedStake", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minDelegatorStake", + "type": "uint256" + }, + { + "internalType": "uint16", + "name": "commissionBps", + "type": "uint16" + } + ], + "name": "setValidatorPoolConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "stake", @@ -287,13 +553,61 @@ const StakingJSONABI = `[ "type": "function" }, { - "inputs": [], - "name": "stakedAmount", + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "stakerInfo", "outputs": [ { - "internalType": "uint256", + "components": [ + { + "internalType": "bool", + "name": "exists", + "type": "bool" + }, + { + "internalType": "bool", + "name": "active", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "index", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "joinedAtBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deactivatedAtBlock", + "type": "uint256" + }, + { + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "blsPubKey", + "type": "bytes" + } + ], + "internalType": "struct StakingV2.Staker", "name": "", - "type": "uint256" + "type": "tuple" } ], "stateMutability": "view", @@ -306,19 +620,246 @@ const StakingJSONABI = `[ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "unstakeDelegation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "validatorBLSPublicKeys", "outputs": [ { "internalType": "bytes[]", - "name": "", + "name": "keys", "type": "bytes[]" } ], "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "validatorDelegatedStakeActive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "validatorDelegatedStakeRaw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "validatorInfo", + "outputs": [ + { + "internalType": "bool", + "name": "exists", + "type": "bool" + }, + { + "internalType": "bool", + "name": "active", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "stakedAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deactivatedAtBlock", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "blsPubKey", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "validatorPool", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "exists", + "type": "bool" + }, + { + "internalType": "bool", + "name": "active", + "type": "bool" + }, + { + "internalType": "bool", + "name": "delegationEnabled", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "maxTotalDelegatedStake", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minDelegatorStake", + "type": "uint256" + }, + { + "internalType": "uint16", + "name": "commissionBps", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "blsPubKey", + "type": "bytes" + } + ], + "internalType": "struct StakingV2.ValidatorPoolConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "validatorSelfStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "idx", + "type": "uint256" + } + ], + "name": "validatorStakerAt", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "validatorStakerCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "validatorTotalStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "validators", @@ -333,8 +874,35 @@ const StakingJSONABI = `[ "type": "function" }, { - "stateMutability": "payable", - "type": "receive" + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawDelegation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ]` diff --git a/contracts/staking/StakingV2.sol b/contracts/staking/StakingV2.sol new file mode 100644 index 0000000..133b587 --- /dev/null +++ b/contracts/staking/StakingV2.sol @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract StakingV2 { + uint256 public constant VALIDATOR_THRESHOLD_TOTAL = 2_000_000 ether; + uint256 public constant VALIDATOR_MIN_SELF_STAKE = 200_000 ether; + uint256 public constant DELEGATOR_MIN_STAKE = 10_000 ether; + uint256 public constant MAX_DELEGATORS_PER_VALIDATOR = 200; + // Backward-compatible alias + uint256 public constant MIN_STAKE = VALIDATOR_THRESHOLD_TOTAL; + + uint256 public VALIDATOR_THRESHOLD; + uint64 public epochSize; + uint64 public minNumValidators; + uint64 public maxNumValidators; + + struct Staker { + bool exists; + bool active; + uint64 index; + uint256 amount; + uint256 joinedAtBlock; + uint256 deactivatedAtBlock; + address validator; + bytes blsPubKey; + } + + struct ValidatorPoolConfig { + bool exists; + bool active; + bool delegationEnabled; + uint256 maxTotalDelegatedStake; + uint256 minDelegatorStake; + uint16 commissionBps; + bytes blsPubKey; + } + + address[] private _validators; + mapping(address => Staker) private _staker; + address[] private _stakers; + mapping(address => ValidatorPoolConfig) private _poolConfig; + mapping(address => address[]) private _stakersByValidator; + mapping(address => mapping(address => uint256)) private _stakerIndexPlusOneByValidator; + mapping(address => uint256) private _validatorDelegatedStakeRaw; + mapping(address => uint256) private _validatorDelegatedStakeActive; + uint256 private _reentrancyStatus; + + event Staked(address indexed account, uint256 amount); + event Unstaked(address indexed account, uint256 amount); + event ValidatorActivationChanged(address indexed account, bool active, uint256 blockNumber); + event ValidatorRemoved(address indexed account); + event ValidatorPoolConfigUpdated( + address indexed validator, + bool delegationEnabled, + uint256 maxTotalDelegatedStake, + uint256 minDelegatorStake, + uint16 commissionBps + ); + event Withdrawn(address indexed account, uint256 amount); + + error InvalidEpochSize(); + error JoinStakeTooLow(); + error ValidatorNotFound(); + error AlreadyInRequestedState(); + error MustDeactivateBeforeUnstake(); + error UnstakeTooEarly(); + error NoStake(); + error InvalidWithdrawAmount(); + error InsufficientStakeAfterWithdraw(); + error InvalidValidatorTarget(); + error DelegationPoolClosed(); + error DelegationPoolMaxExceeded(); + error InvalidCommissionBps(); + error InvalidMinDelegatorStake(); + error DelegatorLimitExceeded(); + + modifier nonReentrant() { + require(_reentrancyStatus == 0, "REENTRANT"); + _reentrancyStatus = 1; + _; + _reentrancyStatus = 0; + } + + constructor(uint64 _minNumValidators, uint64 _maxNumValidators, uint64 _epochSize) { + if (_epochSize == 0) revert InvalidEpochSize(); + minNumValidators = _minNumValidators; + maxNumValidators = _maxNumValidators; + epochSize = _epochSize; + VALIDATOR_THRESHOLD = VALIDATOR_THRESHOLD_TOTAL; + } + + function validators() external view returns (address[] memory) { + return _validators; + } + + function validatorBLSPublicKeys() external view returns (bytes[] memory keys) { + keys = new bytes[](_validators.length); + for (uint256 i = 0; i < _validators.length; i++) { + keys[i] = _staker[_validators[i]].blsPubKey; + } + } + + function accountStake(address account) external view returns (uint256) { + return _staker[account].amount; + } + + function validatorInfo(address account) + external + view + returns (bool exists, bool active, uint256 stakedAmount, uint256 deactivatedAtBlock, bytes memory blsPubKey) + { + Staker storage s = _staker[account]; + return (s.exists, s.active, s.amount, s.deactivatedAtBlock, s.blsPubKey); + } + + function stakerInfo(address account) external view returns (Staker memory) { + return _staker[account]; + } + + function validatorPool(address validator) external view returns (ValidatorPoolConfig memory) { + return _poolConfig[validator]; + } + + function delegationEnabled(address validator) external view returns (bool) { + return _poolConfig[validator].delegationEnabled; + } + + function validatorSelfStake(address validator) external view returns (uint256) { + Staker storage s = _staker[validator]; + if (!s.exists || s.validator != validator) return 0; + return s.amount; + } + + function validatorDelegatedStakeRaw(address validator) external view returns (uint256) { + return _validatorDelegatedStakeRaw[validator]; + } + + function validatorDelegatedStakeActive(address validator) external view returns (uint256) { + return _validatorDelegatedStakeActive[validator]; + } + + function validatorTotalStake(address validator) public view returns (uint256) { + Staker storage s = _staker[validator]; + if (!s.exists || s.validator != validator) return 0; + uint256 selfStake = s.active ? s.amount : 0; + return selfStake + _validatorDelegatedStakeActive[validator]; + } + + function isValidatorThresholdMet(address validator) external view returns (bool) { + return validatorTotalStake(validator) >= VALIDATOR_THRESHOLD; + } + + function validatorStakerCount(address validator) external view returns (uint256) { + return _stakersByValidator[validator].length; + } + + function validatorStakerAt(address validator, uint256 idx) external view returns (address) { + return _stakersByValidator[validator][idx]; + } + + function joinedAtBlock(address account) external view returns (uint256) { + return _staker[account].joinedAtBlock; + } + + function joinEffectiveAtBlock(address account) external view returns (uint256) { + uint256 joined = _staker[account].joinedAtBlock; + if (joined == 0) return 0; + return (_epochOf(joined) + 1) * uint256(epochSize); + } + + function deactivationEffectiveAtBlock(address account) external view returns (uint256) { + Staker storage s = _staker[account]; + if (s.deactivatedAtBlock == 0) return 0; + return (_epochOf(s.deactivatedAtBlock) + 1) * uint256(epochSize); + } + + function registerBLSPublicKey(bytes calldata blsPubKey) external nonReentrant { + Staker storage s = _staker[msg.sender]; + if (!s.exists || s.validator != msg.sender) revert ValidatorNotFound(); + s.blsPubKey = blsPubKey; + _poolConfig[msg.sender].blsPubKey = blsPubKey; + } + + function stake() external payable nonReentrant { + _stakeFor(msg.sender, msg.sender, msg.value); + } + + function delegate(address validator) external payable nonReentrant { + _stakeFor(msg.sender, validator, msg.value); + } + + function setActive(bool active_) external nonReentrant { + _setStakerActive(msg.sender, msg.sender, active_); + } + + function setDelegationActive(address validator, bool active_) external nonReentrant { + _setStakerActive(msg.sender, validator, active_); + } + + function setValidatorPoolConfig( + bool delegationEnabled, + uint256 maxTotalDelegatedStake, + uint256 minDelegatorStake, + uint16 commissionBps + ) external nonReentrant { + Staker storage s = _staker[msg.sender]; + if (!s.exists || s.validator != msg.sender) revert ValidatorNotFound(); + if (commissionBps > 10000) revert InvalidCommissionBps(); + if (minDelegatorStake != 0 && minDelegatorStake < DELEGATOR_MIN_STAKE) revert InvalidMinDelegatorStake(); + + ValidatorPoolConfig storage p = _poolConfig[msg.sender]; + p.delegationEnabled = delegationEnabled; + p.maxTotalDelegatedStake = maxTotalDelegatedStake; + p.minDelegatorStake = minDelegatorStake; + p.commissionBps = commissionBps; + + emit ValidatorPoolConfigUpdated(msg.sender, delegationEnabled, maxTotalDelegatedStake, minDelegatorStake, commissionBps); + } + + function withdraw(uint256 amount) external nonReentrant { + _withdrawStaker(msg.sender, msg.sender, amount, false); + } + + function withdrawDelegation(address validator, uint256 amount) external nonReentrant { + _withdrawStaker(msg.sender, validator, amount, false); + } + + function unstake() external nonReentrant { + _withdrawStaker(msg.sender, msg.sender, 0, true); + } + + function unstakeDelegation(address validator) external nonReentrant { + _withdrawStaker(msg.sender, validator, 0, true); + } + + + + function _effectiveMinDelegatorStake(address validator) internal view returns (uint256) { + uint256 minDelegatorStake = _poolConfig[validator].minDelegatorStake; + return minDelegatorStake == 0 ? DELEGATOR_MIN_STAKE : minDelegatorStake; + } + + function _delegatorCount(address validator) internal view returns (uint256 count) { + address[] storage stakers = _stakersByValidator[validator]; + for (uint256 i = 0; i < stakers.length; i++) { + if (stakers[i] != validator) count++; + } + } + + function _stakeFor(address owner, address validator, uint256 amount) internal { + if (amount == 0) revert NoStake(); + bool selfStake = owner == validator; + + if (!selfStake) { + Staker storage target = _staker[validator]; + if (!target.exists || target.validator != validator) revert InvalidValidatorTarget(); + + ValidatorPoolConfig storage p = _poolConfig[validator]; + if (!p.delegationEnabled) revert DelegationPoolClosed(); + if (_validatorDelegatedStakeRaw[validator] + amount > p.maxTotalDelegatedStake) revert DelegationPoolMaxExceeded(); + } + + Staker storage s = _staker[owner]; + if (!s.exists) { + uint256 minJoin = selfStake ? VALIDATOR_MIN_SELF_STAKE : _effectiveMinDelegatorStake(validator); + if (amount < minJoin) revert JoinStakeTooLow(); + if (!selfStake && _delegatorCount(validator) >= MAX_DELEGATORS_PER_VALIDATOR) revert DelegatorLimitExceeded(); + + s.exists = true; + s.active = true; + s.index = uint64(_stakers.length); + s.joinedAtBlock = block.number; + s.validator = validator; + _stakers.push(owner); + + _stakerIndexPlusOneByValidator[validator][owner] = _stakersByValidator[validator].length + 1; + _stakersByValidator[validator].push(owner); + + if (selfStake) { + _validators.push(owner); + _poolConfig[owner].exists = true; + _poolConfig[owner].active = true; + _poolConfig[owner].delegationEnabled = false; + _poolConfig[owner].maxTotalDelegatedStake = 0; + _poolConfig[owner].minDelegatorStake = 0; + _poolConfig[owner].commissionBps = 0; + } + } else { + if (s.validator != validator) revert InvalidValidatorTarget(); + } + + s.amount += amount; + _syncAggregatorOnStake(owner, validator, amount, s.active); + + emit Staked(owner, amount); + } + + function _setStakerActive(address owner, address expectedValidator, bool active_) internal { + Staker storage s = _staker[owner]; + if (!s.exists || s.validator != expectedValidator) revert ValidatorNotFound(); + if (s.active == active_) revert AlreadyInRequestedState(); + + uint256 minStake = owner == expectedValidator ? VALIDATOR_MIN_SELF_STAKE : _effectiveMinDelegatorStake(expectedValidator); + if (active_ && s.amount < minStake) revert InsufficientStakeAfterWithdraw(); + + s.active = active_; + if (active_) { + s.deactivatedAtBlock = 0; + } else { + s.deactivatedAtBlock = block.number; + } + + if (owner == expectedValidator) { + _poolConfig[owner].active = active_; + } + + _syncAggregatorOnToggle(owner, expectedValidator, s.amount, active_); + emit ValidatorActivationChanged(owner, active_, block.number); + } + + function _withdrawStaker(address owner, address expectedValidator, uint256 amount, bool fullExit) internal { + Staker storage s = _staker[owner]; + if (!s.exists || s.validator != expectedValidator) revert ValidatorNotFound(); + if (s.active) revert MustDeactivateBeforeUnstake(); + if (s.deactivatedAtBlock == 0) revert MustDeactivateBeforeUnstake(); + if (_epochOf(block.number) <= _epochOf(s.deactivatedAtBlock)) revert UnstakeTooEarly(); + + uint256 payout; + if (fullExit) { + payout = s.amount; + } else { + if (amount == 0 || amount >= s.amount) revert InvalidWithdrawAmount(); + uint256 minStake = owner == expectedValidator ? VALIDATOR_MIN_SELF_STAKE : _effectiveMinDelegatorStake(expectedValidator); + uint256 remaining = s.amount - amount; + if (remaining < minStake) revert InsufficientStakeAfterWithdraw(); + payout = amount; + s.amount = remaining; + } + + if (fullExit) { + _syncAggregatorOnUnstake(owner, expectedValidator, s.amount, s.active); + if (owner == expectedValidator) { + _removeValidator(owner); + delete _poolConfig[owner]; + } + _removeFromValidatorDelegators(expectedValidator, owner); + _removeStaker(owner); + emit Unstaked(owner, payout); + if (owner == expectedValidator) { + emit ValidatorRemoved(owner); + } + } else { + _syncAggregatorOnWithdraw(owner, expectedValidator, payout, s.active); + emit Withdrawn(owner, payout); + } + + (bool ok, ) = payable(owner).call{value: payout}(""); + require(ok, "transfer failed"); + } + + function _syncAggregatorOnStake(address owner, address validator, uint256 amount, bool active_) internal { + if (owner == validator) { + return; + } + + _validatorDelegatedStakeRaw[validator] += amount; + if (active_) { + _validatorDelegatedStakeActive[validator] += amount; + } + } + + function _syncAggregatorOnToggle(address owner, address validator, uint256 amount, bool active_) internal { + if (owner == validator) { + return; + } + if (active_) { + _validatorDelegatedStakeActive[validator] += amount; + } else { + _validatorDelegatedStakeActive[validator] -= amount; + } + } + + function _syncAggregatorOnWithdraw(address owner, address validator, uint256 amount, bool active_) internal { + if (owner == validator) { + return; + } + _validatorDelegatedStakeRaw[validator] -= amount; + if (active_) { + _validatorDelegatedStakeActive[validator] -= amount; + } + } + + function _syncAggregatorOnUnstake(address owner, address validator, uint256 amount, bool active_) internal { + if (owner == validator) { + return; + } + _validatorDelegatedStakeRaw[validator] -= amount; + if (active_) { + _validatorDelegatedStakeActive[validator] -= amount; + } + } + + function _removeValidator(address account) internal { + uint256 last = _validators.length - 1; + uint256 idx; + for (uint256 i = 0; i < _validators.length; i++) { + if (_validators[i] == account) { + idx = i; + break; + } + } + if (idx != last) { + _validators[idx] = _validators[last]; + } + _validators.pop(); + } + + function _removeFromValidatorDelegators(address validator, address owner) internal { + uint256 idxPlusOne = _stakerIndexPlusOneByValidator[validator][owner]; + if (idxPlusOne == 0) return; + + uint256 idx = idxPlusOne - 1; + uint256 last = _stakersByValidator[validator].length - 1; + + if (idx != last) { + address moved = _stakersByValidator[validator][last]; + _stakersByValidator[validator][idx] = moved; + _stakerIndexPlusOneByValidator[validator][moved] = idx + 1; + } + + _stakersByValidator[validator].pop(); + delete _stakerIndexPlusOneByValidator[validator][owner]; + } + + function _removeStaker(address owner) internal { + uint256 idx = _staker[owner].index; + uint256 last = _stakers.length - 1; + + if (idx != last) { + address moved = _stakers[last]; + _stakers[idx] = moved; + _staker[moved].index = uint64(idx); + } + + _stakers.pop(); + delete _staker[owner]; + } + + function _epochOf(uint256 blockNumber) internal view returns (uint256) { + return blockNumber / uint256(epochSize); + } +} diff --git a/contracts/staking/query.go b/contracts/staking/query.go index 5592442..14832de 100644 --- a/contracts/staking/query.go +++ b/contracts/staking/query.go @@ -1,8 +1,12 @@ package staking import ( + "encoding/hex" "errors" + "fmt" "math/big" + "reflect" + "strings" "github.com/umbracle/ethgo" @@ -15,6 +19,12 @@ import ( const ( methodValidators = "validators" methodValidatorBLSPublicKeys = "validatorBLSPublicKeys" + methodAccountStake = "accountStake" + methodMinimumNumValidators = "minNumValidators" + methodMaximumNumValidators = "maxNumValidators" + methodValidatorThreshold = "VALIDATOR_THRESHOLD" + methodValidatorInfo = "validatorInfo" + methodStakerInfo = "stakerInfo" ) var ( @@ -37,22 +47,89 @@ type TxQueryHandler interface { // decodeWeb3ArrayOfBytes is a helper function to parse the data // representing array of bytes in contract result -func decodeWeb3ArrayOfBytes( - result interface{}, -) ([][]byte, error) { +func decodeWeb3ArrayOfBytes(method *abi.Method, result interface{}) ([][]byte, error) { mapResult, ok := result.(map[string]interface{}) if !ok { return nil, ErrFailedTypeAssertion } - bytesArray, ok := mapResult["0"].([][]byte) + value, ok := mapResult["0"] + if !ok { + outputs := method.Outputs.TupleElems() + if len(outputs) > 0 { + if name := outputs[0].Name; name != "" { + value, ok = mapResult[name] + } + } + } + if !ok && len(mapResult) == 1 { + for _, v := range mapResult { + value = v + ok = true + } + } if !ok { return nil, ErrFailedTypeAssertion } + bytesArray, err := decodeBytesArray(value) + if err != nil { + return nil, ErrFailedTypeAssertion + } + return bytesArray, nil } +func decodeBytesArray(raw interface{}) ([][]byte, error) { + v := reflect.ValueOf(raw) + if !v.IsValid() { + return nil, fmt.Errorf("unexpected value") + } + if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { + return nil, fmt.Errorf("unexpected value") + } + + out := make([][]byte, 0, v.Len()) + for i := 0; i < v.Len(); i++ { + b, err := toBytes(v.Index(i).Interface()) + if err != nil { + return nil, err + } + out = append(out, b) + } + + return out, nil +} + +func toBytes(raw interface{}) ([]byte, error) { + switch v := raw.(type) { + case []byte: + return v, nil + case string: + v = strings.TrimPrefix(v, "0x") + if v == "" { + return []byte{}, nil + } + return hex.DecodeString(v) + } + + rv := reflect.ValueOf(raw) + if !rv.IsValid() { + return nil, fmt.Errorf("unexpected item type") + } + + if (rv.Kind() == reflect.Array || rv.Kind() == reflect.Slice) && rv.Type().Elem().Kind() == reflect.Uint8 { + b := make([]byte, rv.Len()) + for i := 0; i < rv.Len(); i++ { + b[i] = byte(rv.Index(i).Uint()) + } + + return b, nil + } + + return nil, fmt.Errorf("unexpected item type") +} + // createCallViewTx is a helper function to create a transaction to call view method func createCallViewTx( from types.Address, @@ -133,7 +210,7 @@ func decodeBLSPublicKeys( return nil, err } - blsPublicKeys, err := decodeWeb3ArrayOfBytes(decodedResults) + blsPublicKeys, err := decodeWeb3ArrayOfBytes(method, decodedResults) if err != nil { return nil, err } @@ -166,3 +243,272 @@ func QueryBLSPublicKeys(t TxQueryHandler, from types.Address) ([][]byte, error) return decodeBLSPublicKeys(method, res.ReturnValue) } + +// QueryAccountStake returns account stake amount for the given validator address. +func QueryAccountStake(t TxQueryHandler, from, validator types.Address) (*big.Int, error) { + method, ok := abis.StakingABI.Methods[methodAccountStake] + if !ok { + return nil, ErrMethodNotFoundInABI + } + + methodInput, err := method.Inputs.Encode(map[string]interface{}{ + "account": ethgo.Address(validator), + }) + if err != nil { + return nil, err + } + + t.SetNonPayable(true) + res, err := t.Apply(createCallViewTx( + from, + AddrStakingContract, + append(method.ID(), methodInput...), + t.GetNonce(from), + )) + if err != nil { + return nil, err + } + + if res.Failed() { + return nil, res.Err + } + + decodedResults, err := method.Outputs.Decode(res.ReturnValue) + if err != nil { + return nil, err + } + + mapResult, ok := decodedResults.(map[string]interface{}) + if !ok { + return nil, ErrFailedTypeAssertion + } + + stake, ok := mapResult["0"].(*big.Int) + if !ok { + return nil, ErrFailedTypeAssertion + } + + return stake, nil +} + +// ValidatorInfo is a decoded snapshot of validatorInfo(account). +type ValidatorInfo struct { + Exists bool + Active bool + StakedAmount *big.Int + DeactivatedAtBlock *big.Int + BLSPubKey []byte +} + +type StakerInfo struct { + Exists bool + Active bool + Amount *big.Int + JoinedAtBlock *big.Int + DeactivatedAtBlock *big.Int + Validator types.Address +} + +// QueryValidatorInfo returns validator metadata for the given account. +func QueryValidatorInfo(t TxQueryHandler, from, validator types.Address) (*ValidatorInfo, error) { + method, ok := abis.StakingABI.Methods[methodValidatorInfo] + if !ok { + return nil, ErrMethodNotFoundInABI + } + + methodInput, err := method.Inputs.Encode(map[string]interface{}{ + "account": ethgo.Address(validator), + }) + if err != nil { + return nil, err + } + + t.SetNonPayable(true) + res, err := t.Apply(createCallViewTx( + from, + AddrStakingContract, + append(method.ID(), methodInput...), + t.GetNonce(from), + )) + if err != nil { + return nil, err + } + if res.Failed() { + return nil, res.Err + } + + decodedResults, err := method.Outputs.Decode(res.ReturnValue) + if err != nil { + return nil, err + } + mapResult, ok := decodedResults.(map[string]interface{}) + if !ok { + return nil, ErrFailedTypeAssertion + } + + exists, ok := mapResult["0"].(bool) + if !ok { + exists, ok = mapResult["exists"].(bool) + if !ok { + return nil, ErrFailedTypeAssertion + } + } + active, ok := mapResult["1"].(bool) + if !ok { + active, ok = mapResult["active"].(bool) + if !ok { + return nil, ErrFailedTypeAssertion + } + } + stakedAmount, ok := mapResult["2"].(*big.Int) + if !ok { + stakedAmount, ok = mapResult["stakedAmount"].(*big.Int) + if !ok { + return nil, ErrFailedTypeAssertion + } + } + deact, ok := mapResult["3"].(*big.Int) + if !ok { + deact, ok = mapResult["deactivatedAtBlock"].(*big.Int) + if !ok { + return nil, ErrFailedTypeAssertion + } + } + bls, ok := mapResult["4"].([]byte) + if !ok { + bls, ok = mapResult["blsPubKey"].([]byte) + if !ok { + return nil, ErrFailedTypeAssertion + } + } + + return &ValidatorInfo{ + Exists: exists, + Active: active, + StakedAmount: stakedAmount, + DeactivatedAtBlock: deact, + BLSPubKey: bls, + }, nil +} + +func QueryStakerInfo(t TxQueryHandler, from, account types.Address) (*StakerInfo, error) { + method, ok := abis.StakingABI.Methods[methodStakerInfo] + if !ok { + return nil, ErrMethodNotFoundInABI + } + methodInput, err := method.Inputs.Encode(map[string]interface{}{"account": ethgo.Address(account)}) + if err != nil { + return nil, err + } + + t.SetNonPayable(true) + res, err := t.Apply(createCallViewTx(from, AddrStakingContract, append(method.ID(), methodInput...), t.GetNonce(from))) + if err != nil { + return nil, err + } + if res.Failed() { + return nil, res.Err + } + + decodedResults, err := method.Outputs.Decode(res.ReturnValue) + if err != nil { + return nil, err + } + mp, ok := decodedResults.(map[string]interface{}) + if !ok { + return nil, ErrFailedTypeAssertion + } + raw, ok := mp["0"].(map[string]interface{}) + if !ok { + return nil, ErrFailedTypeAssertion + } + out := &StakerInfo{} + if v, ok := raw["exists"].(bool); ok { + out.Exists = v + } + if v, ok := raw["active"].(bool); ok { + out.Active = v + } + if v, ok := raw["amount"].(*big.Int); ok { + out.Amount = v + } else { + out.Amount = new(big.Int) + } + if v, ok := raw["joinedAtBlock"].(*big.Int); ok { + out.JoinedAtBlock = v + } else { + out.JoinedAtBlock = new(big.Int) + } + if v, ok := raw["deactivatedAtBlock"].(*big.Int); ok { + out.DeactivatedAtBlock = v + } else { + out.DeactivatedAtBlock = new(big.Int) + } + if v, ok := raw["validator"].(ethgo.Address); ok { + out.Validator = types.Address(v) + } + + return out, nil +} +func queryUint256NoArgs(t TxQueryHandler, from types.Address, methodName string) (*big.Int, error) { + method, ok := abis.StakingABI.Methods[methodName] + if !ok { + return nil, ErrMethodNotFoundInABI + } + + t.SetNonPayable(true) + res, err := t.Apply(createCallViewTx( + from, + AddrStakingContract, + method.ID(), + t.GetNonce(from), + )) + if err != nil { + return nil, err + } + if res.Failed() { + return nil, res.Err + } + + decodedResults, err := method.Outputs.Decode(res.ReturnValue) + if err != nil { + return nil, err + } + mapResult, ok := decodedResults.(map[string]interface{}) + if !ok { + return nil, ErrFailedTypeAssertion + } + if val, ok := mapResult["0"].(*big.Int); ok { + return val, nil + } + if valU64, ok := mapResult["0"].(uint64); ok { + return new(big.Int).SetUint64(valU64), nil + } + if valU, ok := mapResult["0"].(uint); ok { + return new(big.Int).SetUint64(uint64(valU)), nil + } + return nil, ErrFailedTypeAssertion +} + +// QueryMinimumNumValidators returns the contract's minimum validator count. +func QueryMinimumNumValidators(t TxQueryHandler, from types.Address) (uint64, error) { + v, err := queryUint256NoArgs(t, from, methodMinimumNumValidators) + if err != nil { + return 0, err + } + return v.Uint64(), nil +} + +// QueryMaximumNumValidators returns the contract's maximum validator count. +func QueryMaximumNumValidators(t TxQueryHandler, from types.Address) (uint64, error) { + v, err := queryUint256NoArgs(t, from, methodMaximumNumValidators) + if err != nil { + return 0, err + } + return v.Uint64(), nil +} + +// QueryValidatorThreshold returns the stake threshold (in wei) required to be considered eligible. +func QueryValidatorThreshold(t TxQueryHandler, from types.Address) (*big.Int, error) { + return queryUint256NoArgs(t, from, methodValidatorThreshold) +} diff --git a/contracts/staking/query_test.go b/contracts/staking/query_test.go index 41b60c0..dfacd62 100644 --- a/contracts/staking/query_test.go +++ b/contracts/staking/query_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/umbracle/ethgo" "github.com/xgr-network/xgr-node/contracts/abis" "github.com/xgr-network/xgr-node/state/runtime" "github.com/xgr-network/xgr-node/types" @@ -59,6 +60,10 @@ func (m *TxMock) Apply(tx *types.Transaction) (*runtime.ExecutionResult, error) return nil, errors.New("not found") } +func (m *TxMock) Call(tx *types.Transaction) (*runtime.ExecutionResult, error) { + return m.Apply(tx) +} + func (m *TxMock) GetNonce(addr types.Address) uint64 { if m.nonce != nil { return m.nonce[addr] @@ -225,3 +230,89 @@ func TestQueryValidators(t *testing.T) { }) } } + +func TestQueryAccountStake(t *testing.T) { + method := abis.StakingABI.Methods["accountStake"] + if method == nil { + t.Fail() + } + + input, err := method.Inputs.Encode(map[string]interface{}{"account": ethgo.Address(addr2)}) + assert.NoError(t, err) + + tx := &types.Transaction{ + From: addr1, + To: &AddrStakingContract, + Value: big.NewInt(0), + Input: append(method.ID(), input...), + GasPrice: big.NewInt(0), + Gas: queryGasLimit, + Nonce: 7, + } + + expectedStake := big.NewInt(12345) + encodedOut, err := method.Outputs.Encode(map[string]interface{}{"0": expectedStake}) + assert.NoError(t, err) + + mock := &TxMock{ + hashToRes: map[types.Hash]*runtime.ExecutionResult{ + tx.ComputeHash(1).Hash: {ReturnValue: encodedOut}, + }, + nonce: map[types.Address]uint64{addr1: 7}, + } + + stake, err := QueryAccountStake(mock, addr1, addr2) + assert.NoError(t, err) + assert.Equal(t, 0, expectedStake.Cmp(stake)) +} + +func TestDecodeBLSPublicKeys(t *testing.T) { + method := abis.StakingABI.Methods["validatorBLSPublicKeys"] + if method == nil { + t.FailNow() + } + + want := [][]byte{{0x01, 0x02}, {0x03, 0x04}} + + encoded, err := method.Outputs.Encode(map[string]interface{}{"keys": want}) + assert.NoError(t, err) + + res, err := decodeBLSPublicKeys(method, encoded) + assert.NoError(t, err) + assert.Equal(t, want, res) +} + +func TestDecodeWeb3ArrayOfBytes_FixedArrayItems(t *testing.T) { + method := abis.StakingABI.Methods["validatorBLSPublicKeys"] + if method == nil { + t.FailNow() + } + + res, err := decodeWeb3ArrayOfBytes(method, map[string]interface{}{"keys": [][2]byte{{0x01, 0x02}, {0x03, 0x04}}}) + assert.NoError(t, err) + assert.Equal(t, [][]byte{{0x01, 0x02}, {0x03, 0x04}}, res) +} + +func TestStakingABI_SetValidatorPoolConfigSignature(t *testing.T) { + m := abis.StakingABI.Methods["setValidatorPoolConfig"] + assert.NotNil(t, m) + assert.Equal(t, 4, len(m.Inputs.TupleElems())) + assert.Equal(t, "delegationEnabled", m.Inputs.TupleElems()[0].Name) + assert.Equal(t, "maxTotalDelegatedStake", m.Inputs.TupleElems()[1].Name) + assert.Equal(t, "minDelegatorStake", m.Inputs.TupleElems()[2].Name) + assert.Equal(t, "commissionBps", m.Inputs.TupleElems()[3].Name) +} + +func TestStakingABI_ValidatorPoolTupleNames(t *testing.T) { + m := abis.StakingABI.Methods["validatorPool"] + assert.NotNil(t, m) + out := m.Outputs.TupleElems()[0] + elems := out.Elem.TupleElems() + assert.Equal(t, "exists", elems[0].Name) + assert.Equal(t, "active", elems[1].Name) + assert.Equal(t, "delegationEnabled", elems[2].Name) + assert.Equal(t, "maxTotalDelegatedStake", elems[3].Name) + assert.Equal(t, "minDelegatorStake", elems[4].Name) + assert.Equal(t, "commissionBps", elems[5].Name) + assert.Equal(t, "blsPubKey", elems[6].Name) +} diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index 85de78e..0000000 --- a/docker/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Deploying local docker cluster - -## Prerequisites - -* [Docker Engine](https://docs.docker.com/engine/install/) - Minimum version 20.10.23 -* [Docker compose v.1.x](https://docs.docker.com/compose/install/) - Minimum version 1.29 - -### `polybft` consensus - -When deploying with `polybft` consensus, there are some additional dependencies: - -* [go 1.20.x](https://go.dev/dl/) - -## Local development - -Running `xgrchain` local cluster with docker can be done very easily by using provided `scripts` folder -or by running `docker-compose` manually. - -### Using provided `scripts` folder - -***All commands need to be run from the repo root / root folder.*** - -* `scripts/cluster ibft --docker` - deploy environment with `ibft` consensus -* `scripts/cluster polybft --docker` - deploy environment with `polybft` consensus -* `scripts/cluster {ibft or polybft} --docker stop` - stop containers -* `scripts/cluster {ibft or polybft}--docker destroy` - destroy environment (delete containers and volumes) - -### Using `docker-compose` - -***All commands need to be run from the repo root / root folder.*** - -#### use `ibft` PoA consensus - -* `export EDGE_CONSENSUS=ibft` - set `ibft` consensus -* `docker-compose -f ./docker/local/docker-compose.yml up -d --build` - deploy environment - -#### use `polybft` consensus - -* `export EDGE_CONSENSUS=polybft` - set `polybft` consensus -* `docker-compose -f ./docker/local/docker-compose.yml up -d --build` - deploy environment - -#### stop / destroy - -* `docker-compose -f ./docker/local/docker-compose.yml stop` - stop containers -* `docker-compose -f ./docker/local/docker-compose.yml down -v` - destroy environment - -## Customization - -Use `docker/local/xgrchain.sh` script to customize chain parameters. -All parameters can be defined at the very beginning of the script, in the `CHAIN_CUSTOM_OPTIONS` variable. -It already has some default parameters, which can be easily modified. -These are the `genesis` parameters from the official [docs](https://wiki.xgr.network/docs/edge/operate/param-reference/). - -Primarily, the `--premine` parameter needs to be edited to include the accounts that the user has access to. - -## Considerations - -### Build times - -When building containers for the first time (or after purging docker build cache), -it might take a while to complete, depending on the hardware that the build operation is running on. - -### Production - -This is **NOT** a production ready deployment. It is to be used in *development* / *test* environments only. -For production usage, please check out the official [docs](https://wiki.xgr.network/docs/edge/). diff --git a/docker/local/docker-compose.yml b/docker/local/docker-compose.yml index da0e258..5d0734a 100644 --- a/docker/local/docker-compose.yml +++ b/docker/local/docker-compose.yml @@ -31,7 +31,7 @@ services: dockerfile: docker/local/Dockerfile image: local/xgrchain container_name: xgrchain-bootstrapper - command: [ "init", "${EDGE_CONSENSUS:-polybft}" ] + command: [ "init", "${EDGE_CONSENSUS:-ibft}" ] depends_on: rootchain: condition: service_started @@ -44,7 +44,7 @@ services: node-1: image: local/xgrchain container_name: xgrchain-validator-1 - command: [ "start-node-1", "${EDGE_CONSENSUS:-polybft}" ] + command: [ "start-node-1", "${EDGE_CONSENSUS:-ibft}" ] depends_on: init: condition: service_completed_successfully diff --git a/docker/local/xgrchain.sh b/docker/local/xgrchain.sh index ed2506d..fc2c7fb 100644 --- a/docker/local/xgrchain.sh +++ b/docker/local/xgrchain.sh @@ -50,89 +50,12 @@ case "$1" in createGenesisConfig "$2" "$secrets" fi ;; - "polybft") - echo "Generating PolyBFT secrets..." - secrets=$("$XGRCHAIN_BIN" polybft-secrets init --insecure --num 4 --data-dir /data/data- --json) - echo "Secrets have been successfully generated" - - rm -f /data/genesis.json - - proxyContractsAdmin=0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed - - createGenesisConfig "$2" "$secrets" \ - --reward-wallet 0xDEADBEEF:1000000 \ - --native-token-config "XGR:XGR:18:true:$(echo "$secrets" | jq -r '.[0] | .address')" \ - --proxy-contracts-admin ${proxyContractsAdmin} - - echo "Deploying stake manager..." - "$XGRCHAIN_BIN" polybft stake-manager-deploy \ - --jsonrpc http://rootchain:8545 \ - --genesis /data/genesis.json \ - --proxy-contracts-admin ${proxyContractsAdmin} \ - --test - - stakeManagerAddr=$(cat /data/genesis.json | jq -r '.params.engine.polybft.bridge.stakeManagerAddr') - stakeToken=$(cat /data/genesis.json | jq -r '.params.engine.polybft.bridge.stakeTokenAddr') - - "$XGRCHAIN_BIN" rootchain deploy \ - --stake-manager ${stakeManagerAddr} \ - --stake-token ${stakeToken} \ - --json-rpc http://rootchain:8545 \ - --genesis /data/genesis.json \ - --proxy-contracts-admin ${proxyContractsAdmin} \ - --test - - customSupernetManagerAddr=$(cat /data/genesis.json | jq -r '.params.engine.polybft.bridge.customSupernetManagerAddr') - supernetID=$(cat /data/genesis.json | jq -r '.params.engine.polybft.supernetID') - addresses="$(echo "$secrets" | jq -r '.[0] | .address'),$(echo "$secrets" | jq -r '.[1] | .address'),$(echo "$secrets" | jq -r '.[2] | .address'),$(echo "$secrets" | jq -r '.[3] | .address')" - - "$XGRCHAIN_BIN" rootchain fund \ - --json-rpc http://rootchain:8545 \ - --stake-token ${stakeToken} \ - --mint \ - --addresses ${addresses} \ - --amounts 1000000000000000000000000,1000000000000000000000000,1000000000000000000000000,1000000000000000000000000 - - "$XGRCHAIN_BIN" polybft whitelist-validators \ - --addresses ${addresses} \ - --supernet-manager ${customSupernetManagerAddr} \ - --private-key aa75e9a7d427efc732f8e4f1a5b7646adcc61fd5bae40f80d13c8419c9f43d6d \ - --jsonrpc http://rootchain:8545 - - counter=1 - while [ $counter -le 4 ]; do - echo "Registering validator: ${counter}" - - "$XGRCHAIN_BIN" polybft register-validator \ - --supernet-manager ${customSupernetManagerAddr} \ - --data-dir /data/data-${counter} \ - --jsonrpc http://rootchain:8545 - - "$XGRCHAIN_BIN" polybft stake \ - --data-dir /data/data-${counter} \ - --amount 1000000000000000000000000 \ - --supernet-id ${supernetID} \ - --stake-manager ${stakeManagerAddr} \ - --stake-token ${stakeToken} \ - --jsonrpc http://rootchain:8545 - - counter=$((counter + 1)) - done - - "$XGRCHAIN_BIN" polybft supernet \ - --private-key aa75e9a7d427efc732f8e4f1a5b7646adcc61fd5bae40f80d13c8419c9f43d6d \ - --supernet-manager ${customSupernetManagerAddr} \ - --finalize-genesis-set \ - --enable-staking \ - --genesis /data/genesis.json \ - --jsonrpc http://rootchain:8545 - ;; esac ;; "start-node-1") relayer_flag="" - # Start relayer only when run in polybft - if [ "$2" == "polybft" ]; then + # Start relayer only when run in ibft + if [ "$2" == "ibft" ]; then echo "Starting relayer..." relayer_flag="--relayer" fi diff --git a/docs/Dockerfile b/docs/Dockerfile deleted file mode 100644 index bb63d18..0000000 --- a/docs/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM python:3.9-alpine - -# Install system dependencies -RUN apk update && \ - apk add --no-cache rsync git nodejs npm && \ - rm -rf /var/cache/apk/* - -# Copy and install Python dependencies -COPY requirements.txt requirements.txt -RUN pip install -r requirements.txt --no-cache-dir - -# Switch to a non-root user (if 'nonroot' is already created) -USER nonroot - -# Build doc by default -ENTRYPOINT ["mkdocs"] -CMD ["serve", "--dev-addr", "0.0.0.0:8000"] diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index d6ace6c..0000000 --- a/docs/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# XGRChain docs - -Welcome to the XGRChain documentation, built with [the Material theme for MkDocs](https://squidfunk.github.io/mkdocs-material/). - -## Build and serve the site - -### Clone the repo - -```sh -https://github.com/xgr-network/xgr-node.git -cd xgrchain/docs -``` - -### Run with Python - -1. Download and install Python 3.11: https://www.python.org/downloads/ - -2. Install the `virtualenv` package: - -```sh -pip install virtualenv -``` - -3. Build and serve the html - -```sh -./run.sh -``` - -The site runs at: http://127.0.0.1:8000/ - -### Run with Docker - -:warning: Remove line 10 from the `Dockerfile` to run locally. - -1. Spin up the image: - -```sh -docker build -t xgrchain-docs . -``` - -2. Run the container: - -``` -docker compose up -``` - -The site runs at: http://127.0.0.1:8000/ \ No newline at end of file diff --git a/docs/docker-compose.yml b/docs/docker-compose.yml deleted file mode 100644 index 62f0aef..0000000 --- a/docs/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: "3.6" - -services: - developer-docs: - image: "xgrchain-docs" - ports: - - "0.0.0.0:8000:8000" - container_name: "serve-edge-docs" - working_dir: /workspace/ - volumes: - - type: bind - source: . - target: /workspace diff --git a/docs/docs/api/json-rpc-bridge.md b/docs/docs/api/json-rpc-bridge.md deleted file mode 100644 index f58f0e6..0000000 --- a/docs/docs/api/json-rpc-bridge.md +++ /dev/null @@ -1,31 +0,0 @@ -## bridge_generateExitProof - -Returns the proof for a given exit event. Used by users that want to exit the L2 chain and withdraw tokens to L1. - -### Parameters - -**exitID** - ID of the exit event submitted by L2StateSender contract when the user wants to exit the L2 contract. - -### Returns - - -- **Object** - A proof object containing: - - **Array of hashes** - representing the proof of membership of a given exit event on some checkpoint. - - **Map** - containing a leaf index of given exit event in the Merkle tree, Exit event and block number in which checkpoint that contains a given exit event was submitted to the rootchain. - ---- - -## bridge_getStateSyncProof - -Returns the proof for a given state sync event. Used by users (Relayer) when they want to execute a state change on the childchain (for example, depositing funds from L1 to L2). - -### Parameters - -**stateSyncID** - ID of the state sync event submitted by StateSender contract. - -### Returns - - -- **Object** - A proof object containing: - - **Array of hashes** - representing the proof of membership of a given state sync event on some commitment. - - **Map** - containing the state sync event data. diff --git a/docs/docs/api/json-rpc-debug.md b/docs/docs/api/json-rpc-debug.md deleted file mode 100644 index 117550f..0000000 --- a/docs/docs/api/json-rpc-debug.md +++ /dev/null @@ -1,137 +0,0 @@ -To enable the debug route namespace, you need to modify the configuration and add the "debug" parameter as shown below: - -``` -[jsonrpc.http] - enabled = true - port = 8545 - host = "0.0.0.0" - api = ["eth", "net", "web3", "txpool", "bor", "debug"] -``` - -## debug_traceBlockByNumber - -Executes all transactions in the block specified by number with a tracer and returns the tracing result. - -### Parameters - -* QUANTITY|TAG - integer of a block number, or the string "latest" -* Object - The tracer options: - - + enableMemory: Boolean - (optional, default: false) The flag indicating enabling memory capture. - + disableStack: Boolean - (optional, default: false) The flag indicating disabling stack capture. - + disableStorage: Boolean - (optional, default: false) The flag indicating disabling storage capture. - + enableReturnData: Boolean - (optional, default: false) The flag indicating enabling return data capture. - + timeOut: String - (optional, default: "5s") The timeout for cancellation of execution. - + tracer: String - (default: "structTracer") Defines the debug tracer used for given call. Supported values: structTracer, callTracer. - - -### Returns - - Array - Array of trace objects with the following fields: - - * failed: Boolean - the tx is successful or not - * gas: QUANTITY - the total consumed gas in the tx - * returnValue: DATA - the return value of the executed contract call - * structLogs: Array - the trace result of each step with the following fields: - - + pc: QUANTITY - the current index in bytecode - + op: String - the name of current executing operation - + gas: QUANTITY - the available gas ßin the execution - + gasCost: QUANTITY - the gas cost of the operation - + depth: QUANTITY - the number of levels of calling functions - + error: String - the error of the execution - + stack: Array - array of values in the current stack - + memory: Array - array of values in the current memory - + storage: Object - mapping of the current storage - + refund: QUANTITY - the total of current refund value - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":["latest"],"id":1}' -```` - -## debug_traceBlockByHash - -Executes all transactions in the block specified by block hash with a tracer and returns the tracing result. - -### Parameters - -* DATA , 32 Bytes - Hash of a block. -* Object - The tracer options. See debug_traceBlockByNumber for more details. - -### Returns - - Array - Array of trace objects. See debug_traceBlockByNumber for more details. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_traceBlockByHash","params":["0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae"],"id":1}' -```` - -## debug_traceBlock - -Executes all transactions in the block given from the first argument with a tracer and returns the tracing result. - -### Parameters - -* DATA - RLP Encoded block bytes -* Object - The tracer options. See debug_traceBlockByNumber for more details. - -### Returns - - Array - Array of trace objects. See debug_traceBlockByNumber for more details. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_traceBlock","params":["0xf9...."],"id":1}' -```` - -## debug_traceTransaction - -Executes the transaction specified by transaction hash with a tracer and returns the tracing result. - -### Parameters - -* DATA , 32 Bytes - Hash of a transaction. -* Object - The tracer options. See debug_traceBlockByNumber for more details. - -### Returns - - Object - Trace object. See debug_traceBlockByNumber for more details. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae"],"id":1}' -```` - -## debug_traceCall - -Executes a new message call with a tracer and returns the tracing result. - -### Parameters - -* Object - The transaction call object - - + from: DATA, 20 Bytes - (optional) The address the transaction is sent from. - + to: DATA, 20 Bytes - The address the transaction is directed to. - + gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. - + gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas - + value: QUANTITY - (optional) Integer of the value sent with this transaction - + data: DATA - (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI in the Solidity documentation - -* QUANTITY|TAG - integer block number, or the string "latest" -* Object - The tracer options. See debug_traceBlockByNumber for more details. - -### Returns - - Object - Trace object. See debug_traceBlockByNumber for more details. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_traceCall","params":[{"to": "0x1234", "data": "0x1234"}, "latest", {}],"id":1}' -```` diff --git a/docs/docs/api/json-rpc-eth.md b/docs/docs/api/json-rpc-eth.md deleted file mode 100644 index aa5e5f0..0000000 --- a/docs/docs/api/json-rpc-eth.md +++ /dev/null @@ -1,580 +0,0 @@ -## eth_chainId - -Returns the currently configured chain id, a value used in replay-protected transaction signing as introduced by EIP-155. - -### Parameters - -* None - -### Returns - - -* QUANTITY - big integer of the current chain id. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' -```` - -## eth_syncing - -Returns information about the sync status of the node - -### Parameters - -* None - -### Returns - - -* Boolean (FALSE) - if the node isn't syncing (which means it has fully synced) - -* Object - an object with sync status data if the node is syncing - * startingBlock: QUANTITY - The block at which the import started (will only be reset, after the sync reached his head) - * currentBlock: QUANTITY - The current block, same as eth_blockNumber - * highestBlock: QUANTITY - The estimated highest block - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' -```` - -## eth_getBlockByNumber - -Returns block information by number. - -### Parameters - -* QUANTITY|TAG - integer of a block number, or the string "latest" -* Boolean - If true it returns the full transaction objects, if false only the hashes of the transactions. - -### Returns - -Object - A block object, or null when no block was found: - -* number: QUANTITY - the block number. -* hash: DATA, 32 Bytes - hash of the block. -* parentHash: DATA, 32 Bytes - hash of the parent block. -* nonce: DATA, 8 Bytes - hash of the generated proof-of-work. -* sha3Uncles: DATA, 32 Bytes - SHA3 of the uncles data in the block. -* logsBloom: DATA, 256 Bytes - the bloom filter for the logs of the block. -* transactionsRoot: DATA, 32 Bytes - the root of the transaction trie of the block. -* stateRoot: DATA, 32 Bytes - the root of the final state trie of the block. -* receiptsRoot: DATA, 32 Bytes - the root of the receipts trie of the block. -* miner: DATA, 20 Bytes - the address of the beneficiary to whom the mining rewards were given. -* difficulty: QUANTITY - integer of the difficulty for this block. -* totalDifficulty: QUANTITY - integer of the total difficulty of the chain until this block. -* extraData: DATA - the β€œextra data” field of this block. -* size: QUANTITY - integer the size of this block in bytes. -* gasLimit: QUANTITY - the maximum gas allowed in this block. -* gasUsed: QUANTITY - the total used gas by all transactions in this block. -* timestamp: QUANTITY - the unix timestamp for when the block was collated. -* transactions: Array - Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter. -* uncles: Array - Array of uncle hashes. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true],"id":1}' -```` - -## eth_getBlockByHash - -Returns block information by hash. - -### Parameters - -* DATA , 32 Bytes - Hash of a block. -* Boolean - If true it returns the full transaction objects, if false only the hashes of the transactions. - -### Returns - - Object - A block object, or null when no block was found: - -* number: QUANTITY - the block number. -* hash: DATA, 32 Bytes - hash of the block. -* parentHash: DATA, 32 Bytes - hash of the parent block. -* nonce: DATA, 8 Bytes - hash of the generated proof-of-work. -* sha3Uncles: DATA, 32 Bytes - SHA3 of the uncles data in the block. -* logsBloom: DATA, 256 Bytes - the bloom filter for the logs of the block. -* transactionsRoot: DATA, 32 Bytes - the root of the transaction trie of the block. -* stateRoot: DATA, 32 Bytes - the root of the final state trie of the block. -* receiptsRoot: DATA, 32 Bytes - the root of the receipts trie of the block. -* miner: DATA, 20 Bytes - the address of the beneficiary to whom the mining rewards were given. -* difficulty: QUANTITY - integer of the difficulty for this block. -* totalDifficulty: QUANTITY - integer of the total difficulty of the chain until this block. -* extraData: DATA - the β€œextra data” field of this block. -* size: QUANTITY - integer the size of this block in bytes. -* gasLimit: QUANTITY - the maximum gas allowed in this block. -* gasUsed: QUANTITY - the total used gas by all transactions in this block. -* timestamp: QUANTITY - the unix timestamp for when the block was collated. -* transactions: Array - Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter. -* uncles: Array - Array of uncle hashes. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByHash","params":["0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae",false],"id":1}' -```` - -## eth_blockNumber - -Returns the number of the most recent block. - -### Parameters - -None - -### Returns - - -* QUANTITY - integer of the current block number the client is on. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' -```` - -## eth_gasPrice - -Returns the current price of gas in wei. -If minimum gas price is enforced by setting the `--price-limit` flag, -this endpoint will return the value defined by this flag as minimum gas price. - ---- - -### Parameters - -None - -### Returns - - -* QUANTITY - integer of the current gas price in wei. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}' -```` - -## eth_getBalance - -Returns the balance of the account of the given address. - -### Parameters - -* DATA, 20 Bytes - address to check for balance. -* QUANTITY|TAG - integer block number, or the string "latest" - -### Returns - - -* QUANTITY - integer of the current balance in wei. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"],"id":1}' -```` - -## eth_sendRawTransaction - -Creates new message call transaction or a contract creation for signed transactions. - -### Parameters - -* DATA - The signed transaction data. - -### Returns - - -* DATA, 32 Bytes - the transaction hash, or the zero hash if the transaction is not yet available. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"],"id":1}' -```` - -## eth_getTransactionByHash - -Returns the information about a transaction requested by transaction hash. - -### Parameters - -* DATA, 32 Bytes - hash of a transaction - -### Returns - - Object - A transaction object, or null when no transaction was found: - -* blockHash: DATA, 32 Bytes - hash of the block where this transaction was in. -* blockNumber: QUANTITY - block number where this transaction was in. -* from: DATA, 20 Bytes - address of the sender. -* gas: QUANTITY - gas provided by the sender. -* gasPrice: QUANTITY - gas price provided by the sender in Wei. -* hash: DATA, 32 Bytes - hash of the transaction. -* input: DATA - the data send along with the transaction. -* nonce: QUANTITY - the number of transactions made by the sender prior to this one. -* to: DATA, 20 Bytes - address of the receiver. null when its a contract creation transaction. -* transactionIndex: QUANTITY - integer of the transactions index position in the block. -* value: QUANTITY - value transferred in Wei. -* v: QUANTITY - ECDSA recovery id -* r: DATA, 32 Bytes - ECDSA signature r -* s: DATA, 32 Bytes - ECDSA signature s - -### Example -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"],"id":1}' -```` - -## eth_getTransactionReceipt - -Returns the receipt of a transaction by transaction hash. - -Note That the receipt is not available for pending transactions. - -### Parameters - -* DATA, 32 Bytes - hash of a transaction - -### Returns - - Object - A transaction receipt object, or null when no receipt was found: - -* transactionHash : DATA, 32 Bytes - hash of the transaction. -* transactionIndex: QUANTITY - integer of the transactions index position in the block. -* blockHash: DATA, 32 Bytes - hash of the block where this transaction was in. -* blockNumber: QUANTITY - block number where this transaction was in. -* from: DATA, 20 Bytes - address of the sender. -* to: DATA, 20 Bytes - address of the receiver. null when its a contract creation transaction. -* cumulativeGasUsed : QUANTITY - The total amount of gas used when this transaction was executed in the block. -* gasUsed : QUANTITY - The amount of gas used by this specific transaction alone. -* contractAddress : DATA, 20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null. -* logs: Array - Array of log objects, which this transaction generated. -* logsBloom: DATA, 256 Bytes - Bloom filter for light clients to quickly retrieve related logs. - -It also returns either : - -* root : DATA 32 bytes - post-transaction stateroot (pre Byzantium) -* status: QUANTITY - either 1 (success) or 0 (failure) - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"],"id":1}' -```` - -## eth_getTransactionCount - -Returns the number of transactions sent from an address. - -### Parameters - -* DATA, 20 Bytes - address. -* QUANTITY|TAG - integer block number, or the string "latest" - -### Returns - - -* QUANTITY - integer of the number of transactions send from this address. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0x407d73d8a49eeb85d32cf465507dd71d507100c1","latest"],"id":1}' -```` - -## eth_getBlockTransactionCountByNumber - -Returns the number of transactions in a block matching the given block number. - -### Parameters - -* QUANTITY|TAG - integer of a block number, or the string "latest" - -### Returns - - -* QUANTITY - integer of the number of transactions in this block. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockTransactionCountByNumber","params":["latest"],"id":1}' -```` - -## eth_getLogs - -Returns an array of all logs matching a given filter object. - -### Parameters - Object - The filter options: - -* fromBlock: QUANTITY|TAG - (optional, default: "latest") Integer block number, or "latest" for the last mined block -* toBlock: QUANTITY|TAG - (optional, default: "latest") Integer block number, or "latest" for the last mined block -* address: DATA|Array, 20 Bytes - (optional) Contract address or a list of addresses from which logs should originate. -* topics: Array of DATA - (optional) Array of 32 Bytes DATA topics. Topics are order-dependent. Each topic can also be an array of DATA with β€œor” options. -* blockhash: DATA, 32 Bytes - (optional, future) With the addition of EIP-234, blockHash will be a new filter option which restricts the logs returned to the single block with the 32-byte hash blockHash. Using blockHash is equivalent to fromBlock = toBlock = the block number with hash blockHash. If blockHash is present in the filter criteria, then neither fromBlock nor toBlock is allowed. - -### Returns - - -* QUANTITY - integer of the number of transactions send from this address. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"topics": ["0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"]}],"id":1}' -```` - -## eth_getCode - -Returns code at a given address. - -### Parameters - -* DATA, 20 Bytes - address -* QUANTITY|TAG - integer block number, or the string "latest" - -### Returns - - -* DATA - the code from the given address. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getCode","params":["0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "0x2"],"id":1}' -```` - -## eth_call - -Executes a new message call immediately without creating a transaction on the blockchain. - -### Parameters - Object - The transaction call object - -* from: DATA, 20 Bytes - (optional) The address the transaction is sent from. -* to: DATA, 20 Bytes - The address the transaction is directed to. -* gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. -* gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas -* value: QUANTITY - (optional) Integer of the value sent with this transaction -* data: DATA - (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI in the Solidity documentation -* QUANTITY|TAG - integer block number, or the string "latest", see the default block paramete - -### Returns - - -* DATA - the return value of executed contract. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_call","params":[{see above}],"id":1}' -```` - -## eth_getStorageAt - -Returns the value from a storage position at a given address. - -### Parameters - -* DATA, 20 Bytes - address of the storage. -* QUANTITY - integer of the position in the storage. -* QUANTITY|TAG - integer block number, or the string "latest" - -### Returns - - -* DATA - the value at this storage position. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"],"id":1}' -```` - -## eth_estimateGas - -Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain. Note that the estimate may be significantly more than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance. - -### Parameters - -Expect that all properties are optional. - - Object - The transaction call object - -* from: DATA, 20 Bytes - The address the transaction is sent from. -* to: DATA, 20 Bytes - The address the transaction is directed to. -* gas: QUANTITY - Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. -* gasPrice: QUANTITY - Integer of the gasPrice used for each paid gas -* value: QUANTITY - Integer of the value sent with this transaction -* data: DATA - Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI in the Solidity documentation -* QUANTITY|TAG - integer block number, or the string "latest", see the default block paramete - -### Returns - - -* QUANTITY - the amount of gas used. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_estimateGas","params":[{see above}],"id":1}' -```` - -## eth_newFilter - -Creates a filter object, based on filter options. -To get all matching logs for specific filter, call eth_getFilterLogs. -To check if the state has changed, call eth_getFilterChanges. - -### Parameters - Object - The filter options: - -* fromBlock: QUANTITY|TAG - (optional, default: "latest") Integer block number, or "latest" for the last mined block -* toBlock: QUANTITY|TAG - (optional, default: "latest") Integer block number, or "latest" for the last mined block -* address: DATA|Array, 20 Bytes - (optional) Contract address or a list of addresses from which logs should originate. -* topics: Array of DATA - (optional) Array of 32 Bytes DATA topics. Topics are order-dependent. Each topic can also be an array of DATA with β€œor” options. - -### Returns - - -* QUANTITY - A filter id. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_newFilter","params":[{"topics":["0x12341234"]}],"id":1}' -```` - -## eth_newBlockFilter - -Creates a filter in the node, to notify when a new block arrives. -To check if the state has changed, call eth_getFilterChanges. - -### Parameters - -None - -### Returns - - -1. QUANTITY - A filter id. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_newBlockFilter","params":[],"id":1}' -```` - - - -## eth_getFilterLogs - -Returns an array of all logs matching filter with given id. - -:::caution eth_getLogs vs. eth_getFilterLogs -These 2 methods will return the same results for same filter options: -1. eth_getLogs with params [options] -2. eth_newFilter with params [options], getting a [filterId] back, then calling eth_getFilterLogs with [filterId] -::: - -### Parameters - -* QUANTITY - the filter id. - -### Returns - - Array - Array of log objects, or an empty array - -* For filters created with eth_newFilter logs are objects with the following params: - * removed: TAG - true when the log was removed, due to a chain reorganization. false if its a valid log. - * logIndex: QUANTITY - integer of the log index position in the block. null when its pending log. - * transactionIndex: QUANTITY - integer of the transactions index position log was created from. null when its pending log. - * transactionHash: DATA, 32 Bytes - hash of the transactions this log was created from. null when its pending log. - * blockHash: DATA, 32 Bytes - hash of the block where this log was in. null when its pending log. - * blockNumber: QUANTITY - the block number where this log was in. null when its pending log. - * address: DATA, 20 Bytes - address from which this log originated. - * data: DATA - contains one or more 32 Bytes non-indexed arguments of the log. - * topics: Array of DATA - Array of 0 to 4 32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.) - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getFilterLogs","params":["0x16"],"id":1}' -```` - -## eth_getFilterChanges - -Polling method for a filter, which returns an array of logs that occurred since the last poll. - -### Parameters - -* QUANTITY - the filter id. - -### Returns - - Array - Array of log objects, or an empty array if nothing has changed since last poll. - -* For filters created with eth_newBlockFilter the return are block hashes (DATA, 32 Bytes), e.g. ["0x3454645634534..."]. -* For filters created with eth_newFilter logs are objects with the following params: - * removed: TAG - true when the log was removed, due to a chain reorganization. false if its a valid log. - * logIndex: QUANTITY - integer of the log index position in the block. null when its pending log. - * transactionIndex: QUANTITY - integer of the transactions index position log was created from. null when its pending log. - * transactionHash: DATA, 32 Bytes - hash of the transactions this log was created from. null when its pending log. - * blockHash: DATA, 32 Bytes - hash of the block where this log was in. null when its pending log. - * blockNumber: QUANTITY - the block number where this log was in. null when its pending log. - * address: DATA, 20 Bytes - address from which this log originated. - * data: DATA - contains one or more 32 Bytes non-indexed arguments of the log. - * topics: Array of DATA - Array of 0 to 4 32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.) - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getFilterChanges","params":["0x16"],"id":1}' -```` - -## eth_uninstallFilter - -Uninstalls a filter with a given id. Should always be called when a watch is no longer needed. -Additionally, filters timeout when they aren’t requested with eth_getFilterChanges for some time. - -### Parameters - -* QUANTITY - The filter id. - -### Returns - - -* Boolean - true if the filter was successfully uninstalled, otherwise false. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_uninstallFilter","params":["0xb"],"id":1}' -```` - -## eth_unsubscribe - -Subscriptions are cancelled with a regular RPC call with eth_unsubscribe as a method and the subscription id as the first parameter. It returns a bool indicating if the subscription was cancelled successfully. - -### Parameters - -* SUBSCRIPTION ID - -### Returns - - -* UNSUBSCRIBED FLAG - true if the subscription was cancelled successful. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_unsubscribe","params":["0x9cef478923ff08bf67fde6c64013158d"],"id":1}' -```` diff --git a/docs/docs/api/json-rpc-net.md b/docs/docs/api/json-rpc-net.md deleted file mode 100644 index 002d23e..0000000 --- a/docs/docs/api/json-rpc-net.md +++ /dev/null @@ -1,56 +0,0 @@ -## net_version - -Returns the current network id. - -### Parameters - -None - -### Returns - - -* String - The current network id. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"net_version","params":[],"id":83}' -```` - -## net_listening - -Returns true if a client is actively listening for network connections. - -### Parameters - -None - -### Returns - - -* Boolean - true when listening, otherwise false. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"net_listening","params":[],"id":83}' -```` - -## net_peerCount - -Returns number of peers currently connected to the client. - -### Parameters - -None - -### Returns - - -* QUANTITY - integer of the number of connected peers. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}' -```` diff --git a/docs/docs/api/json-rpc-txpool.md b/docs/docs/api/json-rpc-txpool.md deleted file mode 100644 index 9f5a5de..0000000 --- a/docs/docs/api/json-rpc-txpool.md +++ /dev/null @@ -1,40 +0,0 @@ -## txpool_content - -Returns a list with the exact details of all the transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. - -### Parameters - -None - -### Example -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"txpool_content","params":[],"id":1}' -```` - -## txpool_inspect - -Returns a list with a textual summary of all the transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. This is a method specifically tailored to developers to quickly see the transactions in the pool and find any potential issues. - -### Parameters - -None - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"txpool_inspect","params":[],"id":1}' -```` - -## txpool_status - -Returns the number of transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. - -### Parameters - -None - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"txpool_status","params":[],"id":1}' -```` diff --git a/docs/docs/api/json-rpc-web3.md b/docs/docs/api/json-rpc-web3.md deleted file mode 100644 index 0dabe70..0000000 --- a/docs/docs/api/json-rpc-web3.md +++ /dev/null @@ -1,37 +0,0 @@ -## web3_clientVersion - -Returns the current client version. - -### Parameters - -None - -### Returns - - -* String - The current client version - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' -```` - -## web3_sha3 - -Returns Keccak-256 (not the standardized SHA3-256) of the given data. - -### Parameters - -* DATA - the data to convert into a SHA3 hash - -### Returns - - -* DATA - The SHA3 result of the given string. - -### Example - -````bash -curl https://rpc-endpoint.io:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":1}' -```` diff --git a/docs/docs/api/json-rpc-xgr.md b/docs/docs/api/json-rpc-xgr.md deleted file mode 100644 index 648fa83..0000000 --- a/docs/docs/api/json-rpc-xgr.md +++ /dev/null @@ -1,22 +0,0 @@ -# JSON-RPC XGR API - -## xgr_getNextProcessId - -Returns the next sequential `processId` for the provided address. The result is a decimal string. - -Example: - -```json -{"jsonrpc":"2.0","id":1,"method":"xgr_getNextProcessId","params":[{"from":"0x"}]} -``` - -## xgr_validateDataTransfer - -The `processId` field in permits is a `uint256` and is serialized as a decimal string in JSON-RPC. This method returns a quick acknowledgement without executing the session. The response contains a `status` field with one of the following values: - -| status | meaning | -|-----------------|-----------------------------------------------------------| -| `queued` | new session or due step queued for immediate execution | -| `already-queued`| session was already active; no additional action taken | -| `scheduled` | session is waiting for its scheduled wake-up time | -| `paused` | session is paused and was not re-queued | diff --git a/docs/docs/design/blockchain.md b/docs/docs/design/blockchain.md deleted file mode 100644 index e16d706..0000000 --- a/docs/docs/design/blockchain.md +++ /dev/null @@ -1,18 +0,0 @@ -Edge-powered chains are built on a common blockchain design that effectively manages and maintains the blockchain data structure, consisting of a sequential chain of blocks containing transactions and other metadata; a state database. - -The core blockchain implementation offers various functionalities, such as: - -- Adding new blocks to the chain. -- Retrieving blocks using their hash or number. -- Handling chain reorganizations (i.e., switching to a different chain with greater difficulty). -- Verifying block headers and gas limits. -- Caching block headers and receipts to improve retrieval speed. -- Updating the chain's average gas price. - -To provide these functionalities, the implementation employs several sub-components, including: - -- A consensus component responsible for validating new blocks and incorporating them into the chain. -- A database component that persistently stores blockchain data. -- An event component that notifies other components of chain reorganizations and new block additions. -- A transaction signer component that verifies transaction signatures and identifies the sender address. -- A gas price calculator component that computes the chain's average gas price. diff --git a/docs/docs/design/bridge/assets/erc/erc1155.md b/docs/docs/design/bridge/assets/erc/erc1155.md deleted file mode 100644 index 6f21a0b..0000000 --- a/docs/docs/design/bridge/assets/erc/erc1155.md +++ /dev/null @@ -1,19 +0,0 @@ -ERC-1155 is a token standard that enables the creation of both fungible and non-fungible tokens on a blockchain network. In the context of Edge, ERC-1155 tokens can be used to represent multiple instances of a single fungible or non-fungible asset on an Edge-powered chain. - -## ChildERC1155 - -Developers can use the [`ChildERC1155`](../../../../interfaces/erc1155/childerc1155.md) token standard to create ERC-1155 tokens on an Edge-powered chain. To create a new ChildERC1155 token, developers can use the `ChildERC1155` contract as a template and deploy it on the Edge-powered chain. The contract requires a name and symbol to determine the unique identity of the token. - -Once created, `ChildERC1155` tokens can be minted and burned on the Edge-powered chain through the corresponding ERC1155Predicate contract. This ensures that the supply of tokens on the rootchain and the Edge-powered chain remains in sync. The `ERC1155Predicate` contract also allows for the transfer of tokens between the two. - -## Predicates - -The [`RootERC1155Predicate`](../../../../interfaces/erc1155/rooterc1155-predicate.md) contract serves as the root token contract, while the [`ChildERC1155Predicate`](../../../../interfaces/erc1155/childerc1155-predicate.md) contract serves as the child token contract. When a user wants to deposit an ERC-1155 token on the rootchain into the Edge-powered chain, they call the deposit function on the `RootERC1155Predicate` contract with the appropriate parameters, including the token ID, sender, and receiver addresses. - -Once the child contract instance is created, the `RootERC1155Predicate` contract maps the root token to the child token using a mapping, allowing users to deposit and withdraw tokens between the root and Edge-powered chain. The ChildERC1155Predicate contract includes functions for minting and burning child tokens, which are triggered when the deposit and withdrawal functions are called. - -To withdraw a child token back to the rootchain, a user calls the appropriate function on the ChildERC1155Predicate contract with the necessary parameters, including the token ID, the amount to withdraw, and the receiver address. The `ChildERC1155Predicate` contract then burns the child token and emits a withdrawal event. The `RootERC1155Predicate` contract listens for these events and relays them to the rootchain using the `L2StateSender` contract. - -## Deposits and Withdrawals - -Depositing and withdrawing ERC-1155 tokens between the rootchain and Edge-powered chain can be done using the `deposit` and `withdraw` functions in the `RootERC1155Predicate` and `ChildERC1155Predicate` contracts, respectively. The `ChildERC1155Predicate` contract includes functions for minting and burning child tokens, which are triggered when the deposit and withdrawal functions are called. To withdraw a child token back to the rootchain, a user calls the appropriate function on the `ChildERC1155Predicate` contract with the necessary parameters, including the token ID, the amount to withdraw, and the receiver address. The `ChildERC1155Predicate` contract then burns the child token and emits a withdrawal event. diff --git a/docs/docs/design/bridge/assets/erc/erc20.md b/docs/docs/design/bridge/assets/erc/erc20.md deleted file mode 100644 index 25a8d42..0000000 --- a/docs/docs/design/bridge/assets/erc/erc20.md +++ /dev/null @@ -1,40 +0,0 @@ -Edge provides developers with two standards for creating ERC-20 fungible tokens on their networks: `NativeERC20` and `ChildERC20`. These standards are based on the widely-used ERC-20 standard and offer similar functionality, including the ability to transfer tokens between addresses, approve others to spend tokens on your behalf, and check your balance. However, there are significant differences in their deployment and management. - -## ChildERC20 - -The [`ChildERC20`](../../../../interfaces/erc20/childerc20.md) token standard is used for tokens mapping and enables developers to create tokens on both the Edge-powered chain and the associated rootchain. To create a new `ChildERC20` token, developers can use the `ChildERC20` contract as a template and deploy it on their Edge-powered chain. The contract requires a name, symbol, and number of decimals to determine the minimum unit of the token. - -`ChildERC20` tokens are minted and burned on the Edge-powered chain through the corresponding [`ERC20Predicate`](../../../../interfaces/erc20/childerc20-predicate.md) contract. This ensures that the supply of tokens on the rootchain and the Edge-powered chain remains in sync. The `ERC20Predicate` contract also allows for the transfer of tokens between the two networks using the native bridge. - -## NativeERC20 - -The [`NativeERC20`](../../../../interfaces/erc20/native-erc20.md) token standard represents native tokens on Edge and offers fast and inexpensive transactions. It is deployed only on the Edge-powered chain and relies on the native transfer precompile to make transfers. The `NativeERC20` tokens can be minted and burned by the associated predicate contract. - -In addition, developers can use the `NativeERC20Mintable` contract to create and manage `NativeMintable` tokens, which are fungible tokens that represent assets on the Edge-powered chain. These tokens can be managed through the native bridge contract and transferred between an Edge-powered chain and rootchain networks. - -## Deposits and Withdrawals - -The deposit and withdrawal functionality plays a critical role in bridging ERC-20 tokens between a rootchain and an Edge-powered chain. - -When a user wants to deposit ERC-20 tokens into an Edge-powered chain, they call the `deposit` function. This function maps the root token to a child token and then mints the equivalent amount of child tokens to the user's address on the Edge-powered chain. By doing this, the ERC-20 tokens are effectively transferred from the rootchain to the Edge-powered chain. The user can then use these tokens on the Edge-powered chain or transfer them to other addresses on the network. - -On the other hand, when a user wants to withdraw ERC-20 tokens from an Edge-powered chain to the rootchain, they call the `withdraw` function. This function burns the equivalent amount of child tokens on the Edge-powered chain and then triggers a function on the rootchain that transfers the equivalent amount of root tokens to the user's address on the rootchain. By doing this, the ERC-20 tokens are effectively transferred from the Edge-powered chain back to the rootchain. - -Both the `deposit` and `withdraw` functions emit events that log the amount and token involved in the transaction. This provides transparency and allows users to track the movement of their tokens between the two networks. - -### EIP1559Burn - -The dynamic gas fee mechanism implements a base fee per gas to improve transaction fee predictability and efficiency. The base fee adjusts dynamically based on gas target deviations and is burned within the protocol. - -- **Burn Contract**: The burn contract parameter specifies the contract address where fees will be sent and utilized. To enable the London hard fork and set the burn contract address, use the `--burn-contract` flag in the genesis command with the following format: `:
`. - -- **Genesis Base Fee**: The genesis base fee parameter sets the initial base fee (in GWEI) for the genesis block. Include this value in the genesis.json file during the genesis command. By default, the genesis base fee is set to 1 GWEI. - -- **Genesis Base FeeEM**: The genesis base fee elasticity multiplier determines the base fee for blocks following the genesis block. It is initially set to 2. If the London hard fork is disabled (i.e., when the burn contract is not set), the base fee remains the same as the genesis base fee. - -- **Burn Contract Parameter Revision**: The burn contract address can be adjusted during a node reboot, which is achieved by modifying the `burnContract` parameter in the genesis.json file. It's crucial to comprehend, however, that merely identifying the burn contract address does not implicitly activate the EIP-1559 feature. If the EIP-1559 was not initialized during genesis, a series of procedures are required to activate it subsequently: - -- The burn contract should be properly deployed onto the Edge-powered chain. -- The genesis file must detail the burn contracts map, incorporating both the block number and the burn contract address. -- The base fee and base fee elasticity multiplier are required to be manually defined in the genesis file. -- Finally, the EIP1559 fork must be activated. diff --git a/docs/docs/design/bridge/assets/erc/erc721.md b/docs/docs/design/bridge/assets/erc/erc721.md deleted file mode 100644 index 7c64a17..0000000 --- a/docs/docs/design/bridge/assets/erc/erc721.md +++ /dev/null @@ -1,18 +0,0 @@ - -ERC-721 is a token standard that allows for the creation of unique, non-fungible tokens on a blockchain network. In the context of Edge, ERC-721 tokens can be used to represent unique assets. - -## ChildERC721 - -Developers can use the [`ChildERC721`](../../../../interfaces/erc721/childerc721.md) token standard to create ERC-721 tokens on the Edge-powered chain. To create a new ChildERC721 token, developers can use the ChildERC721 predicate contract as a template and deploy it on the Edge-powered chain. The contract requires a name and symbol to determine the unique identity of the token. - -Once created, `ChildERC721` tokens can be minted and burned on the Edge-powered chain through the corresponding `ERC721Predicate` contract. This ensures that the supply of tokens on the rootchain and the Edge-powered chain remains in sync. The `ERC721Predicate` contract also allows for the transfer of tokens between the two networks using the native bridge. - -## Predicates - -The [`RootERC721Predicate`](../../../../interfaces/erc721/rooterc721-predicate.md) contract serves as the rootchain token predicate contract, while the [`ChildERC721Predicate`](../../../../interfaces/erc721/childerc721-predicate.md) contract serves as the Edge-powered chain token predicate contract. When a user wants to deposit an ERC-721 token on the rootchain into the Edge-powered chain, they call the `deposit` function on the `RootERC721Predicate` contract with the appropriate parameters, including the token ID, sender, and receiver addresses. - -When a user deposits a token for the first time, the token mapping between the root and Edge-powered chain is created automatically. This mapping allows users to deposit and withdraw tokens between the root and Edge-powered chain. - -## Deposits and Withdrawals - -The `ChildERC721Predicate` contract includes functions for minting and burning child tokens, which are triggered when the deposit and withdrawal functions are called. To withdraw a child token back to the rootchain, a user calls the appropriate function on the `ChildERC721Predicate` contract with the necessary parameters, including the token ID and receiver address. The `ChildERC721Predicate` contract then burns the child token and emits a withdrawal event. The `RootERC721Predicate` contract listens for these events and relays them to the rootchain using the `L2StateSender` contract. diff --git a/docs/docs/design/bridge/checkpoint.md b/docs/docs/design/bridge/checkpoint.md deleted file mode 100644 index e74e9af..0000000 --- a/docs/docs/design/bridge/checkpoint.md +++ /dev/null @@ -1,35 +0,0 @@ - -## Introduction - -Checkpointing is the process of recording and committing a snapshot of the state of a system at a specific point in time. In the context of blockchain, checkpoints are used to increase security by allowing faster verification of the state of the blockchain without needing to process all transactions from the genesis block. Checkpoints can be used to speed up sync times for new nodes and help prevent certain types of attacks, such as 51% attacks. - -## Checkpoints in Edge - -!!! info "Key point" - - - The checkpoints are made **by the validators on the Edge-powered chain** and are committed to the rootchain. - - A checkpoint serves as a snapshot of the Edge state. This snapshot is stored as a Merkle root and **represents the state of the Edge-powered chain** at that point in time it was created. - - The checkpoint process is important for **ensuring the security of the network** as it enables the rootchain to detect and prevent any potential fraud or malicious activity on the child. - - -### CheckpointManager - -A `CheckpointManager` contract responsible for managing checkpoints in the network. - -The checkpoints represent a snapshot of the Edge state, which is periodically checkpointed to the rootchain by the validators. The checkpoints are used as a reference point for the rootchain to verify the integrity and accuracy of the data on the Edge-powered chain. - -The contract has several functions to facilitate the management of checkpoints, such as submitting a new checkpoint with metadata, verifying signatures, and getting the event root by block number or epoch. The contract also has a mapping to store the checkpoints and the current validator set, and an array to keep track of the checkpoint block numbers. - -The contract uses a Merkle tree to efficiently prove the membership of an event in the Edge state. The tree is constructed using the hashed exit events sent by the L2StateSender, which the Edge client saves to its local storage after their transactions are executed. The membership proofs can be verified using the Merkle proofs provided by the users. - -The contract also implements a BLS signature scheme to verify the signatures submitted by the validators. The validators' signatures are aggregated, and the contract checks whether the required voting power threshold is met to accept the checkpoint. - -!!! info "Details of the checkpoint" - - To elaborate, the root of the Merkle tree is a hash value that represents a specific subset of the Edge state at a specific point in time. This state includes only the exit events sent by the L2StateSender contract. When a user wants to exit the L2 chain (transfer their tokens from L2 to L1), their exit transaction is included in this Merkle tree. - - When a checkpoint is made, the root of the Merkle tree is included as part of the checkpoint, along with other metadata. This checkpoint is then sent to the rootchain where it is verified and stored by the validators. - - Later, when a user wants to verify a particular exit event on the Edge-powered chain, they can provide a Merkle proof, which is a cryptographic proof that demonstrates the inclusion of a particular exit event in the Merkle tree. The Merkle proof can be verified by the rootchain using the root of the Merkle tree, which was included in the checkpoint. - - In short, the root of the Merkle tree is a compact representation of the exit events on the Edge-powered chain at a specific point in time, which is included in checkpoints and used for verification purposes. diff --git a/docs/docs/design/bridge/overview.md b/docs/docs/design/bridge/overview.md deleted file mode 100644 index 46c2274..0000000 --- a/docs/docs/design/bridge/overview.md +++ /dev/null @@ -1,41 +0,0 @@ -## Overview - -Edge provides a built-in bridging mechanism that enables cross-chain communication. The bridging mechanism is a technical infrastructure that facilitates the transfer of arbitrary messages between any EVM-compatible PoS blockchain (rootchain), and an Edge-powered chain. - -It relies on mapping between the token contracts on the rootchain and the target chain, which is crucial for tracking assets and ensuring the correct amount of tokens are minted and burned during the transfer process. - -!!! caution "Bridge as an Attack Vector" - The cross-chain bridge can be an attack vector if not properly managed. Ensure you fully understand its functionality, potential vulnerabilities, and security measures before use. Expertise in this area is crucial for maintaining system security. - - -During the transfer process, assets are locked in a contract on the rootchain, and an equivalent amount of tokens are minted on the target chain. When assets are withdrawn from the target chain, the corresponding tokens are burned, and the assets are unlocked on the rootchain. Smart contracts on both the rootchain and the target chain are used to facilitate these processes, ensuring that the asset transfer is secure and transparent. - -!!! note "Enabled by Default" - - The cross-chain bridge in Edge is an integral part of the consensus client and is enabled by default. It cannot be disabled, as it is required for the functioning of the network. Being native to Edge, the bridge is built into the protocol and does not require any external third-party solutions or plug-ins. - - If you choose to use another cross-chain bridging mechanism, you will need to customize it yourself, and there are no guarantees of compatibility with associated contracts and infrastructure. - - -
- bridge -
-

The following diagram provides a visual representation of how messages can be passed between different EVM blockchain layers, allowing for seamless message transfers and coordination between various components of a Super network.

-

How does message passing work?

-

StateSync: real-time synchronization

-

Message passing between a rootchain and an Edge-powered chain is achieved through continuous state synchronization, known as StateSync. This process involves transferring state data between system calls.

-

Check out the StateSync document to learn more.

-

Checkpoints: Ensuring liveliness and reference points

-

When passing messages from an Edge-chain to a rootchain, the validator set commits checkpoints, which are snapshots of the Edge state containing only the root of the Exit events, excluding all transactions. Checkpoints serve as reference points for clients, and validators periodically checkpoint all transactions occurring on the Edge-powered chain to the rootchain. Checkpoints also ensure liveliness and are submitted to the associated rootchain asset contract.

-

Check out the Checkpoint document to learn more.

-

Bridge States: tracking event progress

-

The bridge can exist in one of three states:

-
    -
  • Pending: Events are waiting to be bundled and sent over.
  • -
  • Committed: Event data has been relayed to the associated chain.
  • -
  • Executed: The event has been committed, and the state executed, resulting in a state change.
  • -
-

Assets: supporting a range of standards

-

The bridge is compatible with various token standards, including ERC-20, ERC-721, and ERC-1155, enabling a wide range of assets to be transferred between chains.

-
-
diff --git a/docs/docs/design/bridge/sequences.md b/docs/docs/design/bridge/sequences.md deleted file mode 100644 index 7134412..0000000 --- a/docs/docs/design/bridge/sequences.md +++ /dev/null @@ -1,64 +0,0 @@ -## Deposit - -Bridge ERC 20 tokens from rootchain to childchain via deposit. - -```mermaid -sequenceDiagram - User->>Edge: deposit - Edge->>RootERC20.sol: approve(RootERC20Predicate) - Edge->>RootERC20Predicate.sol: deposit() - RootERC20Predicate.sol->>RootERC20Predicate.sol: mapToken() - RootERC20Predicate.sol->>StateSender.sol: syncState(MAP_TOKEN_SIG), recv=ChildERC20Predicate - RootERC20Predicate.sol-->>Edge: TokenMapped Event - StateSender.sol-->>Edge: StateSynced Event to map tokens on child predicate - RootERC20Predicate.sol->>StateSender.sol: syncState(DEPOSIT_SIG), recv=ChildERC20Predicate - StateSender.sol-->>Edge: StateSynced Event to deposit on child chain - Edge->>User: ok - Edge->>StateReceiver.sol:commit() - StateReceiver.sol-->>Edge: NewCommitment Event - Edge->>StateReceiver.sol:execute() - StateReceiver.sol->>ChildERC20Predicate.sol:onStateReceive() - ChildERC20Predicate.sol->>ChildERC20.sol: mint() - StateReceiver.sol-->>Edge:StateSyncResult Event -``` - -## Withdraw - -Bridge ERC 20 tokens from childchain to rootchain via withdrawal. - -```mermaid -sequenceDiagram - User->>Edge: withdraw - Edge->>ChildERC20Predicate.sol: withdrawTo() - ChildERC20Predicate.sol->>ChildERC20: burn() - ChildERC20Predicate.sol->>L2StateSender.sol: syncState(WITHDRAW_SIG), recv=RootERC20Predicate - Edge->>User: tx hash - User->>Edge: get tx receipt - Edge->>User: exit event id - ChildERC20Predicate.sol-->>Edge: L2ERC20Withdraw Event - L2StateSender.sol-->>Edge: StateSynced Event - Edge->>Edge: Seal block - Edge->>CheckpointManager.sol: submit() -``` -## Exit - -Finalize withdrawal of ERC 20 tokens from childchain to rootchain. - -```mermaid -sequenceDiagram - User->>Edge: exit, event id:X - Edge->>Edge: bridge_generateExitProof() - Edge->>CheckpointManager.sol: getCheckpointBlock() - CheckpointManager.sol->>Edge: blockNum - Edge->>Edge: getExitEventsForProof(epochNum, blockNum) - Edge->>Edge: createExitTree(exitEvents) - Edge->>Edge: generateProof() - Edge->>ExitHelper.sol: exit() - ExitHelper.sol->>CheckpointManager.sol: getEventMembershipByBlockNumber() - ExitHelper.sol->>RootERC20Predicate.sol:onL2StateReceive() - RootERC20Predicate.sol->>RootERC20: transfer() - Edge->>User: ok - RootERC20Predicate.sol-->>Edge: ERC20Withdraw Event - ExitHelper.sol-->>Edge: ExitProcessed Event -``` - diff --git a/docs/docs/design/bridge/statesync.md b/docs/docs/design/bridge/statesync.md deleted file mode 100644 index decaa32..0000000 --- a/docs/docs/design/bridge/statesync.md +++ /dev/null @@ -1,37 +0,0 @@ -## Introduction - -State synchronization (StateSync) is a mechanism used to update the state of a contract on the Edge-powered chain based on events occurring on the rootchain. It is a critical component of blockchain technology as it enables secure and efficient communication between the two chains. State synchronization allows for a more efficient and secure way to update the chain state on the Edge-powered chain without needing to process all transactions from the genesis block. - -## StateSync in Edge - -!!! info "Key points" - - - `StateSync` enables the efficient and secure transfer of data between an Edge-powered chain and rootchain. - - `StateSync` is initiated on the Edge-powered chain through the `StateSender` contract and executed on the rootchain through the `StateReceiver` contract. - - The `StateSync` process is used to update the state of a contract on the rootchain based on events occurring on the Edge-powered chain. - -### StateSender - -The `StateSender` contract is deployed on the rootchain and is triggered by either the associated rootchain predicate or `SupernetManager` rootchain contract. Its main responsibility is to generate sync state events based on the provided data and receiver address. Anyone can call the `syncState` function to emit a sync state event. The data is sent along with the event and represents the state change that needs to be executed on the Edge-powered chain. - -### StateReceiver - -The `StateReceiver` contract is deployed on the Edge and is responsible for executing and relaying the state data sent from the rootchain. It receives the state change data from the rootchain contract bundled up in the form of a commitment, sent with the Merkle Tree root hash. This tree is created by bundling a number of `StateSync` events received by the `StateSender`. Commitments are submitted to the `StateReceiver` by a block proposer, and it is a system (state) transaction. They are used to verify the execution of state data from the rootchain to the Edge-powered chain, such as transferring funds from rootchain to Edge. Commitments are similar to checkpoints but are used in the process of transferring data from rootchain to Edge, while checkpoints are used in the process of transferring data from Edge to rootchain. - -## L2StateSender and ExitHelper - -To enable communication from the Edge-powered chain to the rootchain, the `L2StateSender` contract resides on an Edge-powered chain and is responsible for emitting `L2StateSyncs` (also referred to as exit events). These events are indexed by the rootchain validators and submitted as a checkpoint on the rootchain, allowing for lazy execution. Unlike the `StateSender`, there is no transaction execution on the rootchain for the `L2StateSender`. - -On the rootchain, the `ExitHelper` contract is responsible for verifying the execution of the exit events and enabling users to withdraw their funds from the Edge-powered chain to the rootchain. It is analogous to the StateReceiver on the Edge-powered chain, and both contracts work together to enable two-way communication between the rootchain and the Edge-powered chain. - -!!! info "Synchronization and commitments" - - The `StateSync` process involves two main steps: synchronization and commitments. - - In the synchronization step, the `StateSender` contract on the rootchain generates sync state events based on receiver and data. The `syncState` function allows anyone to call this method to emit an event. The data is sent along with the event and represents the state change that needs to be executed on the Edge. - - In the commitments step, the `StateReceiver` contract on the Edge-powered chain receives the state change data along with a Merkle proof from the `StateSender` contract and verifies the proof to ensure the data's integrity. If the proof is valid, the state change is executed on the Edge-powered chain. - - To ensure the validity of the state change, the `StateSender` contract generates a unique id for each sync state event. This id is used by the `StateReceiver` contract to prevent replay attacks, which could result in the execution of duplicate state changes. - - The `StateReceiver` contract also implements a BLS signature scheme to verify the signatures submitted by the validators. The validators' signatures are aggregated, and the contract checks whether the required voting power threshold is met to accept the state change. diff --git a/docs/docs/design/consensus/polybft/ibft.md b/docs/docs/design/consensus/polybft/ibft.md deleted file mode 100644 index f2976f4..0000000 --- a/docs/docs/design/consensus/polybft/ibft.md +++ /dev/null @@ -1,54 +0,0 @@ -## Consensus Engine - -The PolyBFT consensus mechanism uses the IBFT 2.0 consensus engine to agree on adding new blocks to the blockchain. The validator pool in IBFT 2.0 is responsible for validating candidate blocks proposed by a randomly selected block proposer who is part of the validator pool. The proposer is responsible for constructing a block at the block interval. The proposer mechanism is based on [Tendermint](https://tendermint.com/), where a proposer is chosen based on a deterministic selection algorithm. The frequency of selection is proportional to the voting power of the validator. - -Each block in IBFT 2.0 requires at least one round of voting by the validator to arrive at consensus, which is recorded as a collection of signatures on the block content. In general, a supermajority of validators must confirm that a block is valid for the block to be added to the blockchain. Only when there is no consensus on a given block, multiple rounds of voting are needed. The ideal path would be when the validator pool reaches consensus on a candidate block in the first round of voting, and the block is added to the blockchain without the need for additional rounds of voting. This is the most efficient and optimal outcome, as it allows the network to continue processing transactions and adding new blocks to the chain in a timely manner. - -A validator's voting power is proportional to the amount of stake they have locked up on the network. This means that validators with more stake will have more voting power and, therefore, more influence over the decision-making process on the network. This also provides an economic incentive for validators to behave honestly and act in the network's best interest. - -!!! info "Sealing a block" - - ### State transitions - - PolyBFT's consensus mechanism follows a series of state transitions that ensure network-wide consensus on the blockchain's state. The consensus process starts with a validator proposing a new block to be added to the blockchain, which includes a list of transactions to update the blockchain's state. - - Next, validators in the active set vote on whether to accept the proposed block. Each validator's voting weight determines their influence in voting, and a supermajority of validators must agree to accept the block for consensus to be reached. The protocol tracks the current block's position, also known as its **sequence**. - - The process to finalize a block in PolyBFT is referred to as **sealing**. When a validator proposes a new block, other validators vote on whether to accept it, and this process can be repeated multiple times. Each repetition is called a **round**, and during each round, a set number of validators must agree to seal the proposed block for it to be added to the blockchain. If the required number of votes isn't reached during a particular round, the voting process will continue into the next round, and the protocol "increases the round". Another validator will then attempt to seal the sequence in the new round. - - If the proposed block is accepted, it's added to the blockchain, and the state of the blockchain is updated to reflect the changes introduced by the transactions in the block. This process continues with the next proposer proposing a new block, and the process repeats. - -### Validator Set - -PolyBFT limits network participation to around 100 validators, and a variable amount of stake is used as a fixed stake criterion to limit the system's security and can make the system economically vulnerable. The validator set in the PolyBFT does not update on each block but is fixed during n block periods known as an epoch. - -The `n` block period to define one epoch is determined by `genesis` configuration, and until then, validators will remain the same. At the end of the epoch, a special state transaction to validatorSetManagementContract is emitted, notifying the system about the validators' uptime during the epoch. It is up to the smart contract to reward validators by their uptime and update the validator set for the next epoch. There is a function getValidatorSet which returns the current validator set at any time. - -!!! note - Proposer selection algorithm - Section is being updated - -The proposer selection algorithm is Tendermint-based. - -### Staking - -Staking is managed by staking contracts on the Edge-powered chain. The staking module on Polygon validates staked tokens and is independent of Ethereum's security. In principle, the network is secured by the rootchain and Ethereum. Transaction checkpoints still occur on Ethereum, but Ethereum does not validate staking on Polygon. - -At the end of each epoch, a reward calculation occurs to reward validators who actively participated in that epoch. - -!!! info - Staking details and rewards - Section is being updated - - -## Benefits - -### Consensus benefits - -- **Immediate block finality**: Only one block is proposed at a given chain height; thus, the single chain removes forking, uncle blocks, and the risk that a transaction may be "undone" once on the chain later. -- **Reduced time between blocks**: The effort needed to construct and validate blocks is decreased significantly, which increases the chain's throughput. -- **High data integrity and fault tolerance**: IBFT 2.0 uses a pool of validators to ensure the integrity of each proposed block. A supermajority (~66%) of these validators must sign the block before insertion into the chain, making block forgery very difficult. Also, the proposer of the block rotates over time, ensuring a faulty node cannot exert long-term influence over the chain. -- **Operationally flexible**: The validators can be modified quickly, ensuring the group contains only fully trusted nodes. - -### Network benefits - -- **Liveness**: It has been proven that IBFT 2.0 does not guarantee BFT persistence nor liveness when operating on a synchronous network. If a validator receives enough confirmation about a block, it can lock the proposed block (assuming it has not locked any prior). If a change were to occur because of a fault in the network, it could trigger the activation of the round change protocol, where the protocol would expect to commit the locked block at that specific height. -- **Persistence**: If, for instance, there is a faulty network condition present where two different node subsets lock to two different blocks, the system enters into an infinite cycle of state transitions that cannot converge states and finalize the block. diff --git a/docs/docs/design/consensus/polybft/overview.md b/docs/docs/design/consensus/polybft/overview.md deleted file mode 100644 index 2707897..0000000 --- a/docs/docs/design/consensus/polybft/overview.md +++ /dev/null @@ -1,22 +0,0 @@ -## Overview - -PolyBFT is a sophisticated and robust consensus mechanism employed by XGRChain. -The consensus mechanism comprises two key components, a **consensus engine** and **consensus protocol**. -PolyBFT utilizes the IBFT consensus engine and a Proof-of-Stake architecture to seal blocks, provide specific network capabilities, and govern the network. -The core smart contracts work in tandem with the consensus engine to define all the network's Proof-of-Stake rules. - -![bridge](../../..//img/edge/polybft.excalidraw.png) - -The consensus engine of PolyBFT is based on the Istanbul Byzantine Fault Tolerance [(IBFT 2.0) protocol](https://github.com/xgr-network/go-ibft), which is responsible for sealing blocks on the blockchain. -The IBFT 2.0 protocol ensures that network integrity is maintained even in the presence of malicious or dishonest nodes. - -To achieve fault tolerance, IBFT allows for `f` faulty nodes in a `3f + 1` network, as long as two-thirds of the nodes are honest. This algorithm is also known as a "super-majority rules" algorithm. -Each PolyBFT node maintains a local copy of the blockchain, represented as a list of blocks similar to the blockchain. The height of a block is defined as the number of parent links that separate the block from the genesis block, with the genesis block having a height of 0. Sequential instances of a block finalization protocol are run, with the objective of each instance being to determine which block is to be added at height h of the blockchain. - -PolyBFT's consensus protocol is implemented through a set of core smart contracts. These contracts serve multiple purposes, including enabling staking functionality and defining an incentivization scheme for validators on the network, managing the validator set, and facilitating cross-chain communication through a native bridge. - -![bridge](../../../img/edge/contracts.excalidraw.png) - -## Bridge - -The bridge transfers assets and data between an Edge-powered chain and an external EVM-compatible blockchain (rootchain), making it a critical component of the network's interoperability. Two predicate contracts, one on the childchain and one on the rootchain, implement the core bridge functionality and use the associated core contracts to deposit, withdraw, and verify cross-chain bridge transactions. The diagram above illustrates how the core contracts fit into the overall smart contract system design. diff --git a/docs/docs/design/consensus/polybft/protocol.md b/docs/docs/design/consensus/polybft/protocol.md deleted file mode 100644 index 21cd104..0000000 --- a/docs/docs/design/consensus/polybft/protocol.md +++ /dev/null @@ -1,123 +0,0 @@ -## State Transitions - -In PolyBFT, the consensus protocol follows a set of state transitions that enable the network to reach consensus on the state of the blockchain. The process begins when a validator proposes adding a new block to the blockchain. This block contains a list of transactions that the validator would like to include in the next update to the blockchain's state. - -Other validators in the active set will vote on whether to accept the proposed block. Each validator has a voting weight that influences voting. A super-majority of validators must agree to accept the block to reach consensus. Once a sufficient number of validators have agreed to accept the proposed block, it is added to the blockchain, and the state of the blockchain is updated to reflect the changes introduced by the transactions in the block. The protocol refers to the current block's position (the block height) as its *sequence*. - -The process to finalize a block in PolyBFT is known as *sealing*. When a validator proposes a new block, other validators on the network will vote on whether to accept the block. This process is may be repeated several times; each repetition is known as a *round*. During each round, certain validators must agree to seal the proposed block for it to be added to the blockchain. Another validator will attempt to seal the sequence in the new round. If the required number of votes is not reached during a particular round, the voting process will continue into the next round, and thus, the protocol "increases the round". - -Once the proposed block is accepted, it will be added to the blockchain, and the state of the blockchain will be updated to reflect the changes introduced by the transactions in the block. This process continues with the next proposer proposing a new block, and the process repeats. - -## Validator Set - -PolyBFT limits network participation to around 100 validators, and a variable amount of stake is used as a fixed stake criterion to limit the system's security and can make the system economically vulnerable. The validator set in the PolyBFT does not update on each block but is fixed during n block periods known as an epoch. - -The `n` block period to define one epoch is determined by governance, and until then, validators will remain the same. At the end of the epoch, a special state transaction to validatorSetManagementContract is emitted, notifying the system about the validators' uptime during the epoch. It is up to the smart contract to reward validators by their uptime and update the validator set for the next epoch. There is a function getValidatorSet which returns the current validator set at any time. - -## Staking - -Staking is governed by staking contracts directly on Polygon. To be clear, the staking module validates on Polygon and does not rely on Ethereum's security, but in principle, two chains are securing the network, PoS client and Ethereum. Transaction checkpoints still occur on Ethereum, but Ethereum does not validate staking on Polygon. - -At the end of each epoch, a reward calculation occurs to reward the active validators. - -## Native Bridge Integration - -PolyBFT supports an in-built bridging mechanism that enables arbitrary message passing between an Edge-powered chain and another Proof-of-Stake blockchain (rootchain) with the help of the consensus algorithm. Transfers can occur without mapping, allowing users to move assets between the two chains seamlessly. - -## Slashing - -Like in other Proof-of-Stake systems, validators are subject to slashing for malicious activity or poor performance. The slashing mechanics are still being determined, but PolyBFT will undoubtedly include a mechanism to penalize bad actors. Slashing a validator typically involves a penalty, such as losing some or all of their stake on the network. Examples of malicious activities are double-signing and equivocation. - -## State transitions - -The consensus protocol follows a set of state transitions. While things are still being finalized, the -process will typically follow the steps below. - -1. A validator proposes adding a new block to the blockchain. This block contains a list of transactions - that the validator would like to include in the next update to the blockchain's state. - -A - certain number of validators must agree to accept the block to reach consensus. 2. Other validators in the active set will vote on whether to accept the proposed block. The voting weight of - each validator influences voting. The protocol refers to block height as a *sequence*. - - All nodes in the network exchange information for a given sequence. The process to finalize a block in PolyBFT is known as *sealing*. The sealing of blocks is instant - and final. - - When a validator proposes a new block, other validators on the network will vote on whether to - accept the block. This process is typically repeated several times; each repetition is known as a - *round*. During each round, certain validators must agree to seal the proposed block - for it to be added to the blockchain. If the required number of votes is not reached during a - particular round, the voting process will continue into the next round, and thus, the protocol - "increases the round". Another validator will attempt to seal the sequence in the new round. - > The best case for a proposed block is that it is sealed at round 0. Blocks that are repeatedly - > sealed at a high-order round usually indicates a network problem. - -3. If the proposed block is accepted, it will be added to the blockchain, and the state of the blockchain - will be updated to reflect the changes introduced by the transactions in the block. - > If a malicious actor attempted to fork the network, they would need to obtain control of 2/3 of - > the network, which PolyBFT prevents. - -4. Once the state of the blockchain has been updated, the next proposer will propose a new block, and - the process repeats. - -IBFT limits network participation to around 100 validators. A variable amount of stake is used as a fixed -stake criterion to limit the system's security and can make the system economically vulnerable. The -validator set in the PolyBFT does not update on each block but is fixed during `n` block periods known as -an `epoch`. - -> The `n` block period to define one epoch is to be determined by governance. Until then, validators will -> remain the same. At the end of the epoch, a special `state transaction` to `validatorSetManagementContract` -> is emitted, notifying the system about validators' uptime during the `epoch`. It is up to the smart contract -> to reward validators by their uptime and **update the validator set** for the next `epoch`. There is a -> function `getValidatorSet`, which returns the current validator set at any time. - -Staking is governed by staking contracts directly on Polygon. To be clear, the staking module validates on -Polygon does not rely on Ethereum's security, but in principle, two chains are securing the network, PoS -client and Ethereum. Transaction checkpoints still occur on Ethereum, but Ethereum does not validate staking -on Polygon. - -!!! note - Note that in Tendermint, an epoch is set to 1. However, PolyBFT includes the logic to set a custom epoch time, with the intent of each epoch being one day in blocks, or around 14000 blocks. - -A reward calculation occurs at the end of the epoch to reward the active validators in that epoch. - -!!! caution "Slashing" - - Like in other Proof-of-Stake systems, validators are subject to slashing for malicious activity or - poor performance. Slashing a validator typically involves a penalty, such - as losing some or all of their stake on the network. The slashing mechanics are still being determined, but PolyBFT will undoubtedly - include a mechanism to penalize bad actors. - - Examples of malicious activities are double-signing and equivocation: - - - Double-signing refers to the act of signing two conflicting transactions. When a validator double-signs, - it creates a situation where the network is unable to reach consensus on the state of the blockchain, - which can lead to problems such as an attempt to fork or network instability. - - - Equivocation refers to the act of a validator attempting to create two conflicting versions of the - blockchain, which can also lead to problems such as a fork or network instability. - -## Block timestamp drift validation - -In the PolyBFT consensus mechanism, an important security feature is the validation of block timestamps. This -feature prevents malicious actors from creating blocks with timestamps far in the future, which could disrupt the -blockchain's operation. - -The block time drift validation works by checking if the block's timestamp is within a predefined time slot. If the -timestamp is outside this window, the block is considered a future block and is rejected. This time slot is defined by -a parameter called `blockTimeDrift`, which is nominated in seconds, and can be set during the genesis of the blockchain. -The default value for `blockTimeDrift` is 1 second. - -This feature ensures that all blocks in the blockchain have valid timestamps, preventing potential attacks and maintaining -the integrity of the blockchain. - -## Native bridge integration - -With the help of PolyBFT, Edge supports an -[in-built bridging mechanism (a two-way bridge)](../../bridge/overview.md), -which enables arbitrary message passing between an Edge-powered chain and another Proof-of-Stake -blockchain (`rootchain`). Transfers can occur without mapping. - -![bridge](../../../img/edge/contracts-bridge.excalidraw.png) - -Learn more [here](../../bridge/overview.md)). diff --git a/docs/docs/design/grpc.md b/docs/docs/design/grpc.md deleted file mode 100644 index 74deade..0000000 --- a/docs/docs/design/grpc.md +++ /dev/null @@ -1,20 +0,0 @@ -## What is gRPC used for? - -gRPC is a popular RPC (remote procedure call) framework that enables efficient communication between distributed systems. It is commonly used in microservices architectures and is especially useful for building high-performance, scalable applications that require low-latency communication between services. With its support for multiple programming languages and its built-in support for data serialization, gRPC provides a powerful and flexible tool for building distributed systems. - -## gRPC in Edge - -libp2p provides the underlying networking layer for establishing connections between peers. By creating a gRPC server instance on each peer in the network and allowing gRPC client connections to be established, a network API can be created that enables clients to interact with the network. - -When a client makes a remote procedure call to the server, the gRPC protocol abstracts the details of network communication, serialization, and deserialization. The gRPC client sends a request message to the gRPC server, which deserializes the request message, executes the appropriate method, and serializes the response message. The gRPC server then sends the response message back to the client, which deserializes the response message and returns the result to the caller. - -!!! info "Breakdown of the gRPC API" - - The `GrpcStream` struct lies at the core of the gRPC implementation, providing developers with a flexible and powerful tool for building decentralized applications on the network. By abstracting away the intricacies of network communication, it offers a simple and high-level API for constructing distributed systems. - - To create a new `GrpcStream` object and initialize a gRPC server instance, developers can call the `NewGrpcStream()` function. The `Client()` method allows developers to wrap a network stream in a gRPC client connection, and the `Serve()` method starts the gRPC server in a separate goroutine. - - For registering a gRPC service and its implementation with the server, developers can use the `RegisterService()` method. The `GrpcServer()` method returns the underlying gRPC server instance, making it simple to configure and manage. - - For greater flexibility and control over the network communication process, developers can use the `Accept()` method to wait for and accept incoming connections. The `WrapClient()` function can also wrap a network stream in a gRPC client connection. - diff --git a/docs/docs/design/jsonrpc.md b/docs/docs/design/jsonrpc.md deleted file mode 100644 index 40744ef..0000000 --- a/docs/docs/design/jsonrpc.md +++ /dev/null @@ -1,19 +0,0 @@ -## What is JSON-RPC used for? - -JSON-RPC is a remote procedure call (RPC) protocol encoded in JSON. It enables the communication between distributed systems, often found in microservices architectures. JSON-RPC is a lightweight, language-agnostic protocol that allows developers to build scalable applications with low-latency communication between services. With its simplicity and support for multiple programming languages, JSON-RPC offers an accessible and flexible tool for building distributed systems. - -## JSON-RPC in Edge - -JSON-RPC is implemented in Edge as an API consensus. - -When a client makes a remote procedure call to the server, the JSON-RPC protocol abstracts the details of network communication, serialization, and deserialization. The JSON-RPC client sends a request message to the JSON-RPC server, which deserializes the request message, executes the appropriate method, and serializes the response message. The JSON-RPC server then sends the response message back to the client, which deserializes the response message and returns the result to the caller. - -!!! info "Breakdown of the JSON-RPC API" - - The JSON-RPC implementation in Edge consists of several key components, including the `JSONRPC` struct and the `JSONRPCStore` interface. - - The `JSONRPC` struct handles the core functionality of the JSON-RPC server. It includes methods for setting up the HTTP server, handling WebSocket connections, and managing incoming requests. The `NewJSONRPC()` function is used to create a new instance of the JSONRPC server with a specified logger and configuration. - - The JSONRPCStore interface defines all the methods required by the JSON-RPC endpoints. These methods are implemented by various store types, such as `ethStore`, `networkStore`, `txPoolStore`, `filterManagerStore`, `bridgeStore`, and `debugStore`. These store types interact with different aspects of the system, allowing the JSON-RPC server to provide a comprehensive API for clients. - - For handling WebSocket connections, a `handleWs` function is used to upgrade HTTP connections to WebSocket connections. A `wsWrapper` struct wraps WebSocket connections and provides methods for managing WebSocket communication. diff --git a/docs/docs/design/libp2p.md b/docs/docs/design/libp2p.md deleted file mode 100644 index 22f653b..0000000 --- a/docs/docs/design/libp2p.md +++ /dev/null @@ -1,26 +0,0 @@ -## Overview - -PolyBFT uses a decentralized networking layer based on the libp2p protocol. The protocol provides peer-to-peer networking primitives such as peer discovery, connection management, and secure messaging. The network relies on a secure Identity Service to manage peer connectivity and handshaking, ensuring only valid peers can join the network. - -## Identity - -The Identity Service validates incoming connections and manages peer handshaking. It maintains a list of pending peer connections and uses a networkingServer interface to communicate with the underlying networking layer. - -## Peer discovery - -PolyBFT uses libp2p's distributed hash table (DHT) based on the Kademlia algorithm for peer discovery. The DHT stores information about other peers in the network, such as their addresses and availability. When a new node joins the network, it uses the DHT to find other peers that are currently online. The process of using the DHT to discover peers and then sending out connection requests is repeated periodically to maintain a sufficient number of connections in the network. - -## Peer routing - -Bootnodes act as rendezvous servers that help new nodes discover and connect to the network. The xgrchain command allows you to specify one or more bootnodes while creating the genesis file. Bootnodes are defined using libp2p multiaddrs, which contain information about the protocol, network address, and node port number. - -```bash ---bootnode /ip4/127.0.0.1/tcp/30301/p2p/16Uiu2HAmJxxH1tScDX2rLGSU9exnuvZKNM9SoK3v315azp68DLPW \ ---bootnode /ip4/127.0.0.1/tcp/30302/p2p/16Uiu2HAmS9Nq4QAaEiogE4ieJFUYsoH28magT7wSvJPpfUGBj3Hq \ -``` - -## Gossipsub - -Gossipsub is a decentralized, peer-to-peer messaging protocol used in XGRChain to broadcast messages efficiently across the network. It's used in various network components, including the TxPool, where it's used to broadcast new transactions and relay transaction data between nodes. Gossipsub allows for efficient and reliable message propagation while minimizing the bandwidth requirements of the network. - -To learn more about libp2p networking, check out the [official libp2p documentation](https://docs.libp2p.io/). diff --git a/docs/docs/design/mempool.md b/docs/docs/design/mempool.md deleted file mode 100644 index 4f5df8b..0000000 --- a/docs/docs/design/mempool.md +++ /dev/null @@ -1,17 +0,0 @@ -## What is a Mempool? - -A mempool, short for "memory pool," is a temporary storage area in a blockchain node for pending transactions before they are added to a block and confirmed on the network. When users submit transactions, they are initially held in the mempool, where miners or validators can select them to include in the next block. Transactions in the mempool are generally ordered based on factors such as transaction fees and age, which helps prioritize transactions for faster confirmation. - -## Mempool in Edge - -In Edge, the mempool is crucial in managing and prioritizing pending transactions. When a transaction is submitted to the network, it is first stored in the mempool, which remains until it is confirmed in a block. The mempool helps ensure that transactions are executed fairly and efficiently, as validators can choose which transactions to include in a block based on factors such as fees and age. - -The mempool in Edge is designed to optimize transaction processing and improve network performance. Some of the key features of the mempool in Edge include: - -- **Transaction prioritization**: The mempool prioritizes transactions based on fees and age. Transactions with higher fees are generally processed faster, providing greater incentives for validators to include them in a block. - -- **Mempool management**: Mechanisms for handling congestion, transaction eviction, and memory usage are implemented to help maintain a healthy mempool, ensuring that the network remains stable and responsive. - -- **Transaction validation**: Before a transaction is added to the mempool, validators validate it to ensure that it is valid and adheres to the network's consensus rules. - -- **Mempool synchronization**: Nodes share information about their mempools to maintain a consistent view of the pending transactions in the network. diff --git a/docs/docs/design/overview.md b/docs/docs/design/overview.md deleted file mode 100644 index 72e1f30..0000000 --- a/docs/docs/design/overview.md +++ /dev/null @@ -1,21 +0,0 @@ -The diagram below illustrates the major components of Edge. - -
- Edge architecture overview -
- -## Components - -The following table breaks down each of these components. - -| Component | Description | Link | -| --- | --- | --- | -| libp2p | Provides the networking layer for Edge and is designed for peer-to-peer network architectures. | [libp2p Overview](libp2p.md) | -| Bridge | An in-built bridging mechanism enabled by PolyBFT that allows message passing between an Edge-powered chain and another Proof-of-Stake blockchain without mapping. | [Native Bridge overview](bridge/overview.md) | -| Mempool | Enables multiple validators to aggregate their signatures to create a single, aggregated signature representing all validators in the pool. | [mempool Overview](mempool.md) | -| Consensus | PolyBFT serves as the consensus mechanism of XGRChain and consists of a consensus engine, IBFT 2.0, and a consensus protocol that includes the bridge, staking, and other utilities. | [PolyBFT Overview](consensus/polybft/overview.md) | -| Blockchain | Coordinates everything in the system, curates state transitions, and is responsible for state changes when a new block is added to the chain. | [Blockchain Overview](blockchain.md) | -| Runtime (EVM) | Uses the EVM as the runtime environment for executing smart contracts. | [Runtime Overview](runtime/overview.md) | -| TxPool | Represents the transaction pool, closely linked with other modules in the system. | [TxPool Overview](txpool.md) | -| JSON-RPC | Facilitates interaction between dApp developers and the blockchain, allowing developers to issue JSON-RPC requests to an Edge node and receive responses. | [JSON-RPC Overview](jsonrpc.md) | -| gRPC | Essential for operator interactions, allowing node operators to interact with the client easily and providing a seamless user experience. | [gRPC Overview](grpc.md) | diff --git a/docs/docs/design/runtime/allowlist.md b/docs/docs/design/runtime/allowlist.md deleted file mode 100644 index 9a7593c..0000000 --- a/docs/docs/design/runtime/allowlist.md +++ /dev/null @@ -1,51 +0,0 @@ -## Overview - -Edge offers access control lists (ACLs) to manage and control access to specific network resources, contracts, or functionalities. Network operators can limit access to certain addresses using ACLs, ensuring a controlled and secure environment for their applications. - -## Roles - -There are currently two primary roles within the ACL system: - -- **Admin**: An address with full control over the list, capable of adding, modifying, or removing entries. -- **Enabled**: An address that has been granted access to the specific resource or functionality covered by the list. - -## Initializing and Managing Admins in ACLs - -To effectively manage ACLs, at least one address with the Admin role should be assigned when initializing each list in your blockchain application. Admins have the authority to modify the roles of other addresses, granting or revoking access to resources or functionalities as needed. - -When setting up ACLs, you should: - -- Select trusted addresses to serve as admins. -- Assign the `Admin` role to these addresses using the appropriate flag for each list type. -- Admin addresses will manage the respective ACLs, including adding or removing new addresses. Ensure that the admin addresses are secure and that only authorized users have access to them to maintain the integrity and security of your application. - -## Designating Multiple Admins - -You can designate multiple admin addresses for redundancy and better access control management. Having more than one admin can help: - -- Prevent single points of failure. -- Distribute the responsibility of managing the ACLs among multiple trusted entities. - -However, assigning multiple admins also increases the potential attack surface. It is essential to strike the right balance between security and flexibility when managing admin addresses or use your best judgment based on the needs and purpose of the network. - -## ACL Types - -Edge supports several types of ACLs: - -- **Contract Deployer Allow/Block Lists**: Controls which can deploy contracts on the network. -- **Transactions Allow/Block Lists**: Controls which addresses can send transactions on the network. -- **Bridge Allow/Block Lists**: Controls that can interact with the bridge functionality. - -Each list type serves a different purpose and can be configured separately to provide fine-grained control over various aspects of the network. Network operators can use a combination of these lists to maintain a secure and controlled environment for their applications. - -### Bridge ACLs - -Bridge ACLs are an additional layer of security for cross-chain transactions, allowing only specific authorized contracts to interact with a particular bridge. By using an alternative ACL-enabled contract, network operators can have greater control over cross-chain interactions, ensuring that only trusted parties can perform certain actions. - -However, it's essential to note that using an alternative ACL-enabled contract comes with a trade-off in terms of gas consumption. Since these contracts introduce additional checks and restrictions on transactions, they require more computational resources, leading to higher gas fees. Network operators must carefully weigh the benefits of increased security against the potential increased costs for users. - -## Current Limitations - -- **Limited role definitions**: The current implementation only supports two primary roles (Admin and Enabled). You must modify the implementation if your application requires more granular access control or additional roles. -- **Granularity**: The current implementation applies the ACLs at the network level. More granular control may require custom implementation, such as using ACLs for specific contracts or functionalities. -- **Static call limitation**: Write operations are not allowed in static calls, which might be a constraint in specific scenarios where you would want to perform a write operation within a static call. diff --git a/docs/docs/design/runtime/overview.md b/docs/docs/design/runtime/overview.md deleted file mode 100644 index c3e4cac..0000000 --- a/docs/docs/design/runtime/overview.md +++ /dev/null @@ -1,10 +0,0 @@ - -The EVM is the core interface for executing and interacting with smart contracts on a Edge-powered chain. It operates consistently with the Ethereum EVM and supports the same set of OPCODEs. - -!!! info "Overview of the EVM" - - Smart contracts represent self-executing agreements, with their terms explicitly encoded into the contract's code. Deployed on the Ethereum network, these contracts are processed by the EVM, which provides a secure, decentralized environment for execution. The EVM guarantees that all network nodes adhere to the same rules and yield identical outcomes when running smart contracts. - - Featuring a stack-based architecture, the EVM processes low-level instructions known as opcodes. Each opcode serves a specific function, such as performing arithmetic operations, managing storage access, or facilitating interactions with other contracts. To limit resource consumption during contract execution and prevent issues like infinite loops, the EVM employs gas as a metric for computational work. - - Developers typically create smart contracts using high-level programming languages, such as Solidity, which are subsequently compiled into EVM bytecode. The EVM executes this bytecode, ensuring the contract's logic is implemented as intended. diff --git a/docs/docs/design/txpool.md b/docs/docs/design/txpool.md deleted file mode 100644 index 33598e3..0000000 --- a/docs/docs/design/txpool.md +++ /dev/null @@ -1,21 +0,0 @@ -## What is the TxPool used for? - -The TxPool module manages the incoming transactions for processing. It maintains a list of unprocessed transactions and ensures they conform to specific constraints before entering the pool. - -## TxPool in Edge - -The TxPool has several methods to handle incoming transactions. - -!!! info "Details on how the TxPool works" - - The `addTx()` method is the main entry point for all new transactions. It validates the incoming transaction and adds it to the pool. If the call is successful, an account is created for this address (only once), and an `enqueueRequest` is signaled. - - The `handleEnqueueRequest()` method attempts to enqueue the transaction in the given request to the associated account. If the account is eligible for promotion, a `promoteRequest` is signaled afterward. The `handlePromoteRequest()` method moves promotable transactions of some accounts from enqueued to promoted. It can only be invoked by `handleEnqueueRequest()` or `resetAccount()`. - - The TxPool also has a `validateTx()` method that checks specific constraints before entering the pool. These constraints include checking the transaction size to overcome DOS Attacks, ensuring the transaction has a strictly positive value, and checking if the transaction is appropriately signed. - - The `resetAccounts()` method also updates existing accounts with the new nonce and prunes stale transactions. This ensures that the pool only contains relevant and valid transactions. The `updateAccountSkipsCounts()` method updates the accounts' skips, which is the number of consecutive blocks that do not have the account's transactions. - - The TxPool implementation in XGRChain uses several data structures to manage transactions efficiently. The `accounts` data structure stores all the accounts and transactions in the pool. The `index` data structure maintains a list of all transactions in the pool, and the `gauge` data structure keeps track of the number of slots used in the pool. - - Finally, the TxPool implementation in XGRChain handles transactions gossiped by the network through the `addGossipTx()` method. It verifies that the gossiped transaction message is not empty, decodes the transaction, and adds it to the pool using the `addTx()` method. diff --git a/docs/docs/design/txrelayer.md b/docs/docs/design/txrelayer.md deleted file mode 100644 index 5181ab6..0000000 --- a/docs/docs/design/txrelayer.md +++ /dev/null @@ -1,19 +0,0 @@ -## What is a TxRelayer used for? - -A TxRelayer is a component that facilitates the creation and sending of transactions. It provides a simple interface for executing a message call without creating a dedicated transaction and sending signed transactions to a blockchain. - -## TxRelayer in Edge - -In XGRChain, the TxRelayer module contains a client communicating with an Ethereum node and a wallet for signing transactions. - -!!! info "Details on using the TxRelayer" - - The first step for those looking to use the TxRelayer is to create a new instance by calling the `NewTxRelayer()` function. This function takes several optional arguments that can be used to configure the TxRelayer, such as `clientConfig` and `receiptTimeout`. By default, the client is set to connect to an Ethereum node running on `localhost:8545`, and the `receiptTimeout` is set to 50 milliseconds. - - Once the TxRelayer is created, you can call the `Call()`, `SendTransaction()`, or `SendTransactionLocal()` methods to execute message calls or send signed transactions to the blockchain. - - The `Call()` method allows you to execute a message call without creating a transaction on the blockchain. It takes in the from and to addresses and the input data for the call. The result of the call is returned as a byte array. - - The `SendTransaction()` method signs the provided transaction with the provided key and sends it to the blockchain. It first fetches the nonce for the sender's address, sets the gas price and limit (if they are not already set), signs the transaction with the `EIP155Signer`, and then sends the raw transaction data to the Ethereum node. - - The `SendTransactionLocal()` method sends a non-signed transaction to the blockchain only for testing purposes. It sets the gas price and limit and uses the first account returned by the Ethereum node's `Accounts()` method as the sender. diff --git a/docs/docs/disclaimer.md b/docs/docs/disclaimer.md deleted file mode 100644 index 0e23884..0000000 --- a/docs/docs/disclaimer.md +++ /dev/null @@ -1,7 +0,0 @@ -## XGRChain Wiki Third-Party Content Disclaimer - -The XGRChain Wiki contains third-party content, including websites, products, and services, that are provided for informational purposes only. - -XGR.Network GmbH does not endorse, warrant, or make any representations regarding the accuracy, quality, reliability, or legality of any third-party websites, products, or services. If you decide to access any third-party content linked from the XGRChain Wiki, you do so entirely at your own risk and subject to the terms and conditions of use for such websites. XGR.Network GmbH reserves the right to withdraw such references and links without notice. - -The XGRChain Wiki serves as an industry public good and is made available under the MIT open source license. In addition, please view the official XGR.Network GmbH Terms of Use, available [here](https://xgr.network/terms-of-use). \ No newline at end of file diff --git a/docs/docs/img/edge/bridge-deposit-childchain.excalidraw.png b/docs/docs/img/edge/bridge-deposit-childchain.excalidraw.png deleted file mode 100644 index ff584ff0596a0a8452a5808685c77d7b95f1444a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146942 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 z 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'ZoSkY92K)F>WVBhhb2u~B3vC$ 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≷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 diff --git a/docs/docs/img/edge/bridge-withdraw-rootchain.excalidraw.png b/docs/docs/img/edge/bridge-withdraw-rootchain.excalidraw.png deleted file mode 100644 index c0b32200e82d210e1e582d9f07337e8c40ce50a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142052 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%0A^MMxB|F-bHI??^87y+c3`mmz=?`mqdIATx8 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 diff --git a/docs/docs/img/edge/contracts-bridge.excalidraw.png b/docs/docs/img/edge/contracts-bridge.excalidraw.png deleted file mode 100644 index b9e5d0c1ef0abd5d1e79e926639e497744602819..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3052844 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ℜ&cS2>los>g#QYZ%)Ay9<8 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�)>w}FX&qNSVwV@H)7EeVJ+{uD064#$!H${(0zkLG;&eF9TufdWacwD2#Xzq!8mo_i3 zL6SdOG_eZ=+gE8&CEkjxt>hX@#j%^T4VAN?5SQ#d(9zkX;k;>Uzpq%>r{s1CnkF6J zdTE=?=I?pQcRVZ8uE!|b(r#9GSmACq>1#v{m7vp_7VkXKCo>0)}{99AhC#iHHH z;A=E2jaj`&7|A33!UJ7f88^>aJJL(sEy^h=X^wHCtVccrB_;&e?t zN$1LkZJNT`MJq)s%d;2z)vlRx;l|yO1GLKr5Pf(R$+#fKQSP8GP97__>k|fa92(}! zsMLi`QGL!==kFTY^25O7k)42_i-IYFUN@BMa#KK9oTMbhI35-~26K?pf5q8}w9$2- z4gW;9h~Y}}^$L6}4jj2Mn`NT;mvjnaZW(KKVX@n6y?v)3&YDw}eqjz|qb=m(wvZ~n zOcJZ@OuhF4szK(O*Fwk6B8EaT+l5pC@-F{9Z%wD8u6U}3YbUV<=(u!;mZf<8!YsqF zG0Y_#(thw(fX|1zYMiktp_Yt-DImLZgUz!yXI_Fb7;L4$+Y^-7k=Se`fm|7HVCbMc zcu(!61Vsg!aZd={pgXX7jo~wL$*hJ(y2bXCKK)5~KtJ`ToRX(FiH37yx3py0hj!~Z z#;>?2=~}%IypP#9S=d$v7_)o5RIpX~b5xvk9E&g73AhdGgdD;Z7HylQc#8umX|5?3d zo5TqSTUxbXe%O)*UG<@3d_3`3#HXFW6LWG$WPA$-R1L2%xznYU?l`jLLT?#ue7^S( zl&doMgd2fgSSP?5sgEGqe6zK^p6;r&G67JEsTb{h`F(Fq#mS?p+(hiG`s@XmSu1ZS z0}AYWD2l>syWZ~a_LHcS^^et3F4D|blc4?n$&Qxu&0WY3dsCr)B>G3i+TTC2%yz(18O9%W)6(B}|bKf8bdWAR`GHY%V)mKumAsnzFY=~u9 z#}vm)G2`BaADiKR*&#r{?ijaLiu}`8AH#9#3QweEUV}hLhbn#a3^n(JNRz&5Nkzn4 z`%*`7pcC%{DV%xrP}x5L>biyBfEJ2J85yER+*@ASPjQ6rxqaEtklq~m z4kaEsY)7XB?k-a7y->RCevqF90jN|zx-I9B#;@W{S2UvJ(zMzvLc*DE?z~SM)>~Ze za=NKk>gr3hjUnM(o;egf8fWzl+zIqdo>q;D;x~Q42*Al~5@dQVm`7x@JOTQCe1zSr zw(slQfDP`B?zXETs8t*wcj~y z^iCda^(iSJDGXgppSPpMb0#1!4Q3=LEf|LAq2}dPAvZm+vfehfgGONsmz3p6>13=K z>*>XDYjD2@3*C6Uj>%(l6e;s#$5iJ}QAD{j+N~;4w`~+Kw?s1T(Ll!WpBykw_--ja zbc|yQ8^0$N366NdlMc5HhiQ##(^6WB(+^avXURUb>(spyXz&sN;T*OJc zYB^Opz!E(&%iMN+2>LSd$@_0tR2j3k*7NK{{}Rc{A*>@|7Euz^0~@=6xe0c@&J=H( z^6@=%U}7lr0e6Q;=doZ(w%-U6P2pOhr!_DZdM@^PQrJO5$xS;1>u435xiK z7+LH$;N$1<{U0xULQ(8SHriNQe!iZ=EiaCA-7Z!Q$iVRt&^)5?>G)?b6ANdAz`hoH zh&x56@=ju4!m#o6uAPs`MqZ%YmPUSja2|q`*^~yp%RQNvPeq5Z1W|p`4Z`}pHAHID z`cNCAz;(b%$=vM17DS-`8byhjI@567|d*OXM;2_37<&^H_aenh-$pC!)$ zvlLEMye2@(0=+2>+yYJ;zE=>>J8`poVhC+ z1GvKVI|zTh+2w2FyS!X&mo4bXa5mFWlX$LcpN{&N$ga8{jV~?il$lKy>q6;TL{_p6 z4I$QZa+EZBc37!=d`vP~$G!xPG7QNE`XJ-$T&|eCIP_dz+O~^MxiGT`z^d@uq&xYo zMnnxbNIWlFWUjeJVz4NDNjwmj*-1Y|bVYM;w!u)-Vx1lvDi4%q@|H|%*;fY&6zJaX zZ)*oWRqx;{sMC)nf`_l3=$3AK_7tQ$-3q;QVsRke={e!{#VR6ELE|8z#}Po82EG8Rq-K50+QWSdXCM=O z>2l7SnrovFkphc!hfA=k=#{a=#BuVE>qDHa>05@d=Ham;hL`G;=323d&L8W}V;?#^?fKpo z9-z(Zrw9)e(oKhY7OYCGPB}K$9-&Y!+ zQ#X-#cgN$P85#!_kQXq0#&X%L4Hd%7*wJ@+%ZAV;`;h>af*)!L{cs*uRd43@xV|1J zk#bJ0WZMEH)36ei#RM^VADTD*j7`l$>`PMEmxHW<#vi6?7iz7(y=lHpz?V^998I0| zEWAC;R@?OfQxCWbMssH`N@A?$`rgecd$OG=xH(9+UcSz2@kHJ-F`oo1Wh=y(n{$-lC@j%eNSDNi6 zd?kWTNn5iFTr;$zd_1jEI3*zFnNUUd8W#~tICtLHCX#uA#(N}>**+J+NEHiX)x9L} z{;0&2ytcHxV9jQHVw;=2ko(|T^q-h7&*$u!!OGZ-+rndN3J|vjr6VffYAFTvVN z>$-7*52Jh#Ri0@GreW`moBQb+p>M%_lrVNvCccE(;(`j;N8(Qj7mCG(mZ8w7!FC<8|@p8Pd|Fgj@vUMELKzAXRgR0tbAyh~C%8 zded9m5sV1_48vX)JdP5Vx5s^$fc!0?TVxy@ix~FzEZn0Y2XhBthGUwi#~OVG*zgi= zJ@B2&OD5c+B}^`^1a&3ly;A@16z zM(_l8F3&f0hkm_-9}d@tjQ!A@1(zY;%T_7ZKYaroK0F5cE4C$xBU=tah`g>-slWTV z6}{)G%$)<_D-L=AOjwlgr;wr_riMNEP(#0Yj=W``P>GVkg0DOCEudM1PRZ3xC&^lq*Wz4qvRjN{duKBAe9i$jf%UPZad6Ii zC+kZ)1o|tOgiBQ6EKc~0NH8KEwmr40HXd)4^@P%Zy+t`} zM&`kRBxT*bRkp^U8KPLhajsPD;$24W38cPv&;Vd0 z&5LeMIu)p41%aU3gKN*(^*V$3t__!FZtKB(ZKJuA^ZR01#?wAy+v9-a?1=mW;DTGx z){s9Y`k_q>&I|lTj6SfZ>saU6fNTKf2@I$=?A9;e!AT;`4T;GO?p-_^2tHf%-i$#h z^OqX^lv1=g?}Ccgk4PG2({frc4Fwbm_u3&fJ8WJnb2Esa5|iBwyUL*2FTT?>Z#ANB zkL&H9XQ1ZpO}&~7+kF2pb}a51|2dsZj@Gx{NXc(4U5dB(++-CLM5^xkj&V;!V5?E9 z{@$-N^C&vKNBxMlK?^5Y>ItuYFmvt|z?<4|zd(!54q$H_wpM?kp!>wTQrjO6@2Vpy_Sb&R3$Kx5hV+9rL{IP>TX23BO7Szn8P6sLX4L*G;&${)G{tMUg%EDBc%T(?^qNp{ zL{DxgnpgikTqvy+XtC~5QFfG;2&c6} zWvyFHdIB}^LZPU>hFkqd+DuG2&s^fzYp3$P9{2j;!c$sqVfMC}7$O!j+xqVC{&gB3 zHKbJ%YXz{}PFPY9R!^dA*n1;p8A-%*o#Nln3~KPVIumfujmZnn9F8Rt^HtE$`zepk zNqmqR40*&^uWy<1d-O{3zq|nTEx+pb2Jke(uBpFgQhx6Os(uImwnCVmc_A`>d_bvl zLvIhOUpac z%mM|IrF zLw5L$KNCmzo;0v%z&B_4-coe1Wc`xnEL>I2t21riyDJ2v&>ip~EaPZLw19>2*JP#t zuL7Eut~h@NSNZ9DTA1^RBAH0#8B1^1u_og6(5ggA!Y8XzL1I*TD?9BBr>Cwp=TYk^b!`Wyr2KO*ClQiag(WJC!4@t0 zSeQ8%xzb3i?s!P;$$*uFK*MOqf>o zbs2bw@?vghMsnS^ZBMIrb=`4G%aL`GRKz%vH?d}K>~-BIwh@=eZIlXB*U?o zmi#9wU=hgGN+GWW?RfKdizF|m;-K6@Ak{V1SR4>|c8Q9!^ym<^MkJgF%>5ci$wWpKv5S@t`p zIOFF$=iyn^^Mct&tDzy^dLo|~&!z2vK%ssgy5SWsNcF}|d}SGi^UK79EeXCRl(b|uEnB$kKHOc zNk$i&a+VPcOGU|KIIt(Kawq1QWoDQejNiEMCJy9Nj&Pk_Re(SH0>j7|VNA;!jj4Jr zK$OeTKWaeWS!Cv_3}2nltvi%Lcv4RZTdvF2sXMalf+ z0dW0u$RBU?v#uf-rjzpDv(^8TVEqaIb4fDKlh~bGE{+m_Uze6IzV?T^)=cO*D(-D5 zWx%mDtdpRetW+(j0!38fZwPQ_crg~AXU*;RvJ%~H)uNjuZN6)YZ{gdw*0k;EIeytw zYdEf~y`uuWk0oXtH*HnpJ0Wvd(xExP1E#29k_nZnvoBr>CI0f-b87qez!fL23g=|Yc9|4$GuGt}<=-gJr*M2!~oV!1PqmKdqPm02cayB2_N*V!;K2M?!GqNG~#z9m$ z@F%)9I2FxpzHrl180J++bMvi}2T(EPwf(UPyaQzPKivY8WRanVrvis$ z&>X=pOMfrvxkA0oW+PK&>EG~Z%Ut;9QjQu!!I4+LUpTaS}8N&9DtX zyD&*9%Y1l)z&$qhr6QfNKqh!@E?k4+@_0m}fC6WOWh9-G9by1qP7txh!wb@eMBhyW zat=u}oExYgZFc#Y)qU^G_)~3AXbRT3OOx(H8{f*>GdlWmatFi#=K0zrK$hL`=lRN; zITX{^nD*^6e`%vR%f?(oM*0R5*JTipIxD{OK!F_m*))ZiK<@>XpoU%iFeP>UI>+To zW&=lsmrC_z6RyeU>!Dq`B9~)QcZS1&LhGh*q){Hc@WuObkjOhJ@I&JzLB~VKVRI#F z%4({Mli2Hh>v>D-LSw^FH^#cgYq9aFw)NawoRA{qW@WGRt3U7Y=k<+c$*D_QgMI~> zAUt5MwxjHgI34rP^CQuql7@icJujw#oAkm)O0_1p3tkXMlj9qC@6yrLg%|&9-26Bs z9VOjp3&9Sl4QAGt;(CVULe-K-e<`(+&BUMq+#yWJGY9Wb!k144t{r2?b4^7L^~USO ztu*M@3JCw6VRPB`4wJp^tjAvR;=a*JEEn9kQ(kQ2$C<=kSaBau&C&^cRV2r4<1YsTttj{;gDn0J`p z*mAAjLBPtvbO=%5TM#iku#rI~q`ciyz{1Nh5@`V_iGojpYLks~zs|{+c@i1x)HXK} zMTLJyFd1!wwCUd4e;U*DHV0+aI_6u8DBLxkKb;!{b0YLGG?IX?{Pf#j&|@Rj)F!`D z$gXa-;GQg;12vq++C+=sp4rHvsIf1W!=fLk((+JBMs69HPZkP;VKjF-%s+P_l({z3 z!HN1zmR7?XzxS`u2Zz|ZV^Dp$V&#qo2pk;JviRXe-h%ZARfg#vkx2QHQNjB@gnn=G zagw)hb_PV{Q4?M3d>9#>0ERYU)94WA7oD;1M=*{|GlNjo<}A)HrMw%6 z42nlPy%>@?-+tvB)+1fKy&)!ztUB`Z`7HmTG=48#rb*Nns+jFImrOKYcO0z*u2;c# z>y8c<3gkRApTt3~RP4NBaCgj{YHic}jF0SyKQ}I5`o5wAw`D*jHz9>s97(d~n~54s z>!t=%srzYBvO1ljCGVa25uU4-z7Y%)P$QpS*$?5`46ADTt)vF>Os3;iV;SBY(l?C! z4e$|+s3Lf)4Exz%>duW^Fm<$3t0UZ+la7s3pZyrt8u5{2RWJ*C8!h9zfb+s2!}5g* z0wsu=(gSuP(-Y(w$_kvs>)HpY6ukhw<3Cfi7%)~6kPvwxBfCe>G`~VAcR_6q34T8Z zCu<5k-2IAqgk!vv2o=01BsEP9^{bG@=iQ2qI}>-CDTwi~Q!D{9Kt^AzSXI*lsC65; zi1?n*i@L${ol@hz1aCoK-87ik_Pt(U4=-E0Jx9GO2=20e) z*Ni|%N4_V>ySS2ixhIO&Y+LAdq5EF|HbafLNgIwe%DzOJtvBT&_r01Q=lAbv=1Vv9|V&nekx6V1L(UMt3B39yUm4U z#TBqGf2xMqb`UZ<#N=Bf*$Wwa4Wm1jKb)`YHM6JV$I9$IMg|2Xc{C%zn`PDMes{aU zE}wu0uO^&5+(!<1Ya=zvcu^{)ugum?4KsTf1b6x z%?g^eqsAh6=Fci}qa-?<9*960uz4D?a}h!bEnwy`xEYS9t>gPS# za#BK6*leplxVAm54A@TB#KVSbo5O<0xJD#ld{GF%No%J89m@64jaKnUMB@>gH3t{2 z0d|Y_fPmO9Nq$gx{AsI@9k(!Pq`JZz9Py3(LHAH|l-w(X}Hl1^5Z3#4>y>MIS2qM<@r{4&y zro;tmzhOPv#tHQfo_PN_Z=|?*Xb=DJa#7ncOCs?6*R4kjYr{C*tXmFsF=jOV@{a@Z zc3F2jd~zydjldy&k<%UWz!a6SE^EJMXeZy3EYtWIokLLm0Q|nmEs4Oxi^_gov43Vy zn?InKsE8E7tArrGG9P0UvdXqtUV(IT+1=U{QNzxuvVv1Gc!sd5M!ai^4K`12T41bp zr)8r6X|Vy-*YIxMr2dt7=Xagv=7j!Tm*2VHlcbhk z$rXw*hY$iE0QS&%8B$**SfV*kZpTQ$MyV*vlAa`Zyi`=@fp9!r1js?H>N7XGd1JwBvMV;U3kW< zqsS^^nq;=&A=8mC+sw2o_p1W92q zOUiZq7A9>+=Uhd@?RqX&9U2c&^>q=D!m-}LjB|gs=)AgmLB{-P;$&rYH)`y!c-zB4 zoWHKX{<(=5j#Seajh*X_w^tTDK57pwwH4AiEzTl5Mb$ZY zQw4zMZ;7YK6<*qt)SEIh}h@9#8`oo9OR|2EZ zWZILL0>oQ3dgu~lWjs>4!C>gBxz?*yv)pbTjG6?23C_LsKreWHme%H=SI<70YCcs}ehyu*epYKOUcgDQOK2$4oqb~~FhvZ(0cpyHCU6n$ z(U)+m@tEmaQ(%s(4TnI3SHD1dEXhZ4+g*IH0b^7$=F%3>e#!c)<4U}XM+myRulbmW zb=~3ZVe!LssguJI7Fb)LHi3-X!HY*tM!VdV0nt{|4>kS}$sR|AO$uz6wOWRcsfGGj z{RJR~xe!AX((|0uFVnW}X&b|70l2ZktDA9QzS9OE^L>W-hsJ^o?Fh;)bo@_CuMrAl z)w;L+R|6_8{FV*q@JCE<`pH37LR=cZnV0E5Ljj;X2kf8`2fEDVYKV{i1vv+(0S`&m zwX*P4@||)ov<+0twp^LIz>NhUs{`k*rd4h=cg)nVsX_Y$)?pB)uv~NB{OV_1qD#%c zChdH_J`3&nqxc2L%H3`F8&|B=6uUA3Pr$MIgA%d=K6hWQ0l4y1@sIqP1`E=4$&E1q z4oa0ymF`YCK!@2Nj*+ zGdh05ymam>^RM2si9p;Uo~`{C@a0Osr4#r}yi7oh+6(}eH{9xN1}cvO@0!j7^RZ|V zuUxm*JTp~g+nd;ST{p0QI{teuzHK0Hv^|O=&zjR)Ip;K)w&y9hHBB|BYdrp z!|vhM4_-YY=;I81WVn_ka2S{JJ@QBV0vibT99*mCtU1ChGrtY!$!5?=%qWjK=oDq0 z8=KA)TI8<}-9WWacy#9zOqG_{Y1|y}O@p0E74BL7G{(wj13ugG5goq*Eq9sDKx_XK76?HeYEP;zYXMZFboLtCXX6af1LghR zgSP7`s!iO5pm;m%um?dYWN2UcSEk;Km*_9x588iC@mCake*UL2-wHa|8@o?qVlAa5 zmn5x|1KcX1zD@&azV2bpKcJT?Vvn=fGZjSX;?`9!Nj^*lIfZ;{q$p{uaPJw_f+MmH zY;38gT4Ua-y9;;r@b}{t7c7k3)DIhbabyhUSYuVzcrW<@+)|9Ps&h-KKI)lZDjrh# zWARzP^Ut9*T4E(`iWP41likbw(Mi}_fw-B;tW7^fR`JU9cO5fInQR=F0t~HdaH3Li zSFPIlNBa039rq$RaB_}U|1;6^#qEswqMTp`9YnzQ&HMLd`OB}6a<&@CA4d44*yiry zPiG3JQMl?YOS9nAlNgyF9RF z4bXnMRW_8)5BS7&YYljJ_x($B;<^|AQkeFp$b0P*5PP7$)wu?BP|0ie=CvC~qV7+t z$%_Yl$2yny)%hDgZPXqE;)m>|gY8fexN+n~Hg@=Dalo+e2Jct%Bkjd`GSFnm#a7T! z?+Ip2l@+W)?6Jmtk;s>PBwAOB$2~%bleJH;&(julchA?dZ1=}?)B5n5xmaF}U|8Gfx3>bRq@_O7qR5s5@_>*mdb`ooMySV zMgR7`yM4cX?i-hq*@hmvl`#x~J6s35M2?DEIBlxfzUhOV#X{+2F5VW#R+2LjiXFs~ z=MN;BbF8wu6EJiM*3#3nrAyw%uO6oWSQum*c|!lWJ0wyJzRY3Kovoi)uH0@vsFwEl zVE2~(UxdA7RGU%v?Tb4FS}5+`;=#2PFAk+pTuOqwySvlk9;CQyaCdjN;%>n?dCwXD z5BJ{pjQjP;m+WUGd#^cvbFT${JA=P97*pTA{V#!vw_^g1TJ^F5YdrM?vblYq1gYCV zFlrycKO~=Fme>~QVZ)^qO(#fa1)b_4k4YY_qV~GUkaNR7RZtCA4vJ4%)qX-MsRgRc zz~5K^k{M0vG?`_4sh-*sK=CvoscCJMjU{lDX9^+v1HYa^VLp4>o%kt*i8*E9DXw88 z04zY8*JqD9XmeO~)a^i99 zRf697NV_}bbu}^DKE425kc_M%>tjR=mqf&D1fOEw9XWfUcgkJZ%k3o`I6!lq_hle- z^?6ZWFbEb*Sz>(ZG-rx~OwlLbH{QQIv9~}h)L(BB369KIeF30Z$6mr}l-`Fbw2$|j zYzuauLqOgVw$$$?SYq`dqI`m%B%^|ikdfRGQm4UI%b_tShw{&OaWeiH^RU1pRhGa^ z8Kc`8md>1A7T~1$wmuLH$mO4y`qkrepEbBWF=l7&2?Qq0} zt7_F;Z&}#WRMJVE`v)AoDHq)9bl~q)y3(_@d2dZEgybJv=vTS^NJe~Wi9GZqjx0ju z&c%M1vAI;R!Oxji<$|7i*1M}F^SkESHhI5EsmjQAhd7>{5vy$!cses&*!Aaid*>_2 zyb6+hVN}a<12^io4;vpuC_WP`OmYK}k}|s_gq4&?Oy$fQ_ft)$I`Ma6u3K93)ZiFBWLr4eyrzc@6(oRUX7+l2fr_GSpI>bP#v?`H zuINf$XeiDU4T!uu*lZsQILNJbbvPhov&o}3AErGA<9lTbA(E2O1LvlLr0){PQX>o3 z^k7(*(a2llGMtfrNq9Bo!KJR4q4D6;bOq*Lvn*HBz3$>J#HMJNbtGj)^5^^)OT_v! z)P#}mk!esosO=l&#EsT_>%XMme`8PNo%zQSFZ8wTE-|wTB|PWnGs>P@^1R_tTu8@W z9bh^?Orhqk#!Gru%yCE|b^JYCKp{SSSZAHyhZkVDyB>rH_9^qn192*ACV zr?fic5t&nfmcR#?a#}}XK~T;zBjeq4hmJt=b%#}mjHOE*%GUVpk{9H|kr zjCEiBo>W5qu!zWSVx+{hh2?om_u627@(hn9M0eucjxWSps)QxTpurZtnmCB=bl)w% ztI+rFC?3o-B2q1;&YlqVC$?eTo_r+|>MHwS*wI_}0;V=^RzQlc+9&MSc5m`1d` z+^v(4t)(t!JrDaiT>5??4+bNc>>B-fGG8=S*)lV$gfSgUeGUTvYuVbmoO*_*q)qN> zy=vfmC%!>c;Om!Ihir_m9Hj7b6!-!p_HXkcQ-~X`8~d_VF-awxv5Lq)31NV=oQS>O z@;A$hFwJMC(Ny^+^tq!OqmN78y8LFI9;F;>rNOoSR~dY zoE&KM4(3(Tqq#qA@!Z>e+~!R@r?n0T!Fq7l#QPH1gh21j!8WsKkwe)6U`yI>Y&qGq z4U6)`s3e8yf?5|W+a&>?7oNbz*V=#`F#(;Dv{M_a=-`F(<+TJ!>Dc4>hw9ns2nq@} zN@i{e)RHk5?V{xGln3eq5KlxT^zN3v<7w{}>$iVgfH#gWT;g8EN4~6R*qp}B>f-Yf z@zW*M9AkMNwqKt`+b{RW%-wjMBMJR0**|MODq z+l4oQ;`gphF>+oFNMte}4zi#>vaNwqey4`5YxLNJS!&s^uxw#a_d-*i*dP|I_HvoC z14iQYsGUFI6$NzX^){yeIA&kgzV8n%*40k6kTApA7i~?%4g}6l<%AW9@)PVwSCT26 zXh&YcUm~XsYuEKSkjB2WZ1@u-xDT+j8`5EWMOg7J3-0%O052g{85(zHqtzYXJ`_^# z>2OcUAokXAX{!T))L;I-!a3I7ZZW2$=5Nb)s6o(#W3pu#q=488`Nz-OhlI2VM`6h>!?!VtmKyy{<2lfY;?jWB4M3X78m$#)aQRVXNB4}DV)v?w*H?i zfco3FIHOzVRJT#;whmse(}_s3V56Mdz$>U9tF3U8BefDgb&{a%p#Z*Z3;efuOOjBW zdtrXcmVWXuK!firKi5F~&K8MaH)qr2C)3&(n+S*{#f#-#sJn*LwIzZbrV2%gb(u`Lt|n%95>Dym!Pta8hh z$d@h+9FzN8+yXagg5|`M)6VVnJ`Q(`jklUUZ0X&EL_2Yd+U^IfXU~-6bq=nle}m7% zkldJ>5_X-Ekdz#+x7ZBKj!D{k9$Z#T+@f_qN#OG&-;u}{b)Qh7bzU_vrOv0)Y6^V=mj?G4oZBx1BSiVL0uPik&7 z@L8X751HeqHd+O_J5*p6&^PrfsOv@R<`>M%K*Z92o>wJ1{am{Mh#vA*Ug6`Be%=L* z@1oYeGs7PH;_%GDsY{9(oONUDL-{^1x0TZ9COEc-`FTfiRFzZy6+TKg+aTDjZcJ<<~w00#cT3Ow{*e_Z>}^* zBc!3{=rzH_>ie1^iX5mG6R^BQJ9~Y8o)|XjhXjjWS^qWEJ_}lhz&PXB*Yjoixlzi= z##YyX-{Ws{y3fi6mDV;Z>(ET7ZBS=-z){M?lr&J^(XRgz@I-O+$nm-oY5VbdFnCAw z_3m2Jw?b%8bM#jC<-M@R_w0aua?m6M&WFAqJ;vjRclugc9x|}(`C-nEe{9}6&edVQ zD|4ChSU)noe)V-|gSYK*rjWfgPWbG}gJ}@PQ=ZsV$l47slzFX*mfy;MDZdgA5t|oi z^dY#*D=Kp@vtM#hIy!g3>&$*S*mV8^fc8|X2cu*e*; zI}~J^#)(}BWg8UaJq#Pq%nr{Fdix*;6~kEEE8W;Hhm!N$?TvvQ^f5j3M{V+sco$X> zLiQ|I#hk_OszFfY{QLUm&w#dO=(=AU>EH277>24 zIG1nD`u~P5Hal`*Fg0+~?QK7#CFD5U$A{9tidXE2Vl!xrMaplFB9g;?#!%n5J#(e3 zsogtFGj)0Bix&bNcUnjc?0%`x5emVYnK39ru0R!btw<_WK5t`wmmmhm<|0~7wAH?C zxCkkFlhiTNhT!0rA0`*!OS*Xt8=tmS?@;@B?5gijR1V&TDw`1*;_^h;lPicV_0s%w zlUU~K3u=&VNfp5C?~Th&fg45^)xQs_Whzt5F?Sfwvs6O7L*0qo#76@rQgDJPxslzz zhHqEIbBg<`YqhK&)1{PS7%pJ#F@oPh+n_O(B?=3#mB;#)MU(RHW#RDfJV^&Cz92Bs z88gCb1!640_eKz~?4W^TzP`sLQ2Z*7um~|Z|0YKzbdz+_Ht*e$4MN>>_1TZg za-VRY(1Fbx>9}Wcx%0WZExRp)eQvP8w?y@uu~^xYNQ*I9K6=Zfax5b1^bJ*+mMx5sJT2R?l1a|?OVK*&y;NYST9 zIft{5WX0S?Del#kT85)A<9lYYwD|aNZDcNy4WZio0Q4bLyi!bXqVOgfpGvITUm15Hg}*5{m19$p^(sYaRgT=X7tX>OHj zLRtsMvw(1eG78ZxmDqJUG!KklKVnP5m0Kow{i12}Y`-j4m>Bj5u|@DUzr=W9T>*o= z6D9{)J=t2p_W$G>DR6erVvu62}{?4?NYxF~6tb^sZ za|3nmOUbm=Rg%pHEK1PlDRI`ai&0XXuAYkv_^(lXpl9B?(987Jv}s5p%2l4+2XfBQ zFb&d`omRxpV0-9;WkeppoXF^-?o=soxD>K4!1_V%k$B8zyp-9qC-t1v>c!g(JnwBh zo4s5x^nA+t=!xS1V$wP#=Jn{E#j2F3VqLxC$FA&3#^K5RFm|GTISFeR>Y*VcpRSzM z>RTF8pk%A=lyAH6ukA-eO>tEj2YmX$xFZhHiX-8YtEuV@n(`I}zjv}GIqP!UFJ62p z+aUU(#oHTuo%Mi&7@lBLw))F&;I@6H9(>W^-g)Tk`F&9L`V|4m`y$EFGwkr4Rac9p zaMEdjmRX&pnHQmP2e|Qg@pA;l9tGNmlg}ZP}Y0Js)H4|+S znjDBu5rc>FLZ@-EKD+xiN+f@48}xp(lw=Xjt; zXKB0lgd`UgM84c+72#fJwTWbH%Vgw4cE#3b)Lx=;GFkH0%*bjO&wOf~qr^1Tj`B2@ z3vK%y@kX0ZV_-~*!B3U0=#?v*?*9^GCB+|7CV5Z)xfm-@1cfCn(LKdHG7u z{0v-|tQ((Lr`*xq{(N9|wGJ7h4>WML6yx5n)u%Y~Kulwtbhnbo|ZvL^0X zRooO!w%q2`$HCPU8gedaHQusKUD@=NVvwdz4^u3lKYNN}k-?mpjfW^}K#F8h;G$q^ zc3b7s-)t^Cuf8zfK>twxXAI!DFb^Lj@9f=VuwB(qm}1FQM)qFi0@ayMEjDl75TLn> zz$`&3Hi{eSX~>St9()R*99#Uj%Yd7Wf|(t7k1ElOV;1XA_G|fWr6)>}sxfl8eQ%3u zYBSs!9LhRWtoy@)k>D%{-htoAp$FJS@s60vturoQW(Q zPtK?v!+WIgUUCR}b;TD%F6BB8QZByaJT!{_eG)bsFYN*Zw;%L6aYxsAv=pxo zaxrT@Stu7wA!B)e-SL<%A^XE!TI17wLklJ4Y_W~q@v;7C$31`IJ_7WxDE(T0bPK|9 z=PPnpdf*^^rzI8${_#$U;R`5vK@?$_NZq^HeKhV@nBw>Krf2Vf0jv+_JtC*E;8tW;(JzakenUkMAbhZd?-es$>JY6gFs7-;<7IRKM5h zxgUqXp0kyi;|FZgN8Se(H%w2{0v6Ml>HQyd$h2+_gjk3}2j&T>RRkiFkVgGbIo+yMdgaP=NBhu`;ieLB20{`lJ z2%1u>a1`@vi&Baz1pM8W585h7-eyC6qq{`G96A!%x*p9;ci&^7daasguc5BzH{yRC z#soZXwUR|3HrygHyo3$~UD0FUPX(~RKM6kjC?nxWOqm&< zOA4nG@(tlTZ)t0l$Haj54`5|WVNJdcjKpcl-H~}`*Gar2kO6kg29&UZt_l>?G{JQuW8|E=XonGZFaFM7yVxTxm!2hlrhiPb+5o-&l9&LYMcK2(Ux1aF5~E|1?31pcWNXx+eRaUiXcLU?8@Em z^I7f|*;zOnHE$HBkIq(5`Lr+yyX^noq4V)&nd8)`3ch^v|BdjUE&KoSVDpU`IU~NI zn1}Ota$!B4IONj&-Md^MXM}4}XOe(pPTKoJ^TegJ1EWsZuxz+=U0f$#hb&tRKH!wR zFhYvwyy$0M580ZH@j0}pEdg#DuK0PCwq#Bnm61v4+KR0T#qk+pb2tWPond zFMJGL9^dDEjjHD0&K>X<*w|T4k3BOKj5LW%4iz9R1mzTP3yI6)czhBUMN#Bk082Ka zhV@ic5yIb}mNU`Y@(weMY(bi{dJ?re6^o4%%l4?Z5Z;+YO@f3X2(CrROIMH# zgts(v(fy$zA@7$#0XXB+iO-}DZsLjyO$`HiIXh8R2QizTSW72zBtv7FyR!-Lvb&%6 zJTIz5vd}d}vN=L3NXzD(a4BPVQ%hAoHu5t_>iH|!DwMZ?)BP}0Jx5tSOpRh z$6RrD92GfxAsl?7{5MpksCS&Ap?l6{%n3oaBZD!!Z=8htb5Y(acVK2-h$)Rz{bkEy zN^b1G?`$GN3K_)n!K-(u*15+6k_T3ySc`eDN4&ZVg0@es^GE zeH4zz3!6+Gk6+SCB6&IkY_y*HR_E`-Z;sQ>T%Fon-{u&%##J;=Ic=9DjMqKD9}(`r zc(!AdPAM6aV9Oeu%j~uGjTV!UI(DD+@69!rkGJ5^=JP!d5~YtlTF$(tRZ(bwv0aov zUq)`xIGTZU4_Cfe;u}>LsWp1I$7VkhQ2Whj`-t4c0VVX;SO~e$0M+jm7UQ;Jmr|ju zK4`>3CeOuyp6~vv%gAso3VA$rhxhs~%~^dZ2bRI}=O+Cbx3ftDkI@R!a*7DO74lIB z^Y|e}6BM5`J$*3>Qn6nx>!F)FQmuZv>P-r^2Y zJ2rSe-;NgVI-?PG+K;~XFAQmsd1yh0J{B^}p(bfJ1?Q&V2g~@?x)Td0=IwL<2seP+$^YW`*F{+Df8 z+NW zp|?4rx0CdiD^1&@&!YHMR3^xik-I@t9EOj{f(nRwqYAfqdu*Ur^!xE#S(}7OSwt&d z%W~MB^K+)F3$`6lnO)O!p}lBjpe7IK6^|a3!PD+-Dv4|QwHD-iX;y3Y&nT z;ZY~@wcCFO42`~uQ@uK7+oN!R>k_bBV%KB0r)Q(_K0@a-*1Bn131j z@cI_Y?A{NVqDl%1R=h4EGz;8q1%%iDGf=1O+y(VSu2Pgjx^Akh1IB&+TnLF0@2EgEdX zi~na0uHJ-N>z{00SN|9v)cq+p(^BR)wnnvD@|TZPEK6XIZwUuAEgXW9a5C#&e3hTH zx9@)1S2qHmUMq2@6DMV^N*F}4y-vewa`UB;SBV17p%rYIC!@vY#BKqd+8B?eMgrZv zpKoYfNpDYE$*&Y73iV{JsEB@$RBOt6?f=o6A}EiaKc00$8qCxudamBjV;1{qAG}a? zS}TNh{vJ8&RPXd}$+(u6wTGvZFY;!VD621@qJy)l6XgWQMCWaLp$^4u+>vThi&BGH7xl|OhFC^Il zEi`P#@aCfyA+xAshAjb%Z*= z4#b_O;LIOuKEp|d(gP0^6hA4SvAkaonWuxsX1HN!6bb>nC*Czh{h7Ki+1k0gYaNAM z*Qe$0iMMi{sFk588!xxMYX;Ep;s1Go2Nrq65Aqn%G~olE65KTWKhsM7RMK#AFJltq zf@9;BV2pEi*C>#4P?IpaaYD>c^@&s_t$*^;0@R$)gxKxvMCi|12WiRl+gMF7DvseL zZVVp#39V|8hR9UH*^{92l_g)7$Y<;8aXV91-}fNB7W#qZ#P*Qq%|y?0m$ToCHn>rf z6;Bcy-ErrX0wyX21UxdB*m<z zy3#w%D2bK#P#zv+ z=CpUN@U*altE1@>G@uG}%^C!ds0OYw^O*zE@{?-HP%j6)bp= zQ&+eA8;(~`x!_Z6dd#2>5sSH3J{)yJ1CpZK@OJaXyI}f z;o)K&g52`^@X8-KgQ|1Df(UL=W+?AW9dp^#+4_yn|CQmYCg?EA~NZZUtOI z0`^K13Lp0;s#ou7b+W5#F(U9mj4d5}O;5{#O}$j-G*PjZqEGoDu)`aN6O&xdhZJ@z zxGvPy<1%(8g_E4bNAep21946TAVnUlEiHGQB!Y~lDv}JF=%T&e$+m=$?j5XWN!(py z_0uV*#dFt7?8^ekAO|S&>Bo(;;^HdPR8MA z7~AIwvRP4B849?(jayiMwK$DQdZ?bV?ynaC&6!%A`)RJ-k^%uHEN%^<^XInRm*3Wu zOem*;j4WP#U|+o7&D>)lO(Yy=%i)>nfOweejd0uMN{rNp@)X{$L+#bpelXa)VYMhK z%1--IrG!_hz8|)KI||ddyA7G*O-6SUmZ-DkD@@t_yqQ-^#E4=FSrfIEp%E9HxE*y9 z{lsE~_;ooI;}honJ;0&un$Kac^gq-tyc;9v2eQ@~q3jc6j-8@!QlIm{>pm(oRYprv zw!WfVXwK1~CZopY8G=zPCis7T9G%gm<3k1+dOx3-6k|Ay;pcu7aPqQmIgemVyjdW4 zzMq#<1Yt~VBmBH&x z8)Hl9ZLo1q$`qtTk>TS_eRl-sNB{8%OyjgA<(aw|`_&7heg{jK$oNmSssxZBu|bNB zaM{sDRJMYz9{bb!LSB~~!*n>1a@9-rY*Yuj1}%v$@4C(Z2WxN_Dt?!8LV3@Z-mE|m zhuQCawdM)vFxVl1^Qq!+_<6m=$o*?V|4t}u{agGT&;!>`28-+~CQtvZ>o33)8hD;ot)Ik5D_PZVM&Vn57K1sAO2%TVzbO?{!1A z_4?gyoYXlxu7oY99f+%>YH$bMLO`fiA)_Z@7btOc@J@$#HCfzqjLko^>uoA@4INAT zySn5c$Hb#Omm07X<9CTNH$GBzM6bG)VuKVAvv zZbsQl82LwD(mYw|XPil3lZtKtHi~v%cdn}Xz}h^|m+l6wE+{~pnbsW~d*yyAF`seO z+_@V0@biyz0n&Kz))Q<;+>i)|**v!SL(LZ#d}ff#>I5*EJH?XY@vFi=3hAdLi`qm> zU#ZjRuP6KOS0#E34Fq`b*~!ruVox%8PA|dlQfBf#J+(edkDnbszg4))dn^6_XxjgW z>b;-%lmjFk@zsZpI8%D@5Pm8(2BR`s=8Wr-gb#Y5Zh!IUFIFrf-^toPuSit{P$jLu zkC7vhHz*UEkGyb5xdqJFM*Uz2uOgqGmTu3?cf0^aLj;|si$mOD>kh`U3iEuU120(l!e#qCdo#IY0^Z)anfDFGDhu2d+*zg+ z{P~|OKs!_?Xu_G!Q!vfUGK4R<*9fK5CAY~xwmDsCVgvRc_2nGgQ7ETGs#LSa?2PyG z{$dv!r&itm%g*|T)af~Ti?%%~fRy_80`_HJ-Js&Kbgx%6t1jU_;$2J~L&X|7!diXn z-96eraY9*(IMQ@^6jsNLa@M9FY*b~jUx7TySg(m-H4EW01eolb1p^N(x4pk&K0+=# z?HF*!?RvkfjB9$-{-?u!8Ww?ODni=VU*vx+O=Y%ItGxHxv3kS=&M?f`Au5-vgh_Xb z_gwGlgb{=kSFjQvFgHYAQg`0@2JwWm0=iTt`u1xOh-E|15zeBsK|Ox&YifD zU+)v^QPS%Db*jZgUARjH%<$Qqr-iwJ1d3qY^g^$y7gL@GYC!D7W?8~in;+F%KRyY* zRi6YVQ4Xsx5?NJBF;Y~YHja~`HjWkl{mc_DVZMyMseAoGfYrkcY{_r_HJGg{%inb^ zbvmLFml^i70F>TF{5s64a;6e8k&r-ZL8_zEW=ngx)nkW9V1=7lQ{NZ(Lr63PGy4tu zbpF=Oe2G};k6DM4(OKn78(>QGacuaftMBU$=5a(Qi{hQ1 z3NXMex}=s$pV^Rn)ZcviLyRds8ueulZck0SI9f8;kA z(;|W2b9WxRtUP2!x6|LAJor3(p=I>FIp~uqCZ4`|NM)J~PBV);+r-am99WaH+x!T- z=@!)o%+=RlgB&ONfY<0w9~HeYwDcSYWHKyiXDUgQ@H~Z~kzrJd=cj;Go2M*&=#Zmr z`Nk}3)F2XJ&U0*?9I^RZc&*rN!hdnR-u{#Ni!x(8Pk9?hJe?43h{o79j*7@50P;uJ zGmq;DUV1l9q}G^uky((MJ1Cy9(Vi~jO{q&e-j9goM%Z|wOu++Z#(jaU8$g^D@rLu- zlo&z58nGmR+iJKxTv{_aL%0)DqS045P@@_82vG`Fb)E4lL~w;^T*ZWjdHkekh7%x9 z8Pd(=I!|KKE5}L0_~|qkWr#SL_ejpQhlaBDJfaFfC$ZnkK=?`e;gmmfo6ZHld( zOtkfgM5IUc@h@yeEc3>1)?6}Tn9?hpBw0n5OIx_*6fKz|-q8>RFSAS2(U34J23?}j zcm?^%y9lZIC*3P4$WADT;va6waGlE{2^Dq5;r29s!x}$`v)5=GwofO;V9Yu;6K2!E zs!P9ysoeJqM|h@$Xx12hN3BO@S9?v?zBdBYA-K;bHqPetD~%Sty#V3iHoEppKlGSM z%c;NIt&v5U&GRCz`SvywY2l9Z_|hY0^WuXGf9#~wG|_D9X~;lvE4bfk@4K{!uA)wI zNkpe0K(&7@iC|A;3{co=Xm6;y#?DjtdyR1@Qc>E# zgL~zl_No>p?QP<5p6*oEh4aEx7#+<#gpTvxRr=cK&t(qnU4-CGU4Ma)?@>q&?ds2c z5Np^r)864_VVLAmYAd##%kPs@jAYPfj{_HL9aH#m^m^N4r<}>0@eOAiZK+bVprV~! zZz%^D5w{u!2mAb{5qp0xtr!Yo;HtO~D(SEWJ+ncH&oVxQ@ZFZBWz3pu7}br^vf)UG z&Vr26tKAmt??3>o{H@~E7*yfz;RHKt)g|-XCFcqPz3_*&l5Zexe8odclBRm2c-#&? z!)RKcO4bM^UJjAQlAC#FDbs@7pB->sdeP)eM{2!7mJ}TR@g2vMN7?e+3PRMx4~0N_M7y8 z6|4(IoGX{uv~XL#kJ6R)IO!fgq$UD&LjApm@~P?S3t-m&#l{zSz!P~~{+~Dh-O{zs zmJ_brU&FkJP+FIW@iRG-Hr>>9aJu#kg>0b!n$t~22C81ZeABcGICLHh8a8pq?+N`F z6+uTs_^qJ+7e!8X8HbO3le?o|P}S~DN%ek|n1E5)l|Pma&KTK@&800KP!Gigom?$L zzP#f$rVkh_AK#qwwpsJZ+f&eMW8z}wwXl=gC;Z_QeIJh5w;4#3`};hcPVTj7ve!#I zM{b50^~>S>U5uYIFQyQBGY<71_<;Ja=T+Yn49bSGDn=rfh z&*UYG2e)91b{8og%$@zu0)7@UQ&ze&2h^CLx7ie5mE?EVcZahTj%i`?_s?(EF+W;( z(B5a#w9B8oBu}h#43%gJN*!ccOezpVli|myzj&$4kcNxR_aFM-wtEyTyx;wYE;S$0L!>bGkwo?62tC}ZP z`XdN5Z4AEo}8xI`54p417`1FSlg>##yz$CU^zSKp_ICS3nWMlQBNDd^2{A@#9Ywq;uO zsCBnGnRuQ1_gC5_$K>eHC2HUzow1NMizxltbd3Kdg1=W~L`;rG-!}}4n$JgqME1y0 zgqXM3oNqDp^nu0|n_p>4`g_nlTZ{SMESkDUY^_|q9$!X_I&@~>bIZe`nNzYfRK>6- z6*yZrBg#;8n2kMyKOLvbU)frN$4VducDWSUGv<%~YNUQX;k0CZg+6`i?p(JZ0rV0F zRU4m%sFUbyE0ZP8HB0D2Zr)Km*v8Zwkm_&7P+#4{eqHhS#J@Oj`u~kZiL7S+d@ydB zqrzNcMILk+XF2SW{&Nz4{c0j}n*IFzHmnH$;?y&*I3xOIK93oQ(4pxLHmh z+4TED^!r~O6M_(tgNNF*agr1T$oQv9cHAQ;u$5WIn3C0DK$a1LNe)YnS)@PooVOA* zR!qv=Efal*j0O5!SNo-cx@lu;P0+>nH7F2bS|Fnf=<)qq)lN@QMh+c<{&DpEnJ#e& z{7IxQTWz5^Yb%s}${8TW5XZEx<*>ciaNB&wi%U3tvjZca6Aus<0_O!pmrU_>^!TRX zHc?NT7iSex&o9Y$*>l-$yTxy{1B$xNPlb|x<9Kfnr}!!<=UAt2cVcyDZ-IOr58(54 z_p?x*L$+TmNzsht)Vz?Ib|RoF@yQ537n_Tv*Zeh4b3&5O2jRoNxkAW_B5rK6hJgcv zPX3-YelH5A_LBK+Ofhkjz;wzpl|L)GDgj_o#FJ4KO0IY>T8tcyvk@g52`I5jKWXU8wUP z-rDA-&EPTtd3JhJGx_{A9^&xR{g&e*OgX=!pBD4$meI);X}+A2@Ax>R7qj3h92fjg zA_Qd|+e2)k(NE8uO+s1|yljwegsW=WuahANfLG8y+wJ3jsU!ajBKbdT^}_cXyi9)d z=d6>L>6$x~{&X4amO1AeQ;m5&rJit(6`SAAT`uWIjg1s^mGzTpo4JupD-U&{2*IWF z&Wd;H=xjk(vI$}dT@IMdV!e(p?u?WG;>Ra!LsYk zOue>=ar=n+KwsZg(3}u&{hL-3A*%y~)2{y62;+s5HSU;noLTnnjZP61vO>j=#c&>W zgn8VL-XVR%X8VS7T z$8WFvsGWw{!6QNWWn)q9?ZpL+JiI{}?Ys<|KCpeXLW_4<;act-Q$>gdtD}g~9Oy=f zf|@6Q8*sJwH-K)HzBF$g**lh-u>W-B_Z}dW#nJi2&m&o-A><b*&Xp3;WX`%3wBwTD(6j;k(WRFc&(U%b zZ4C;?dbvo(S6m@z{&~$XEMKAJOB7+IyVZECGH*AGCseb~_BmjAt%6cR_^su@-*Z>L z&h?!BFpGb(>urja`{0hstyaC45;l@?NtP&va~V#C3%RNh{UdLdz`b?$vj$DIrGL{^ zCVO&g-KY7?S=+RT6|7jczk0&IyARn%XFJz5mp{dNk%T~}Q+<|>U`N`?M=D6o`YltF z_WFcTnr;WAVa3#CEej8apaKZ&W?vpX;gt?AB0kc*XH6+edPwMXxJ|f^bObM_{F=b6s{45bGBrQVrO5)+cHktE2T@oa zZJb70V;N0+SQbKJEMp&1p6`Qb&aZ#lNp-R#Ta0Gw6e`WT<64N6bg);qf8Fr(g%Q$F z(!3W?k+VH%*%p^ibmS|#d(Qu2}{N&kA zkH{rLps4;l_gP|6x$LBI3~`({ZrRE!WIbx^@D;E&O&M9D zFt)<7Mm&E=G4j9sh)&61XTwH!W3Lo^gr@g72PZx;;D)43D_uhLLROScn-Ab!d-kIGplMrP8ozJ#iJVdD zcIUPD%^SMk5nPGNWH~>PNJ|zGmyr&#xY8blC1AtYn`${7-Lc+vW2#c7K__( z(Z;XN+q14e*B=EpXLz|8KO`@MpY%}3vgv>nJtM@?CE$aI&QzgTwS%(txx~U#ikY7PtG_(3DXLKWYD;Z&){2uFx`Oy=4{dOOKS@9;6nO2$0fXTeMvN z9>Z(}cbMEAld(cJOMUzXJ^4#$qxc&eNLLpY__atQGwsFrH2!TGR0@uSiF(mJJWDbM z%>Kami^5KvN~&tnMnS>i>Z&r8F`N8N&rdBsku-%q0Cz6T(2>vJRm3_;vo(3BCZpBtk@ z;i0wX&{um?dUTs8mM5c`#IO-|ztYJCBxSt2=d-b*J9C}3u zPo-nGekE>ShFB!%9Iv(`V!5Fic5uhf)aYklXsqyq1L1^mdSosxWuST;V$5lY%x)|X zOB^z?UU>`0Q<@9-(zXm7oq?_fVVzTvd>)eCaJnVA<6)j#vBV$k55te6M#0;st7!*2 zF@CKED21QwGl+ciZQ8C3$~d8^mtWJQ)+`gR1%#Isk#4DYrCf5DibPw+rV0UED!IK` z2*Sxaj9Kb_oQL3;3T%v-oC3l(Q} zp9z!Nzg);7p4tDp$S;Itj?GAvQ11(HuRZU^GduP|Z!_r<+z_~-9KuT5j z$u@0N67Aw$`gF9p=7fHz)}eZht{R8Za4~sJAqotJg$@6yk6uZc41tVrf89M__GT~$ z+b>nM^IxuMyuL}CqJC=^-%oJJV$QO|UpN_VS%IT?S6fcCV5;M?luVD>zlLZ7|LwOG zT@}i2kTDp=cfsUrx>mv!`{R6H1m-J6Q-Yqp(1)DQEzix(`D0iqjfUco`@44l>Q(ts zeVk%n22(#G%-=~S7NjEaR9FHliITP{y1%-ct6^fj2_)!0`ht_rDo*#bz99&PtCL} zf0MY8y{PpJ9?XDd!?EMfaF2M z=2svw3htNmxzA&D$ZT;)Zb7+OHth=*M^V8f2t>Q$z@SM0)hf&O#57e?QuqI2?3;o_ z3EE`Gwr$(CZJn__bH=u9+qP}nwv99P&L43%?!(5#Yj;0Xb(HeUtjs0_>z*Jxv8rPR zErUAjY`{M-?P79~sHfz>(jG~xyLn4idD?iJTlY-bJ|aY%B@^%I47_-ji$+EvWWeek zJgp4k%nQ0;Z;|Cb<&US3!mhWDc~rXS2Rw0;hQn{MCe!84OKZINJPy0SGnc8ZkG(52 zESovpf7hKfn1)RCuzmrXPFxq=X%apEBu0m4wY;Go9d@v}3a&cGho4C@7>*7Pg1gb9Y)oLNc;4KQx`8WvW>!|gwxLWQ#( zJ%4JC87*BaEIEK8^t@e`RG4-u&Kb_;(2P6!qNa#iqm>-RJI72HXV(EcsxehYD=K@i z&X)R>UODq!%FyeNRDQTI?3d6ysN$@oD&z3-XSN%X2sFG}Q@zj!+qA^>*uRQOTR z2Y#jSS;4o>W4eZe4nYvtZ@OQ8cXYjW+6FiupnqF&yTE>{ z!3zxL8gqnEH&#rL`&+O+0*_fcjrhi8YWyZPawdEGrmxoMkR2>z9yn21$~B)^ga1Z~ z(7A?r9e>C#c*Y6T23T_e-f`R|tn^w(*5~lc>pZ=NNgUCLp_{M`kzU{f34w8IND{o$ z?47y3^?F_loY`u1{E+*JxBo8eeY2f>rU$3}&eJh3i$*=wTWIN7(gpV*Z_h+aqmx;v z-kiC=H|2=0!)3X>DVY!4kqLp-umc7KPa^_8!inSWdz6E;-yPT&=W#M$jRF5*K4jHD z4p6PvBlG&FQ&3;b8t10banpsoyZ+TG-urx_ntIha`jz^4n@#w6ZDemnwAH>k`F9Jr zOnhS2-|Y4S(y?DD6Go;mUsWg*)nojSay5-DP@>9z-I!*4E0Z$#`=emcGTk>laW z0E$i6UGKNP!DL?cv9(bs>K@BH59HFxeB7A_Fdel8n}|F$Q2Yx<$Z{F4n8?x7^CJOr zshk|4we)60PRb>fxDW)UL8EEO(jem*<6$~^>xJb4*YwE(W~X1^7({H5erakVdUIQz z^}8c7SJ)lrX)bx4GpswutI2+1M3aBd%)jSMKBXC@W$~5UjM*x)owKl*{z;%mmbD@c zNx}AXTB)>Se4VG8MEqEu=MG=f9i>{y|1ZJq{~{-US}A@*YXI=b-Oc1ozDNW-GdylF zo>aomWVf)YmZu)MVOByjNFr*Utw#rE+LpTw*Etxmf%{txt6Qt7F~5^RWTph@A5M^+qBdPEBUDd@%HZ^R2}%{24a)!Mg_2)f*?Q@JZZ3 zEbtZnB)0?OU!Cc{0Dh7MdDHKOGrYmg1qy!Df3fksK8S+S#=N(=EbVpb=pEKtYe}e= zns0W@mh;L6EedMkP~$n+KWDv15^$mIO@tNgE-+lQ8Bb578zr(LHuwoZ3wLiyuSN^* z?Lr^$rr{Kc{AyJKb7pfouLdBjV_6ITC3=VVw&31vXdO?}hLfT4^nO=;z9i-f(w2CL zrxB|Q=~>ZXI{3;RFC4<`$l8%<=yw;3jBgKZE?ir>3;4 zgGGnire|^9pN#Q46kdZhoJ1sATwbAtIq84+mZ-}X&|8Q*ogYMEJwfF0I8GOEIN$;JKsyr#XN$m<5Qoch;L@ zH+N5d?DEsh2chnGox$7s&88TobVfdR*K*P*Zz*BP+WLH^z!_;;-&U-LW59C}4s0(0 z&-{eBSH9K`ntPf?SwlE{4Yd8RJR0yWG z=|V%#1#@Qx$Rg!tuvli`>zIFqJpRTQ!3*I$JMpfSIg80Q9USQSSh`q;9}cg1 z_bDQ`wTgf8*2z!-J%Zo&0XU|R6OZ|_$f+9B0Ef>>C9!WYw47Dpu zr>6>H3DOS5ZteQSou2`MoOIT+fqVQ|_ID@j{DySnneB^KK);Cff%uY4+LMX%9%^Gy znPxTYYRcX`B1D>*$w006~5bueWo5aj3R#eP|DRK3Oa=aK> zFp39^ZxzGAggKkLsk_|$Ik`ahwe1>E-nw~5{GQh?j%xa8)!tV={2#jgS6?MSW^Sau z&fkOQOt+*Zp?ygR4SKzD{tol+A$_Temy$~f)W#U+Ai2v@oBMMy`*?`W%=P?7j@_jg=H=VPxNjwb@Xn=2^g<`X_!4aPU7YiU zHw92SpfsD3^pvpNRnPNnx?l*{U`6o^9HXR&u8MFvTBu!AG45tyD#*vuP-#cKP0==7 zU6+gnTi0_vzCY`Q6_0Pr!lf=R3kQqK36QeeNon()T$-D|5DhHt>J(X?1m@81j+ptJ z!dP5<#~KT7o0~8UX$`O-kie_RZ28D_6-ACC=MLAMOc?g%sXUyEiUCVp(qdQKkGrw5V3({{UGY&A>%Dwmcy=(6S1;CQhoj&Ms2DZk*LTFj zD=jW`SUHs=H4$y2(7qoO%ov+PYp4eF)k|0PysJ5Q-biSWgYZ45B=)%`J&8q>W6>I6 zAl(l4t)y*sz?{jwa{nlN&|(Lf!IOPTqO;`Xb8{6t$_i?d3X~&ZpwdB)-kX>6C3wCb zk8^5?C!!}si`628c}>t+$=qwHFdObp3xki~%J4NUM1d*VBRp-1x+xJ5+zj9+u>01p zra*JkXNT8O4hoX>iG)B0ftxBq2}wZ-1XKno&3=olq;#s#=2uTD~Qb-n{ zK9$-G^vUPN=jX-SD!W@Pg~Mzni+8q$`Sycnb_lp){?5)V zhK#yxrZZ$~&y)Pt4AVM2d4DdZv7Es@=Qi6b^~%2ND-}78rcXqcz?l&@#Ssv5A|Q_n zLWZkk=-pIH^M;d5F-PeYEGkW`k&=EZW``2Oyg`DKu^YS?zwAC-pesWpa}FNa#p~#i zaV?Dy$_ME+No|0to^LIl?U06u7(NdJ;zpjz29Frk!u=3}4%6OIN%F>ky(r@`-F3~* z7SuR@f9cdJ;CN(;^4gH|zyxt#uH33CClG#!ru|mH#N&n->IhvSnTT`8ph|7eMS&_2 z;w@&dP_q0+{|zf+Ypj1WL2awCo9Wl<5CWNC0U+3>$8eHVNUh}0YQUL`|EH1g(rT(P zI%>Od_yo(P8imaAjAW0Y^ba~K@lCjEfD0f*#+d?%qK%%^O=E5Oh42*eH6BSAa=+Sc zdYbkdp&4QnW5E(K$xO5US!zEjNb*cjsH{1v5awYR2iV*OpbL4jhO&$CNijuAVxCSt zWOxa=p~zLj_(vtvzZwL@YZa845N5)+>~y`hCmdVRi0ERFCbWY;lEZI*JB_He7iPw~ zpE@aBuHGak&8injoG3DcM~~sJH@K%PeFgRb&0RHv#aYL9hb_jI+@3w8(J~TE9-GQnQTowaY-kvGKM~@22H`MaICcD603BM$P~W7g-Bm5rz#vkg5NgK9 z!b#g}0$pWGmi0PqffJzmiF+1IWlXfT5u7gSaQbj?jbHiOUqh&Hg$RQB=n)X&QLe9i z7#q7EoePh|xnKG!K*o4puY>ONr!&!I=#+ikDrTp#b-g0VXo zGj0eAd_*42ZJzhs8KKo)$L`GYQLeRj;{9$_g!c$-QGNJuR!Ax+APz+Ln>(tKTn{2v z&f43#MNq@y!qGn`Uoa zGg!F)o%5-?wtlE-+c{=Vi?c=OzJ|E2+T6Ynm351z)qY{`ORo_{A8V;C?{+hn{WyTv ztb7k?C&7TaWBOsg{JSbRWLhj|ln>wxwj|5C9@z6p`$fX!X7{r_hjBg%KUevEi;tW4|NnC2bW@WCsrmh==x$R#gk266M zJP^*+aC`6aquIQj-f3nBw=laKX#uK%$xKZO5+sn?MNUJ=02eX4dWzE?Uz8;Ag1Sj2HFO1*)#>)Hm;DA%InXE zNB1rU9NlTzz#xUtf-+84YJT_0B~VE{9#6B5dk)tNw>|6ur#pEMoNxc zG}@b5@kd=Aa=AXIq$5smm2#!xov8sf&7!S7pEjI0>ysfcMETiV1mtk7sHF6m)-9;V zTY$$hg3)ipTzk3*??UsV4S#R7BO>Iui^6aLI=337S-?9&n?*r#L6S3ntxFu!^21{R_{|;6ENG7 z#hZahC@J6?l%)o2k&yAc09NHTlCPSS(jg+^3b-cE{gf?%U&9x6 zCs^z#aa;O*e8$)1RWW0;)}&82kSMbOhYF%#nVlT!}B2hx?v z&s*Oh_(0395*X6LO&N7Dg?~G{>rhwdiEzK2%N8K3A9hNfpIqDJJqGb`;v(W*PKm)&7EsBuD=GAJni3&(RFv1quF zg;tYIp#jBLESC2tfoPKoBDQUmkqV64-@Fe4rqY0Z}+- z{uBrXzo%hi^UmcN8S(kM+vq&^HHYqu>Pbr>cMK8E!O-SlX{qEC2QvsJz$u_Kb_swr zt4H+=wLn!kBf{z_`SGXLl^IN!TvMoO~|DjyPk4A8t+Xt?eA=cCr=62QHV*P&N zEPcMZAhjGNn%FhQfBi^_xQF*`<%Nri1Td@dor-g=2Q^7eOmOR)tM`^=Sw7g%$v;y4 z5iO^0I}*9yfwe0Xu<2-%_Okmv@4op8j3M})<-Nf7AKz6f>TRm2Z2_!ikxGqCwO}mc z3-PiFENFS4-&aYZ85C8bRr`#$=%bhr32G#53fQeI8UGa8Y|cFSv-8Nf4x-2l2E)u8R6=k*aRm#8nRRU86g7G zAsf8E7YT2?9?F?xXC*z{RYOZ49GMX5NcMYIO-noEM(cws@xfHUgnJ%2oW!5nlBzST zX$*9)@7%RZ%kG--OS2iX&syh~2qx)v51N@JDO4$YLUEVjhXRKfZXpZ%TF6Gzgz^|T zgp3J5^O}F#@>hz2CdQdW`!uL!-hJXKj~f$(Ae5hUQF%CqS6F~wm;(o-Vp49oos^5M z6{hGy^sXcT1Hx6!fp{B0X*{AXG#E)X(+pfj`Jw^L%el$kk-!}P>fcG{EoD6UutKTM z`t&OEPti?jq(s1XHo?*9ekK3z_w&Lhrw3 z1h4b;e|xw$38YZe^>yB%^5^x<4+}X+ALB8o#DjbtjFJs<*yqf5#rCLoF`Oo50Pk%+ z@UAyL0-nWrcRgHTBU_C7J&>Zz5@s)ke#~hMXA%0JA`nPLHv9eoC*^RTDe|HjLa!M6 zMLC5v<|bXhoTTCoMk!3OLlpUg?79;bJ^;gha0#6fGQpXvn(4T@M1{IWV>fM2Ygw|H zR0LjY7mPYK0<*{x6|B&FqH_2D=WWY&wh`21uD(8kUVI=#SAR8)m0va(tT&ogcgDwk1;W!J}O%koQ}xqx{Q| z|GA31_6GQcnYZRw7vT@VCtoymRi+u2AZaEjWO#P-UE$lSHyMfZ^$UH892s1nmB(5w z7(09HXu&x7@0FvK&Q8GJvqN|w=W`P@1Hx&R?BnKP7+Hh2y2*4%5o`-KfD2tw*ON)T z6IWN(dB(UfAXbx+qTD63b*|h<{HAvz)FA9X$r`ZaPin2?Z1k%o;l_|ENT{@A$JZII zpn2Pxi0y16w5LD}13mCvkF*mHUX*V;OD2kA;$pg$jp((rFw*V2fI8C%;5Uv@kUlcF zo0P7xNC(j(a!EwkqMt+;m1PR}EQsu0z+8HvorO+Hk>14zW+JI+G-nizMD;QwpvH|L z>PVfo*~+1w{`oQ(iv;mXT#FU6Vqc$_I`1B4JfSGy2+5+Zo=jftCi2sro z1e;(w`?-rtyJO;O7OKGpkU9D@wSZ%+urAR%pV^6=ytJG^|Cs?M zED>l3CN;lPZ_*Vl)bo{Ok};rs#XfwJqYW!w$<0JGh&E)9Nbh3zjJT@=weY<0Jgwk5u6hh;*P=xRU?xEf zc#47G7$E{}P<$AUobz`dtmH1Nkv8lI5&l}>qgSaa$?4Te>~e{w!7};vHeR3Lr=8V5 zDXrT+JVa2Z&rOV#dE3@Ed(xC5t{pu8DxC2`=fE4XZ9o=M(YQExE)S}#3K#(G7 zSJW+h>+!av*gsMcXXgTXUHKu)DlvwWuF!5SR>6q2>H15FxKRY{+|jn` z)=EuIq9)Y%`Hag{dJj+AE-t6Lw?a%-12s>mCpc<^S5r-QAO=}vgxye&JKwJcxo--< z1r<0M_7pPID%U?rs&*uk$E{2?Oe&0=(_F|Aonipm0%UoC#^M2|`kg~6F-1V$lA~+M zFUO4W55qOM*epD|fWNrI?Nog%#x!MxgJG^hjF_B0E*R8oz%ohCd>ozW=pBWGc3C9-+1kKmDq9(O_TCXew- zSFxlmmuby8V>tbn*iG)j2i(V)Z~Xp4%^jn_D-3~y3y3)XF{fIJ=4CnYI#sa1_LxwT3vamx08$E6BE|)tC(wW`GhI+`XLSUiK3>#ryOvN zCqhbb&v-4oC2Fa=B06(NQGw)$v}_FRy5QnMnnBM8LDbYt`r{BNfPp|^meJ94u z0BTZ&+VP8txy9PB~Y)Kx;M zyo$n`kV^rBY@m`eb?77SOOeZOY)8SD_3AhNhl_djM~F*6Hke&W-KQ|-KQ)_`xDzf5 zCM*VQsp^MTsSd$~&RLMX4vy~%>sTEXzS}sfdk<11%Ay`pCET@oa3<`;eG^9Oadd?s zr3QQk-HK_fN8PD_ZTO;3NI60LJ9;al?FDkhdKIE_>CB^2&rlVtJqY zQe6AmPZiYWFa5=op6deFY5wjP^wE~^v-gMok;}sYp6mhG!`|gDXPxUC)WDq$FCXpA zuh@u$Tu>?Sbq_AM6D%+jWa%cq6quO5na_{uqdGf$Q^-G;o~xXIo;Ig@I|6s1Mw+5s z@O!7Sp71x`cON#&Tw)=hHP`d^*=Y>&x4(A8cm*!K$a-JQ9Pqxnf4OB{|86-g7kYbk zqiH=mqgYyayt6J2P5nkAEIy3*6ZZK~sQMiZV(-Kk#Rn4+5? zhI8t3>xrj}ZRynv;b*z^*|Rxob=_bB!ut2Y;SIf(sToiS$f`q}v4lC1D=k_!!bJ}c zFv-sK;siyP9Bfr6mh+FI)l3qRnppyo1@gf)-rw;p=B>_LJza#w)c+>%_ExVEeRR<1 zzQ`g+5{B4&Z=d<+w)2+&e~Fs>P1qAThJ>rdWWA#HepNUaEYzP5hi`OV5Ph7i5f(-$@YZRE}FrJ^v+zdVdfpDfoLPh<{Y7`QAhMeUj@MD4$*4b6~i7o zgXxo$ZkJ7AJwy|mB{>}-sKh{i!zO0~Sqo=sf=$ZTb^^`(k7(v4apOQewjIZ}BXYqE z=ml4(8TDAE1Ukt^-RAVaGsa;#dJU*{kEEu6cyA%_%l@QdMT6!U@;{9WQzANasmr?B(C#Hln?96VB4sNd&i*W(T z5#m2$NLBvdc=CUL(f5N20*7dkOIFZngkHr18p0?Z?loBC(KIx|>fZS0F5PE=L|Sy_ zFh&?Mq&y^^mVRR{Cc%kPqJ;uHQ$*29cwA|ek<0H@(A-czPVADy)nVTgQ5K~Qb_!V1 zpcTUVY*x-bn`Qgu7St*yROuY1$K6cr!VtG2N~`Ia74aN&4^dvk$>8{I5pD!6MHWGz z)9;3K1PuyInv=$wXQ=$2S@99j>vr4zjey;5y);|bUj1@cmF+z}uh)CMrpCu=7d_ye zOeyTmtEdc#dPQ<7o4r$fV|-<4nko1^W=W?@KaVDP)D-suOGqBZK8Y?@DGsTNYDmHAmnHz%GM=z=?O)j!ykhr z_pMRM)Pm8>#4R)0I!zA|SiJ**$;L0CvA=7JpRD;m=U&ug5tJoM!h2{S__q(2hv-%3;Da2>(3F8ikq9@AqQkr21UZ>l&Yh&^ zSO^Aq{#?hoTcqC|H@Ntp^0B4BEsgvWEKW5CRvUFBKI5T`=9Y8yHM|A~_$|cTo%z?; zm9dOAPZX3J1=|Udqw6-Oo@!2XeLCSP%u0DmL`{dMS5a0@`+zGU0isY7v)&ax< zjrl>{eXfUv*+)>TyJPy#2bv~xS?~V#ihnP!qQ~3ZN3unsug&VNG0ylrPaVGF*lYo` zF@8@sAahWhBB|&po&81wr0+9sL<*NeWHAFQPu3k@Ttq7O#<+iPpFJ9Fp2+g%KNRZ} z5^cJn;vMm^x;uVZo1_I#$&rQasN&SM#z=jMx8?h|PO&rILMuy+ZgbYyEw@iy z{8c!Un{eCkozFuzgjzL5qCgKI{bux_NrELKB~KW*=khJ})$SZ)2hZktH21c%@)kbE zB*pKU2ug(;R_PQLIpww@YX%HncBvd|o%({6huDWPI9iI9<;t%&f-(Er z2#$JYB*Ov)1v`OAE0U$3sB9ggZ@3A27_qvv&g*ib9!mR@2jTNu-FkJWoz^PB(I+9 z>RTCv>Ut4;VplFTi+_ZmPKxia%k_`h*VJcs&4e@e`<*L!uo~mK=;88^^?W7xo;0N; z@>_I0g|m2@aq{F$Bv=n6nWjKSduy!rPms9mr6rJi3YG0V?gPIB6P;&rB%a&0DS zy?g%^s_j~vqLi4z9!0o&DRQ8f+k3oKPVV}L^8>$^PuKOkswmC1(W3XVhD-QVV=zht@GBh4+)-!w0r&LIcdrC1vNT=u6|Icn3+a7OWz!e+-~2I}hIAp4`Yj$7wX+^8mK zJ)!WY0loHGey&m`N7KK%4Vb&%JDmI_{75T@>fXA@+7$XJt#J%7pSq$OQ$~q!h+;83 zK21I9*(uB<`*D}M^b8YeE>X3wc&YjeN<$4(7w~5ZLkrrgHaBBRiMHA3zGKzbwR`#F z@b|?lBfH${1QXjZc#0JZ|K#}vKD}d+q&xk-3a&8u8l^XjnYEdg;)nUKa)#E97qx-< z;!Qj~zVl*8Icwf$;!VUmk}I--VtL5UV}n%hYvwap?k^vjsdX0qnVvTEDp4&u(%mN? zUBd3@QL$RQnJtSxlzK;0_0{&)op)K_cGk9?vL9A(8I3?f4m0g_Yv_?8@>bkqjT>Q(w)W>iWyfR(WhhCfq(sruMy ziS}QciOht=6;GLXsM760U2*MgJFRlRuK$~xBMryR*yN>~ox)C%&$8;=VNK~l$G+|< zrzF{PdEjPTF4amWO;fM?M*UJbJAaL)mr;mJ@2kk-9w*S7KKXOuEPNV_@Ve{wTl7e% zP&0+9#N+Fh9$5IJ<%7l5E?Y$Y&sKJXI37;Mjk$cyL#`9PA>j~XhDSI`5@aU{q4-4Y zc9bU7Sfc-w5KlJaT`|l@B}jK*MoiYOQfjsnI1c7oY20upS(|Cw+dv^b}Fs=tVe_iwTQQX$Zef3>>&cKHiZgx&2`tnq(q0dT`Q5mmSma+c|$H|CWoqtH^I zs)bGnWo!&TR04U_1G82omh`J_MFNBn7BI|3jrjy6W?ep})ll|i$N+xo^5zjT_5*UT z5d9#VZIm9P&MmQGd=7I~-id%h=31Cz`BuXd7pBUyvA_)YKB-=PaX4Dp%vAe?y?K!I zyUL@|K!f)6uc>4y*M1K*<&}Ov5WPX_bX!hXkDo+1LXE%AI*Fz)G^DJ5-}Mf|-vXc- z1p>qbbNe!}-A*zD?t*RKvQz;$raJ+L@F&6Lx-1zAqY#0DHHB1?-o>sHm0b32XzUZI zYPw{%vFN8$nM=3Deig8RqygK(N0~@Hrs@(dZ&RwlbQ%FaKYEzhQ$_oL`k25!6M1Io z*)@Jr!T#wH7C~Vm77i9BnlmE)T!|enfcJ1)9!3v&n4+@i-y74K@ zF=K7H5<7A< zAH4(tfwIa;>|S7i+_LgtgZ%ly|%Y z9`Ygwum_}&q;JwsKG;$<&lL=n;h8n6K{`+ubh0uZIX(G4Wed{7_vbV&) z82Ss)ytFGHq)LLOo~-jaJ>D0nu?s??4T4Tu)3zP*COMrM%r9D<;1!I@t#Bt&t@ux1 z`WX|@MubGNI#1){ca4L_PO*?zJ>a_<%JX_uEX?4#y!F;K1Zo--pXqu{n1>{@Vn>|L z0xy$jVvNXv#@r#nJg$fDpX~VE*kF7V32IW7hGX{%CYudTg4s$0uM_HKvDPVWRErf4 z(Iv98vEghrjhhEC(T?O&5Ho>z+sxma3&$xIsVb&t_f?w?9Dts{P_u3ri9rgNR>AA2 zPb^}BGr(3rY%-7@&lqTx4iJGhj1srGIVR6@W9&6@OBk54Ne zw>kl^2s-4Mx{8d`4PP;wXeDxp^Yd>W4Kr%%ZFY%-tO6EPt{4Zz1dGrGkszA>ymSlu zhd7HohSJA;vsbm|bNz=$x*LTElkf$|pfMhzp~4oFL53lHz$L2=z*J=?JC^$z*dXkY z7KcZ%u&jJDpB4q?i=xzK4$C8h9ES^YF+Z)fY*a#bb@O(oHwi%n?4m!*pW-D}em>L0 z;qql8&^73h>x{iPobIRS&5y{Cl# z5Q1?6Y&{1hHL5xtA(&6AeL>k5y_$6Y$vF_h>x!%b}y2cU|S2{XKo z7@cN0IlGi{sQWh;voPIWX*bzMTxv^~8O1wFmV-_p4g!`6OqIf*T2Csl3)~?iUZd>m z!eOW6XEm}|Xa*UfU?%^vC$<8A(lM2#)hQ0@g(|{f4sXy>$I=R#SGSj#-D8{$S6jq| z4^H1lIL(r}dLz=sA_mB=+o05*2+D)(XvflOk{AQm@t_KFs6(=_JRLgE9`*G8_Fk(v zdpdrBHDqUE%W|KI4v9u&^-rCdrm^gY>04kCI0C${TXCtYL}7I>``rKIAU~TXE>ICb z`)E|ww$EHEXin_+f~0)+D>~oFxH90fD>H>etb@~X=L z5qe6~MMjHyA!VGbIy01b`~WkSb0!zX^<#ZwECyS{J5hkr{`mGQ)`7A9ZG>-;JT)Y{ zlS<&2UONp#a9YcMySi*Q>$m)_!=-u*<~o~afiE@_W9AXGnydmtSs)2i#kqfQTP#Q6 z-pYjy-XxSHfF0?9Y#v>sdw6XhdPX&l#NpU@3wh!fSKzs?6Lf7f&q4h8shJPZYAXW< zT?376B9+{|_A?OS>=6AokdF${Os-xBnssm-ZfsBQA76PMs=ljb20fGT%nx8uAs4B;`P1-nBq-&KUdxuO4)zWP72;y;ogPCnAh{Urs7Dc!wD z0wn=eRpWBr-NN%3&`EQ$L~AkvW=K7fqujr&ohh&vDWW^h!8&)N()Oo0t`<>nX8IYq zD9hw8f}1@=ui;5dJ|z@z>Z(|4{OBXr{fmNcEvgO?2b5r5_ddjs6}*gI?6ZC4RYA9U z`<8D65#k{U%83OwgP2kbw5$%fT37y@xJTpQ)>A!|2Pi(;D~B|u&M-Cptq9B_e{8N@ zQ+t4x+zXmNzbOWLhGRfsGho-$P^+)?G!dTrIk##0Jz^!MtH)TeJF`~ckezFqAd#LQ z=PtFmqM9gwma;|6^%*c^S9@CvaZIVlzSOHwX&gnfJi-K;oujg8<@2h5JOKvmqOn-VD;+_@MLSo6>8rY9Wu_`V_Y z+}vgJTE8ECA6W%8M2I7k`Rk{FU22TD`4F&wf+jEUFYh*3B^$yO;UAx7s`tzb-Tt2p zJpTbsARhJ9a%+4J3i+#ZM4P>|yI0k%%-$z-Nc$D{ zBy5>!#Ypd#@~Yl1(+}G7J<`tdS9K|Jva}+*Q|LitYC=H1b7vr6N_L+3EB;thmT1($ zA%j}Y%<_k)*D_9rbsDA0?D)3TSh%MUotr!8dt4EU=bd=g)u{Bf}y~ROdz#w2`A3x3^hvF8`Ig4vYu`T>RpWCO!RZgKV-W zY2aura#u><^7}BhF>u0pMZwBL?iU=gSGA%)>`02fqr_^X#2dX-yg#P?Fs6e!>7O%G zgCM;XO`TBU3DhW(f>AlDf9IYAdTEMh)knXkFPV z8!|cbqR5G`gWNiuC8Q}D@HmRO#-wS(eI-W=rbsgeSqqM~fGVZG%|oZdQ9WPL1{7rv zZqC^_t`4^isL(2X?6M$Q&>>0!P^`Q*IK_4DV~gp%DRM6#Due} zCEIJl*=2Q-%M_D81UlXe4?_JR3F@p-BSOafH2z2UQhm}=FXsgDVwo@;FR~x;_^BM` z#ucxb-9|I&th{7$d6%x!6_W^m$KG@B=^5Mg3m`rvfoxrkbI}DcI@lr!7dI6Ci?#l#aJu9G zbV8b^-|+R+oRI*&K*Mmm8sqEz`m~S?IrP@8?@F)(QCxFICy@bFsjp=@O;7RMWOr~0 z{fhP<4S)64yXQacI_WV!52BG*3NKzZ+bTHiQfs>8g8)UAcDOAf1_7*Cr2U#(_-6O! z8_bZ4vTec4@;Q2~{T=A2Q-OV!Eq{mkMG>eL%(4o`(jXOI$ngYYY9^*8fhL_>^mRv- zCP{5rS{=Xtz)9A`DaWZ~{T%Rrq;j9iI)~+)yM;lN@>gQ96~ZSm;!1gEw9%Z}+K;6p>}I&5p}%nB21v zz0as;c{caJr|yBTMmKl6Fv!r%IA_N*Nvk6l_}#(qdDObCRAvqs?7SUI6d1+F-E07zF&>8n^F9T_;R;UtEYMpwOm;^)IeZOI&5XyjM zKA`!&eMjtGw?|)H#I)qn@g0ysV2E^bzxRz)YF`WwF$1p1=hTqAF3;H2q-Gp~(5dg$ z*r}<<#T2Qs(h7goBCaaMTIi63nL(PD-j2$|yj75%+`rbO`Yam{7+z?Qcz4N~H&_i{ zpXt0Zp}EF_QPVToqg7+8^lu8;rqOn**ackn<~1%T+wlh3)=+px1QdM@MZzDj*I3dK zRAHoI?B+*(p>sDw4 z`s}bMVT)R8hX>uuuKfZ z8`t6HQiI9yI`+IZsJQ^HHX)Bbtwv5e=Q3fV)l}~C)@1gQz$?QItifs5`Ci&Jcb};V zOG{w=#Yva#aep%Ey#&wvCqV-LPft8^>QJ4}&|N1y-Bbr4%EXJJC6AR#Nh}+GQA@lONT7?U! zu@DaD% zM%;tbbW~J!P$PoqCc!h77kIPrrlJ#@!Fww0Ot`m zST+)z_9}rFSA2)8z_8R23E)?fN``0ePAk(9OP#f6a{JhlU0b zs=1G$g>8J!1CMUUfPw?E@Nc4~c!c7ypdgqHUW^^4=LJJRPRasN*fIjJhD7`zmH~$h%gzDycb8E zu(Q*j@h6tY&HqS{KRqGB)P#QBxvsk}5+KxJ0j5SJiguzn7@8bj?Obb7)SFCEM5a;h z&;W1dNL0Leezow;P;JTie3g@URid)mhF63ye83`^Uw;BiUEI2>(9@k@A=&-Qazx%a zqTY<%tmC|~0V6{5 z1d~uKNLSMW8YD$9gz|L!lq+zp;FljX5VpbMl_k!k_w{X60?gFCamm8w!Zq8~DVuXe zp959shySG`Bs4paa(AU9b-)ob72vlFyWpOf+QC!ytI=0%>;i$V9DQ>&%Hbk$!&c|JZM6y|9(xooyc%{|+AgL$Y znn3>M@BpHubkXk5Feq5MCW%oir7atm`m^R^u$~@LP|mMC_w(AKE1z1ppRZa^w_R6n zZ8BVQu)#U9gG?b_S_H zQ|(o*K1u~b&d_ZX;H*;Ss6P5ZaEVc-D=}lPNYW48f6nTBFH!s{(xUQ()mMXQ#jEFL zQOC0tI`WWl;I_Fq|0_$`gV#}%Jy)&aOHWx%{q|k|>Ycuz`JxeuOYN7#wQ9O#g4BT@ z%Sk7HdA;kvlia|4eTj(jeVGiVu5hspQD7ku!+5*3PFU0STjMvaqlR;k_%F)NDY&wz zUDt84I<{@w>e#kzyJOo{$F}WsY}>ZYlYiIwZ%^%WyDnzcs+u)wj^}&c_nXzC=Il9` zmQCMs%wtaJbo*+(3a5{{+>4ZjdJg%n$n|Qg`8p&l!nY+70rt1>d&QwxB_g`E2q=JB z3~0p@ejkYUQ*;haJHj2?W%foieG)r0gMFglDz|*<(acG?ufPUi_VbbcOugA`_1qp5QuA8Mf%(OikB0Oo!|`vb>i`puG_}D< z9mxYBxBnu;#3=z+a?tq4s*rXO4rLpmKZoQ zpCC(&uye;!Ce8$aBH28SOU2I~aaBBEJa@qiNCaU8@rae6Xqo48t0*n(&AjTD1CJa< z!7T=`$*&|d`M0GX@)TUH6dFwJNqtBOWPwQ0t^w(}-mSj0ge^kfK;Tax^>H&jI|`yn z;b(LG%N(R_Md^G9hSo|)eT^@w{|sSsIi#{r^5g@pHs|3;#Yg|7xx2KDaoH8U@6~a@cURgP1Odg?bdpFx+=`n zvkcQ0UHV8s#*D-Jl<~~M+HBx<6s;VntsYgE5F3Gugh`y2Jq zW{W>!YX5N&wGG-_y?DibduwIldCreCgnm;mF9~aS^WkPmZ=$H_;stU|5XJ{eoBk9L zl@rpK6F4j8a1P>EYH$`HEJizKU2?`R4yb)HWmJa z%Hq#|w-_w-r|yzW@3D=|dBXpAnwW#fS2cd=?WAOai-cQfD<-taXoODCYV`8-!2$@KODoR)Sh$>-6;@3k1MY0C7#|*7lB>t84udkW+OV}Kli_O!; z4!M&zXugfvH?Bwq(8o2Dl0FA#I?!OuF%z3_>dRHfPrK&@UsnCWVW;pL_S%I%T20vq z<|}T?&*(6%H~q9JV$pHA=Hv#eD4>(`${sh)5b;ySOZ2h9qPcILB(tu1PUQU#2` zK%uBli8JY3-B9svKe->hFlvg1+){F8Gz!wdAj%oCH~p>dVzyBv7|UIMJYmZpYcqFw zt+Sy!>sbE=PevGGR@CwD-CCWH>JOaT{*?d&>>mD?{3oQP&D`-KQW456?S!&GMsGQ6 zX=Z{Fff(tc1m;DE*j3UiWm&JNHcU^IdBi{(zjqzvkRfn1F3_~l)Jg{_yG^#;AQ7QYe{*67<_`7xa(&%GiUM*Ur+ zWMvY@ZBX$0wWM<+)$O8FsVU|U|5G?N3#Ou6+K_)g ztPb{1*#O&O`=Z?CJ>Amq%3S72ir<+{2 zZzq#wEHchncGHu4Ei!?o@1n(2@m^c}_@$QKc}mmkyy*phk?ZK4PsmsRf5kD9U5)IT z%WPKCnbbGF=QS|yV0@E7PfNR*D5qKY-#}oaT3}=n27+*FVt?W(i_0cPSw8{dUe4XW z`-Yh(e$gb-2>OB1GE5i@oRqPS7#61O=U*k7;^&)>YaQP$-(TBqF=sgM_LiEPtSuGg zp5<5Xe4cdsckNd`fgQ&uynCoFDvL_<%|b%adlAsM-dsoPZ_P?XQWaYr&&Iy{&Znf! zR}$+s-fNWg746M8c+_W>IMi6>q$0S^59g%`V@c2-whg&Nv!0FiVR-1Sb4wjvM3b7s znTJjB-A03fO|`vDS~Vmh8ue?$*cl++Vv!IDJ)1Vy>4n`5E;^W4He%L5wlLYA02Tg#C>z5F$;S2XTgsOM^3Y%~OgP`_rQC-}=j-I`#*RO97@r*2LUNc|Rw-cr~ZxZ)|qKbC8 z^TjJNkDu>}BZq{+G$-t2$_*H&EDu(cnkx|c3e6-XKh{~NwLfkK$=8|lsi+9CynB5S z(bPs=G@}_Xj;@73ojyTW8TixPHE50IF5S9B8*I#=kO_i}ovLl8Z)WRjoX4ODe5s%u zl)R}qC1UXk)DPi`2>2`#o!A)}EVUMh_;tyGL4G7xUoc;ox(G$pu47TMVFZJhr6NAgjf+Z{1ZXJE zOGLtR7Y~P2Fy_aknrg^2D(sw;2I^rEOEu2mWQ**cDzN@`Uz^afF)m;EIOHZ zmp+^C{zg30vpiqa4X@8!*EolJz~kTr1CF8Uf$q5A(c_nY^WuN?;V6dE;OjByN(w7{ z~2FY_C1CD zI8{g0SJLJ)?k{CxA>+2*JJt4N3n<@uo08f7;8}5Nzd-W5j&YBPMUS$?IwW=z+o!`eCmAl>n&Y`b*JQd{E@72eyPc)8>K4fd>_>(h1(lS=Nh-hFWQoadLRt zT!%w6?hg>iIMNcQidNMe4M(xNpF0Pkn?l8V2q|A6n;z7ygZ5OvDu~0PvYC1yWGr$k z8)4}*k7lw(wAS(gWT1Dqp9mGFECw*NX6Z?UsCv?wc6nH4`0=PG?J}MCxbE+PED8G* zq~^~i8R%>Mu<(I3YTCx;rPsnsd>h0UJtSaL)$o7Qi18v43c{W>7G48E1Q52eV-A`N z@A)jp$`JJeFQop&)dly;kNwKW`vlSi{nPz3N5kG-JIZ}8!IT*@bSZKB;_`KqwY&(Z zb5)hCUvW%y9M(XgEIY2iN#X>frvJRhqz9Rb30@gM$uUnXq2^Z&Hup9MEv7m!T0ue9 zizKKMrshkM3F5$dBy+RsaC`zSm1cv)JP^;X8-mCH@XZLLw1_nKhtd?JJDWk;$IHix zS*OAn)GTX5WF-iF=)R126kFlPNxxfU|DE?i;3e-kf?PumNMrBDaM>9Z{5yIk2R?(9 z4;uY&@8CJkJRYKL7oLA1gwPWwGbQ)B{Md-tBr!vQ`SK%J6WABI)#Fs79xEmLyh86K z5b-t)H$z(DzLzAUy&{2i3MZ0eav*;FQ$2Zg!@TkggUm+|g1ys6_D>EyU%39Sc*a*4 zEX70GkReFK?=a)yd8X1gkuEwKC8Sst5>>(*U2R$B@Gkv{4RM1O->F=A%J>c2t_DTG zH*7z1ieHjN8%(uN+h+MO_H+@g^t{AICD>1bgWkUYui%#1x1$GXKxZ&&Qy4-2VhKb%H;4GG}zTS_mKu18*T47@#>=)cQt_$#ZPS+LuuhY_g2~Sx5R4-INW7` zC!Mv^@a<29sJtnIZc4XW-s%?(lt2nRs(fPH{$DjVR|oc-$qBMxBnTeedG3<^x8Vrr z`G#Sn7e4VuWDxk_Ll$gL3g+zmw7tbb3jW@U)(x7-5Z#3ClWDSXX+ve|H+4bXrfJ1JV1PP?Sd&FoOd6+0*Pb&Qs1E4LL+cst| zju@BD2p$kXnt84@usw)eue5TYmW+rX^{PGhX1eDz%W

RVlek+L&!fps@*i^@r72ziBGx%5qL7{ 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