diff --git a/Dockerfile b/Dockerfile index 11635f3d82..b43c2ca501 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,4 +59,4 @@ EXPOSE 8545 EXPOSE 8546 EXPOSE 9090 EXPOSE 26657 -EXPOSE 9091 +EXPOSE 9091 \ No newline at end of file diff --git a/changelog.md b/changelog.md index aadd110032..f1cefeb587 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,10 @@ * [1783](https://github.com/zeta-chain/node/pull/1783) - refactor zetaclient metrics naming and structure * [1774](https://github.com/zeta-chain/node/pull/1774) - split params and config in zetaclient * [1831](https://github.com/zeta-chain/node/pull/1831) - removing unnecessary pointers in context structure +* [1864](https://github.com/zeta-chain/node/pull/1864) - prevent panic in param management +* [1848](https://github.com/zeta-chain/node/issues/1848) - create a method to observe deposits to tss address in one evm block +* [1885](https://github.com/zeta-chain/node/pull/1885) - change important metrics on port 8123 to be prometheus compatible +* [1863](https://github.com/zeta-chain/node/pull/1863) - remove duplicate ValidateChainParams function ### Features @@ -37,10 +41,13 @@ * [1791](https://github.com/zeta-chain/node/pull/1791) - add e2e tests for feature of restricted address * [1787](https://github.com/zeta-chain/node/pull/1787) - add unit tests for cross-chain evm hooks and e2e test failed withdraw to BTC legacy address * [1840](https://github.com/zeta-chain/node/pull/1840) - fix code coverage test failures ignored in CI +* [1870](https://github.com/zeta-chain/node/pull/1870) - enable emissions pool in local e2e testing * [1868](https://github.com/zeta-chain/node/pull/1868) - run e2e btc tests locally * [1851](https://github.com/zeta-chain/node/pull/1851) - rename usdt to erc20 in e2e tests * [1872](https://github.com/zeta-chain/node/pull/1872) - remove usage of RPC in unit test * [1805](https://github.com/zeta-chain/node/pull/1805) - add admin and performance test and fix upgrade test +* [1879](https://github.com/zeta-chain/node/pull/1879) - full coverage for messages in types packages +* [1899](https://github.com/zeta-chain/node/pull/1899) - add empty test files so packages are included in coverage ### Fixes @@ -53,6 +60,8 @@ ### CI * [1867](https://github.com/zeta-chain/node/pull/1867) - default restore_type for full node docker-compose to snapshot instead of statesync for reliability. +* [1891](https://github.com/zeta-chain/node/pull/1891) - fix typo that was introduced to docker-compose and a typo in start.sh for the docker start script for full nodes. +* [1894](https://github.com/zeta-chain/node/pull/1894) - added download binaries and configs to the start sequence so it will download binaries that don't exist ## Version: v14 diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 3816699c78..f402b26b4d 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -162,12 +162,15 @@ func start(_ *cobra.Command, _ []string) error { } } - metrics, err := metrics.NewMetrics() + m, err := metrics.NewMetrics() if err != nil { log.Error().Err(err).Msg("NewMetrics") return err } - metrics.Start() + m.Start() + + metrics.Info.WithLabelValues(common.Version).Set(1) + metrics.LastStartTime.SetToCurrentTime() var tssHistoricalList []observerTypes.TSS tssHistoricalList, err = zetaBridge.GetTssHistory() diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 3dd40d92c3..6aef91156a 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -44,71 +44,19 @@ func NewLocalCmd() *cobra.Command { Short: "Run Local E2E tests", Run: localE2ETest, } - cmd.Flags().Bool( - flagContractsDeployed, - false, - "set to to true if running tests again with existing state", - ) - cmd.Flags().Int64( - flagWaitForHeight, - 0, - "block height for tests to begin, ex. --wait-for 100", - ) - cmd.Flags().String( - FlagConfigFile, - "", - "config file to use for the tests", - ) - cmd.Flags().String( - flagConfigOut, - "", - "config file to write the deployed contracts from the setup", - ) - cmd.Flags().Bool( - flagVerbose, - false, - "set to true to enable verbose logging", - ) - cmd.Flags().Bool( - flagTestAdmin, - false, - "set to true to run admin tests", - ) - cmd.Flags().Bool( - flagTestPerformance, - false, - "set to true to run performance tests", - ) - cmd.Flags().Bool( - flagTestCustom, - false, - "set to true to run custom tests", - ) - cmd.Flags().Bool( - flagSkipRegular, - false, - "set to true to skip regular tests", - ) - cmd.Flags().Bool( - flagLight, - false, - "run the most basic regular tests, useful for quick checks", - ) - cmd.Flags().Bool( - flagSetupOnly, - false, - "set to true to only setup the networks", - ) - cmd.Flags().Bool( - flagSkipSetup, - false, - "set to true to skip setup", - ) - cmd.Flags().Bool( - flagSkipBitcoinSetup, - false, - "set to true to skip bitcoin wallet setup", - ) + cmd.Flags().Bool(flagContractsDeployed, false, "set to to true if running tests again with existing state") + cmd.Flags().Int64(flagWaitForHeight, 0, "block height for tests to begin, ex. --wait-for 100") + cmd.Flags().String(FlagConfigFile, "", "config file to use for the tests") + cmd.Flags().Bool(flagVerbose, false, "set to true to enable verbose logging") + cmd.Flags().Bool(flagTestAdmin, false, "set to true to run admin tests") + cmd.Flags().Bool(flagTestPerformance, false, "set to true to run performance tests") + cmd.Flags().Bool(flagTestCustom, false, "set to true to run custom tests") + cmd.Flags().Bool(flagSkipRegular, false, "set to true to skip regular tests") + cmd.Flags().Bool(flagLight, false, "run the most basic regular tests, useful for quick checks") + cmd.Flags().Bool(flagSetupOnly, false, "set to true to only setup the networks") + cmd.Flags().String(flagConfigOut, "", "config file to write the deployed contracts from the setup") + cmd.Flags().Bool(flagSkipSetup, false, "set to true to skip setup") + cmd.Flags().Bool(flagSkipBitcoinSetup, false, "set to true to skip bitcoin wallet setup") return cmd } @@ -242,7 +190,16 @@ func localE2ETest(cmd *cobra.Command, _ []string) { startTime := time.Now() deployerRunner.SetupEVM(contractsDeployed) deployerRunner.SetZEVMContracts() + + // NOTE: this method return an error so we handle it and panic if it occurs unlike other method that panics directly + // TODO: all methods should return errors instead of panicking and this current function should also return an error + // https://github.com/zeta-chain/node/issues/1500 + if err := deployerRunner.FundEmissionsPool(); err != nil { + panic(err) + } + deployerRunner.MintERC20OnEvm(10000) + logger.Print("✅ setup completed in %s", time.Since(startTime)) } @@ -346,4 +303,17 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } logger.Print("✅ e2e tests completed in %s", time.Since(testStartTime).String()) + + // print and validate report + networkReport, err := deployerRunner.GenerateNetworkReport() + if err != nil { + logger.Print("❌ failed to generate network report %v", err) + } + deployerRunner.PrintNetworkReport(networkReport) + if err := networkReport.Validate(); err != nil { + logger.Print("❌ network report validation failed %v", err) + os.Exit(1) + } + + os.Exit(0) } diff --git a/codecov.yml b/codecov.yml index 948e5aef5f..93d974911b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -68,4 +68,6 @@ ignore: - "scripts/" - "server/" - "testutil/" + - "testutils/" + - "errors/" - "typescript/" \ No newline at end of file diff --git a/common/bitcoin/proof_test.go b/common/bitcoin/proof_test.go new file mode 100644 index 0000000000..7e3ae1ff51 --- /dev/null +++ b/common/bitcoin/proof_test.go @@ -0,0 +1 @@ +package bitcoin_test diff --git a/common/cosmos/cosmos_test.go b/common/cosmos/cosmos_test.go new file mode 100644 index 0000000000..712dd321d1 --- /dev/null +++ b/common/cosmos/cosmos_test.go @@ -0,0 +1 @@ +package cosmos_test diff --git a/contrib/athens3/zetacored/docker-compose.yml b/contrib/athens3/zetacored/docker-compose.yml index f215b92239..cee5b695bc 100644 --- a/contrib/athens3/zetacored/docker-compose.yml +++ b/contrib/athens3/zetacored/docker-compose.yml @@ -6,7 +6,7 @@ services: # build: # context: ../../.. # dockerfile: Dockerfile - image: zetachain/zetacored:${DOCKER_TAG:-ubuntu-v14} + image: zetachain/zetacored:${DOCKER_TAG:-ubuntu-v14.0.1} environment: DAEMON_HOME: "/root/.zetacored" NETWORK: athens3 @@ -35,8 +35,8 @@ services: - "9090:9090" - "9091:9091" volumes: - - zetacored_data:/root/.zetacored/ - entrypoint: bash /scripts/start-zetae2e.sh + - zetacored_data_athens3:/root/.zetacored/ + entrypoint: bash /scripts/start.sh volumes: - zetacored_data: + zetacored_data_athens3: diff --git a/contrib/docker-scripts/start.sh b/contrib/docker-scripts/start.sh index b9ea1b482b..d60f131c2c 100644 --- a/contrib/docker-scripts/start.sh +++ b/contrib/docker-scripts/start.sh @@ -4,6 +4,49 @@ logt() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" } + +function load_defaults { + #DEFAULT: Mainnet Statesync. + export DAEMON_HOME=${DAEMON_HOME:=/root/.zetacored} + export NETWORK=${NETWORK:=mainnet} + export RESTORE_TYPE=${RESTORE_TYPE:=statesync} + export SNAPSHOT_API=${SNAPSHOT_API:=https://snapshots.zetachain.com} + export TRUST_HEIGHT_DIFFERENCE_STATE_SYNC=${TRUST_HEIGHT_DIFFERENCE_STATE_SYNC:=40000} + export COSMOVISOR_VERSION=${COSMOVISOR_VERSION:=v1.5.0} + export CHAIN_ID=${CHAIN_ID:=zetachain_7000-1} + export COSMOVISOR_CHECKSUM=${COSMOVISOR_CHECKSUM:=626dfc58c266b85f84a7ed8e2fe0e2346c15be98cfb9f9b88576ba899ed78cdc} + export VISOR_NAME=${VISOR_NAME:=cosmovisor} + export DAEMON_NAME=${DAEMON_NAME:=zetacored} + export DAEMON_ALLOW_DOWNLOAD_BINARIES=${DAEMON_ALLOW_DOWNLOAD_BINARIES:=false} + export DAEMON_RESTART_AFTER_UPGRADE=${DAEMON_RESTART_AFTER_UPGRADE:=true} + export UNSAFE_SKIP_BACKUP=${UNSAFE_SKIP_BACKUP:=true} + export CLIENT_DAEMON_NAME=${CLIENT_DAEMON_NAME:=zetaclientd} + export CLIENT_DAEMON_ARGS=${CLIENT_DAEMON_ARGS:""} + export CLIENT_SKIP_UPGRADE=${CLIENT_SKIP_UPGRADE:=true} + export CLIENT_START_PROCESS=${CLIENT_START_PROCESS:=false} + export MONIKER=${MONIKER:=local-test} + export RE_DO_START_SEQUENCE=${RE_DO_START_SEQUENCE:=false} + + #ATHENS3 + export BINARY_LIST_ATHENS3=${BINARY_LIST_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/binary_list.json} + export STATE_SYNC_RPC_NODE_FILE_ATHENS3=${STATE_SYNC_RPC_NODE_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/state_sync_node} + export RPC_STATE_SYNC_RPC_LIST_FILE_ATHENS3=${RPC_STATE_SYNC_RPC_LIST_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/rpc_state_sync_nodes} + export APP_TOML_FILE_ATHENS3=${APP_TOML_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/app.toml} + export CONFIG_TOML_FILE_ATHENS3=${CONFIG_TOML_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/config.toml} + export CLIENT_TOML_FILE_ATHENS3=${CLIENT_TOML_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/client.toml} + export GENESIS_FILE_ATHENS3=${GENESIS_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/genesis.json} + + #MAINNET + export BINARY_LIST_MAINNET=${BINARY_LIST_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/binary_list.json} + export STATE_SYNC_RPC_NODE_FILE_MAINNET=${STATE_SYNC_RPC_NODE_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/state_sync_node} + export RPC_STATE_SYNC_RPC_LIST_FILE_MAINNET=${RPC_STATE_SYNC_RPC_LIST_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/rpc_state_sync_nodes} + export APP_TOML_FILE_MAINNET=${APP_TOML_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/app.toml} + export CONFIG_TOML_FILE_MAINNET=${CONFIG_TOML_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/config.toml} + export CLIENT_TOML_FILE_MAINNET=${CLIENT_TOML_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/client.toml} + export GENESIS_FILE_MAINNET=${GENESIS_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/genesis.json} + +} + function init_chain { if [ -d "${DAEMON_HOME}/config" ]; then logt "${DAEMON_NAME} home directory already initialized." @@ -193,6 +236,7 @@ function move_zetacored_binaries { } function start_network { + ${VISOR_NAME} version ${VISOR_NAME} run start --home ${DAEMON_HOME} \ --log_level info \ --moniker ${MONIKER} \ @@ -200,54 +244,18 @@ function start_network { --minimum-gas-prices 1.0azeta "--grpc.enable=true" } -function load_defaults { - #DEFAULT: Mainnet Statesync. - export DAEMON_HOME=${DAEMON_HOME:=/root/.zetacored} - export NETWORK=${NETWORK:=mainnet} - export RESTORE_TYPE=${RESTORE_TYPE:=statesync} - export SNAPSHOT_API=${SNAPSHOT_API:=https://snapshots.zetachain.com} - export TRUST_HEIGHT_DIFFERENCE_STATE_SYNC=${TRUST_HEIGHT_DIFFERENCE_STATE_SYNC:=40000} - export COSMOVISOR_VERSION=${COSMOVISOR_VERSION:=v1.5.0} - export CHAIN_ID=${CHAIN_ID:=zetachain_7000-1} - export COSMOVISOR_CHECKSUM=${COSMOVISOR_CHECKSUM:=626dfc58c266b85f84a7ed8e2fe0e2346c15be98cfb9f9b88576ba899ed78cdc} - export VISOR_NAME=${VISOR_NAME:=cosmovisor} - export DAEMON_NAME=${DAEMON_NAME:=zetacored} - export DAEMON_ALLOW_DOWNLOAD_BINARIES=${DAEMON_ALLOW_DOWNLOAD_BINARIES:=false} - export DAEMON_RESTART_AFTER_UPGRADE=${DAEMON_RESTART_AFTER_UPGRADE:=true} - export UNSAFE_SKIP_BACKUP=${UNSAFE_SKIP_BACKUP:=true} - export CLIENT_DAEMON_NAME=${CLIENT_DAEMON_NAME:=zetaclientd} - export CLIENT_DAEMON_ARGS=${CLIENT_DAEMON_ARGS:""} - export CLIENT_SKIP_UPGRADE=${CLIENT_SKIP_UPGRADE:=true} - export CLIENT_START_PROCESS=${CLIENT_START_PROCESS:=false} - export MONIKER=${MONIKER:=local-test} - export RE_DO_START_SEQUENCE=${RE_DO_START_SEQUENCE:=false} - - #ATHENS3 - export BINARY_LIST_ATHENS3=${BINARY_LIST_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/binary_list.json} - export STATE_SYNC_RPC_NODE_FILE_ATHENS3=${STATE_SYNC_RPC_NODE_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/state_sync_node} - export RPC_STATE_SYNC_RPC_LIST_FILE_ATHENS3=${RPC_STATE_SYNC_RPC_LIST_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/rpc_state_sync_nodes} - export APP_TOML_FILE_ATHENS3=${APP_TOML_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/app.toml} - export CONFIG_TOML_FILE_ATHENS3=${CONFIG_TOML_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/config.toml} - export CLIENT_TOML_FILE_ATHENS3=${CLIENT_TOML_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/client.toml} - export GENESIS_FILE_ATHENS3=${GENESIS_FILE_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/athens3/genesis.json} - - #MAINNET - export BINARY_LIST_MAINNET=${BINARY_LIST_ATHENS3:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/binary_list.json} - export STATE_SYNC_RPC_NODE_FILE_MAINNET=${STATE_SYNC_RPC_NODE_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/state_sync_node} - export RPC_STATE_SYNC_RPC_LIST_FILE_MAINNET=${RPC_STATE_SYNC_RPC_LIST_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/rpc_state_sync_nodes} - export APP_TOML_FILE_MAINNET=${APP_TOML_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/app.toml} - export CONFIG_TOML_FILE_MAINNET=${CONFIG_TOML_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/config.toml} - export CLIENT_TOML_FILE_MAINNET=${CLIENT_TOML_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/client.toml} - export GENESIS_FILE_MAINNET=${GENESIS_FILE_MAINNET:=https://raw.githubusercontent.com/zeta-chain/network-config/main/mainnet/genesis.json} - -} - logt "Load Default Values for ENV Vars if not set." load_defaults if [[ -f "${DAEMON_HOME}/start_sequence_status" ]] && grep -q "START_SEQUENCE_COMPLETE" "${DAEMON_HOME}/start_sequence_status" && [[ "$RE_DO_START_SEQUENCE" != "true" ]]; then logt "The start sequence is complete and no redo is required." + logt "Download Configs" + download_configs + + logt "Download Historical Binaries" + download_binary_version + if [ "${RESTORE_TYPE}" == "statesync" ]; then logt "Setup Restore Type: ${RESTORE_TYPE}" logt "During restarts, we re-do this to ensure to update the configs with valid values. When you call change config the stuff that gets set in this function for statesync needs to be set. Doesn't effect to re-set this." @@ -296,5 +304,4 @@ else logt "Start Network" start_network -fi - +fi \ No newline at end of file diff --git a/contrib/mainnet/zetacored/docker-compose.yml b/contrib/mainnet/zetacored/docker-compose.yml index 2f550825cd..ed75d0254c 100644 --- a/contrib/mainnet/zetacored/docker-compose.yml +++ b/contrib/mainnet/zetacored/docker-compose.yml @@ -6,7 +6,7 @@ services: # build: # context: ../../.. # dockerfile: Dockerfile - image: zetachain/zetacored:${DOCKER_TAG:-ubuntu-v14} + image: zetachain/zetacored:${DOCKER_TAG:-ubuntu-v14.0.1} container_name: zetachain_mainnet_rpc environment: DAEMON_HOME: "/root/.zetacored" @@ -36,10 +36,10 @@ services: - "9090:9090" - "9091:9091" volumes: - - zetacored_data:/root/.zetacored/ - entrypoint: bash /scripts/start-zetae2e.sh + - zetacored_data_mainnet:/root/.zetacored/ + entrypoint: bash /scripts/start.sh #for debugging #entrypoint: ["/bin/sh", "-c"] #command: ["while true; do sleep 86400; done"] volumes: - zetacored_data: + zetacored_data_mainnet: diff --git a/e2e/runner/e2etest.go b/e2e/runner/e2etest.go new file mode 100644 index 0000000000..499d45144d --- /dev/null +++ b/e2e/runner/e2etest.go @@ -0,0 +1,105 @@ +package runner + +import "fmt" + +// E2ETestFunc is a function representing a E2E test +// It takes a E2ERunner as an argument +type E2ETestFunc func(*E2ERunner, []string) + +// E2ETest represents a E2E test with a name, args, description and test func +type E2ETest struct { + Name string + Description string + Args []string + ArgsDefinition []ArgDefinition + E2ETest E2ETestFunc +} + +// NewE2ETest creates a new instance of E2ETest with specified parameters. +func NewE2ETest(name, description string, argsDefinition []ArgDefinition, e2eTestFunc E2ETestFunc) E2ETest { + return E2ETest{ + Name: name, + Description: description, + ArgsDefinition: argsDefinition, + E2ETest: e2eTestFunc, + Args: []string{}, + } +} + +// ArgDefinition defines a structure for holding an argument's description along with it's default value. +type ArgDefinition struct { + Description string + DefaultValue string +} + +// DefaultArgs extracts and returns array of default arguments from the ArgsDefinition. +func (e E2ETest) DefaultArgs() []string { + defaultArgs := make([]string, len(e.ArgsDefinition)) + for i, spec := range e.ArgsDefinition { + defaultArgs[i] = spec.DefaultValue + } + return defaultArgs +} + +// ArgsDescription returns a string representing the arguments description in a readable format. +func (e E2ETest) ArgsDescription() string { + argsDescription := "" + for _, def := range e.ArgsDefinition { + argDesc := fmt.Sprintf("%s (%s)", def.Description, def.DefaultValue) + if argsDescription != "" { + argsDescription += ", " + } + argsDescription += argDesc + } + return argsDescription +} + +// E2ETestRunConfig defines the basic configuration for initiating an E2E test, including its name and optional runtime arguments. +type E2ETestRunConfig struct { + Name string + Args []string +} + +// GetE2ETestsToRunByName prepares a list of E2ETests to run based on given test names without arguments +func (runner *E2ERunner) GetE2ETestsToRunByName(availableTests []E2ETest, testNames ...string) ([]E2ETest, error) { + tests := []E2ETestRunConfig{} + for _, testName := range testNames { + tests = append(tests, E2ETestRunConfig{ + Name: testName, + Args: []string{}, + }) + } + return runner.GetE2ETestsToRunByConfig(availableTests, tests) +} + +// GetE2ETestsToRunByConfig prepares a list of E2ETests to run based on provided test names and their corresponding arguments +func (runner *E2ERunner) GetE2ETestsToRunByConfig(availableTests []E2ETest, testConfigs []E2ETestRunConfig) ([]E2ETest, error) { + tests := []E2ETest{} + for _, testSpec := range testConfigs { + e2eTest, found := findE2ETestByName(availableTests, testSpec.Name) + if !found { + return nil, fmt.Errorf("e2e test %s not found", testSpec.Name) + } + e2eTestToRun := NewE2ETest( + e2eTest.Name, + e2eTest.Description, + e2eTest.ArgsDefinition, + e2eTest.E2ETest, + ) + // update e2e test args + e2eTestToRun.Args = testSpec.Args + tests = append(tests, e2eTestToRun) + } + + return tests, nil +} + +// findE2ETest finds a e2e test by name +func findE2ETestByName(e2eTests []E2ETest, e2eTestName string) (E2ETest, bool) { + for _, test := range e2eTests { + if test.Name == e2eTestName { + return test, true + } + } + return E2ETest{}, false +} diff --git a/e2e/runner/report.go b/e2e/runner/report.go index a502c442dd..e798eadd4b 100644 --- a/e2e/runner/report.go +++ b/e2e/runner/report.go @@ -5,9 +5,16 @@ import ( "strings" "text/tabwriter" "time" + + sdkmath "cosmossdk.io/math" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" + "github.com/zeta-chain/zetacore/e2e/txserver" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) -// TestReport is a struct that contains the test report +// TestReport is a struct that contains the report for a specific e2e test +// It can be generated with the RunE2ETestsIntoReport method type TestReport struct { Name string Success bool @@ -53,3 +60,70 @@ func (runner *E2ERunner) PrintTestReports(tr TestReports) { } runner.Logger.PrintNoPrefix(table) } + +// NetworkReport is a struct that contains the report for the network used after running e2e tests +// This report has been initialized to check the emissions pool balance and if the pool is decreasing +// TODO: add more complete data and validation to the network +// https://github.com/zeta-chain/node/issues/1873 +type NetworkReport struct { + EmissionsPoolBalance sdkmath.Int + Height uint64 + CctxCount int +} + +// Validate validates the network report +// This method is used to validate the network after running e2e tests +// It checks the emissions pool balance and if the pool is decreasing +func (nr NetworkReport) Validate() error { + if nr.EmissionsPoolBalance.GTE(sdkmath.NewIntFromBigInt(EmissionsPoolFunding)) { + return fmt.Errorf( + "emissions pool balance is not decreasing, expected less than %s, got %s", + EmissionsPoolFunding, + nr.EmissionsPoolBalance, + ) + } + return nil +} + +// GenerateNetworkReport generates a report for the network used after running e2e tests +func (runner *E2ERunner) GenerateNetworkReport() (NetworkReport, error) { + // get the emissions pool balance + balanceRes, err := runner.BankClient.Balance(runner.Ctx, &banktypes.QueryBalanceRequest{ + Address: txserver.EmissionsPoolAddress, + Denom: config.BaseDenom, + }) + if err != nil { + return NetworkReport{}, err + } + emissionsPoolBalance := balanceRes.Balance + + // fetch the height and number of cctxs, this gives a better idea on the activity of the network + + // get the block height + blockRes, err := runner.ZEVMClient.BlockNumber(runner.Ctx) + if err != nil { + return NetworkReport{}, err + } + + // get the number of cctxs + cctxsRes, err := runner.CctxClient.CctxAll(runner.Ctx, &crosschaintypes.QueryAllCctxRequest{}) + if err != nil { + return NetworkReport{}, err + } + cctxCount := len(cctxsRes.CrossChainTx) + + return NetworkReport{ + EmissionsPoolBalance: emissionsPoolBalance.Amount, + Height: blockRes, + CctxCount: cctxCount, + }, nil +} + +// PrintNetworkReport prints the network report +func (runner *E2ERunner) PrintNetworkReport(nr NetworkReport) { + runner.Logger.Print(" ---📈 Network Report ---") + runner.Logger.Print("Block Height: %d", nr.Height) + runner.Logger.Print("CCTX Processed: %d", nr.CctxCount) + runner.Logger.Print("Emissions Pool Balance: %sZETA", nr.EmissionsPoolBalance.Quo(sdkmath.NewIntFromUint64(1e18))) + +} diff --git a/e2e/runner/run.go b/e2e/runner/run.go new file mode 100644 index 0000000000..f24ff9d9f8 --- /dev/null +++ b/e2e/runner/run.go @@ -0,0 +1,94 @@ +package runner + +import ( + "fmt" + "runtime" + "time" +) + +// RunE2ETests runs a list of e2e tests +func (runner *E2ERunner) RunE2ETests(e2eTests []E2ETest) (err error) { + for _, e2eTest := range e2eTests { + if err := runner.RunE2ETest(e2eTest, true); err != nil { + return err + } + } + return nil +} + +// RunE2ETest runs a e2e test +func (runner *E2ERunner) RunE2ETest(e2eTest E2ETest, checkAccounting bool) (err error) { + // return an error on panic + // https://github.com/zeta-chain/node/issues/1500 + defer func() { + if r := recover(); r != nil { + // print stack trace + stack := make([]byte, 4096) + n := runtime.Stack(stack, false) + err = fmt.Errorf("%s failed: %v, stack trace %s", e2eTest.Name, r, stack[:n]) + } + }() + + startTime := time.Now() + runner.Logger.Print("⏳running - %s", e2eTest.Description) + + // run e2e test, if args are not provided, use default args + args := e2eTest.Args + if len(args) == 0 { + args = e2eTest.DefaultArgs() + } + e2eTest.E2ETest(runner, args) + + //check supplies + if checkAccounting { + if err := runner.CheckZRC20ReserveAndSupply(); err != nil { + return err + } + } + + runner.Logger.Print("✅ completed in %s - %s", time.Since(startTime), e2eTest.Description) + + return err +} + +// RunE2ETestsIntoReport runs a list of e2e tests by name in a list of e2e tests and returns a report +// The function doesn't return an error, it returns a report with the error +func (runner *E2ERunner) RunE2ETestsIntoReport(e2eTests []E2ETest) (TestReports, error) { + // go through all tests + reports := make(TestReports, 0, len(e2eTests)) + for _, test := range e2eTests { + // get info before test + balancesBefore, err := runner.GetAccountBalances(true) + if err != nil { + return nil, err + } + timeBefore := time.Now() + + // run test + testErr := runner.RunE2ETest(test, false) + if testErr != nil { + runner.Logger.Print("test %s failed: %s", test.Name, testErr.Error()) + } + + // wait 5 sec to make sure we get updated balances + time.Sleep(5 * time.Second) + + // get info after test + balancesAfter, err := runner.GetAccountBalances(true) + if err != nil { + return nil, err + } + timeAfter := time.Now() + + // create report + report := TestReport{ + Name: test.Name, + Success: testErr == nil, + Time: timeAfter.Sub(timeBefore), + GasSpent: GetAccountBalancesDiff(balancesBefore, balancesAfter), + } + reports = append(reports, report) + } + + return reports, nil +} diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index 06b123c12f..22f549b296 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -2,16 +2,9 @@ package runner import ( "context" - "fmt" - "runtime" "sync" "time" - "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" - "github.com/zeta-chain/zetacore/e2e/contracts/erc20" - "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" - "github.com/zeta-chain/zetacore/e2e/txserver" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcutil" @@ -29,6 +22,10 @@ import ( "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" uniswapv2router "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" + "github.com/zeta-chain/zetacore/e2e/contracts/erc20" + "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" + "github.com/zeta-chain/zetacore/e2e/txserver" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -159,195 +156,6 @@ func NewE2ERunner( } } -// E2ETestFunc is a function representing a E2E test -// It takes a E2ERunner as an argument -type E2ETestFunc func(*E2ERunner, []string) - -// E2ETest represents a E2E test with a name, args, description and test func -type E2ETest struct { - Name string - Description string - Args []string - ArgsDefinition []ArgDefinition - E2ETest E2ETestFunc -} - -// NewE2ETest creates a new instance of E2ETest with specified parameters. -func NewE2ETest(name, description string, argsDefinition []ArgDefinition, e2eTestFunc E2ETestFunc) E2ETest { - return E2ETest{ - Name: name, - Description: description, - ArgsDefinition: argsDefinition, - E2ETest: e2eTestFunc, - Args: []string{}, - } -} - -// ArgDefinition defines a structure for holding an argument's description along with it's default value. -type ArgDefinition struct { - Description string - DefaultValue string -} - -// DefaultArgs extracts and returns array of default arguments from the ArgsDefinition. -func (e E2ETest) DefaultArgs() []string { - defaultArgs := make([]string, len(e.ArgsDefinition)) - for i, spec := range e.ArgsDefinition { - defaultArgs[i] = spec.DefaultValue - } - return defaultArgs -} - -// ArgsDescription returns a string representing the arguments description in a readable format. -func (e E2ETest) ArgsDescription() string { - argsDescription := "" - for _, def := range e.ArgsDefinition { - argDesc := fmt.Sprintf("%s (%s)", def.Description, def.DefaultValue) - if argsDescription != "" { - argsDescription += ", " - } - argsDescription += argDesc - } - return argsDescription -} - -// E2ETestRunConfig defines the basic configuration for initiating an E2E test, including its name and optional runtime arguments. -type E2ETestRunConfig struct { - Name string - Args []string -} - -// GetE2ETestsToRunByName prepares a list of E2ETests to run based on given test names without arguments -func (runner *E2ERunner) GetE2ETestsToRunByName(availableTests []E2ETest, testNames ...string) ([]E2ETest, error) { - tests := []E2ETestRunConfig{} - for _, testName := range testNames { - tests = append(tests, E2ETestRunConfig{ - Name: testName, - Args: []string{}, - }) - } - return runner.GetE2ETestsToRunByConfig(availableTests, tests) -} - -// GetE2ETestsToRunByConfig prepares a list of E2ETests to run based on provided test names and their corresponding arguments -func (runner *E2ERunner) GetE2ETestsToRunByConfig(availableTests []E2ETest, testConfigs []E2ETestRunConfig) ([]E2ETest, error) { - tests := []E2ETest{} - for _, testSpec := range testConfigs { - e2eTest, found := findE2ETestByName(availableTests, testSpec.Name) - if !found { - return nil, fmt.Errorf("e2e test %s not found", testSpec.Name) - } - e2eTestToRun := NewE2ETest( - e2eTest.Name, - e2eTest.Description, - e2eTest.ArgsDefinition, - e2eTest.E2ETest, - ) - // update e2e test args - e2eTestToRun.Args = testSpec.Args - tests = append(tests, e2eTestToRun) - } - - return tests, nil -} - -// RunE2ETests runs a list of e2e tests -func (runner *E2ERunner) RunE2ETests(e2eTests []E2ETest) (err error) { - for _, e2eTest := range e2eTests { - if err := runner.RunE2ETest(e2eTest, true); err != nil { - return err - } - } - return nil -} - -// RunE2ETestsFromNamesIntoReport runs a list of e2e tests by name in a list of e2e tests and returns a report -// The function doesn't return an error, it returns a report with the error -func (runner *E2ERunner) RunE2ETestsIntoReport(e2eTests []E2ETest) (TestReports, error) { - // go through all tests - reports := make(TestReports, 0, len(e2eTests)) - for _, test := range e2eTests { - // get info before test - balancesBefore, err := runner.GetAccountBalances(true) - if err != nil { - return nil, err - } - timeBefore := time.Now() - - // run test - testErr := runner.RunE2ETest(test, false) - if testErr != nil { - runner.Logger.Print("test %s failed: %s", test.Name, testErr.Error()) - } - - // wait 5 sec to make sure we get updated balances - time.Sleep(5 * time.Second) - - // get info after test - balancesAfter, err := runner.GetAccountBalances(true) - if err != nil { - return nil, err - } - timeAfter := time.Now() - - // create report - report := TestReport{ - Name: test.Name, - Success: testErr == nil, - Time: timeAfter.Sub(timeBefore), - GasSpent: GetAccountBalancesDiff(balancesBefore, balancesAfter), - } - reports = append(reports, report) - } - - return reports, nil -} - -// RunE2ETest runs a e2e test -func (runner *E2ERunner) RunE2ETest(e2eTest E2ETest, checkAccounting bool) (err error) { - // return an error on panic - // https://github.com/zeta-chain/node/issues/1500 - defer func() { - if r := recover(); r != nil { - // print stack trace - stack := make([]byte, 4096) - n := runtime.Stack(stack, false) - err = fmt.Errorf("%s failed: %v, stack trace %s", e2eTest.Name, r, stack[:n]) - } - }() - - startTime := time.Now() - runner.Logger.Print("⏳running - %s", e2eTest.Description) - - // run e2e test, if args are not provided, use default args - args := e2eTest.Args - if len(args) == 0 { - args = e2eTest.DefaultArgs() - } - e2eTest.E2ETest(runner, args) - - //check supplies - if checkAccounting { - if err := runner.CheckZRC20ReserveAndSupply(); err != nil { - return err - } - } - - runner.Logger.Print("✅ completed in %s - %s", time.Since(startTime), e2eTest.Description) - - return err -} - -// findE2ETest finds a e2e test by name -func findE2ETestByName(e2eTests []E2ETest, e2eTestName string) (E2ETest, bool) { - for _, test := range e2eTests { - if test.Name == e2eTestName { - return test, true - } - } - return E2ETest{}, false -} - // CopyAddressesFrom copies addresses from another E2ETestRunner that initialized the contracts func (runner *E2ERunner) CopyAddressesFrom(other *E2ERunner) (err error) { // copy TSS address diff --git a/e2e/runner/setup_zeta.go b/e2e/runner/setup_zeta.go index 16f8f3b64e..bb058a87d6 100644 --- a/e2e/runner/setup_zeta.go +++ b/e2e/runner/setup_zeta.go @@ -4,25 +4,29 @@ import ( "math/big" "time" - "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" - "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" - utils2 "github.com/zeta-chain/zetacore/e2e/utils" - - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/connectorzevm.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" + "github.com/zeta-chain/zetacore/e2e/txserver" "github.com/btcsuite/btcutil" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/connectorzevm.sol" "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" uniswapv2router "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" + "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" + e2eutils "github.com/zeta-chain/zetacore/e2e/utils" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +// EmissionsPoolFunding represents the amount of ZETA to fund the emissions pool with +// This is the same value as used originally on mainnet (20M ZETA) +var EmissionsPoolFunding = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(2e7)) + // SetTSSAddresses set TSS addresses from information queried from ZetaChain func (runner *E2ERunner) SetTSSAddresses() error { runner.Logger.Print("⚙️ setting up TSS address") @@ -71,7 +75,7 @@ func (runner *E2ERunner) SetZEVMContracts() { // deploy system contracts and ZRC20 contracts on ZetaChain uniswapV2FactoryAddr, uniswapV2RouterAddr, zevmConnectorAddr, wzetaAddr, erc20zrc20Addr, err := runner.ZetaTxServer.DeploySystemContractsAndZRC20( - utils2.FungibleAdminName, + e2eutils.FungibleAdminName, runner.ERC20Addr.Hex(), ) if err != nil { @@ -154,14 +158,14 @@ func (runner *E2ERunner) SetZEVMContracts() { panic(err) } - receipt := utils2.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txZEVMSwapApp, runner.Logger, runner.ReceiptTimeout) + receipt := e2eutils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txZEVMSwapApp, runner.Logger, runner.ReceiptTimeout) if receipt.Status != 1 { panic("ZEVMSwapApp deployment failed") } runner.ZEVMSwapAppAddr = zevmSwapAppAddr runner.ZEVMSwapApp = zevmSwapApp - receipt = utils2.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txContextApp, runner.Logger, runner.ReceiptTimeout) + receipt = e2eutils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txContextApp, runner.Logger, runner.ReceiptTimeout) if receipt.Status != 1 { panic("ContextApp deployment failed") } @@ -169,6 +173,7 @@ func (runner *E2ERunner) SetZEVMContracts() { runner.ContextApp = contextApp } +// SetupETHZRC20 sets up the ETH ZRC20 in the runner from the values queried from the chain func (runner *E2ERunner) SetupETHZRC20() { ethZRC20Addr, err := runner.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(common.GoerliLocalnetChain().ChainId)) if err != nil { @@ -185,6 +190,7 @@ func (runner *E2ERunner) SetupETHZRC20() { runner.ETHZRC20 = ethZRC20 } +// SetupBTCZRC20 sets up the BTC ZRC20 in the runner from the values queried from the chain func (runner *E2ERunner) SetupBTCZRC20() { BTCZRC20Addr, err := runner.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(common.BtcRegtestChain().ChainId)) if err != nil { @@ -198,3 +204,10 @@ func (runner *E2ERunner) SetupBTCZRC20() { } runner.BTCZRC20 = BTCZRC20 } + +// FundEmissionsPool funds the emissions pool on ZetaChain with the same value as used originally on mainnet (20M ZETA) +func (runner *E2ERunner) FundEmissionsPool() error { + runner.Logger.Print("⚙️ funding the emissions pool on ZetaChain with 20M ZETA (%s)", txserver.EmissionsPoolAddress) + + return runner.ZetaTxServer.FundEmissionsPool(e2eutils.FungibleAdminName, EmissionsPoolFunding) +} diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index 4aedf29c92..910224d567 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "os" "strings" @@ -32,6 +33,7 @@ import ( etherminttypes "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" rpchttp "github.com/tendermint/tendermint/rpc/client/http" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/common" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" @@ -39,6 +41,10 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +// EmissionsPoolAddress is the address of the emissions pool +// This address is constant for all networks because it is derived from emissions name +const EmissionsPoolAddress = "zeta1w43fn2ze2wyhu5hfmegr6vp52c3dgn0srdgymy" + // ZetaTxServer is a ZetaChain tx server for E2E test type ZetaTxServer struct { clientCtx client.Context @@ -279,6 +285,36 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20(account, erc20Addr string) return uniswapV2FactoryAddr, uniswapV2RouterAddr, zevmConnectorAddr, wzetaAddr, erc20zrc20Addr, nil } +// FundEmissionsPool funds the emissions pool with the given amount +func (zts ZetaTxServer) FundEmissionsPool(account string, amount *big.Int) error { + // retrieve account + acc, err := zts.clientCtx.Keyring.Key(account) + if err != nil { + return err + } + addr, err := acc.GetAddress() + if err != nil { + return err + } + + // retrieve account address + emissionPoolAccAddr, err := sdktypes.AccAddressFromBech32(EmissionsPoolAddress) + if err != nil { + return err + } + + // convert amount + amountInt := sdktypes.NewIntFromBigInt(amount) + + // fund emissions pool + _, err = zts.BroadcastTx(account, banktypes.NewMsgSend( + addr, + emissionPoolAccAddr, + sdktypes.NewCoins(sdktypes.NewCoin(config.BaseDenom, amountInt)), + )) + return err +} + // newCodec returns the codec for msg server func newCodec() (*codec.ProtoCodec, codectypes.InterfaceRegistry) { interfaceRegistry := codectypes.NewInterfaceRegistry() diff --git a/readme.md b/readme.md index 7ae0f1337e..b4ba24455f 100644 --- a/readme.md +++ b/readme.md @@ -105,74 +105,187 @@ To create a release simply execute the publish-release workflow and follow the s Once the release is approved the pipeline will continue and will publish the releases with the title / version you specified in the user input. - -Here is the formatted documentation in Markdown: - --- +## Full Deployment Guide for Zetacored and Bitcoin Nodes -### Starting Full Zetacored Nodes - -#### Step 1: Choose the Network +This guide details deploying Zetacored nodes on both ZetaChain mainnet and Athens3 (testnet), alongside setting up a Bitcoin node for mainnet. The setup utilizes Docker Compose with environment variables for a streamlined deployment process. -To start a node, use the `make` command with the `DOCKER_TAG` of the image you wish to use from Docker Hub. +### Deploying Zetacored Nodes -- **For Mainnet:** +#### Launching a Node +**For Mainnet:** +- Use the `make` command with a specified Docker tag to initiate a mainnet Zetacored node. ```shell - # Use this command to start a mainnet node with a specific Docker tag - make mainnet-zetarpc-node DOCKER_TAG={THE_DOCKER_TAG_FROM_DOCKER_HUB_YOU_WANT_TO_USE} - # Example: - make mainnet-zetarpc-node DOCKER_TAG=ubuntu-v12.3.0-docker-test + make mainnet-zetarpc-node DOCKER_TAG=ubuntu-v14.0.1 ``` -- **For Athens3:** - +**For Athens3 (Testnet):** +- Similar command structure for Athens3, ensuring the correct Docker tag is used. ```shell - # The command is the same for Athens3, just ensure you're specifying the correct Docker tag - make mainnet-zetarpc-node DOCKER_TAG={THE_DOCKER_TAG_FROM_DOCKER_HUB_YOU_WANT_TO_USE} - # Example: - make mainnet-zetarpc-node DOCKER_TAG=ubuntu-v12.3.0-docker-test + make testnet-zetarpc-node DOCKER_TAG=ubuntu-v14.0.1 + ``` + +#### Modifying the Sync Type + +**To change the sync type for your node:** +- Edit docker-compose.yml in contrib/{NETWORK}/zetacored/. +- Set RESTORE_TYPE to your desired method (snapshot, snapshot-archive, statesync). + +#### Zetacored Environment Variables + +| Variable | Description | +|----------|-------------| +| `DAEMON_HOME` | Daemon's home directory (`/root/.zetacored`). | +| `NETWORK` | Network identifier: `mainnet` or `athens3` (for testnet). | +| `RESTORE_TYPE` | Node restoration method: `snapshot`, `snapshot-archive`, `statesync`. | +| `SNAPSHOT_API` | API URL for fetching snapshots. | +| `TRUST_HEIGHT_DIFFERENCE_STATE_SYNC` | Trust height difference for state synchronization. | +| `CHAIN_ID` | Chain ID for the network. | +| `VISOR_NAME` | Visor software name, typically `cosmovisor`. | +| `DAEMON_NAME` | Daemon software name, `zetacored`. | +| `DAEMON_ALLOW_DOWNLOAD_BINARIES` | Enable daemon to download binaries. | +| `DAEMON_RESTART_AFTER_UPGRADE` | Restart daemon after software upgrade. | +| `UNSAFE_SKIP_BACKUP` | Skip backup during potentially unsafe operations. | +| `CLIENT_DAEMON_NAME` | Client daemon name, such as `zetaclientd`. | +| `CLIENT_DAEMON_ARGS` | Extra arguments for the client daemon. | +| `CLIENT_SKIP_UPGRADE` | Skip client software upgrade. | +| `CLIENT_START_PROCESS` | Begin client process start-up. | +| `MONIKER` | Node's moniker or nickname. | +| `RE_DO_START_SEQUENCE` | Restart node setup from scratch if necessary. | + +### Bitcoin Node Setup for Mainnet + +**Restoring a BTC Watcher Node:** +- To deploy a Bitcoin mainnet node, specify the `DOCKER_TAG` for your Docker image. + ```shell + make mainnet-bitcoind-node DOCKER_TAG=36-mainnet ``` -**Note:** The default configuration is to restore from state sync. This process will download the necessary configurations and information from [Zeta-Chain Network Config](https://github.com/zeta-chain/network-config) and configure the node for state sync restore. +#### Bitcoin Node Environment Variables -#### Changing the Sync Type +| Variable | Description | +|----------|-------------| +| `bitcoin_username` | Username for Bitcoin RPC. | +| `bitcoin_password` | Password for Bitcoin RPC. | +| `NETWORK_HEIGHT_URL` | URL to fetch the latest block height. | +| `WALLET_NAME` | Name of the Bitcoin wallet. | +| `WALLET_ADDRESS` | Bitcoin wallet address for transactions. | +| `SNAPSHOT_URL` | URL for downloading the blockchain snapshot. | +| `SNAPSHOT_RESTORE` | Enable restoration from snapshot. | +| `CLEAN_SNAPSHOT` | Clean existing data before restoring snapshot. | +| `DOWNLOAD_SNAPSHOT` | Download the snapshot if not present. | -If you wish to change the sync type, you will need to modify the `docker-compose.yml` file located in `contrib/{NETWORK}/zetacored/`. +### Docker Compose Configurations -Change the following values according to your needs: +#### Zetacored Mainnet ```yaml -# Possible values for RESTORE_TYPE are "snapshot", "snapshot-archive", or "statesync" -RESTORE_TYPE: "statesync" -MONIKER: "local-test" -RE_DO_START_SEQUENCE: "false" +version: '3.8' +services: + zetachain_mainnet_rpc: + platform: linux/amd64 + image: zetachain/zetacored:${DOCKER_TAG:-ubuntu-v14.0.1} + environment: + DAEMON_HOME: "/root/.zetacored" + NETWORK: mainnet + RESTORE_TYPE: "snapshot" + SNAPSHOT_API: https://snapshots.zetachain.com + TRUST_HEIGHT_DIFFERENCE_STATE_SYNC: 40000 + CHAIN_ID: "zetachain_7000-1" + VISOR_NAME: "cosmovisor" + DAEMON_NAME: "zetacored" + DAEMON_ALLOW_DOWNLOAD_BINARIES: "false" + DAEMON_RESTART_AFTER_UPGRADE: "true" + UNSAFE_SKIP_BACKUP: "true" + CLIENT_DAEMON_NAME: "zetaclientd" + CLIENT_DAEMON_ARGS: "" + CLIENT_SKIP_UPGRADE: "true" + CLIENT_START_PROCESS: "false" + MONIKER: local-test + RE_DO_START_SEQUENCE: "false" + ports: + - "26656:26656" + - "1317:1317" + - "8545:8545" + - "8546:8546" + - "26657:26657" + - "9090:9090" + - "9091:9091" + volumes: + + + - zetacored_data_mainnet:/root/.zetacored/ + entrypoint: bash /scripts/start.sh +volumes: + zetacored_data_mainnet: ``` -To perform a snapshot restore from the latest snapshot, simply change the `RESTORE_TYPE` to either `snapshot` or `snapshot-archive`. - ---- - -Here's the formatted documentation in Markdown for starting a full Bitcoind Mainnet node: +#### Zetacored Athens3/Testnet ---- - -### Starting Full Bitcoind Mainnet Node - -#### Step 1: Restore a Mainnet BTC Watcher Node - -To restore a mainnet BTC watcher node from a BTC snapshot, run the following `make` command and specify the `DOCKER_TAG` with the image you want to use from Docker Hub. - -```commandline -make mainnet-bitcoind-node DOCKER_TAG={DOCKER_TAG_FROM_DOCKER_HUB_TO_USE} -# Example: -make mainnet-bitcoind-node DOCKER_TAG=36-mainnet +```yaml +version: '3.8' +services: + zetachain_testnet_rpc: + platform: linux/amd64 + image: zetachain/zetacored:${DOCKER_TAG:-ubuntu-v14-testnet} + environment: + DAEMON_HOME: "/root/.zetacored" + NETWORK: athens3 + RESTORE_TYPE: "snapshot" + SNAPSHOT_API: https://snapshots.zetachain.com + TRUST_HEIGHT_DIFFERENCE_STATE_SYNC: 40000 + CHAIN_ID: "athens_7001-1" + VISOR_NAME: "cosmovisor" + DAEMON_NAME: "zetacored" + DAEMON_ALLOW_DOWNLOAD_BINARIES: "false" + DAEMON_RESTART_AFTER_UPGRADE: "true" + UNSAFE_SKIP_BACKUP: "true" + CLIENT_DAEMON_NAME: "zetaclientd" + CLIENT_DAEMON_ARGS: "" + CLIENT_SKIP_UPGRADE: "true" + CLIENT_START_PROCESS: "false" + MONIKER: local-test + RE_DO_START_SEQUENCE: "false" + ports: + - "26656:26656" + - "1317:1317" + - "8545:8545" + - "8546:8546" + - "26657:26657" + - "9090:9090" + - "9091:9091" + volumes: + - zetacored_data_athens3:/root/.zetacored/ + entrypoint: bash /scripts/start.sh +volumes: + zetacored_data_athens3: ``` -#### Updating the TSS Address - -If you need to update the TSS (Threshold Signature Scheme) address being watched, please edit the `docker-compose.yml` file located at `contrib/mainnet/bitcoind/docker-compose.yml`. +#### Bitcoin Mainnet Node -To update, simply change the user and password you wish to use, and the TSS address to watch. Then, run the command provided above to apply your changes. +```yaml +version: '3' +services: + bitcoin: + image: zetachain/bitcoin:${DOCKER_TAG:-36-mainnet} + platform: linux/amd64 + environment: + - bitcoin_username=test + - bitcoin_password=test + - NETWORK_HEIGHT_URL=https://blockstream.info/api/blocks/tip/height + - WALLET_NAME=tssMainnet + - WALLET_ADDRESS=bc1qm24wp577nk8aacckv8np465z3dvmu7ry45el6y + - SNAPSHOT_URL=https://storage.googleapis.com/bitcoin-rpc-snapshots-prod/bitcoind-mainnet-2024-02-20-00-22-06.tar.gz + - SNAPSHOT_RESTORE=true + - CLEAN_SNAPSHOT=true + - DOWNLOAD_SNAPSHOT=true + volumes: + - bitcoin_data:/root/ + ports: + - 8332:8332 +volumes: + bitcoin_data: +``` ---- \ No newline at end of file +Replace placeholders in Docker Compose files and `make` commands with actual values appropriate for your deployment scenario. This complete setup guide is designed to facilitate the deployment and management of Zetacored and Bitcoin nodes in various environments. \ No newline at end of file diff --git a/testutil/keeper/emissions.go b/testutil/keeper/emissions.go index a359d93249..e61f6cd76e 100644 --- a/testutil/keeper/emissions.go +++ b/testutil/keeper/emissions.go @@ -15,18 +15,15 @@ import ( ) type EmissionMockOptions struct { - UseBankMock bool - UseStakingMock bool - UseObserverMock bool - UseAccountMock bool + UseBankMock bool + UseStakingMock bool + UseObserverMock bool + UseAccountMock bool + UseParamStoreMock bool } func EmissionsKeeper(t testing.TB) (*keeper.Keeper, sdk.Context, SDKKeepers, ZetaKeepers) { - return EmissionKeeperWithMockOptions(t, EmissionMockOptions{ - UseBankMock: false, - UseStakingMock: false, - UseObserverMock: false, - }) + return EmissionKeeperWithMockOptions(t, EmissionMockOptions{}) } func EmissionKeeperWithMockOptions( t testing.TB, @@ -91,18 +88,31 @@ func EmissionKeeperWithMockOptions( observerKeeper = emissionsmocks.NewEmissionObserverKeeper(t) } + var paramStore types.ParamStore + if mockOptions.UseParamStoreMock { + mock := emissionsmocks.NewEmissionParamStore(t) + // mock this method for the keeper constructor + mock.On("HasKeyTable").Maybe().Return(true) + paramStore = mock + } else { + paramStore = sdkKeepers.ParamsKeeper.Subspace(types.ModuleName) + } + k := keeper.NewKeeper( cdc, storeKey, memStoreKey, - sdkKeepers.ParamsKeeper.Subspace(types.ModuleName), + paramStore, authtypes.FeeCollectorName, bankKeeper, stakingKeeper, observerKeeper, authKeeper, ) - k.SetParams(ctx, types.DefaultParams()) + + if !mockOptions.UseParamStoreMock { + k.SetParams(ctx, types.DefaultParams()) + } return k, ctx, sdkKeepers, zetaKeepers } @@ -112,3 +122,9 @@ func GetEmissionsBankMock(t testing.TB, keeper *keeper.Keeper) *emissionsmocks.E require.True(t, ok) return cbk } + +func GetEmissionsParamStoreMock(t testing.TB, keeper *keeper.Keeper) *emissionsmocks.EmissionParamStore { + m, ok := keeper.GetParamStore().(*emissionsmocks.EmissionParamStore) + require.True(t, ok) + return m +} diff --git a/testutil/keeper/mocks/crosschain/observer.go b/testutil/keeper/mocks/crosschain/observer.go index 8e2a40b9cb..d100894b02 100644 --- a/testutil/keeper/mocks/crosschain/observer.go +++ b/testutil/keeper/mocks/crosschain/observer.go @@ -555,24 +555,6 @@ func (_m *CrosschainObserverKeeper) GetObserverSet(ctx types.Context) (observert return r0, r1 } -// GetParams provides a mock function with given fields: ctx -func (_m *CrosschainObserverKeeper) GetParams(ctx types.Context) observertypes.Params { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetParams") - } - - var r0 observertypes.Params - if rf, ok := ret.Get(0).(func(types.Context) observertypes.Params); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(observertypes.Params) - } - - return r0 -} - // GetPendingNonces provides a mock function with given fields: ctx, tss, chainID func (_m *CrosschainObserverKeeper) GetPendingNonces(ctx types.Context, tss string, chainID int64) (observertypes.PendingNonces, bool) { ret := _m.Called(ctx, tss, chainID) diff --git a/testutil/keeper/mocks/emissions/param_store.go b/testutil/keeper/mocks/emissions/param_store.go new file mode 100644 index 0000000000..f9923f3d94 --- /dev/null +++ b/testutil/keeper/mocks/emissions/param_store.go @@ -0,0 +1,76 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionParamStore is an autogenerated mock type for the EmissionParamStore type +type EmissionParamStore struct { + mock.Mock +} + +// GetParamSetIfExists provides a mock function with given fields: ctx, ps +func (_m *EmissionParamStore) GetParamSetIfExists(ctx types.Context, ps paramstypes.ParamSet) { + _m.Called(ctx, ps) +} + +// HasKeyTable provides a mock function with given fields: +func (_m *EmissionParamStore) HasKeyTable() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for HasKeyTable") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// SetParamSet provides a mock function with given fields: ctx, ps +func (_m *EmissionParamStore) SetParamSet(ctx types.Context, ps paramstypes.ParamSet) { + _m.Called(ctx, ps) +} + +// WithKeyTable provides a mock function with given fields: table +func (_m *EmissionParamStore) WithKeyTable(table paramstypes.KeyTable) paramstypes.Subspace { + ret := _m.Called(table) + + if len(ret) == 0 { + panic("no return value specified for WithKeyTable") + } + + var r0 paramstypes.Subspace + if rf, ok := ret.Get(0).(func(paramstypes.KeyTable) paramstypes.Subspace); ok { + r0 = rf(table) + } else { + r0 = ret.Get(0).(paramstypes.Subspace) + } + + return r0 +} + +// NewEmissionParamStore creates a new instance of EmissionParamStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionParamStore(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionParamStore { + mock := &EmissionParamStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/fungible/observer.go b/testutil/keeper/mocks/fungible/observer.go index 3010f8faaf..83cdb37bcc 100644 --- a/testutil/keeper/mocks/fungible/observer.go +++ b/testutil/keeper/mocks/fungible/observer.go @@ -6,8 +6,6 @@ import ( mock "github.com/stretchr/testify/mock" common "github.com/zeta-chain/zetacore/common" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" - types "github.com/cosmos/cosmos-sdk/types" ) @@ -16,24 +14,6 @@ type FungibleObserverKeeper struct { mock.Mock } -// GetParams provides a mock function with given fields: ctx -func (_m *FungibleObserverKeeper) GetParams(ctx types.Context) observertypes.Params { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetParams") - } - - var r0 observertypes.Params - if rf, ok := ret.Get(0).(func(types.Context) observertypes.Params); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(observertypes.Params) - } - - return r0 -} - // GetSupportedChains provides a mock function with given fields: ctx func (_m *FungibleObserverKeeper) GetSupportedChains(ctx types.Context) []*common.Chain { ret := _m.Called(ctx) diff --git a/testutil/keeper/mocks/mocks.go b/testutil/keeper/mocks/mocks.go index 0da231a7e5..1180fca27a 100644 --- a/testutil/keeper/mocks/mocks.go +++ b/testutil/keeper/mocks/mocks.go @@ -94,6 +94,11 @@ type EmissionObserverKeeper interface { emissionstypes.ObserverKeeper } +//go:generate mockery --name EmissionParamStore --filename param_store.go --case underscore --output ./emissions +type EmissionParamStore interface { + emissionstypes.ParamStore +} + /** * Observer Mocks */ diff --git a/x/authority/types/message_update_policies_test.go b/x/authority/types/message_update_policies_test.go index 0b03f3acb2..def81cfad6 100644 --- a/x/authority/types/message_update_policies_test.go +++ b/x/authority/types/message_update_policies_test.go @@ -3,9 +3,11 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/authority/types" ) @@ -49,3 +51,53 @@ func TestMsgUpdatePolicies_ValidateBasic(t *testing.T) { }) } } + +func TestMsgUpdatePolicies_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *types.MsgUpdatePolicies + panics bool + }{ + { + name: "valid signer", + msg: types.NewMsgUpdatePolicies(signer, sample.Policies()), + panics: false, + }, + { + name: "invalid signer", + msg: types.NewMsgUpdatePolicies("invalid", sample.Policies()), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgUpdatePolicies_Type(t *testing.T) { + msg := types.NewMsgUpdatePolicies(sample.AccAddress(), sample.Policies()) + require.Equal(t, types.TypeMsgUpdatePolicies, msg.Type()) +} + +func TestMsgUpdatePolicies_Route(t *testing.T) { + msg := types.NewMsgUpdatePolicies(sample.AccAddress(), sample.Policies()) + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgUpdatePolicies_GetSignBytes(t *testing.T) { + msg := types.NewMsgUpdatePolicies(sample.AccAddress(), sample.Policies()) + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go b/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go index 013f992fd9..50e8164eb7 100644 --- a/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go +++ b/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go @@ -16,7 +16,7 @@ import ( ) func setupVerificationParams(zk keepertest.ZetaKeepers, ctx sdk.Context, tx_index int64, chainID int64, header ethtypes.Header, headerRLP []byte, block *ethtypes.Block) { - params := zk.ObserverKeeper.GetParams(ctx) + params := zk.ObserverKeeper.GetParamsIfExists(ctx) zk.ObserverKeeper.SetParams(ctx, params) zk.ObserverKeeper.SetBlockHeader(ctx, common.BlockHeader{ Height: block.Number().Int64(), diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 4286bbf48b..6f77d4c2dc 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -33,7 +33,6 @@ type BankKeeper interface { type ObserverKeeper interface { GetObserverSet(ctx sdk.Context) (val observertypes.ObserverSet, found bool) GetBallot(ctx sdk.Context, index string) (val observertypes.Ballot, found bool) - GetParams(ctx sdk.Context) (params observertypes.Params) GetChainParamsByChainID(ctx sdk.Context, chainID int64) (params *observertypes.ChainParams, found bool) GetNodeAccount(ctx sdk.Context, address string) (nodeAccount observertypes.NodeAccount, found bool) GetAllNodeAccount(ctx sdk.Context) (nodeAccounts []observertypes.NodeAccount) diff --git a/x/crosschain/types/message_abort_stuck_cctx.go b/x/crosschain/types/message_abort_stuck_cctx.go index 102290ab9b..47c383276b 100644 --- a/x/crosschain/types/message_abort_stuck_cctx.go +++ b/x/crosschain/types/message_abort_stuck_cctx.go @@ -43,5 +43,8 @@ func (msg *MsgAbortStuckCCTX) ValidateBasic() error { if err != nil { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } + if len(msg.CctxIndex) != 66 { + return ErrInvalidCCTXIndex + } return nil } diff --git a/x/crosschain/types/message_abort_stuck_cctx_test.go b/x/crosschain/types/message_abort_stuck_cctx_test.go index 40bdcf4794..14b1e82d78 100644 --- a/x/crosschain/types/message_abort_stuck_cctx_test.go +++ b/x/crosschain/types/message_abort_stuck_cctx_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -12,24 +13,23 @@ import ( func TestMsgAbortStuckCCTX_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgAbortStuckCCTX + msg *types.MsgAbortStuckCCTX err error }{ { name: "invalid address", - msg: types.MsgAbortStuckCCTX{ - Creator: "invalid_address", - CctxIndex: "cctx_index", - }, - err: sdkerrors.ErrInvalidAddress, + msg: types.NewMsgAbortStuckCCTX("invalid_address", "cctx_index"), + err: sdkerrors.ErrInvalidAddress, + }, + { + name: "invalid cctx index", + msg: types.NewMsgAbortStuckCCTX(sample.AccAddress(), "cctx_index"), + err: types.ErrInvalidCCTXIndex, }, { name: "valid", - msg: types.MsgAbortStuckCCTX{ - Creator: sample.AccAddress(), - CctxIndex: "cctx_index", - }, - err: nil, + msg: types.NewMsgAbortStuckCCTX(sample.AccAddress(), sample.GetCctxIndexFromString("test")), + err: nil, }, } for _, tt := range tests { @@ -43,3 +43,53 @@ func TestMsgAbortStuckCCTX_ValidateBasic(t *testing.T) { }) } } + +func TestMsgAbortStuckCCTX_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *types.MsgAbortStuckCCTX + panics bool + }{ + { + name: "valid signer", + msg: types.NewMsgAbortStuckCCTX(signer, "cctx_index"), + panics: false, + }, + { + name: "invalid signer", + msg: types.NewMsgAbortStuckCCTX("invalid", "cctx_index"), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgAbortStuckCCTX_Type(t *testing.T) { + msg := types.NewMsgAbortStuckCCTX(sample.AccAddress(), "cctx_index") + require.Equal(t, types.TypeMsgAbortStuckCCTX, msg.Type()) +} + +func TestMsgAbortStuckCCTX_Route(t *testing.T) { + msg := types.NewMsgAbortStuckCCTX(sample.AccAddress(), "cctx_index") + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgAbortStuckCCTX_GetSignBytes(t *testing.T) { + msg := types.NewMsgAbortStuckCCTX(sample.AccAddress(), "cctx_index") + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_add_to_in_tx_tracker_test.go b/x/crosschain/types/message_add_to_in_tx_tracker_test.go index c05272224f..2200f04421 100644 --- a/x/crosschain/types/message_add_to_in_tx_tracker_test.go +++ b/x/crosschain/types/message_add_to_in_tx_tracker_test.go @@ -4,6 +4,7 @@ import ( "testing" errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" @@ -14,37 +15,56 @@ import ( func TestMsgAddToInTxTracker_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgAddToInTxTracker + msg *types.MsgAddToInTxTracker err error }{ { name: "invalid address", - msg: types.MsgAddToInTxTracker{ - Creator: "invalid_address", - ChainId: common.GoerliChain().ChainId, - TxHash: "hash", - CoinType: common.CoinType_Gas, - }, + msg: types.NewMsgAddToInTxTracker( + "invalid_address", + common.GoerliChain().ChainId, + common.CoinType_Gas, + "hash", + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid chain id", - msg: types.MsgAddToInTxTracker{ + msg: types.NewMsgAddToInTxTracker( + sample.AccAddress(), + 42, + common.CoinType_Gas, + "hash", + ), + err: errorsmod.Wrapf(types.ErrInvalidChainID, "chain id (%d)", 42), + }, + { + name: "invalid proof", + msg: &types.MsgAddToInTxTracker{ Creator: sample.AccAddress(), - ChainId: 42, - TxHash: "hash", + ChainId: common.ZetaTestnetChain().ChainId, CoinType: common.CoinType_Gas, + Proof: &common.Proof{}, }, - err: errorsmod.Wrapf(types.ErrInvalidChainID, "chain id (%d)", 42), + err: errorsmod.Wrapf(types.ErrProofVerificationFail, "chain id %d does not support proof-based trackers", common.ZetaTestnetChain().ChainId), }, { - name: "valid", - msg: types.MsgAddToInTxTracker{ + name: "invalid coin type", + msg: &types.MsgAddToInTxTracker{ Creator: sample.AccAddress(), - ChainId: common.GoerliChain().ChainId, - TxHash: "hash", - CoinType: common.CoinType_Gas, + ChainId: common.ZetaTestnetChain().ChainId, + CoinType: 5, }, + err: errorsmod.Wrapf(types.ErrProofVerificationFail, "coin-type not supported"), + }, + { + name: "valid", + msg: types.NewMsgAddToInTxTracker( + sample.AccAddress(), + common.GoerliChain().ChainId, + common.CoinType_Gas, + "hash", + ), err: nil, }, } @@ -59,3 +79,78 @@ func TestMsgAddToInTxTracker_ValidateBasic(t *testing.T) { }) } } + +func TestMsgAddToInTxTracker_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *types.MsgAddToInTxTracker + panics bool + }{ + { + name: "valid signer", + msg: types.NewMsgAddToInTxTracker( + signer, + common.GoerliChain().ChainId, + common.CoinType_Gas, + "hash", + ), + panics: false, + }, + { + name: "invalid signer", + msg: types.NewMsgAddToInTxTracker( + "invalid_address", + common.GoerliChain().ChainId, + common.CoinType_Gas, + "hash", + ), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgAddToInTxTracker_Type(t *testing.T) { + msg := types.NewMsgAddToInTxTracker( + sample.AccAddress(), + common.GoerliChain().ChainId, + common.CoinType_Gas, + "hash", + ) + require.Equal(t, types.TypeMsgAddToInTxTracker, msg.Type()) +} + +func TestMsgAddToInTxTracker_Route(t *testing.T) { + msg := types.NewMsgAddToInTxTracker( + sample.AccAddress(), + common.GoerliChain().ChainId, + common.CoinType_Gas, + "hash", + ) + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgAddToInTxTracker_GetSignBytes(t *testing.T) { + msg := types.NewMsgAddToInTxTracker( + sample.AccAddress(), + common.GoerliChain().ChainId, + common.CoinType_Gas, + "hash", + ) + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_add_to_out_tx_tracker.go b/x/crosschain/types/message_add_to_out_tx_tracker.go index 44d7b7f3e0..45084ec7b3 100644 --- a/x/crosschain/types/message_add_to_out_tx_tracker.go +++ b/x/crosschain/types/message_add_to_out_tx_tracker.go @@ -58,7 +58,7 @@ func (msg *MsgAddToOutTxTracker) ValidateBasic() error { return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if msg.ChainId < 0 { - return cosmoserrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ChainId) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidChainID, "chain id (%d)", msg.ChainId) } return nil } diff --git a/x/crosschain/types/message_add_to_out_tx_tracker_test.go b/x/crosschain/types/message_add_to_out_tx_tracker_test.go index 8a06041989..2fc4631835 100644 --- a/x/crosschain/types/message_add_to_out_tx_tracker_test.go +++ b/x/crosschain/types/message_add_to_out_tx_tracker_test.go @@ -3,29 +3,56 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/crosschain/types" ) -func TestMsgAddToWatchList_ValidateBasic(t *testing.T) { +func TestMsgAddToOutTxTracker_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgAddToOutTxTracker + msg *types.MsgAddToOutTxTracker err error }{ { name: "invalid address", - msg: types.MsgAddToOutTxTracker{ - Creator: "invalid_address", - }, + msg: types.NewMsgAddToOutTxTracker( + "invalid", + 1, + 1, + "", + nil, + "", + 1, + ), err: sdkerrors.ErrInvalidAddress, - }, { + }, + { + name: "invalid chain id", + msg: types.NewMsgAddToOutTxTracker( + sample.AccAddress(), + -1, + 1, + "", + nil, + "", + 1, + ), + err: sdkerrors.ErrInvalidChainID, + }, + { name: "valid address", - msg: types.MsgAddToOutTxTracker{ - Creator: sample.AccAddress(), - }, + msg: types.NewMsgAddToOutTxTracker( + sample.AccAddress(), + 1, + 1, + "", + nil, + "", + 1, + ), }, } for _, tt := range tests { @@ -39,3 +66,63 @@ func TestMsgAddToWatchList_ValidateBasic(t *testing.T) { }) } } + +func TestMsgAddToOutTxTracker_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgAddToOutTxTracker + panics bool + }{ + { + name: "valid signer", + msg: types.MsgAddToOutTxTracker{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgAddToOutTxTracker{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgAddToOutTxTracker_Type(t *testing.T) { + msg := types.MsgAddToOutTxTracker{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgAddToOutTxTracker, msg.Type()) +} + +func TestMsgAddToOutTxTracker_Route(t *testing.T) { + msg := types.MsgAddToOutTxTracker{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgAddToOutTxTracker_GetSignBytes(t *testing.T) { + msg := types.MsgAddToOutTxTracker{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_gas_price_voter.go b/x/crosschain/types/message_gas_price_voter.go index a42d9b692e..e21fa80dce 100644 --- a/x/crosschain/types/message_gas_price_voter.go +++ b/x/crosschain/types/message_gas_price_voter.go @@ -46,7 +46,7 @@ func (msg *MsgGasPriceVoter) ValidateBasic() error { return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if msg.ChainId < 0 { - return cosmoserrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ChainId) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidChainID, "chain id (%d)", msg.ChainId) } return nil } diff --git a/x/crosschain/types/message_gas_price_voter_test.go b/x/crosschain/types/message_gas_price_voter_test.go new file mode 100644 index 0000000000..1c8ec9bd47 --- /dev/null +++ b/x/crosschain/types/message_gas_price_voter_test.go @@ -0,0 +1,123 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/stretchr/testify/require" + common "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestMsgGasPriceVoter_ValidateBasic(t *testing.T) { + tests := []struct { + name string + msg *types.MsgGasPriceVoter + err error + }{ + { + name: "invalid address", + msg: types.NewMsgGasPriceVoter( + "invalid", + 1, + 1, + "1000", + 1, + ), + err: sdkerrors.ErrInvalidAddress, + }, + { + name: "invalid chain id", + msg: types.NewMsgGasPriceVoter( + sample.AccAddress(), + -1, + 1, + "1000", + 1, + ), + err: sdkerrors.ErrInvalidChainID, + }, + { + name: "valid address", + msg: types.NewMsgGasPriceVoter( + sample.AccAddress(), + 1, + 1, + "1000", + 1, + ), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.err != nil { + require.ErrorIs(t, err, tt.err) + return + } + require.NoError(t, err) + }) + } +} + +func TestMsgGasPriceVoter_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgGasPriceVoter + panics bool + }{ + { + name: "valid signer", + msg: types.MsgGasPriceVoter{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgGasPriceVoter{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgGasPriceVoter_Type(t *testing.T) { + msg := types.MsgGasPriceVoter{ + Creator: sample.AccAddress(), + } + require.Equal(t, common.GasPriceVoter.String(), msg.Type()) +} + +func TestMsgGasPriceVoter_Route(t *testing.T) { + msg := types.MsgGasPriceVoter{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgGasPriceVoter_GetSignBytes(t *testing.T) { + msg := types.MsgGasPriceVoter{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_migrate_tss_funds.go b/x/crosschain/types/message_migrate_tss_funds.go index b667f4192c..ec5076caa0 100644 --- a/x/crosschain/types/message_migrate_tss_funds.go +++ b/x/crosschain/types/message_migrate_tss_funds.go @@ -8,6 +8,8 @@ import ( "github.com/zeta-chain/zetacore/common" ) +const TypeMsgMigrateTssFunds = "MigrateTssFunds" + var _ sdk.Msg = &MsgMigrateTssFunds{} func NewMsgMigrateTssFunds(creator string, chainID int64, amount sdkmath.Uint) *MsgMigrateTssFunds { @@ -23,7 +25,7 @@ func (msg *MsgMigrateTssFunds) Route() string { } func (msg *MsgMigrateTssFunds) Type() string { - return "MigrateTssFunds" + return TypeMsgMigrateTssFunds } func (msg *MsgMigrateTssFunds) GetSigners() []sdk.AccAddress { diff --git a/x/crosschain/types/message_migrate_tss_funds_test.go b/x/crosschain/types/message_migrate_tss_funds_test.go index a03a4e9ef4..e3461982f9 100644 --- a/x/crosschain/types/message_migrate_tss_funds_test.go +++ b/x/crosschain/types/message_migrate_tss_funds_test.go @@ -4,49 +4,60 @@ import ( "testing" sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/crosschain/types" ) -func TestNewMsgMigrateTssFunds(t *testing.T) { +func TestNewMsgMigrateTssFunds_ValidateBasic(t *testing.T) { + keeper.SetConfig(false) tests := []struct { name string - msg types.MsgMigrateTssFunds + msg *types.MsgMigrateTssFunds error bool }{ { name: "invalid creator", - msg: types.MsgMigrateTssFunds{ - Creator: "invalid_address", - ChainId: common.DefaultChainsList()[0].ChainId, - Amount: sdkmath.NewUintFromString("100000"), - }, + msg: types.NewMsgMigrateTssFunds( + "invalid address", + common.DefaultChainsList()[0].ChainId, + sdkmath.NewUintFromString("100000"), + ), error: true, }, { name: "invalid chain id", - msg: types.MsgMigrateTssFunds{ - Creator: "zeta15ruj2tc76pnj9xtw64utktee7cc7w6vzaes73z", - ChainId: 999, - Amount: sdkmath.NewUintFromString("100000"), - }, + msg: types.NewMsgMigrateTssFunds( + sample.AccAddress(), + 999, + sdkmath.NewUintFromString("100000"), + ), + error: true, + }, + { + name: "invalid amount", + msg: types.NewMsgMigrateTssFunds( + sample.AccAddress(), + common.DefaultChainsList()[0].ChainId, + sdkmath.NewUintFromString("0"), + ), error: true, }, { name: "valid msg", - msg: types.MsgMigrateTssFunds{ - Creator: "zeta15ruj2tc76pnj9xtw64utktee7cc7w6vzaes73z", - ChainId: common.DefaultChainsList()[0].ChainId, - Amount: sdkmath.NewUintFromString("100000"), - }, + msg: types.NewMsgMigrateTssFunds( + sample.AccAddress(), + common.DefaultChainsList()[0].ChainId, + sdkmath.NewUintFromString("100000"), + ), error: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - keeper.SetConfig(false) err := tt.msg.ValidateBasic() if tt.error { require.Error(t, err) @@ -57,3 +68,73 @@ func TestNewMsgMigrateTssFunds(t *testing.T) { }) } } + +func TestNewMsgMigrateTssFunds_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgMigrateTssFunds + panics bool + }{ + { + name: "valid signer", + msg: types.MsgMigrateTssFunds{ + Creator: signer, + ChainId: common.DefaultChainsList()[0].ChainId, + Amount: sdkmath.NewUintFromString("100000"), + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgMigrateTssFunds{ + Creator: "invalid_address", + ChainId: common.DefaultChainsList()[0].ChainId, + Amount: sdkmath.NewUintFromString("100000"), + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestNewMsgMigrateTssFunds_Type(t *testing.T) { + msg := types.MsgMigrateTssFunds{ + Creator: sample.AccAddress(), + ChainId: common.DefaultChainsList()[0].ChainId, + Amount: sdkmath.NewUintFromString("100000"), + } + require.Equal(t, types.TypeMsgMigrateTssFunds, msg.Type()) +} + +func TestNewMsgMigrateTssFunds_Route(t *testing.T) { + msg := types.MsgMigrateTssFunds{ + Creator: sample.AccAddress(), + ChainId: common.DefaultChainsList()[0].ChainId, + Amount: sdkmath.NewUintFromString("100000"), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestNewMsgMigrateTssFunds_GetSignBytes(t *testing.T) { + msg := types.MsgMigrateTssFunds{ + Creator: sample.AccAddress(), + ChainId: common.DefaultChainsList()[0].ChainId, + Amount: sdkmath.NewUintFromString("100000"), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_refund_aborted_test.go b/x/crosschain/types/message_refund_aborted_test.go index c0bb3f7537..2884b8b7c1 100644 --- a/x/crosschain/types/message_refund_aborted_test.go +++ b/x/crosschain/types/message_refund_aborted_test.go @@ -3,12 +3,13 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/crosschain/types" ) -func TestNewMsgRefundAbortedCCTX(t *testing.T) { +func TestMsgRefundAbortedCCTX_ValidateBasic(t *testing.T) { t.Run("successfully validate message", func(t *testing.T) { cctx := sample.CrossChainTx(t, "test") msg := types.NewMsgRefundAbortedCCTX(sample.AccAddress(), cctx.Index, "") @@ -34,3 +35,53 @@ func TestNewMsgRefundAbortedCCTX(t *testing.T) { require.ErrorContains(t, msg.ValidateBasic(), "invalid address") }) } + +func TestMsgRefundAbortedCCTX_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *types.MsgRefundAbortedCCTX + panics bool + }{ + { + name: "valid signer", + msg: types.NewMsgRefundAbortedCCTX(signer, "test", ""), + panics: false, + }, + { + name: "invalid signer", + msg: types.NewMsgRefundAbortedCCTX("invalid", "invalid", ""), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgRefundAbortedCCTX_Type(t *testing.T) { + msg := types.NewMsgRefundAbortedCCTX(sample.AccAddress(), "test", "") + require.Equal(t, types.RefundAborted, msg.Type()) +} + +func TestMsgRefundAbortedCCTX_Route(t *testing.T) { + msg := types.NewMsgRefundAbortedCCTX(sample.AccAddress(), "test", "") + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgRefundAbortedCCTX_GetSignBytes(t *testing.T) { + msg := types.NewMsgRefundAbortedCCTX(sample.AccAddress(), "test", "") + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_remove_from_out_tx_tracker.go b/x/crosschain/types/message_remove_from_out_tx_tracker.go index 145d20a76c..3a6d1a9ffc 100644 --- a/x/crosschain/types/message_remove_from_out_tx_tracker.go +++ b/x/crosschain/types/message_remove_from_out_tx_tracker.go @@ -45,7 +45,7 @@ func (msg *MsgRemoveFromOutTxTracker) ValidateBasic() error { return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if msg.ChainId < 0 { - return cosmoserrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ChainId) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidChainID, "chain id (%d)", msg.ChainId) } return nil } diff --git a/x/crosschain/types/message_remove_from_out_tx_tracker_test.go b/x/crosschain/types/message_remove_from_out_tx_tracker_test.go new file mode 100644 index 0000000000..737b2e4d48 --- /dev/null +++ b/x/crosschain/types/message_remove_from_out_tx_tracker_test.go @@ -0,0 +1,96 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/testutil/sample" + + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestMsgRemoveFromOutTxTracker_ValidateBasic(t *testing.T) { + tests := []struct { + name string + msg *types.MsgRemoveFromOutTxTracker + err error + }{ + { + name: "valid message", + msg: types.NewMsgRemoveFromOutTxTracker(sample.AccAddress(), 1, 0), + }, + { + name: "invalid creator address", + msg: types.NewMsgRemoveFromOutTxTracker("invalid", 1, 0), + err: sdkerrors.ErrInvalidAddress, + }, + { + name: "invalid chain id", + msg: types.NewMsgRemoveFromOutTxTracker(sample.AccAddress(), -1, 0), + err: sdkerrors.ErrInvalidChainID, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.err != nil { + require.ErrorIs(t, err, tt.err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgRemoveFromOutTxTracker_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *types.MsgRemoveFromOutTxTracker + panics bool + }{ + { + name: "valid signer", + msg: types.NewMsgRemoveFromOutTxTracker(signer, 1, 0), + panics: false, + }, + { + name: "invalid signer", + msg: types.NewMsgRemoveFromOutTxTracker("invalid", 1, 0), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgRemoveFromOutTxTracker_Type(t *testing.T) { + msg := types.NewMsgRemoveFromOutTxTracker(sample.AccAddress(), 1, 0) + require.Equal(t, types.TypeMsgRemoveFromOutTxTracker, msg.Type()) +} + +func TestMsgRemoveFromOutTxTracker_Route(t *testing.T) { + msg := types.NewMsgRemoveFromOutTxTracker(sample.AccAddress(), 1, 0) + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgRemoveFromOutTxTracker_GetSignBytes(t *testing.T) { + msg := types.NewMsgRemoveFromOutTxTracker(sample.AccAddress(), 1, 0) + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_tss_voter.go b/x/crosschain/types/message_tss_voter.go index d88147ce5a..6d65f19140 100644 --- a/x/crosschain/types/message_tss_voter.go +++ b/x/crosschain/types/message_tss_voter.go @@ -9,6 +9,8 @@ import ( "github.com/zeta-chain/zetacore/common" ) +const TypeMsgCreateTSSVoter = "CreateTSSVoter" + var _ sdk.Msg = &MsgCreateTSSVoter{} func NewMsgCreateTSSVoter(creator string, pubkey string, keygenZetaHeight int64, status common.ReceiveStatus) *MsgCreateTSSVoter { @@ -25,7 +27,7 @@ func (msg *MsgCreateTSSVoter) Route() string { } func (msg *MsgCreateTSSVoter) Type() string { - return "CreateTSSVoter" + return TypeMsgCreateTSSVoter } func (msg *MsgCreateTSSVoter) GetSigners() []sdk.AccAddress { diff --git a/x/crosschain/types/message_tss_voter_test.go b/x/crosschain/types/message_tss_voter_test.go new file mode 100644 index 0000000000..6fc5ca89ec --- /dev/null +++ b/x/crosschain/types/message_tss_voter_test.go @@ -0,0 +1,97 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/testutil/sample" + + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestMsgCreateTSSVoter_ValidateBasic(t *testing.T) { + tests := []struct { + name string + msg *types.MsgCreateTSSVoter + err error + }{ + { + name: "valid message", + msg: types.NewMsgCreateTSSVoter(sample.AccAddress(), "pubkey", 1, common.ReceiveStatus_Created), + }, + { + name: "invalid creator address", + msg: types.NewMsgCreateTSSVoter("invalid", "pubkey", 1, common.ReceiveStatus_Created), + err: sdkerrors.ErrInvalidAddress, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.err != nil { + require.ErrorIs(t, err, tt.err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgCreateTSSVoter_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *types.MsgCreateTSSVoter + panics bool + }{ + { + name: "valid signer", + msg: types.NewMsgCreateTSSVoter(signer, "pubkey", 1, common.ReceiveStatus_Created), + panics: false, + }, + { + name: "invalid signer", + msg: types.NewMsgCreateTSSVoter("invalid", "pubkey", 1, common.ReceiveStatus_Created), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgCreateTSSVoter_Type(t *testing.T) { + msg := types.NewMsgCreateTSSVoter(sample.AccAddress(), "pubkey", 1, common.ReceiveStatus_Created) + require.Equal(t, types.TypeMsgCreateTSSVoter, msg.Type()) +} + +func TestMsgCreateTSSVoter_Route(t *testing.T) { + msg := types.NewMsgCreateTSSVoter(sample.AccAddress(), "pubkey", 1, common.ReceiveStatus_Created) + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgCreateTSSVoter_GetSignBytes(t *testing.T) { + msg := types.NewMsgCreateTSSVoter(sample.AccAddress(), "pubkey", 1, common.ReceiveStatus_Created) + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} + +func TestMsgCreateTSSVoter_Digest(t *testing.T) { + msg := types.NewMsgCreateTSSVoter(sample.AccAddress(), "pubkey", 1, common.ReceiveStatus_Created) + require.Equal(t, "1-tss-keygen", msg.Digest()) +} diff --git a/x/crosschain/types/message_update_tss_address.go b/x/crosschain/types/message_update_tss_address.go index 57da099e9a..d9740514c3 100644 --- a/x/crosschain/types/message_update_tss_address.go +++ b/x/crosschain/types/message_update_tss_address.go @@ -7,6 +7,8 @@ import ( "github.com/zeta-chain/zetacore/common/cosmos" ) +const TypeMsgUpdateTssAddress = "UpdateTssAddress" + var _ sdk.Msg = &MsgUpdateTssAddress{} func NewMsgUpdateTssAddress(creator string, pubkey string) *MsgUpdateTssAddress { @@ -21,7 +23,7 @@ func (msg *MsgUpdateTssAddress) Route() string { } func (msg *MsgUpdateTssAddress) Type() string { - return "UpdateTssAddress" + return TypeMsgUpdateTssAddress } func (msg *MsgUpdateTssAddress) GetSigners() []sdk.AccAddress { diff --git a/x/crosschain/types/message_update_tss_address_test.go b/x/crosschain/types/message_update_tss_address_test.go index ae2bcb25a4..1d92774717 100644 --- a/x/crosschain/types/message_update_tss_address_test.go +++ b/x/crosschain/types/message_update_tss_address_test.go @@ -3,46 +3,48 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/keeper" - crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" ) func TestMessageUpdateTssAddress_ValidateBasic(t *testing.T) { + keeper.SetConfig(false) tests := []struct { name string - msg crosschaintypes.MsgUpdateTssAddress + msg *types.MsgUpdateTssAddress error bool }{ { name: "invalid creator", - msg: crosschaintypes.MsgUpdateTssAddress{ - Creator: "invalid_address", - TssPubkey: "zetapub1addwnpepq28c57cvcs0a2htsem5zxr6qnlvq9mzhmm76z3jncsnzz32rclangr2g35p", - }, + msg: types.NewMsgUpdateTssAddress( + "invalid_address", + sample.PubKeyString(), + ), error: true, }, { name: "invalid pubkey", - msg: crosschaintypes.MsgUpdateTssAddress{ - Creator: "zeta15ruj2tc76pnj9xtw64utktee7cc7w6vzaes73z", - TssPubkey: "zetapub1addwnpepq28c57cvcs0a2htsem5zxr6qnlvq9mzhmm", - }, + msg: types.NewMsgUpdateTssAddress( + sample.AccAddress(), + "zetapub1addwnpepq28c57cvcs0a2htsem5zxr6qnlvq9mzhmm", + ), error: true, }, { name: "valid msg", - msg: crosschaintypes.MsgUpdateTssAddress{ - Creator: "zeta15ruj2tc76pnj9xtw64utktee7cc7w6vzaes73z", - TssPubkey: "zetapub1addwnpepq28c57cvcs0a2htsem5zxr6qnlvq9mzhmm76z3jncsnzz32rclangr2g35p", - }, + msg: types.NewMsgUpdateTssAddress( + sample.AccAddress(), + sample.PubKeyString(), + ), error: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - keeper.SetConfig(false) err := tt.msg.ValidateBasic() if tt.error { require.Error(t, err) @@ -53,3 +55,63 @@ func TestMessageUpdateTssAddress_ValidateBasic(t *testing.T) { }) } } + +func TestMessageUpdateTssAddress_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateTssAddress + panics bool + }{ + { + name: "valid signer", + msg: types.MsgUpdateTssAddress{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgUpdateTssAddress{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMessageUpdateTssAddress_Type(t *testing.T) { + msg := types.MsgUpdateTssAddress{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateTssAddress, msg.Type()) +} + +func TestMessageUpdateTssAddress_Route(t *testing.T) { + msg := types.MsgUpdateTssAddress{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMessageUpdateTssAddress_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateTssAddress{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_vote_on_observed_inbound_tx_test.go b/x/crosschain/types/message_vote_on_observed_inbound_tx_test.go index 777fb6f244..ea3bff7bf7 100644 --- a/x/crosschain/types/message_vote_on_observed_inbound_tx_test.go +++ b/x/crosschain/types/message_vote_on_observed_inbound_tx_test.go @@ -6,6 +6,7 @@ import ( "math/rand" "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" @@ -18,106 +19,106 @@ func TestMsgVoteOnObservedInboundTx_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgVoteOnObservedInboundTx + msg *types.MsgVoteOnObservedInboundTx err error }{ { name: "valid message", - msg: types.MsgVoteOnObservedInboundTx{ - Creator: sample.AccAddress(), - Sender: sample.AccAddress(), - SenderChainId: 42, - TxOrigin: sample.String(), - Receiver: sample.String(), - ReceiverChain: 42, - Amount: math.NewUint(42), - Message: sample.String(), - InTxHash: sample.String(), - InBlockHeight: 42, - GasLimit: 42, - CoinType: common.CoinType_Zeta, - Asset: sample.String(), - EventIndex: 42, - }, + msg: types.NewMsgVoteOnObservedInboundTx( + sample.AccAddress(), + sample.AccAddress(), + 42, + sample.String(), + sample.String(), + 42, + math.NewUint(42), + sample.String(), + sample.String(), + 42, + 42, + common.CoinType_Zeta, + sample.String(), + 42, + ), }, { name: "invalid address", - msg: types.MsgVoteOnObservedInboundTx{ - Creator: "invalid_address", - Sender: sample.AccAddress(), - SenderChainId: 42, - TxOrigin: sample.String(), - Receiver: sample.String(), - ReceiverChain: 42, - Amount: math.NewUint(42), - Message: sample.String(), - InTxHash: sample.String(), - InBlockHeight: 42, - GasLimit: 42, - CoinType: common.CoinType_Zeta, - Asset: sample.String(), - EventIndex: 42, - }, + msg: types.NewMsgVoteOnObservedInboundTx( + "invalid_address", + sample.AccAddress(), + 42, + sample.String(), + sample.String(), + 42, + math.NewUint(42), + sample.String(), + sample.String(), + 42, + 42, + common.CoinType_Zeta, + sample.String(), + 42, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid sender chain ID", - msg: types.MsgVoteOnObservedInboundTx{ - Creator: sample.AccAddress(), - Sender: sample.AccAddress(), - SenderChainId: -1, - TxOrigin: sample.String(), - Receiver: sample.String(), - ReceiverChain: 42, - Amount: math.NewUint(42), - Message: sample.String(), - InTxHash: sample.String(), - InBlockHeight: 42, - GasLimit: 42, - CoinType: common.CoinType_Zeta, - Asset: sample.String(), - EventIndex: 42, - }, + msg: types.NewMsgVoteOnObservedInboundTx( + sample.AccAddress(), + sample.AccAddress(), + -1, + sample.String(), + sample.String(), + 42, + math.NewUint(42), + sample.String(), + sample.String(), + 42, + 42, + common.CoinType_Zeta, + sample.String(), + 42, + ), err: types.ErrInvalidChainID, }, { name: "invalid receiver chain ID", - msg: types.MsgVoteOnObservedInboundTx{ - Creator: sample.AccAddress(), - Sender: sample.AccAddress(), - SenderChainId: 42, - TxOrigin: sample.String(), - Receiver: sample.String(), - ReceiverChain: -1, - Amount: math.NewUint(42), - Message: sample.String(), - InTxHash: sample.String(), - InBlockHeight: 42, - GasLimit: 42, - CoinType: common.CoinType_Zeta, - Asset: sample.String(), - EventIndex: 42, - }, + msg: types.NewMsgVoteOnObservedInboundTx( + sample.AccAddress(), + sample.AccAddress(), + 42, + sample.String(), + sample.String(), + -1, + math.NewUint(42), + sample.String(), + sample.String(), + 42, + 42, + common.CoinType_Zeta, + sample.String(), + 42, + ), err: types.ErrInvalidChainID, }, { name: "invalid message length", - msg: types.MsgVoteOnObservedInboundTx{ - Creator: sample.AccAddress(), - Sender: sample.AccAddress(), - SenderChainId: 42, - TxOrigin: sample.String(), - Receiver: sample.String(), - ReceiverChain: 42, - Amount: math.NewUint(42), - Message: sample.StringRandom(r, types.MaxMessageLength+1), - InTxHash: sample.String(), - InBlockHeight: 42, - GasLimit: 42, - CoinType: common.CoinType_Zeta, - Asset: sample.String(), - EventIndex: 42, - }, + msg: types.NewMsgVoteOnObservedInboundTx( + sample.AccAddress(), + sample.AccAddress(), + 42, + sample.String(), + sample.String(), + 42, + math.NewUint(42), + sample.StringRandom(r, types.MaxMessageLength+1), + sample.String(), + 42, + 42, + common.CoinType_Zeta, + sample.String(), + 42, + ), err: sdkerrors.ErrInvalidRequest, }, } @@ -239,3 +240,63 @@ func TestMsgVoteOnObservedInboundTx_Digest(t *testing.T) { hash2 = msg2.Digest() require.NotEqual(t, hash, hash2, "event index should change hash") } + +func TestMsgVoteOnObservedInboundTx_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgVoteOnObservedInboundTx + panics bool + }{ + { + name: "valid signer", + msg: types.MsgVoteOnObservedInboundTx{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgVoteOnObservedInboundTx{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgVoteOnObservedInboundTx_Type(t *testing.T) { + msg := types.MsgVoteOnObservedInboundTx{ + Creator: sample.AccAddress(), + } + require.Equal(t, common.InboundVoter.String(), msg.Type()) +} + +func TestMsgVoteOnObservedInboundTx_Route(t *testing.T) { + msg := types.MsgVoteOnObservedInboundTx{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgVoteOnObservedInboundTx_GetSignBytes(t *testing.T) { + msg := types.MsgVoteOnObservedInboundTx{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_vote_on_observed_outbound_tx_test.go b/x/crosschain/types/message_vote_on_observed_outbound_tx_test.go index cce6a29c3c..5e669aefa3 100644 --- a/x/crosschain/types/message_vote_on_observed_outbound_tx_test.go +++ b/x/crosschain/types/message_vote_on_observed_outbound_tx_test.go @@ -5,6 +5,7 @@ import ( "testing" "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" @@ -15,75 +16,60 @@ import ( func TestMsgVoteOnObservedOutboundTx_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgVoteOnObservedOutboundTx + msg *types.MsgVoteOnObservedOutboundTx err error }{ { name: "valid message", - msg: types.MsgVoteOnObservedOutboundTx{ - Creator: sample.AccAddress(), - CctxHash: sample.String(), - ObservedOutTxHash: sample.String(), - ObservedOutTxBlockHeight: 42, - ObservedOutTxGasUsed: 42, - ObservedOutTxEffectiveGasPrice: math.NewInt(42), - ObservedOutTxEffectiveGasLimit: 42, - ValueReceived: math.NewUint(42), - Status: common.ReceiveStatus_Created, - OutTxChain: 42, - OutTxTssNonce: 42, - CoinType: common.CoinType_Zeta, - }, - }, - { - name: "effective gas price can be nil", - msg: types.MsgVoteOnObservedOutboundTx{ - Creator: sample.AccAddress(), - CctxHash: sample.String(), - ObservedOutTxHash: sample.String(), - ObservedOutTxBlockHeight: 42, - ObservedOutTxGasUsed: 42, - ValueReceived: math.NewUint(42), - Status: common.ReceiveStatus_Created, - OutTxChain: 42, - OutTxTssNonce: 42, - CoinType: common.CoinType_Zeta, - }, + msg: types.NewMsgVoteOnObservedOutboundTx( + sample.AccAddress(), + sample.String(), + sample.String(), + 42, + 42, + math.NewInt(42), + 42, + math.NewUint(42), + common.ReceiveStatus_Created, + 42, + 42, + common.CoinType_Zeta, + ), }, { name: "invalid address", - msg: types.MsgVoteOnObservedOutboundTx{ - Creator: "invalid_address", - CctxHash: sample.String(), - ObservedOutTxHash: sample.String(), - ObservedOutTxBlockHeight: 42, - ObservedOutTxGasUsed: 42, - ObservedOutTxEffectiveGasPrice: math.NewInt(42), - ObservedOutTxEffectiveGasLimit: 42, - ValueReceived: math.NewUint(42), - Status: common.ReceiveStatus_Created, - OutTxChain: 42, - OutTxTssNonce: 42, - CoinType: common.CoinType_Zeta, - }, + msg: types.NewMsgVoteOnObservedOutboundTx( + "invalid_address", + sample.String(), + sample.String(), + 42, + 42, + math.NewInt(42), + 42, + math.NewUint(42), + common.ReceiveStatus_Created, + 42, + 42, + common.CoinType_Zeta, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid chain ID", - msg: types.MsgVoteOnObservedOutboundTx{ - Creator: sample.AccAddress(), - CctxHash: sample.String(), - ObservedOutTxHash: sample.String(), - ObservedOutTxBlockHeight: 42, - ObservedOutTxGasUsed: 42, - ObservedOutTxEffectiveGasPrice: math.NewInt(42), - ObservedOutTxEffectiveGasLimit: 42, - ValueReceived: math.NewUint(42), - Status: common.ReceiveStatus_Created, - OutTxChain: -1, - OutTxTssNonce: 42, - CoinType: common.CoinType_Zeta, - }, + msg: types.NewMsgVoteOnObservedOutboundTx( + sample.AccAddress(), + sample.String(), + sample.String(), + 42, + 42, + math.NewInt(42), + 42, + math.NewUint(42), + common.ReceiveStatus_Created, + -1, + 42, + common.CoinType_Zeta, + ), err: types.ErrInvalidChainID, }, } @@ -191,3 +177,63 @@ func TestMsgVoteOnObservedOutboundTx_Digest(t *testing.T) { hash2 = msg2.Digest() require.NotEqual(t, hash, hash2, "coin type should change hash") } + +func TestMsgVoteOnObservedOutboundTx_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgVoteOnObservedOutboundTx + panics bool + }{ + { + name: "valid signer", + msg: types.MsgVoteOnObservedOutboundTx{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgVoteOnObservedOutboundTx{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgVoteOnObservedOutboundTx_Type(t *testing.T) { + msg := types.MsgVoteOnObservedOutboundTx{ + Creator: sample.AccAddress(), + } + require.Equal(t, common.OutboundVoter.String(), msg.Type()) +} + +func TestMsgVoteOnObservedOutboundTx_Route(t *testing.T) { + msg := types.MsgVoteOnObservedOutboundTx{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgVoteOnObservedOutboundTx_GetSignBytes(t *testing.T) { + msg := types.MsgVoteOnObservedOutboundTx{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_whitelist_erc20.go b/x/crosschain/types/message_whitelist_erc20.go index e2811287b7..48987df470 100644 --- a/x/crosschain/types/message_whitelist_erc20.go +++ b/x/crosschain/types/message_whitelist_erc20.go @@ -27,7 +27,7 @@ func NewMsgWhitelistERC20( } func (msg *MsgWhitelistERC20) Route() string { - return types.RouterKey + return RouterKey } func (msg *MsgWhitelistERC20) Type() string { @@ -43,7 +43,7 @@ func (msg *MsgWhitelistERC20) GetSigners() []sdk.AccAddress { } func (msg *MsgWhitelistERC20) GetSignBytes() []byte { - bz := types.ModuleCdc.MustMarshalJSON(msg) + bz := ModuleCdc.MustMarshalJSON(msg) return sdk.MustSortJSON(bz) } diff --git a/x/crosschain/types/message_whitelist_erc20_test.go b/x/crosschain/types/message_whitelist_erc20_test.go new file mode 100644 index 0000000000..d13016aef3 --- /dev/null +++ b/x/crosschain/types/message_whitelist_erc20_test.go @@ -0,0 +1,158 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestMsgWhitelistERC20_ValidateBasic(t *testing.T) { + keeper.SetConfig(false) + tests := []struct { + name string + msg *types.MsgWhitelistERC20 + error bool + }{ + { + name: "invalid creator", + msg: types.NewMsgWhitelistERC20( + "invalid_address", + "0x0", + 1, + "name", + "symbol", + 6, + 10, + ), + error: true, + }, + { + name: "invalid erc20", + msg: types.NewMsgWhitelistERC20( + sample.AccAddress(), + "0x0", + 1, + "name", + "symbol", + 6, + 10, + ), + error: true, + }, + { + name: "invalid decimals", + msg: types.NewMsgWhitelistERC20( + sample.AccAddress(), + sample.EthAddress().Hex(), + 1, + "name", + "symbol", + 130, + 10, + ), + error: true, + }, + { + name: "invalid gas limit", + msg: types.NewMsgWhitelistERC20( + sample.AccAddress(), + sample.EthAddress().Hex(), + 1, + "name", + "symbol", + 6, + -10, + ), + error: true, + }, + { + name: "valid", + msg: types.NewMsgWhitelistERC20( + sample.AccAddress(), + sample.EthAddress().Hex(), + 1, + "name", + "symbol", + 6, + 10, + ), + error: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.error { + require.Error(t, err) + return + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgWhitelistERC20_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgWhitelistERC20 + panics bool + }{ + { + name: "valid signer", + msg: types.MsgWhitelistERC20{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgWhitelistERC20{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgWhitelistERC20_Type(t *testing.T) { + msg := types.MsgWhitelistERC20{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgWhitelistERC20, msg.Type()) +} + +func TestMsgWhitelistERC20_Route(t *testing.T) { + msg := types.MsgWhitelistERC20{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgWhitelistERC20_GetSignBytes(t *testing.T) { + msg := types.MsgWhitelistERC20{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/emissions/abci.go b/x/emissions/abci.go index 96be4ae613..6472ae9e20 100644 --- a/x/emissions/abci.go +++ b/x/emissions/abci.go @@ -18,9 +18,10 @@ func BeginBlocker(ctx sdk.Context, keeper keeper.Keeper) { ctx.Logger().Info(fmt.Sprintf("Block rewards %s are greater than emission pool balance %s", blockRewards.String(), emissionPoolBalance.String())) return } - validatorRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() - observerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() - tssSignerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() + + // Get the distribution of rewards + params := keeper.GetParamsIfExists(ctx) + validatorRewards, observerRewards, tssSignerRewards := types.GetRewardsDistributions(params) // TODO : Replace hardcoded slash amount with a parameter // https://github.com/zeta-chain/node/pull/1861 @@ -70,7 +71,6 @@ func DistributeValidatorRewards(ctx sdk.Context, amount sdkmath.Int, bankKeeper // The total rewards are distributed equally among all Successful votes // NotVoted or Unsuccessful votes are slashed // rewards given or slashed amounts are in azeta - func DistributeObserverRewards( ctx sdk.Context, amount sdkmath.Int, @@ -126,7 +126,6 @@ func DistributeObserverRewards( continue } if observerRewardUnits < 0 { - keeper.SlashObserverEmission(ctx, observerAddress.String(), slashAmount) finalDistributionList = append(finalDistributionList, &types.ObserverEmission{ EmissionType: types.EmissionType_Slash, diff --git a/x/emissions/abci_test.go b/x/emissions/abci_test.go index f1ee855e98..d27f3d11c0 100644 --- a/x/emissions/abci_test.go +++ b/x/emissions/abci_test.go @@ -102,9 +102,9 @@ func TestBeginBlocker(t *testing.T) { emissionPool := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.ModuleName).GetAddress() blockRewards := emissionstypes.BlockReward - observerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ObserverEmissionPercentage)).TruncateInt() - validatorRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ValidatorEmissionPercentage)).TruncateInt() - tssSignerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).TssSignerEmissionPercentage)).TruncateInt() + observerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).ObserverEmissionPercentage)).TruncateInt() + validatorRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).ValidatorEmissionPercentage)).TruncateInt() + tssSignerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).TssSignerEmissionPercentage)).TruncateInt() distributedRewards := observerRewardsForABlock.Add(validatorRewardsForABlock).Add(tssSignerRewardsForABlock) require.True(t, blockRewards.TruncateInt().GT(distributedRewards)) diff --git a/x/emissions/genesis.go b/x/emissions/genesis.go index 4805bec1ef..b278330eeb 100644 --- a/x/emissions/genesis.go +++ b/x/emissions/genesis.go @@ -19,7 +19,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) // ExportGenesis returns the emissions module's exported genesis. func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { var genesis types.GenesisState - genesis.Params = k.GetParams(ctx) + genesis.Params = k.GetParamsIfExists(ctx) genesis.WithdrawableEmissions = k.GetAllWithdrawableEmission(ctx) return &genesis diff --git a/x/emissions/keeper/block_rewards_components.go b/x/emissions/keeper/block_rewards_components.go index ab70e13de5..fe4140805d 100644 --- a/x/emissions/keeper/block_rewards_components.go +++ b/x/emissions/keeper/block_rewards_components.go @@ -17,9 +17,9 @@ func (k Keeper) GetBlockRewardComponents(ctx sdk.Context) (sdk.Dec, sdk.Dec, sdk return reservesFactor, bondFactor, durationFactor } func (k Keeper) GetBondFactor(ctx sdk.Context, stakingKeeper types.StakingKeeper) sdk.Dec { - targetBondRatio := sdk.MustNewDecFromStr(k.GetParams(ctx).TargetBondRatio) - maxBondFactor := sdk.MustNewDecFromStr(k.GetParams(ctx).MaxBondFactor) - minBondFactor := sdk.MustNewDecFromStr(k.GetParams(ctx).MinBondFactor) + targetBondRatio := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).TargetBondRatio) + maxBondFactor := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).MaxBondFactor) + minBondFactor := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).MinBondFactor) currentBondedRatio := stakingKeeper.BondedRatio(ctx) // Bond factor ranges between minBondFactor (0.75) to maxBondFactor (1.25) @@ -37,10 +37,10 @@ func (k Keeper) GetBondFactor(ctx sdk.Context, stakingKeeper types.StakingKeeper } func (k Keeper) GetDurationFactor(ctx sdk.Context) sdk.Dec { - avgBlockTime := sdk.MustNewDecFromStr(k.GetParams(ctx).AvgBlockTime) + avgBlockTime := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).AvgBlockTime) NumberOfBlocksInAMonth := sdk.NewDec(types.SecsInMonth).Quo(avgBlockTime) monthFactor := sdk.NewDec(ctx.BlockHeight()).Quo(NumberOfBlocksInAMonth) - logValueDec := sdk.MustNewDecFromStr(k.GetParams(ctx).DurationFactorConstant) + logValueDec := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).DurationFactorConstant) // month * log(1 + 0.02 / 12) fractionNumerator := monthFactor.Mul(logValueDec) // (month * log(1 + 0.02 / 12) ) + 1 diff --git a/x/emissions/keeper/grpc_query_params.go b/x/emissions/keeper/grpc_query_params.go index 7b6b16965a..acc804124d 100644 --- a/x/emissions/keeper/grpc_query_params.go +++ b/x/emissions/keeper/grpc_query_params.go @@ -15,5 +15,5 @@ func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types } ctx := sdk.UnwrapSDKContext(c) - return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil + return &types.QueryParamsResponse{Params: k.GetParamsIfExists(ctx)}, nil } diff --git a/x/emissions/keeper/keeper.go b/x/emissions/keeper/keeper.go index b0fe4d26ee..aadabb61f7 100644 --- a/x/emissions/keeper/keeper.go +++ b/x/emissions/keeper/keeper.go @@ -9,7 +9,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) type ( @@ -17,7 +16,7 @@ type ( cdc codec.BinaryCodec storeKey storetypes.StoreKey memKey storetypes.StoreKey - paramstore paramtypes.Subspace + paramStore types.ParamStore feeCollectorName string bankKeeper types.BankKeeper stakingKeeper types.StakingKeeper @@ -30,7 +29,7 @@ func NewKeeper( cdc codec.BinaryCodec, storeKey, memKey storetypes.StoreKey, - ps paramtypes.Subspace, + ps types.ParamStore, feeCollectorName string, bankKeeper types.BankKeeper, stakingKeeper types.StakingKeeper, @@ -46,7 +45,7 @@ func NewKeeper( cdc: cdc, storeKey: storeKey, memKey: memKey, - paramstore: ps, + paramStore: ps, feeCollectorName: feeCollectorName, bankKeeper: bankKeeper, stakingKeeper: stakingKeeper, @@ -78,3 +77,7 @@ func (k Keeper) GetObserverKeeper() types.ObserverKeeper { func (k Keeper) GetAuthKeeper() types.AccountKeeper { return k.authKeeper } + +func (k Keeper) GetParamStore() types.ParamStore { + return k.paramStore +} diff --git a/x/emissions/keeper/params.go b/x/emissions/keeper/params.go index 17d506bbca..5369e0953a 100644 --- a/x/emissions/keeper/params.go +++ b/x/emissions/keeper/params.go @@ -5,13 +5,14 @@ import ( "github.com/zeta-chain/zetacore/x/emissions/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - k.paramstore.GetParamSet(ctx, ¶ms) +// GetParamsIfExists get all parameters as types.Params if they exist +// non existent parameters will return zero values +func (k Keeper) GetParamsIfExists(ctx sdk.Context) (params types.Params) { + k.paramStore.GetParamSetIfExists(ctx, ¶ms) return } // SetParams set the params func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { - k.paramstore.SetParamSet(ctx, ¶ms) + k.paramStore.SetParamSet(ctx, ¶ms) } diff --git a/x/emissions/keeper/params_test.go b/x/emissions/keeper/params_test.go index e73031b824..6b85f76444 100644 --- a/x/emissions/keeper/params_test.go +++ b/x/emissions/keeper/params_test.go @@ -219,9 +219,9 @@ func TestKeeper_GetParams(t *testing.T) { }, tt.isPanic) if tt.isPanic != "" { - require.Equal(t, emissionstypes.DefaultParams(), k.GetParams(ctx)) + require.Equal(t, emissionstypes.DefaultParams(), k.GetParamsIfExists(ctx)) } else { - require.Equal(t, tt.params, k.GetParams(ctx)) + require.Equal(t, tt.params, k.GetParamsIfExists(ctx)) } }) } diff --git a/x/emissions/types/distributions.go b/x/emissions/types/distributions.go new file mode 100644 index 0000000000..fb67135ea9 --- /dev/null +++ b/x/emissions/types/distributions.go @@ -0,0 +1,34 @@ +package types + +import ( + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetRewardsDistributions returns the current distribution of rewards +// for validators, observers and TSS signers +// If the percentage is not set, it returns 0 +func GetRewardsDistributions(params Params) (sdkmath.Int, sdkmath.Int, sdkmath.Int) { + // Fetch the validator rewards, use 0 if the percentage is not set + validatorRewards := sdk.NewInt(0) + validatorRewardsDec, err := sdk.NewDecFromStr(params.ValidatorEmissionPercentage) + if err == nil { + validatorRewards = validatorRewardsDec.Mul(BlockReward).TruncateInt() + } + + // Fetch the observer rewards, use 0 if the percentage is not set + observerRewards := sdk.NewInt(0) + observerRewardsDec, err := sdk.NewDecFromStr(params.ObserverEmissionPercentage) + if err == nil { + observerRewards = observerRewardsDec.Mul(BlockReward).TruncateInt() + } + + // Fetch the TSS signer rewards, use 0 if the percentage is not set + tssSignerRewards := sdk.NewInt(0) + tssSignerRewardsDec, err := sdk.NewDecFromStr(params.TssSignerEmissionPercentage) + if err == nil { + tssSignerRewards = tssSignerRewardsDec.Mul(BlockReward).TruncateInt() + } + + return validatorRewards, observerRewards, tssSignerRewards +} diff --git a/x/emissions/types/distributions_test.go b/x/emissions/types/distributions_test.go new file mode 100644 index 0000000000..76d2534583 --- /dev/null +++ b/x/emissions/types/distributions_test.go @@ -0,0 +1,34 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func TestKeeper_GetRewardsDistributions(t *testing.T) { + t.Run("Return fractions of block reward", func(t *testing.T) { + val, obs, tss := types.GetRewardsDistributions(types.Params{ + ValidatorEmissionPercentage: "0.5", + ObserverEmissionPercentage: "0.25", + TssSignerEmissionPercentage: "0.25", + }) + + require.EqualValues(t, "4810474537037037037", val.String()) // 0.5 * block reward + require.EqualValues(t, "2405237268518518518", obs.String()) // 0.25 * block reward + require.EqualValues(t, "2405237268518518518", tss.String()) // 0.25 * block reward + }) + + t.Run("Return zero in case of invalid string", func(t *testing.T) { + val, obs, tss := types.GetRewardsDistributions(types.Params{ + ValidatorEmissionPercentage: "invalid", + ObserverEmissionPercentage: "invalid", + TssSignerEmissionPercentage: "invalid", + }) + + require.True(t, val.IsZero()) + require.True(t, obs.IsZero()) + require.True(t, tss.IsZero()) + }) +} diff --git a/x/emissions/types/expected_keepers.go b/x/emissions/types/expected_keepers.go index 286490e7c5..298a0caf42 100644 --- a/x/emissions/types/expected_keepers.go +++ b/x/emissions/types/expected_keepers.go @@ -3,6 +3,7 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -28,3 +29,11 @@ type BankKeeper interface { type StakingKeeper interface { BondedRatio(ctx sdk.Context) sdk.Dec } + +// ParamStore defines the expected paramstore methods to store and load Params (noalias) +type ParamStore interface { + GetParamSetIfExists(ctx sdk.Context, ps paramstypes.ParamSet) + SetParamSet(ctx sdk.Context, ps paramstypes.ParamSet) + WithKeyTable(table paramstypes.KeyTable) paramstypes.Subspace + HasKeyTable() bool +} diff --git a/x/emissions/types/message_withdraw_emissons_test.go b/x/emissions/types/message_withdraw_emissons_test.go index fcc7ae7fc5..d375d6fe94 100644 --- a/x/emissions/types/message_withdraw_emissons_test.go +++ b/x/emissions/types/message_withdraw_emissons_test.go @@ -4,6 +4,7 @@ import ( "testing" sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -41,3 +42,53 @@ func TestMsgWithdrawEmission_ValidateBasic(t *testing.T) { require.NoError(t, err) }) } + +func TestMsgWithdrawEmission_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *emissionstypes.MsgWithdrawEmission + panics bool + }{ + { + name: "valid signer", + msg: emissionstypes.NewMsgWithdrawEmissions(signer, sample.IntInRange(1, 100)), + panics: false, + }, + { + name: "invalid signer", + msg: emissionstypes.NewMsgWithdrawEmissions("invalid", sample.IntInRange(1, 100)), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgWithdrawEmission_Type(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(1, 100)) + require.Equal(t, emissionstypes.MsgWithdrawEmissionType, msg.Type()) +} + +func TestMsgWithdrawEmission_Route(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(1, 100)) + require.Equal(t, emissionstypes.RouterKey, msg.Route()) +} + +func TestMsgWithdrawEmission_GetSignBytes(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(1, 100)) + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/fungible/types/expected_keepers.go b/x/fungible/types/expected_keepers.go index feddedf4b7..5ffc04c723 100644 --- a/x/fungible/types/expected_keepers.go +++ b/x/fungible/types/expected_keepers.go @@ -13,7 +13,6 @@ import ( evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/zeta-chain/zetacore/common" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // AccountKeeper defines the expected account keeper used for simulations (noalias) @@ -32,7 +31,6 @@ type BankKeeper interface { } type ObserverKeeper interface { - GetParams(ctx sdk.Context) (params observertypes.Params) GetSupportedChains(ctx sdk.Context) []*common.Chain } diff --git a/x/fungible/types/message_deploy_fungible_coin_zrc20_test.go b/x/fungible/types/message_deploy_fungible_coin_zrc20_test.go index 12e6e0750e..4450dffff4 100644 --- a/x/fungible/types/message_deploy_fungible_coin_zrc20_test.go +++ b/x/fungible/types/message_deploy_fungible_coin_zrc20_test.go @@ -4,8 +4,10 @@ import ( "testing" cosmoserrors "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/fungible/types" ) @@ -13,37 +15,63 @@ import ( func TestMsgDeployFungibleCoinZRC4_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgDeployFungibleCoinZRC20 + msg *types.MsgDeployFungibleCoinZRC20 err error }{ { name: "invalid address", - msg: types.MsgDeployFungibleCoinZRC20{ - Creator: "invalid_address", - }, + msg: types.NewMsgDeployFungibleCoinZRC20( + "invalid_address", + "test erc20", + 1, + 6, + "test", + "test", + common.CoinType_ERC20, + 10, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid gas limit", - msg: types.MsgDeployFungibleCoinZRC20{ - Creator: sample.AccAddress(), - GasLimit: -1, - }, + msg: types.NewMsgDeployFungibleCoinZRC20( + sample.AccAddress(), + "test erc20", + 1, + 6, + "test", + "test", + common.CoinType_ERC20, + -1, + ), err: sdkerrors.ErrInvalidGasLimit, }, { name: "invalid decimals", - msg: types.MsgDeployFungibleCoinZRC20{ - Creator: sample.AccAddress(), - Decimals: 78, - }, + msg: types.NewMsgDeployFungibleCoinZRC20( + sample.AccAddress(), + "test erc20", + 1, + 78, + "test", + "test", + common.CoinType_ERC20, + 10, + ), err: cosmoserrors.Wrapf(sdkerrors.ErrInvalidRequest, "decimals must be less than 78"), }, { name: "valid message", - msg: types.MsgDeployFungibleCoinZRC20{ - Creator: sample.AccAddress(), - }, + msg: types.NewMsgDeployFungibleCoinZRC20( + sample.AccAddress(), + "test erc20", + 1, + 6, + "test", + "test", + common.CoinType_ERC20, + 10, + ), }, } for _, tt := range tests { @@ -57,3 +85,63 @@ func TestMsgDeployFungibleCoinZRC4_ValidateBasic(t *testing.T) { }) } } + +func TestMsgDeployFungibleCoinZRC4_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgDeployFungibleCoinZRC20 + panics bool + }{ + { + name: "valid signer", + msg: types.MsgDeployFungibleCoinZRC20{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgDeployFungibleCoinZRC20{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgDeployFungibleCoinZRC4_Type(t *testing.T) { + msg := types.MsgDeployFungibleCoinZRC20{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgDeployFungibleCoinZRC20, msg.Type()) +} + +func TestMsgDeployFungibleCoinZRC4_Route(t *testing.T) { + msg := types.MsgDeployFungibleCoinZRC20{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgDeployFungibleCoinZRC4_GetSignBytes(t *testing.T) { + msg := types.MsgDeployFungibleCoinZRC20{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/fungible/types/message_deploy_system_contracts.go b/x/fungible/types/message_deploy_system_contracts.go index 4c4515e856..8a62b7c233 100644 --- a/x/fungible/types/message_deploy_system_contracts.go +++ b/x/fungible/types/message_deploy_system_contracts.go @@ -21,7 +21,7 @@ func (msg *MsgDeploySystemContracts) Route() string { } func (msg *MsgDeploySystemContracts) Type() string { - return TypeMsgDeployFungibleCoinZRC20 + return TypeMsgDeploySystemContracts } func (msg *MsgDeploySystemContracts) GetSigners() []sdk.AccAddress { diff --git a/x/fungible/types/message_deploy_system_contracts_test.go b/x/fungible/types/message_deploy_system_contracts_test.go index b14aa419ed..5040db4345 100644 --- a/x/fungible/types/message_deploy_system_contracts_test.go +++ b/x/fungible/types/message_deploy_system_contracts_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -12,21 +13,17 @@ import ( func TestMsgDeploySystemContract_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgDeploySystemContracts + msg *types.MsgDeploySystemContracts err error }{ { name: "invalid address", - msg: types.MsgDeploySystemContracts{ - Creator: "invalid_address", - }, - err: sdkerrors.ErrInvalidAddress, + msg: types.NewMsgDeploySystemContracts("invalid"), + err: sdkerrors.ErrInvalidAddress, }, { name: "valid message", - msg: types.MsgDeploySystemContracts{ - Creator: sample.AccAddress(), - }, + msg: types.NewMsgDeploySystemContracts(sample.AccAddress()), }, } for _, tt := range tests { @@ -40,3 +37,59 @@ func TestMsgDeploySystemContract_ValidateBasic(t *testing.T) { }) } } + +func TestMsgDeploySystemContract_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *types.MsgDeploySystemContracts + panics bool + }{ + { + name: "valid signer", + msg: types.NewMsgDeploySystemContracts(signer), + panics: false, + }, + { + name: "invalid signer", + msg: types.NewMsgDeploySystemContracts("invalid"), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgDeploySystemContract_Type(t *testing.T) { + msg := types.MsgDeploySystemContracts{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgDeploySystemContracts, msg.Type()) +} + +func TestMsgDeploySystemContract_Route(t *testing.T) { + msg := types.MsgDeploySystemContracts{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgDeploySystemContract_GetSignBytes(t *testing.T) { + msg := types.MsgDeploySystemContracts{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/fungible/types/message_remove_foreign_coin_test.go b/x/fungible/types/message_remove_foreign_coin_test.go index 66a4c143f3..ae5c811c21 100644 --- a/x/fungible/types/message_remove_foreign_coin_test.go +++ b/x/fungible/types/message_remove_foreign_coin_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -12,21 +13,17 @@ import ( func TestMsgRemoveForeignCoin_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgRemoveForeignCoin + msg *types.MsgRemoveForeignCoin err error }{ { name: "invalid address", - msg: types.MsgRemoveForeignCoin{ - Creator: "invalid_address", - }, - err: sdkerrors.ErrInvalidAddress, + msg: types.NewMsgRemoveForeignCoin("invalid_address", "name"), + err: sdkerrors.ErrInvalidAddress, }, { name: "valid address", - msg: types.MsgRemoveForeignCoin{ - Creator: sample.AccAddress(), - }, + msg: types.NewMsgRemoveForeignCoin(sample.AccAddress(), "name"), }, } for _, tt := range tests { @@ -40,3 +37,63 @@ func TestMsgRemoveForeignCoin_ValidateBasic(t *testing.T) { }) } } + +func TestMsgRemoveForeignCoin_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgRemoveForeignCoin + panics bool + }{ + { + name: "valid signer", + msg: types.MsgRemoveForeignCoin{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgRemoveForeignCoin{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgRemoveForeignCoin_Type(t *testing.T) { + msg := types.MsgRemoveForeignCoin{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgRemoveForeignCoin, msg.Type()) +} + +func TestMsgRemoveForeignCoin_Route(t *testing.T) { + msg := types.MsgRemoveForeignCoin{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgRemoveForeignCoin_GetSignBytes(t *testing.T) { + msg := types.MsgRemoveForeignCoin{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/fungible/types/message_update_contract_bytecode_test.go b/x/fungible/types/message_update_contract_bytecode_test.go index 02e8b3cfdd..cb8f3ab65e 100644 --- a/x/fungible/types/message_update_contract_bytecode_test.go +++ b/x/fungible/types/message_update_contract_bytecode_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/fungible/types" @@ -11,43 +12,43 @@ import ( func TestMsgUpdateContractBytecode_ValidateBasic(t *testing.T) { tt := []struct { name string - msg types.MsgUpdateContractBytecode + msg *types.MsgUpdateContractBytecode wantError bool }{ { name: "valid", - msg: types.MsgUpdateContractBytecode{ - Creator: sample.AccAddress(), - ContractAddress: sample.EthAddress().Hex(), - NewCodeHash: sample.Hash().Hex(), - }, + msg: types.NewMsgUpdateContractBytecode( + sample.AccAddress(), + sample.EthAddress().Hex(), + sample.Hash().Hex(), + ), wantError: false, }, { name: "invalid creator", - msg: types.MsgUpdateContractBytecode{ - Creator: "invalid", - ContractAddress: sample.EthAddress().Hex(), - NewCodeHash: sample.Hash().Hex(), - }, + msg: types.NewMsgUpdateContractBytecode( + "invalid", + sample.EthAddress().Hex(), + sample.Hash().Hex(), + ), wantError: true, }, { name: "invalid contract address", - msg: types.MsgUpdateContractBytecode{ - Creator: sample.AccAddress(), - ContractAddress: "invalid", - NewCodeHash: sample.Hash().Hex(), - }, + msg: types.NewMsgUpdateContractBytecode( + sample.AccAddress(), + "invalid", + sample.Hash().Hex(), + ), wantError: true, }, { name: "invalid new code hash", - msg: types.MsgUpdateContractBytecode{ - Creator: sample.AccAddress(), - ContractAddress: sample.EthAddress().Hex(), - NewCodeHash: "invalid", - }, + msg: types.NewMsgUpdateContractBytecode( + sample.AccAddress(), + sample.EthAddress().Hex(), + "invalid", + ), wantError: true, }, } @@ -63,3 +64,63 @@ func TestMsgUpdateContractBytecode_ValidateBasic(t *testing.T) { }) } } + +func TestMsgUpdateContractBytecode_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateContractBytecode + panics bool + }{ + { + name: "valid signer", + msg: types.MsgUpdateContractBytecode{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgUpdateContractBytecode{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgUpdateContractBytecode_Type(t *testing.T) { + msg := types.MsgUpdateContractBytecode{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateContractBytecode, msg.Type()) +} + +func TestMsgUpdateContractBytecode_Route(t *testing.T) { + msg := types.MsgUpdateContractBytecode{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgUpdateContractBytecode_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateContractBytecode{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/fungible/types/message_update_system_contract_test.go b/x/fungible/types/message_update_system_contract_test.go index 8ee196ad39..7ffe048f47 100644 --- a/x/fungible/types/message_update_system_contract_test.go +++ b/x/fungible/types/message_update_system_contract_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -12,31 +13,22 @@ import ( func TestMsgUpdateSystemContract_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgUpdateSystemContract + msg *types.MsgUpdateSystemContract err error }{ { name: "invalid address", - msg: types.MsgUpdateSystemContract{ - Creator: "invalid_address", - NewSystemContractAddress: sample.EthAddress().String(), - }, - err: sdkerrors.ErrInvalidAddress, + msg: types.NewMsgUpdateSystemContract("invalid_address", sample.EthAddress().String()), + err: sdkerrors.ErrInvalidAddress, }, { name: "invalid new system contract address", - msg: types.MsgUpdateSystemContract{ - Creator: sample.AccAddress(), - NewSystemContractAddress: "invalid_address", - }, - err: sdkerrors.ErrInvalidAddress, + msg: types.NewMsgUpdateSystemContract(sample.AccAddress(), "invalid_address"), + err: sdkerrors.ErrInvalidAddress, }, { name: "valid message", - msg: types.MsgUpdateSystemContract{ - Creator: sample.AccAddress(), - NewSystemContractAddress: sample.EthAddress().String(), - }, + msg: types.NewMsgUpdateSystemContract(sample.AccAddress(), sample.EthAddress().String()), }, } for _, tt := range tests { @@ -50,3 +42,63 @@ func TestMsgUpdateSystemContract_ValidateBasic(t *testing.T) { }) } } + +func TestMsgUpdateSystemContract_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateSystemContract + panics bool + }{ + { + name: "valid signer", + msg: types.MsgUpdateSystemContract{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgUpdateSystemContract{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgUpdateSystemContract_Type(t *testing.T) { + msg := types.MsgUpdateSystemContract{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateSystemContract, msg.Type()) +} + +func TestMsgUpdateSystemContract_Route(t *testing.T) { + msg := types.MsgUpdateSystemContract{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgUpdateSystemContract_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateSystemContract{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/fungible/types/message_update_zrc20_liquidity_cap.go b/x/fungible/types/message_update_zrc20_liquidity_cap.go index 45bda0e1f5..c03c08b105 100644 --- a/x/fungible/types/message_update_zrc20_liquidity_cap.go +++ b/x/fungible/types/message_update_zrc20_liquidity_cap.go @@ -25,7 +25,7 @@ func (msg *MsgUpdateZRC20LiquidityCap) Route() string { } func (msg *MsgUpdateZRC20LiquidityCap) Type() string { - return TypeMsgUpdateSystemContract + return TypeMsgUpdateZRC20LiquidityCap } func (msg *MsgUpdateZRC20LiquidityCap) GetSigners() []sdk.AccAddress { diff --git a/x/fungible/types/message_update_zrc20_liquidity_cap_test.go b/x/fungible/types/message_update_zrc20_liquidity_cap_test.go index d59198ce39..f509fa65f9 100644 --- a/x/fungible/types/message_update_zrc20_liquidity_cap_test.go +++ b/x/fungible/types/message_update_zrc20_liquidity_cap_test.go @@ -5,6 +5,7 @@ import ( "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -14,46 +15,49 @@ import ( func TestNewMsgUpdateZRC20LiquidityCap_ValidateBasics(t *testing.T) { tests := []struct { name string - msg types.MsgUpdateZRC20LiquidityCap + msg *types.MsgUpdateZRC20LiquidityCap err error }{ { name: "valid message", - msg: types.MsgUpdateZRC20LiquidityCap{ - Creator: sample.AccAddress(), - Zrc20Address: sample.EthAddress().String(), - LiquidityCap: math.NewUint(1000), - }, + msg: types.NewMsgUpdateZRC20LiquidityCap( + sample.AccAddress(), + sample.EthAddress().String(), + math.NewUint(1000), + ), }, { name: "valid message with liquidity cap 0", - msg: types.MsgUpdateZRC20LiquidityCap{ - Creator: sample.AccAddress(), - Zrc20Address: sample.EthAddress().String(), - LiquidityCap: math.ZeroUint(), - }, + msg: types.NewMsgUpdateZRC20LiquidityCap( + sample.AccAddress(), + sample.EthAddress().String(), + math.ZeroUint(), + ), }, { name: "valid message with liquidity cap nil", - msg: types.MsgUpdateZRC20LiquidityCap{ - Creator: sample.AccAddress(), - Zrc20Address: sample.EthAddress().String(), - }, + msg: types.NewMsgUpdateZRC20LiquidityCap( + sample.AccAddress(), + sample.EthAddress().String(), + math.NewUint(1000), + ), }, { name: "invalid address", - msg: types.MsgUpdateZRC20LiquidityCap{ - Creator: "invalid_address", - Zrc20Address: sample.EthAddress().String(), - }, + msg: types.NewMsgUpdateZRC20LiquidityCap( + "invalid_address", + sample.EthAddress().String(), + math.NewUint(1000), + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid contract address", - msg: types.MsgUpdateZRC20LiquidityCap{ - Creator: sample.AccAddress(), - Zrc20Address: "invalid_address", - }, + msg: types.NewMsgUpdateZRC20LiquidityCap( + sample.AccAddress(), + "invalid_address", + math.NewUint(1000), + ), err: sdkerrors.ErrInvalidAddress, }, } @@ -68,3 +72,63 @@ func TestNewMsgUpdateZRC20LiquidityCap_ValidateBasics(t *testing.T) { }) } } + +func TestNewMsgUpdateZRC20LiquidityCap_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateZRC20LiquidityCap + panics bool + }{ + { + name: "valid signer", + msg: types.MsgUpdateZRC20LiquidityCap{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgUpdateZRC20LiquidityCap{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestNewMsgUpdateZRC20LiquidityCap_Type(t *testing.T) { + msg := types.MsgUpdateZRC20LiquidityCap{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateZRC20LiquidityCap, msg.Type()) +} + +func TestNewMsgUpdateZRC20LiquidityCap_Route(t *testing.T) { + msg := types.MsgUpdateZRC20LiquidityCap{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestNewMsgUpdateZRC20LiquidityCap_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateZRC20LiquidityCap{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/fungible/types/message_update_zrc20_paused_status_test.go b/x/fungible/types/message_update_zrc20_paused_status_test.go index 326af3fe96..f70d0d81d2 100644 --- a/x/fungible/types/message_update_zrc20_paused_status_test.go +++ b/x/fungible/types/message_update_zrc20_paused_status_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/fungible/types" @@ -11,81 +12,81 @@ import ( func TestMMsgUpdateZRC20PausedStatus_ValidateBasic(t *testing.T) { tt := []struct { name string - msg types.MsgUpdateZRC20PausedStatus + msg *types.MsgUpdateZRC20PausedStatus wantErr bool }{ { name: "valid pause message", - msg: types.MsgUpdateZRC20PausedStatus{ - Creator: sample.AccAddress(), - Zrc20Addresses: []string{ + msg: types.NewMsgUpdateZRC20PausedStatus( + sample.AccAddress(), + []string{ sample.EthAddress().String(), sample.EthAddress().String(), sample.EthAddress().String(), }, - Action: types.UpdatePausedStatusAction_PAUSE, - }, + types.UpdatePausedStatusAction_PAUSE, + ), wantErr: false, }, { name: "valid unpause message", - msg: types.MsgUpdateZRC20PausedStatus{ - Creator: sample.AccAddress(), - Zrc20Addresses: []string{ + msg: types.NewMsgUpdateZRC20PausedStatus( + sample.AccAddress(), + []string{ sample.EthAddress().String(), sample.EthAddress().String(), sample.EthAddress().String(), }, - Action: types.UpdatePausedStatusAction_UNPAUSE, - }, + types.UpdatePausedStatusAction_UNPAUSE, + ), wantErr: false, }, { name: "invalid creator address", - msg: types.MsgUpdateZRC20PausedStatus{ - Creator: "invalid", - Zrc20Addresses: []string{ + msg: types.NewMsgUpdateZRC20PausedStatus( + "invalid", + []string{ sample.EthAddress().String(), sample.EthAddress().String(), sample.EthAddress().String(), }, - Action: types.UpdatePausedStatusAction_PAUSE, - }, + types.UpdatePausedStatusAction_PAUSE, + ), wantErr: true, }, { name: "invalid empty zrc20 address", - msg: types.MsgUpdateZRC20PausedStatus{ - Creator: sample.AccAddress(), - Zrc20Addresses: []string{}, - Action: types.UpdatePausedStatusAction_PAUSE, - }, + msg: types.NewMsgUpdateZRC20PausedStatus( + sample.AccAddress(), + []string{}, + types.UpdatePausedStatusAction_PAUSE, + ), wantErr: true, }, { name: "invalid zrc20 address", - msg: types.MsgUpdateZRC20PausedStatus{ - Creator: sample.AccAddress(), - Zrc20Addresses: []string{ + msg: types.NewMsgUpdateZRC20PausedStatus( + sample.AccAddress(), + []string{ sample.EthAddress().String(), "invalid", sample.EthAddress().String(), }, - Action: types.UpdatePausedStatusAction_PAUSE, - }, + types.UpdatePausedStatusAction_PAUSE, + ), wantErr: true, }, { name: "invalid action", - msg: types.MsgUpdateZRC20PausedStatus{ - Creator: sample.AccAddress(), - Zrc20Addresses: []string{ + msg: types.NewMsgUpdateZRC20PausedStatus( + sample.AccAddress(), + []string{ sample.EthAddress().String(), sample.EthAddress().String(), sample.EthAddress().String(), }, - Action: 3, - }, + 3, + ), wantErr: true, }, } @@ -101,3 +102,63 @@ func TestMMsgUpdateZRC20PausedStatus_ValidateBasic(t *testing.T) { }) } } + +func TestMMsgUpdateZRC20PausedStatus_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateZRC20PausedStatus + panics bool + }{ + { + name: "valid signer", + msg: types.MsgUpdateZRC20PausedStatus{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgUpdateZRC20PausedStatus{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMMsgUpdateZRC20PausedStatus_Type(t *testing.T) { + msg := types.MsgUpdateZRC20PausedStatus{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateZRC20PausedStatus, msg.Type()) +} + +func TestMMsgUpdateZRC20PausedStatus_Route(t *testing.T) { + msg := types.MsgUpdateZRC20PausedStatus{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMMsgUpdateZRC20PausedStatus_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateZRC20PausedStatus{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/fungible/types/message_update_zrc20_withdraw_fee_test.go b/x/fungible/types/message_update_zrc20_withdraw_fee_test.go index 2aba73d42a..21e6fb7fcd 100644 --- a/x/fungible/types/message_update_zrc20_withdraw_fee_test.go +++ b/x/fungible/types/message_update_zrc20_withdraw_fee_test.go @@ -5,6 +5,7 @@ import ( math "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -14,89 +15,135 @@ import ( func TestMsgUpdateZRC20WithdrawFee_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgUpdateZRC20WithdrawFee + msg *types.MsgUpdateZRC20WithdrawFee err error }{ { name: "invalid address", - msg: types.MsgUpdateZRC20WithdrawFee{ - Creator: "invalid_address", - Zrc20Address: sample.EthAddress().String(), - NewWithdrawFee: math.NewUint(1), - }, + msg: types.NewMsgUpdateZRC20WithdrawFee( + "invalid_address", + sample.EthAddress().String(), + math.NewUint(1), + math.Uint{}, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid new system contract address", - msg: types.MsgUpdateZRC20WithdrawFee{ - Creator: sample.AccAddress(), - Zrc20Address: "invalid_address", - NewWithdrawFee: math.NewUint(1), - }, + msg: types.NewMsgUpdateZRC20WithdrawFee( + sample.AccAddress(), + "invalid_address", + math.NewUint(1), + math.Uint{}, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "both withdraw fee and gas limit nil", - msg: types.MsgUpdateZRC20WithdrawFee{ - Creator: sample.AccAddress(), - Zrc20Address: sample.EthAddress().String(), - NewGasLimit: math.Uint{}, - NewWithdrawFee: math.Uint{}, - }, + msg: types.NewMsgUpdateZRC20WithdrawFee( + sample.AccAddress(), + sample.EthAddress().String(), + math.Uint{}, + math.Uint{}, + ), err: sdkerrors.ErrInvalidRequest, }, { name: "valid message", - msg: types.MsgUpdateZRC20WithdrawFee{ - Creator: sample.AccAddress(), - Zrc20Address: sample.EthAddress().String(), - NewWithdrawFee: math.NewUint(42), - NewGasLimit: math.NewUint(42), - }, + msg: types.NewMsgUpdateZRC20WithdrawFee( + sample.AccAddress(), + sample.EthAddress().String(), + math.NewUint(42), + math.NewUint(42), + ), }, { name: "withdraw fee can be zero", - msg: types.MsgUpdateZRC20WithdrawFee{ - Creator: sample.AccAddress(), - Zrc20Address: sample.EthAddress().String(), - NewWithdrawFee: math.ZeroUint(), - NewGasLimit: math.NewUint(42), - }, + msg: types.NewMsgUpdateZRC20WithdrawFee( + sample.AccAddress(), + sample.EthAddress().String(), + math.ZeroUint(), + math.NewUint(42), + ), }, { - name: "withdraw fee can be nil", - msg: types.MsgUpdateZRC20WithdrawFee{ - Creator: sample.AccAddress(), - Zrc20Address: sample.EthAddress().String(), - NewGasLimit: math.NewUint(42), - }, + name: "gas limit can be zero", + msg: types.NewMsgUpdateZRC20WithdrawFee( + sample.AccAddress(), + sample.EthAddress().String(), + math.ZeroUint(), + math.NewUint(42), + ), }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.err != nil { + require.ErrorIs(t, err, tt.err) + return + } + require.NoError(t, err) + }) + } +} + +func TestMsgUpdateZRC20WithdrawFee_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateZRC20WithdrawFee + panics bool + }{ { - name: "gas limit can be zero", + name: "valid signer", msg: types.MsgUpdateZRC20WithdrawFee{ - Creator: sample.AccAddress(), - Zrc20Address: sample.EthAddress().String(), - NewGasLimit: math.ZeroUint(), - NewWithdrawFee: math.NewUint(42), + Creator: signer, }, + panics: false, }, { - name: "gas limit can be nil", + name: "invalid signer", msg: types.MsgUpdateZRC20WithdrawFee{ - Creator: sample.AccAddress(), - Zrc20Address: sample.EthAddress().String(), - NewWithdrawFee: math.NewUint(42), + Creator: "invalid", }, + panics: true, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.msg.ValidateBasic() - if tt.err != nil { - require.ErrorIs(t, err, tt.err) - return + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) } - require.NoError(t, err) }) } } + +func TestMsgUpdateZRC20WithdrawFee_Type(t *testing.T) { + msg := types.MsgUpdateZRC20WithdrawFee{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateZRC20WithdrawFee, msg.Type()) +} + +func TestMsgUpdateZRC20WithdrawFee_Route(t *testing.T) { + msg := types.MsgUpdateZRC20WithdrawFee{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgUpdateZRC20WithdrawFee_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateZRC20WithdrawFee{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/observer/genesis.go b/x/observer/genesis.go index 8098394f65..1c1b10ef26 100644 --- a/x/observer/genesis.go +++ b/x/observer/genesis.go @@ -141,7 +141,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) // ExportGenesis returns the observer module's exported genesis. func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { - params := k.GetParams(ctx) + params := k.GetParamsIfExists(ctx) chainParams, found := k.GetChainParamsList(ctx) if !found { diff --git a/x/observer/keeper/ballot.go b/x/observer/keeper/ballot.go index 787afd7831..4c960743ac 100644 --- a/x/observer/keeper/ballot.go +++ b/x/observer/keeper/ballot.go @@ -63,7 +63,7 @@ func (k Keeper) AddBallotToList(ctx sdk.Context, ballot types.Ballot) { // GetMaturedBallotList Returns a list of ballots which are matured at current height func (k Keeper) GetMaturedBallotList(ctx sdk.Context) []string { - maturityBlocks := k.GetParams(ctx).BallotMaturityBlocks + maturityBlocks := k.GetParamsIfExists(ctx).BallotMaturityBlocks list, found := k.GetBallotList(ctx, ctx.BlockHeight()-maturityBlocks) if !found { return []string{} diff --git a/x/observer/keeper/grpc_query_params.go b/x/observer/keeper/grpc_query_params.go index f02060de25..ef095420af 100644 --- a/x/observer/keeper/grpc_query_params.go +++ b/x/observer/keeper/grpc_query_params.go @@ -15,5 +15,5 @@ func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types } ctx := sdk.UnwrapSDKContext(c) return &types.QueryParamsResponse{ - Params: k.GetParams(ctx)}, nil + Params: k.GetParamsIfExists(ctx)}, nil } diff --git a/x/observer/keeper/params.go b/x/observer/keeper/params.go index 3c570c9cf0..f137d04a18 100644 --- a/x/observer/keeper/params.go +++ b/x/observer/keeper/params.go @@ -5,12 +5,6 @@ import ( "github.com/zeta-chain/zetacore/x/observer/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - k.paramstore.GetParamSet(ctx, ¶ms) - return -} - func (k Keeper) GetParamsIfExists(ctx sdk.Context) (params types.Params) { k.paramstore.GetParamSetIfExists(ctx, ¶ms) return diff --git a/x/observer/keeper/params_test.go b/x/observer/keeper/params_test.go index 5ec4c260a3..c487ea0f73 100644 --- a/x/observer/keeper/params_test.go +++ b/x/observer/keeper/params_test.go @@ -18,7 +18,7 @@ func TestGetParams(t *testing.T) { k.SetParams(ctx, params) - require.EqualValues(t, params, k.GetParams(ctx)) + require.EqualValues(t, params, k.GetParamsIfExists(ctx)) } func TestGenerateAddress(t *testing.T) { diff --git a/x/observer/migrations/v3/migrate_test.go b/x/observer/migrations/v3/migrate_test.go index 81e3747517..2ff0b36255 100644 --- a/x/observer/migrations/v3/migrate_test.go +++ b/x/observer/migrations/v3/migrate_test.go @@ -19,7 +19,7 @@ func TestMigrateStore(t *testing.T) { k.SetParams(ctx, params) err := v3.MigrateStore(ctx, k) require.NoError(t, err) - params = k.GetParams(ctx) + params = k.GetParamsIfExists(ctx) require.Len(t, params.AdminPolicy, 0) // update admin policy @@ -42,7 +42,7 @@ func TestMigrateStore(t *testing.T) { k.SetParams(ctx, params) err = v3.MigrateStore(ctx, k) require.NoError(t, err) - params = k.GetParams(ctx) + params = k.GetParamsIfExists(ctx) require.Len(t, params.AdminPolicy, 2) require.Equal(t, params.AdminPolicy[0].PolicyType, types.Policy_Type_group1) require.Equal(t, params.AdminPolicy[1].PolicyType, types.Policy_Type_group2) diff --git a/x/observer/migrations/v4/migrate.go b/x/observer/migrations/v4/migrate.go index 7444c46233..9ca22467a2 100644 --- a/x/observer/migrations/v4/migrate.go +++ b/x/observer/migrations/v4/migrate.go @@ -10,7 +10,6 @@ import ( // observerKeeper prevents circular dependency type observerKeeper interface { - GetParams(ctx sdk.Context) types.Params SetParams(ctx sdk.Context, params types.Params) GetChainParamsList(ctx sdk.Context) (params types.ChainParamsList, found bool) SetChainParamsList(ctx sdk.Context, params types.ChainParamsList) diff --git a/x/observer/migrations/v5/migrate.go b/x/observer/migrations/v5/migrate.go index a626ce192b..2eb261a3f4 100644 --- a/x/observer/migrations/v5/migrate.go +++ b/x/observer/migrations/v5/migrate.go @@ -10,7 +10,7 @@ import ( // observerKeeper prevents circular dependency type observerKeeper interface { - GetParams(ctx sdk.Context) types.Params + GetParamsIfExists(ctx sdk.Context) types.Params SetParams(ctx sdk.Context, params types.Params) GetChainParamsList(ctx sdk.Context) (params types.ChainParamsList, found bool) SetChainParamsList(ctx sdk.Context, params types.ChainParamsList) @@ -64,7 +64,7 @@ func MigrateObserverParams(ctx sdk.Context, observerKeeper observerKeeper) error } // search for the observer params with chain params entry - observerParams := observerKeeper.GetParams(ctx).ObserverParams + observerParams := observerKeeper.GetParamsIfExists(ctx).ObserverParams for _, observerParam := range observerParams { for i := range chainParamsList.ChainParams { // if the chain is found, update the chain params with the observer params diff --git a/x/observer/migrations/v7/migrate.go b/x/observer/migrations/v7/migrate.go index 2b2c759e09..414a87a38e 100644 --- a/x/observer/migrations/v7/migrate.go +++ b/x/observer/migrations/v7/migrate.go @@ -8,7 +8,7 @@ import ( // observerKeeper prevents circular dependency type observerKeeper interface { - GetParams(ctx sdk.Context) (params types.Params) + GetParamsIfExists(ctx sdk.Context) (params types.Params) GetAuthorityKeeper() types.AuthorityKeeper } @@ -20,7 +20,7 @@ func MigrateStore(ctx sdk.Context, observerKeeper observerKeeper) error { // MigratePolicies migrates policies from observer to authority func MigratePolicies(ctx sdk.Context, observerKeeper observerKeeper) error { - params := observerKeeper.GetParams(ctx) + params := observerKeeper.GetParamsIfExists(ctx) authorityKeeper := observerKeeper.GetAuthorityKeeper() var policies authoritytypes.Policies diff --git a/x/observer/types/message_add_blame_vote_test.go b/x/observer/types/message_add_blame_vote_test.go new file mode 100644 index 0000000000..810cdad433 --- /dev/null +++ b/x/observer/types/message_add_blame_vote_test.go @@ -0,0 +1,132 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestNewMsgAddBlameVoteMsg_ValidateBasic(t *testing.T) { + keeper.SetConfig(false) + tests := []struct { + name string + msg *types.MsgAddBlameVote + error bool + }{ + { + name: "invalid creator", + msg: types.NewMsgAddBlameVoteMsg( + "invalid_address", + 1, + sample.BlameRecordsList(t, 1)[0], + ), + error: true, + }, + { + name: "invalid chain id", + msg: types.NewMsgAddBlameVoteMsg( + sample.AccAddress(), + -1, + sample.BlameRecordsList(t, 1)[0], + ), + error: true, + }, + { + name: "valid", + msg: types.NewMsgAddBlameVoteMsg( + sample.AccAddress(), + 5, + sample.BlameRecordsList(t, 1)[0], + ), + error: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.error { + require.Error(t, err) + return + } else { + require.NoError(t, err) + } + }) + } +} + +func TestNewMsgAddBlameVoteMsg_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgAddBlameVote + panics bool + }{ + { + name: "valid signer", + msg: types.MsgAddBlameVote{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgAddBlameVote{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestNewMsgAddBlameVoteMsg_Type(t *testing.T) { + msg := types.MsgAddBlameVote{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgAddBlameVote, msg.Type()) +} + +func TestNewMsgAddBlameVoteMsg_Route(t *testing.T) { + msg := types.MsgAddBlameVote{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestNewMsgAddBlameVoteMsg_GetSignBytes(t *testing.T) { + msg := types.MsgAddBlameVote{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} + +func TestNewMsgAddBlameVoteMsg_Digest(t *testing.T) { + msg := types.MsgAddBlameVote{ + Creator: sample.AccAddress(), + } + + digest := msg.Digest() + msg.Creator = "" + expectedDigest := crypto.Keccak256Hash([]byte(msg.String())) + require.Equal(t, expectedDigest.Hex(), digest) +} diff --git a/x/observer/types/message_add_block_header_test.go b/x/observer/types/message_add_block_header_test.go new file mode 100644 index 0000000000..f3b86b6bc1 --- /dev/null +++ b/x/observer/types/message_add_block_header_test.go @@ -0,0 +1,177 @@ +package types_test + +import ( + "bytes" + "os" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestMsgAddBlockHeader_ValidateBasic(t *testing.T) { + keeper.SetConfig(false) + var header ethtypes.Header + file, err := os.Open("../../../common/testdata/eth_header_18495266.json") + require.NoError(t, err) + defer file.Close() + headerBytes := make([]byte, 4096) + n, err := file.Read(headerBytes) + require.NoError(t, err) + err = header.UnmarshalJSON(headerBytes[:n]) + require.NoError(t, err) + var buffer bytes.Buffer + err = header.EncodeRLP(&buffer) + require.NoError(t, err) + headerData := common.NewEthereumHeader(buffer.Bytes()) + tests := []struct { + name string + msg *types.MsgAddBlockHeader + error bool + }{ + { + name: "invalid creator", + msg: types.NewMsgAddBlockHeader( + "invalid_address", + 1, + []byte{}, + 6, + common.HeaderData{}, + ), + error: true, + }, + { + name: "invalid chain id", + msg: types.NewMsgAddBlockHeader( + sample.AccAddress(), + -1, + []byte{}, + 6, + common.HeaderData{}, + ), + error: true, + }, + { + name: "invalid header", + msg: types.NewMsgAddBlockHeader( + sample.AccAddress(), + 5, + sample.Hash().Bytes(), + 6, + common.HeaderData{}, + ), + error: true, + }, + { + name: "invalid blockHash length", + msg: types.NewMsgAddBlockHeader( + sample.AccAddress(), + 5, + sample.Hash().Bytes()[:31], + 6, + common.HeaderData{}, + ), + error: true, + }, + { + name: "valid", + msg: types.NewMsgAddBlockHeader( + sample.AccAddress(), + 5, + header.Hash().Bytes(), + 18495266, + headerData, + ), + error: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.error { + require.Error(t, err) + return + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgAddBlockHeader_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgAddBlockHeader + panics bool + }{ + { + name: "valid signer", + msg: types.MsgAddBlockHeader{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgAddBlockHeader{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgAddBlockHeader_Type(t *testing.T) { + msg := types.MsgAddBlockHeader{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgAddBlockHeader, msg.Type()) +} + +func TestMsgAddBlockHeader_Route(t *testing.T) { + msg := types.MsgAddBlockHeader{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgAddBlockHeader_GetSignBytes(t *testing.T) { + msg := types.MsgAddBlockHeader{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} + +func TestMsgAddBlockHeader_Digest(t *testing.T) { + msg := types.MsgAddBlockHeader{ + Creator: sample.AccAddress(), + } + + digest := msg.Digest() + msg.Creator = "" + expectedDigest := crypto.Keccak256Hash([]byte(msg.String())) + require.Equal(t, expectedDigest.Hex(), digest) +} diff --git a/x/observer/types/message_add_observer_test.go b/x/observer/types/message_add_observer_test.go index b60ade5dc3..455868d49f 100644 --- a/x/observer/types/message_add_observer_test.go +++ b/x/observer/types/message_add_observer_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -12,50 +13,47 @@ import ( func TestMsgAddObserver_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgAddObserver + msg *types.MsgAddObserver err error }{ - { - name: "invalid msg", - msg: types.MsgAddObserver{ - Creator: "invalid_address", - }, - err: sdkerrors.ErrInvalidAddress, - }, { name: "invalid creator", - msg: types.MsgAddObserver{ - Creator: "invalid_address", - ObserverAddress: sample.AccAddress(), - ZetaclientGranteePubkey: sample.PubKeyString(), - }, + msg: types.NewMsgAddObserver( + "invalid_address", + sample.AccAddress(), + sample.PubKeyString(), + true, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid pubkey", - msg: types.MsgAddObserver{ - Creator: sample.AccAddress(), - ObserverAddress: sample.AccAddress(), - ZetaclientGranteePubkey: "sample.PubKey()", - }, + msg: types.NewMsgAddObserver( + sample.AccAddress(), + sample.AccAddress(), + "sample.PubKey()", + true, + ), err: sdkerrors.ErrInvalidPubKey, }, { name: "invalid observer address", - msg: types.MsgAddObserver{ - Creator: sample.AccAddress(), - ObserverAddress: "invalid_address", - ZetaclientGranteePubkey: sample.PubKeyString(), - }, + msg: types.NewMsgAddObserver( + sample.AccAddress(), + "invalid_address", + sample.PubKeyString(), + true, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "valid address", - msg: types.MsgAddObserver{ - Creator: sample.AccAddress(), - ObserverAddress: sample.AccAddress(), - ZetaclientGranteePubkey: sample.PubKeyString(), - }, + msg: types.NewMsgAddObserver( + sample.AccAddress(), + sample.AccAddress(), + sample.PubKeyString(), + true, + ), }, } for _, tt := range tests { @@ -69,3 +67,63 @@ func TestMsgAddObserver_ValidateBasic(t *testing.T) { }) } } + +func TestMsgAddObserver_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgAddObserver + panics bool + }{ + { + name: "valid signer", + msg: types.MsgAddObserver{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgAddObserver{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgAddObserver_Type(t *testing.T) { + msg := types.MsgAddObserver{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgAddObserver, msg.Type()) +} + +func TestMsgAddObserver_Route(t *testing.T) { + msg := types.MsgAddObserver{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgAddObserver_GetSignBytes(t *testing.T) { + msg := types.MsgAddObserver{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/observer/types/message_crosschain_flags_test.go b/x/observer/types/message_crosschain_flags_test.go index 1ccd1d7040..34af456348 100644 --- a/x/observer/types/message_crosschain_flags_test.go +++ b/x/observer/types/message_crosschain_flags_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -232,3 +233,67 @@ func TestMsgUpdateCrosschainFlags_GetRequiredPolicyType(t *testing.T) { }) } } + +func TestMsgUpdateCrosschainFlags_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg *types.MsgUpdateCrosschainFlags + panics bool + }{ + { + name: "valid signer", + msg: types.NewMsgUpdateCrosschainFlags( + signer, + true, + true, + ), + panics: false, + }, + { + name: "invalid signer", + msg: types.NewMsgUpdateCrosschainFlags( + "invalid", + true, + true, + ), + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgUpdateCrosschainFlags_Type(t *testing.T) { + msg := types.MsgUpdateCrosschainFlags{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateCrosschainFlags, msg.Type()) +} + +func TestMsgUpdateCrosschainFlags_Route(t *testing.T) { + msg := types.MsgUpdateCrosschainFlags{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgUpdateCrosschainFlags_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateCrosschainFlags{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/observer/types/message_remove_chain_params_test.go b/x/observer/types/message_remove_chain_params_test.go index 10d5a4c777..9168821413 100644 --- a/x/observer/types/message_remove_chain_params_test.go +++ b/x/observer/types/message_remove_chain_params_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" @@ -13,31 +14,31 @@ import ( func TestMsgRemoveChainParams_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgRemoveChainParams + msg *types.MsgRemoveChainParams err error }{ { name: "valid message", - msg: types.MsgRemoveChainParams{ - Creator: sample.AccAddress(), - ChainId: common.ExternalChainList()[0].ChainId, - }, + msg: types.NewMsgRemoveChainParams( + sample.AccAddress(), + common.ExternalChainList()[0].ChainId, + ), }, { name: "invalid address", - msg: types.MsgRemoveChainParams{ - Creator: "invalid_address", - ChainId: common.ExternalChainList()[0].ChainId, - }, + msg: types.NewMsgRemoveChainParams( + "invalid_address", + common.ExternalChainList()[0].ChainId, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid chain ID", - msg: types.MsgRemoveChainParams{ - Creator: sample.AccAddress(), - ChainId: 999, - }, + msg: types.NewMsgRemoveChainParams( + sample.AccAddress(), + 999, + ), err: sdkerrors.ErrInvalidChainID, }, } @@ -52,3 +53,63 @@ func TestMsgRemoveChainParams_ValidateBasic(t *testing.T) { }) } } + +func TestMsgRemoveChainParams_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgRemoveChainParams + panics bool + }{ + { + name: "valid signer", + msg: types.MsgRemoveChainParams{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgRemoveChainParams{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgRemoveChainParams_Type(t *testing.T) { + msg := types.MsgRemoveChainParams{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgRemoveChainParams, msg.Type()) +} + +func TestMsgRemoveChainParams_Route(t *testing.T) { + msg := types.MsgRemoveChainParams{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgRemoveChainParams_GetSignBytes(t *testing.T) { + msg := types.MsgRemoveChainParams{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/observer/types/message_update_chain_params_test.go b/x/observer/types/message_update_chain_params_test.go index 3f3efc4e2f..633720d6b7 100644 --- a/x/observer/types/message_update_chain_params_test.go +++ b/x/observer/types/message_update_chain_params_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" @@ -13,30 +14,31 @@ import ( func TestMsgUpdateChainParams_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgUpdateChainParams + msg *types.MsgUpdateChainParams err error }{ { name: "valid message", - msg: types.MsgUpdateChainParams{ - Creator: sample.AccAddress(), - ChainParams: sample.ChainParams(common.ExternalChainList()[0].ChainId), - }, + msg: types.NewMsgUpdateChainParams( + sample.AccAddress(), + sample.ChainParams(common.ExternalChainList()[0].ChainId), + ), }, { name: "invalid address", - msg: types.MsgUpdateChainParams{ - Creator: "invalid_address", - ChainParams: sample.ChainParams(common.ExternalChainList()[0].ChainId), - }, + msg: types.NewMsgUpdateChainParams( + "invalid_address", + sample.ChainParams(common.ExternalChainList()[0].ChainId), + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid chain params (nil)", - msg: types.MsgUpdateChainParams{ - Creator: sample.AccAddress(), - }, + msg: types.NewMsgUpdateChainParams( + sample.AccAddress(), + nil, + ), err: types.ErrInvalidChainParams, }, } @@ -51,3 +53,63 @@ func TestMsgUpdateChainParams_ValidateBasic(t *testing.T) { }) } } + +func TestMsgUpdateChainParams_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateChainParams + panics bool + }{ + { + name: "valid signer", + msg: types.MsgUpdateChainParams{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgUpdateChainParams{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgUpdateChainParams_Type(t *testing.T) { + msg := types.MsgUpdateChainParams{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateChainParams, msg.Type()) +} + +func TestMsgUpdateChainParams_Route(t *testing.T) { + msg := types.MsgUpdateChainParams{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgUpdateChainParams_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateChainParams{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/observer/types/message_update_keygen_test.go b/x/observer/types/message_update_keygen_test.go index b7ad69741c..2e494d6dc4 100644 --- a/x/observer/types/message_update_keygen_test.go +++ b/x/observer/types/message_update_keygen_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -12,20 +13,22 @@ import ( func TestMsgUpdateKeygen_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgUpdateKeygen + msg *types.MsgUpdateKeygen err error }{ { name: "invalid address", - msg: types.MsgUpdateKeygen{ - Creator: "invalid_address", - }, + msg: types.NewMsgUpdateKeygen( + "invalid_address", + 1, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "valid address", - msg: types.MsgUpdateKeygen{ - Creator: sample.AccAddress(), - }, + msg: types.NewMsgUpdateKeygen( + sample.AccAddress(), + 1, + ), }, } for _, tt := range tests { @@ -39,3 +42,63 @@ func TestMsgUpdateKeygen_ValidateBasic(t *testing.T) { }) } } + +func TestMsgUpdateKeygen_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateKeygen + panics bool + }{ + { + name: "valid signer", + msg: types.MsgUpdateKeygen{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgUpdateKeygen{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgUpdateKeygen_Type(t *testing.T) { + msg := types.MsgUpdateKeygen{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateKeygen, msg.Type()) +} + +func TestMsgUpdateKeygen_Route(t *testing.T) { + msg := types.MsgUpdateKeygen{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgUpdateKeygen_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateKeygen{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/observer/types/message_update_observer_test.go b/x/observer/types/message_update_observer_test.go index 0cff6bd25b..041142c0a9 100644 --- a/x/observer/types/message_update_observer_test.go +++ b/x/observer/types/message_update_observer_test.go @@ -3,76 +3,77 @@ package types_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/observer/types" ) -func TestNewMsgUpdateObserver(t *testing.T) { +func TestNewMsgUpdateObserver_ValidateBasic(t *testing.T) { tests := []struct { name string - msg types.MsgUpdateObserver + msg *types.MsgUpdateObserver err error }{ { name: "invalid creator", - msg: types.MsgUpdateObserver{ - Creator: "invalid_address", - OldObserverAddress: sample.AccAddress(), - NewObserverAddress: sample.AccAddress(), - UpdateReason: types.ObserverUpdateReason_AdminUpdate, - }, + msg: types.NewMsgUpdateObserver( + "invalid_address", + sample.AccAddress(), + sample.AccAddress(), + types.ObserverUpdateReason_AdminUpdate, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid old observer address", - msg: types.MsgUpdateObserver{ - Creator: sample.AccAddress(), - OldObserverAddress: "invalid_address", - NewObserverAddress: sample.AccAddress(), - UpdateReason: types.ObserverUpdateReason_AdminUpdate, - }, + msg: types.NewMsgUpdateObserver( + sample.AccAddress(), + "invalid_address", + sample.AccAddress(), + types.ObserverUpdateReason_AdminUpdate, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "invalid new observer address", - msg: types.MsgUpdateObserver{ - Creator: sample.AccAddress(), - OldObserverAddress: sample.AccAddress(), - NewObserverAddress: "invalid_address", - UpdateReason: types.ObserverUpdateReason_AdminUpdate, - }, + msg: types.NewMsgUpdateObserver( + sample.AccAddress(), + sample.AccAddress(), + "invalid_address", + types.ObserverUpdateReason_AdminUpdate, + ), err: sdkerrors.ErrInvalidAddress, }, { name: "old observer address is not creator", - msg: types.MsgUpdateObserver{ - Creator: sample.AccAddress(), - OldObserverAddress: sample.AccAddress(), - NewObserverAddress: sample.AccAddress(), - UpdateReason: types.ObserverUpdateReason_Tombstoned, - }, + msg: types.NewMsgUpdateObserver( + sample.AccAddress(), + sample.AccAddress(), + sample.AccAddress(), + types.ObserverUpdateReason_Tombstoned, + ), err: types.ErrUpdateObserver, }, { name: "invalid Update Reason", - msg: types.MsgUpdateObserver{ - Creator: sample.AccAddress(), - OldObserverAddress: sample.AccAddress(), - NewObserverAddress: sample.AccAddress(), - UpdateReason: types.ObserverUpdateReason(100), - }, + msg: types.NewMsgUpdateObserver( + sample.AccAddress(), + sample.AccAddress(), + sample.AccAddress(), + types.ObserverUpdateReason(100), + ), err: types.ErrUpdateObserver, }, { name: "valid message", - msg: types.MsgUpdateObserver{ - Creator: sample.AccAddress(), - OldObserverAddress: sample.AccAddress(), - NewObserverAddress: sample.AccAddress(), - UpdateReason: types.ObserverUpdateReason_AdminUpdate, - }, + msg: types.NewMsgUpdateObserver( + sample.AccAddress(), + sample.AccAddress(), + sample.AccAddress(), + types.ObserverUpdateReason_AdminUpdate, + ), }, } for _, tt := range tests { @@ -86,3 +87,63 @@ func TestNewMsgUpdateObserver(t *testing.T) { }) } } + +func TestNewMsgUpdateObserver_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateObserver + panics bool + }{ + { + name: "valid signer", + msg: types.MsgUpdateObserver{ + Creator: signer, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgUpdateObserver{ + Creator: "invalid", + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestNewMsgUpdateObserver_Type(t *testing.T) { + msg := types.MsgUpdateObserver{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.TypeMsgUpdateObserver, msg.Type()) +} + +func TestNewMsgUpdateObserver_Route(t *testing.T) { + msg := types.MsgUpdateObserver{ + Creator: sample.AccAddress(), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestNewMsgUpdateObserver_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateObserver{ + Creator: sample.AccAddress(), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/zetaclient/app_context/app_context_test.go b/zetaclient/app_context/app_context_test.go new file mode 100644 index 0000000000..9669dfdedb --- /dev/null +++ b/zetaclient/app_context/app_context_test.go @@ -0,0 +1 @@ +package appcontext_test diff --git a/zetaclient/authz/authz_signer_test.go b/zetaclient/authz/authz_signer_test.go new file mode 100644 index 0000000000..00a25722c4 --- /dev/null +++ b/zetaclient/authz/authz_signer_test.go @@ -0,0 +1 @@ +package authz_test diff --git a/zetaclient/bitcoin/bitcoin_client.go b/zetaclient/bitcoin/bitcoin_client.go index b9c42049c7..034287a7d7 100644 --- a/zetaclient/bitcoin/bitcoin_client.go +++ b/zetaclient/bitcoin/bitcoin_client.go @@ -292,8 +292,7 @@ func (ob *BTCChainClient) SetLastBlockHeightScanned(height int64) { panic("lastBlockScanned is negative") } atomic.StoreInt64(&ob.lastBlockScanned, height) - // #nosec G701 checked as positive - ob.ts.SetLastScannedBlockNumber((ob.chain.ChainId), uint64(height)) + metrics.LastScannedBlockNumber.WithLabelValues(ob.chain.ChainName.String()).Set(float64(height)) } func (ob *BTCChainClient) GetLastBlockHeightScanned() int64 { @@ -888,7 +887,7 @@ func (ob *BTCChainClient) FetchUTXOS() error { } ob.Mu.Lock() - ob.ts.SetNumberOfUTXOs(len(utxosFiltered)) + metrics.NumberOfUTXO.Set(float64(len(utxosFiltered))) ob.utxos = utxosFiltered ob.Mu.Unlock() return nil diff --git a/zetaclient/bitcoin/bitcoin_client_test.go b/zetaclient/bitcoin/bitcoin_client_test.go index f92f3a01fd..68cb840e0f 100644 --- a/zetaclient/bitcoin/bitcoin_client_test.go +++ b/zetaclient/bitcoin/bitcoin_client_test.go @@ -24,7 +24,7 @@ import ( func MockBTCClientMainnet() *BTCChainClient { return &BTCChainClient{ chain: common.BtcMainnetChain(), - zetaClient: stub.NewZetaCoreBridge(), + zetaClient: stub.NewMockZetaCoreBridge(), Tss: stub.NewTSSMainnet(), } } diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index 200066f109..69e4e8da2c 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -2,13 +2,10 @@ package config import ( "encoding/json" - "fmt" "strings" "sync" - ethcommon "github.com/ethereum/go-ethereum/common" "github.com/zeta-chain/zetacore/common" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // KeyringBackend is the type of keyring backend to use for the hotkey @@ -138,63 +135,3 @@ func (c *Config) GetKeyringBackend() KeyringBackend { defer c.cfgLock.RUnlock() return c.KeyringBackend } - -// TODO: remove this duplicated function https://github.com/zeta-chain/node/issues/1838 -// ValidateChainParams performs some basic checks on core params -func ValidateChainParams(chainParams *observertypes.ChainParams) error { - if chainParams == nil { - return fmt.Errorf("invalid chain params: nil") - } - chain := common.GetChainFromChainID(chainParams.ChainId) - if chain == nil { - return fmt.Errorf("invalid chain params: chain %d not supported", chainParams.ChainId) - } - if chainParams.ConfirmationCount < 1 { - return fmt.Errorf("invalid chain params: ConfirmationCount %d", chainParams.ConfirmationCount) - } - // zeta chain skips the rest of the checks for now - if chain.IsZetaChain() { - return nil - } - - // check tickers - if chainParams.GasPriceTicker < 1 { - return fmt.Errorf("invalid chain params: GasPriceTicker %d", chainParams.GasPriceTicker) - } - if chainParams.InTxTicker < 1 { - return fmt.Errorf("invalid chain params: InTxTicker %d", chainParams.InTxTicker) - } - if chainParams.OutTxTicker < 1 { - return fmt.Errorf("invalid chain params: OutTxTicker %d", chainParams.OutTxTicker) - } - if chainParams.OutboundTxScheduleInterval < 1 { - return fmt.Errorf("invalid chain params: OutboundTxScheduleInterval %d", chainParams.OutboundTxScheduleInterval) - } - if chainParams.OutboundTxScheduleLookahead < 1 { - return fmt.Errorf("invalid chain params: OutboundTxScheduleLookahead %d", chainParams.OutboundTxScheduleLookahead) - } - - // chain type specific checks - if common.IsBitcoinChain(chainParams.ChainId) && chainParams.WatchUtxoTicker < 1 { - return fmt.Errorf("invalid chain params: watchUtxo ticker %d", chainParams.WatchUtxoTicker) - } - if common.IsEVMChain(chainParams.ChainId) { - if !validCoreContractAddress(chainParams.ZetaTokenContractAddress) { - return fmt.Errorf("invalid chain params: zeta token contract address %s", chainParams.ZetaTokenContractAddress) - } - if !validCoreContractAddress(chainParams.ConnectorContractAddress) { - return fmt.Errorf("invalid chain params: connector contract address %s", chainParams.ConnectorContractAddress) - } - if !validCoreContractAddress(chainParams.Erc20CustodyContractAddress) { - return fmt.Errorf("invalid chain params: erc20 custody contract address %s", chainParams.Erc20CustodyContractAddress) - } - } - return nil -} - -func validCoreContractAddress(address string) bool { - if !strings.HasPrefix(address, "0x") { - return false - } - return ethcommon.IsHexAddress(address) -} diff --git a/zetaclient/config/types_test.go b/zetaclient/config/types_test.go new file mode 100644 index 0000000000..d7b82b3200 --- /dev/null +++ b/zetaclient/config/types_test.go @@ -0,0 +1 @@ +package config_test diff --git a/zetaclient/evm/evm_client.go b/zetaclient/evm/evm_client.go index 6ba3581ac5..443510f545 100644 --- a/zetaclient/evm/evm_client.go +++ b/zetaclient/evm/evm_client.go @@ -69,9 +69,9 @@ type Log struct { type ChainClient struct { chain common.Chain evmClient interfaces.EVMRPCClient + evmJSONRPC interfaces.EVMJSONRPCClient zetaClient interfaces.ZetaCoreBridger Tss interfaces.TSSSigner - evmJSONRPC *ethrpc.EthRPC lastBlockScanned uint64 lastBlock uint64 BlockTimeExternalChain uint64 // block time in seconds @@ -81,7 +81,6 @@ type ChainClient struct { outTxPendingTransactions map[string]*ethtypes.Transaction outTXConfirmedReceipts map[string]*ethtypes.Receipt outTXConfirmedTransactions map[string]*ethtypes.Transaction - OutTxChan chan OutTx // send to this channel if you want something back! stop chan struct{} logger Log coreContext *corecontext.ZetaCoreContext @@ -128,7 +127,6 @@ func NewEVMChainClient( ob.outTxPendingTransactions = make(map[string]*ethtypes.Transaction) ob.outTXConfirmedReceipts = make(map[string]*ethtypes.Receipt) ob.outTXConfirmedTransactions = make(map[string]*ethtypes.Transaction) - ob.OutTxChan = make(chan OutTx, 100) ob.logger.ChainLogger.Info().Msgf("Chain %s endpoint %s", ob.chain.ChainName.String(), evmCfg.Endpoint) client, err := ethclient.Dial(evmCfg.Endpoint) @@ -176,13 +174,13 @@ func (ob *ChainClient) WithLogger(logger zerolog.Logger) { } } -func (ob *ChainClient) WithEvmClient(client *ethclient.Client) { +func (ob *ChainClient) WithEvmClient(client interfaces.EVMRPCClient) { ob.Mu.Lock() defer ob.Mu.Unlock() ob.evmClient = client } -func (ob *ChainClient) WithEvmJSONRPC(client *ethrpc.EthRPC) { +func (ob *ChainClient) WithEvmJSONRPC(client interfaces.EVMJSONRPCClient) { ob.Mu.Lock() defer ob.Mu.Unlock() ob.evmJSONRPC = client @@ -807,7 +805,7 @@ func (ob *ChainClient) CheckTxInclusion(tx *ethtypes.Transaction, receipt *ethty // SetLastBlockHeightScanned set last block height scanned (not necessarily caught up with external block; could be slow/paused) func (ob *ChainClient) SetLastBlockHeightScanned(height uint64) { atomic.StoreUint64(&ob.lastBlockScanned, height) - ob.ts.SetLastScannedBlockNumber(ob.chain.ChainId, height) + metrics.LastScannedBlockNumber.WithLabelValues(ob.chain.ChainName.String()).Set(float64(height)) } // GetLastBlockHeightScanned get last block height scanned (not necessarily caught up with external block; could be slow/paused) @@ -946,13 +944,13 @@ func (ob *ChainClient) observeInTX(sampledLogger zerolog.Logger) error { startBlock, toBlock := ob.calcBlockRangeToScan(confirmedBlockNum, lastScanned, config.MaxBlocksPerPeriod) // task 1: query evm chain for zeta sent logs (read at most 100 blocks in one go) - lastScannedZetaSent := ob.observeZetaSent(startBlock, toBlock) + lastScannedZetaSent := ob.ObserveZetaSent(startBlock, toBlock) // task 2: query evm chain for deposited logs (read at most 100 blocks in one go) - lastScannedDeposited := ob.observeERC20Deposited(startBlock, toBlock) + lastScannedDeposited := ob.ObserveERC20Deposited(startBlock, toBlock) // task 3: query the incoming tx to TSS address (read at most 100 blocks in one go) - lastScannedTssRecvd := ob.observerTSSReceive(startBlock, toBlock, flags) + lastScannedTssRecvd := ob.ObserverTSSReceive(startBlock, toBlock, flags) // note: using lowest height for all 3 events is not perfect, but it's simple and good enough lastScannedLowest := lastScannedZetaSent @@ -975,13 +973,13 @@ func (ob *ChainClient) observeInTX(sampledLogger zerolog.Logger) error { return nil } -// observeZetaSent queries the ZetaSent event from the connector contract and posts to zetabridge +// ObserveZetaSent queries the ZetaSent event from the connector contract and posts to zetabridge // returns the last block successfully scanned -func (ob *ChainClient) observeZetaSent(startBlock, toBlock uint64) uint64 { +func (ob *ChainClient) ObserveZetaSent(startBlock, toBlock uint64) uint64 { // filter ZetaSent logs addrConnector, connector, err := ob.GetConnectorContract() if err != nil { - ob.logger.ChainLogger.Warn().Err(err).Msgf("observeZetaSent: GetConnectorContract error:") + ob.logger.ChainLogger.Warn().Err(err).Msgf("ObserveZetaSent: GetConnectorContract error:") return startBlock - 1 // lastScanned } iter, err := connector.FilterZetaSent(&bind.FilterOpts{ @@ -991,7 +989,7 @@ func (ob *ChainClient) observeZetaSent(startBlock, toBlock uint64) uint64 { }, []ethcommon.Address{}, []*big.Int{}) if err != nil { ob.logger.ChainLogger.Warn().Err(err).Msgf( - "observeZetaSent: FilterZetaSent error from block %d to %d for chain %d", startBlock, toBlock, ob.chain.ChainId) + "ObserveZetaSent: FilterZetaSent error from block %d to %d for chain %d", startBlock, toBlock, ob.chain.ChainId) return startBlock - 1 // lastScanned } @@ -1004,7 +1002,7 @@ func (ob *ChainClient) observeZetaSent(startBlock, toBlock uint64) uint64 { events = append(events, iter.Event) continue } - ob.logger.ExternalChainWatcher.Warn().Err(err).Msgf("observeZetaSent: invalid ZetaSent event in tx %s on chain %d at height %d", + ob.logger.ExternalChainWatcher.Warn().Err(err).Msgf("ObserveZetaSent: invalid ZetaSent event in tx %s on chain %d at height %d", iter.Event.Raw.TxHash.Hex(), ob.chain.ChainId, iter.Event.Raw.BlockNumber) } sort.SliceStable(events, func(i, j int) bool { @@ -1030,7 +1028,7 @@ func (ob *ChainClient) observeZetaSent(startBlock, toBlock uint64) uint64 { } // guard against multiple events in the same tx if guard[event.Raw.TxHash.Hex()] { - ob.logger.ExternalChainWatcher.Warn().Msgf("observeZetaSent: multiple remote call events detected in tx %s", event.Raw.TxHash) + ob.logger.ExternalChainWatcher.Warn().Msgf("ObserveZetaSent: multiple remote call events detected in tx %s", event.Raw.TxHash) continue } guard[event.Raw.TxHash.Hex()] = true @@ -1047,13 +1045,13 @@ func (ob *ChainClient) observeZetaSent(startBlock, toBlock uint64) uint64 { return toBlock } -// observeERC20Deposited queries the ERC20CustodyDeposited event from the ERC20Custody contract and posts to zetabridge +// ObserveERC20Deposited queries the ERC20CustodyDeposited event from the ERC20Custody contract and posts to zetabridge // returns the last block successfully scanned -func (ob *ChainClient) observeERC20Deposited(startBlock, toBlock uint64) uint64 { +func (ob *ChainClient) ObserveERC20Deposited(startBlock, toBlock uint64) uint64 { // filter ERC20CustodyDeposited logs addrCustody, erc20custodyContract, err := ob.GetERC20CustodyContract() if err != nil { - ob.logger.ExternalChainWatcher.Warn().Err(err).Msgf("observeERC20Deposited: GetERC20CustodyContract error:") + ob.logger.ExternalChainWatcher.Warn().Err(err).Msgf("ObserveERC20Deposited: GetERC20CustodyContract error:") return startBlock - 1 // lastScanned } iter, err := erc20custodyContract.FilterDeposited(&bind.FilterOpts{ @@ -1063,7 +1061,7 @@ func (ob *ChainClient) observeERC20Deposited(startBlock, toBlock uint64) uint64 }, []ethcommon.Address{}) if err != nil { ob.logger.ExternalChainWatcher.Warn().Err(err).Msgf( - "observeERC20Deposited: FilterDeposited error from block %d to %d for chain %d", startBlock, toBlock, ob.chain.ChainId) + "ObserveERC20Deposited: FilterDeposited error from block %d to %d for chain %d", startBlock, toBlock, ob.chain.ChainId) return startBlock - 1 // lastScanned } @@ -1076,7 +1074,7 @@ func (ob *ChainClient) observeERC20Deposited(startBlock, toBlock uint64) uint64 events = append(events, iter.Event) continue } - ob.logger.ExternalChainWatcher.Warn().Err(err).Msgf("observeERC20Deposited: invalid Deposited event in tx %s on chain %d at height %d", + ob.logger.ExternalChainWatcher.Warn().Err(err).Msgf("ObserveERC20Deposited: invalid Deposited event in tx %s on chain %d at height %d", iter.Event.Raw.TxHash.Hex(), ob.chain.ChainId, iter.Event.Raw.BlockNumber) } sort.SliceStable(events, func(i, j int) bool { @@ -1103,14 +1101,14 @@ func (ob *ChainClient) observeERC20Deposited(startBlock, toBlock uint64) uint64 tx, _, err := ob.TransactionByHash(event.Raw.TxHash.Hex()) if err != nil { ob.logger.ExternalChainWatcher.Error().Err(err).Msgf( - "observeERC20Deposited: error getting transaction for intx %s chain %d", event.Raw.TxHash, ob.chain.ChainId) + "ObserveERC20Deposited: error getting transaction for intx %s chain %d", event.Raw.TxHash, ob.chain.ChainId) return beingScanned - 1 // we have to re-scan from this block next time } sender := ethcommon.HexToAddress(tx.From) // guard against multiple events in the same tx if guard[event.Raw.TxHash.Hex()] { - ob.logger.ExternalChainWatcher.Warn().Msgf("observeERC20Deposited: multiple remote call events detected in tx %s", event.Raw.TxHash) + ob.logger.ExternalChainWatcher.Warn().Msgf("ObserveERC20Deposited: multiple remote call events detected in tx %s", event.Raw.TxHash) continue } guard[event.Raw.TxHash.Hex()] = true @@ -1127,9 +1125,9 @@ func (ob *ChainClient) observeERC20Deposited(startBlock, toBlock uint64) uint64 return toBlock } -// observerTSSReceive queries the incoming gas asset to TSS address and posts to zetabridge +// ObserverTSSReceive queries the incoming gas asset to TSS address and posts to zetabridge // returns the last block successfully scanned -func (ob *ChainClient) observerTSSReceive(startBlock, toBlock uint64, flags observertypes.CrosschainFlags) uint64 { +func (ob *ChainClient) ObserverTSSReceive(startBlock, toBlock uint64, flags observertypes.CrosschainFlags) uint64 { if !ob.GetChainParams().IsSupported { return startBlock - 1 // lastScanned } @@ -1147,27 +1145,12 @@ func (ob *ChainClient) observerTSSReceive(startBlock, toBlock uint64, flags obse } } - // TODO: we can track the total number of 'getBlockByNumber' RPC calls made - block, err := ob.GetBlockByNumberCached(bn) + // observe TSS received gas token in block 'bn' + err := ob.ObserveTSSReceiveInBlock(bn) if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msgf("observerTSSReceive: error getting block %d for chain %d", bn, ob.chain.ChainId) + ob.logger.ExternalChainWatcher.Error().Err(err).Msgf("ObserverTSSReceive: error observing TSS received token in block %d for chain %d", bn, ob.chain.ChainId) return bn - 1 // we have to re-scan from this block next time } - for i := range block.Transactions { - tx := block.Transactions[i] - if ethcommon.HexToAddress(tx.To) == ob.Tss.EVMAddress() { - receipt, err := ob.evmClient.TransactionReceipt(context.Background(), ethcommon.HexToHash(tx.Hash)) - if err != nil { - ob.logger.ExternalChainWatcher.Err(err).Msgf("observerTSSReceive: error getting receipt for intx %s chain %d", tx.Hash, ob.chain.ChainId) - return bn - 1 // we have to re-scan from this block next time - } - _, err = ob.CheckAndVoteInboundTokenGas(&tx, receipt, true) - if err != nil { - ob.logger.ExternalChainWatcher.Err(err).Msgf("observerTSSReceive: error checking and voting inbound gas asset for intx %s chain %d", tx.Hash, ob.chain.ChainId) - return bn - 1 // we have to re-scan this block next time - } - } - } } // successful processed all gas asset deposits in [startBlock, toBlock] return toBlock diff --git a/zetaclient/evm/evm_signer.go b/zetaclient/evm/evm_signer.go index 9819b280fb..474864c246 100644 --- a/zetaclient/evm/evm_signer.go +++ b/zetaclient/evm/evm_signer.go @@ -616,7 +616,7 @@ func getEVMRPC(endpoint string) (interfaces.EVMRPCClient, *big.Int, ethtypes.Sig if endpoint == stub.EVMRPCEnabled { chainID := big.NewInt(common.BscMainnetChain().ChainId) ethSigner := ethtypes.NewEIP155Signer(chainID) - client := stub.EvmClient{} + client := &stub.MockEvmClient{} return client, chainID, ethSigner, nil } diff --git a/zetaclient/evm/evm_signer_test.go b/zetaclient/evm/evm_signer_test.go index 711db544d1..3bbee44279 100644 --- a/zetaclient/evm/evm_signer_test.go +++ b/zetaclient/evm/evm_signer_test.go @@ -56,7 +56,7 @@ func getNewEvmChainClient() (*ChainClient, error) { coreCTX := corecontext.NewZetaCoreContext(cfg) appCTX := appcontext.NewAppContext(coreCTX, cfg) - return NewEVMChainClient(appCTX, stub.NewZetaCoreBridge(), tss, "", logger, evmcfg, ts) + return NewEVMChainClient(appCTX, stub.NewMockZetaCoreBridge(), tss, "", logger, evmcfg, ts) } func getNewOutTxProcessor() *outtxprocessor.Processor { @@ -85,7 +85,7 @@ func TestSigner_TryProcessOutTx(t *testing.T) { mockChainClient, err := getNewEvmChainClient() require.NoError(t, err) - evmSigner.TryProcessOutTx(cctx, processorManager, "123", mockChainClient, stub.NewZetaCoreBridge(), 123) + evmSigner.TryProcessOutTx(cctx, processorManager, "123", mockChainClient, stub.NewMockZetaCoreBridge(), 123) //Check if cctx was signed and broadcasted list := evmSigner.GetReportedTxList() @@ -280,7 +280,7 @@ func TestSigner_BroadcastOutTx(t *testing.T) { tx, err := evmSigner.SignERC20WithdrawTx(txData) require.NoError(t, err) - evmSigner.BroadcastOutTx(tx, cctx, zerolog.Logger{}, sdktypes.AccAddress{}, stub.NewZetaCoreBridge(), txData) + evmSigner.BroadcastOutTx(tx, cctx, zerolog.Logger{}, sdktypes.AccAddress{}, stub.NewMockZetaCoreBridge(), txData) //Check if cctx was signed and broadcasted list := evmSigner.GetReportedTxList() diff --git a/zetaclient/evm/inbounds.go b/zetaclient/evm/inbounds.go index 5cc08f8126..919ba6f9e2 100644 --- a/zetaclient/evm/inbounds.go +++ b/zetaclient/evm/inbounds.go @@ -354,3 +354,25 @@ func (ob *ChainClient) BuildInboundVoteMsgForTokenSentToTSS(tx *ethrpc.Transacti 0, // not a smart contract call ) } + +// ObserveTSSReceiveInBlock queries the incoming gas asset to TSS address in a single block and posts votes +func (ob *ChainClient) ObserveTSSReceiveInBlock(blockNumber uint64) error { + block, err := ob.GetBlockByNumberCached(blockNumber) + if err != nil { + return errors.Wrapf(err, "error getting block %d for chain %d", blockNumber, ob.chain.ChainId) + } + for i := range block.Transactions { + tx := block.Transactions[i] + if ethcommon.HexToAddress(tx.To) == ob.Tss.EVMAddress() { + receipt, err := ob.evmClient.TransactionReceipt(context.Background(), ethcommon.HexToHash(tx.Hash)) + if err != nil { + return errors.Wrapf(err, "error getting receipt for intx %s chain %d", tx.Hash, ob.chain.ChainId) + } + _, err = ob.CheckAndVoteInboundTokenGas(&tx, receipt, true) + if err != nil { + return errors.Wrapf(err, "error checking and voting inbound gas asset for intx %s chain %d", tx.Hash, ob.chain.ChainId) + } + } + } + return nil +} diff --git a/zetaclient/evm/inbounds_test.go b/zetaclient/evm/inbounds_test.go index 472cdc65ae..0663d71eeb 100644 --- a/zetaclient/evm/inbounds_test.go +++ b/zetaclient/evm/inbounds_test.go @@ -7,6 +7,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" + lru "github.com/hashicorp/golang-lru" "github.com/onrik/ethrpc" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" @@ -22,6 +23,9 @@ import ( // MockEVMClient creates a mock ChainClient with custom chain, TSS, params etc func MockEVMClient( chain common.Chain, + evmClient interfaces.EVMRPCClient, + evmJSONRPC interfaces.EVMJSONRPCClient, + zetClient interfaces.ZetaCoreBridger, tss interfaces.TSSSigner, lastBlock uint64, params observertypes.ChainParams) *evm.ChainClient { @@ -30,25 +34,36 @@ func MockEVMClient( Mu: &sync.Mutex{}, } client.WithChain(chain) - client.WithZetaClient(stub.NewZetaCoreBridge()) + client.WithEvmClient(evmClient) + client.WithEvmJSONRPC(evmJSONRPC) + if zetClient != nil { + client.WithZetaClient(zetClient) + } else { + client.WithZetaClient(stub.NewMockZetaCoreBridge()) + } client.SetLastBlockHeight(lastBlock) client.SetChainParams(params) + blockCache, _ := lru.New(1000) + client.WithBlockCache(blockCache) + return client } func TestEVM_CheckAndVoteInboundTokenZeta(t *testing.T) { // load archived ZetaSent intx, receipt and cctx // https://etherscan.io/tx/0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76 - chainID := int64(1) - intxHash := "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76" + chain := common.EthChain() confirmation := uint64(10) + chainID := chain.ChainId + chainParam := stub.MockChainParams(chain.ChainId, confirmation) + intxHash := "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76" t.Run("should pass for archived intx, receipt and cctx", func(t *testing.T) { tx, receipt, cctx := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Zeta) require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenZeta(tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundTxParams.InboundTxBallotIndex, ballot) @@ -58,7 +73,7 @@ func TestEVM_CheckAndVoteInboundTokenZeta(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) _, err := ob.CheckAndVoteInboundTokenZeta(tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) @@ -68,18 +83,18 @@ func TestEVM_CheckAndVoteInboundTokenZeta(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenZeta(tx, receipt, true) require.NoError(t, err) require.Equal(t, "", ballot) }) t.Run("should not act if emitter is not ZetaConnector", func(t *testing.T) { tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Zeta) - chainID = 56 // use BSC chain connector require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + chainID = 56 // use BSC chain connector + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) _, err := ob.CheckAndVoteInboundTokenZeta(tx, receipt, true) require.ErrorContains(t, err, "emitter address mismatch") }) @@ -88,16 +103,18 @@ func TestEVM_CheckAndVoteInboundTokenZeta(t *testing.T) { func TestEVM_CheckAndVoteInboundTokenERC20(t *testing.T) { // load archived ERC20 intx, receipt and cctx // https://etherscan.io/tx/0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da - chainID := int64(1) - intxHash := "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da" + chain := common.EthChain() confirmation := uint64(10) + chainID := chain.ChainId + chainParam := stub.MockChainParams(chain.ChainId, confirmation) + intxHash := "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da" t.Run("should pass for archived intx, receipt and cctx", func(t *testing.T) { tx, receipt, cctx := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_ERC20) require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenERC20(tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundTxParams.InboundTxBallotIndex, ballot) @@ -107,7 +124,7 @@ func TestEVM_CheckAndVoteInboundTokenERC20(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) _, err := ob.CheckAndVoteInboundTokenERC20(tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) @@ -117,18 +134,18 @@ func TestEVM_CheckAndVoteInboundTokenERC20(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenERC20(tx, receipt, true) require.NoError(t, err) require.Equal(t, "", ballot) }) t.Run("should not act if emitter is not ERC20 Custody", func(t *testing.T) { tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_ERC20) - chainID = 56 // use BSC chain ERC20 custody require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + chainID = 56 // use BSC chain ERC20 custody + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) _, err := ob.CheckAndVoteInboundTokenERC20(tx, receipt, true) require.ErrorContains(t, err, "emitter address mismatch") }) @@ -137,16 +154,18 @@ func TestEVM_CheckAndVoteInboundTokenERC20(t *testing.T) { func TestEVM_CheckAndVoteInboundTokenGas(t *testing.T) { // load archived Gas intx, receipt and cctx // https://etherscan.io/tx/0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532 - chainID := int64(1) - intxHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" + chain := common.EthChain() confirmation := uint64(10) + chainID := chain.ChainId + chainParam := stub.MockChainParams(chain.ChainId, confirmation) + intxHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" t.Run("should pass for archived intx, receipt and cctx", func(t *testing.T) { tx, receipt, cctx := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Gas) require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundTxParams.InboundTxBallotIndex, ballot) @@ -156,7 +175,7 @@ func TestEVM_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) _, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) @@ -166,7 +185,7 @@ func TestEVM_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) require.ErrorContains(t, err, "not TSS address") require.Equal(t, "", ballot) @@ -177,7 +196,7 @@ func TestEVM_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) require.ErrorContains(t, err, "not a successful tx") require.Equal(t, "", ballot) @@ -188,7 +207,7 @@ func TestEVM_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ob := MockEVMClient(chain, nil, nil, nil, stub.NewTSSMainnet(), lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) require.NoError(t, err) require.Equal(t, "", ballot) @@ -199,12 +218,13 @@ func TestEVM_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { // load archived ZetaSent receipt // https://etherscan.io/tx/0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76 chainID := int64(1) + chain := common.EthChain() intxHash := "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76" receipt := testutils.LoadEVMIntxReceipt(t, chainID, intxHash, common.CoinType_Zeta) cctx := testutils.LoadEVMIntxCctx(t, chainID, intxHash, common.CoinType_Zeta) // parse ZetaSent event - ob := MockEVMClient(common.EthChain(), nil, 1, stub.MockChainParams(1, 1)) + ob := MockEVMClient(chain, nil, nil, nil, nil, 1, stub.MockChainParams(1, 1)) connector := stub.MockConnectorNonEth(chainID) event := testutils.ParseReceiptZetaSent(receipt, connector) @@ -244,13 +264,14 @@ func TestEVM_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { func TestEVM_BuildInboundVoteMsgForDepositedEvent(t *testing.T) { // load archived Deposited receipt // https://etherscan.io/tx/0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da - chainID := int64(1) + chain := common.EthChain() + chainID := chain.ChainId intxHash := "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da" tx, receipt := testutils.LoadEVMIntxNReceipt(t, chainID, intxHash, common.CoinType_ERC20) cctx := testutils.LoadEVMIntxCctx(t, chainID, intxHash, common.CoinType_ERC20) // parse Deposited event - ob := MockEVMClient(common.EthChain(), nil, 1, stub.MockChainParams(1, 1)) + ob := MockEVMClient(chain, nil, nil, nil, nil, 1, stub.MockChainParams(1, 1)) custody := stub.MockERC20Custody(chainID) event := testutils.ParseReceiptERC20Deposited(receipt, custody) sender := ethcommon.HexToAddress(tx.From) @@ -288,7 +309,8 @@ func TestEVM_BuildInboundVoteMsgForDepositedEvent(t *testing.T) { func TestEVM_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) { // load archived gas token transfer to TSS // https://etherscan.io/tx/0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532 - chainID := int64(1) + chain := common.EthChain() + chainID := chain.ChainId intxHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" tx, receipt := testutils.LoadEVMIntxNReceipt(t, chainID, intxHash, common.CoinType_Gas) require.NoError(t, evm.ValidateEvmTransaction(tx)) @@ -301,7 +323,7 @@ func TestEVM_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(txDonation)) // create test compliance config - ob := MockEVMClient(common.EthChain(), nil, 1, stub.MockChainParams(1, 1)) + ob := MockEVMClient(chain, nil, nil, nil, nil, 1, stub.MockChainParams(1, 1)) cfg := config.Config{ ComplianceConfig: config.ComplianceConfig{}, } @@ -333,3 +355,62 @@ func TestEVM_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) { require.Nil(t, msg) }) } + +func TestEVM_ObserveTSSReceiveInBlock(t *testing.T) { + // https://etherscan.io/tx/0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532 + chain := common.EthChain() + chainID := chain.ChainId + confirmation := uint64(1) + chainParam := stub.MockChainParams(chain.ChainId, confirmation) + intxHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" + + // load archived tx and receipt + tx, receipt := testutils.LoadEVMIntxNReceipt(t, chainID, intxHash, common.CoinType_Gas) + require.NoError(t, evm.ValidateEvmTransaction(tx)) + + // load archived evm block + // https://etherscan.io/block/19363323 + blockNumber := receipt.BlockNumber.Uint64() + block := testutils.LoadEVMBlock(t, chainID, blockNumber, true) + + // create mock client + evmClient := stub.NewMockEvmClient() + evmJSONRPC := stub.NewMockJSONRPCClient() + zetaClient := stub.NewMockZetaCoreBridge() + tss := stub.NewTSSMainnet() + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + t.Run("should observe TSS receive in block", func(t *testing.T) { + ob := MockEVMClient(chain, evmClient, evmJSONRPC, zetaClient, tss, lastBlock, chainParam) + + // feed archived block and receipt + evmJSONRPC.WithBlock(block) + evmClient.WithReceipt(receipt) + err := ob.ObserveTSSReceiveInBlock(blockNumber) + require.NoError(t, err) + }) + t.Run("should not observe on error getting block", func(t *testing.T) { + ob := MockEVMClient(chain, evmClient, evmJSONRPC, zetaClient, tss, lastBlock, chainParam) + err := ob.ObserveTSSReceiveInBlock(blockNumber) + // error getting block is expected because the mock JSONRPC contains no block + require.ErrorContains(t, err, "error getting block") + }) + t.Run("should not observe on error getting receipt", func(t *testing.T) { + ob := MockEVMClient(chain, evmClient, evmJSONRPC, zetaClient, tss, lastBlock, chainParam) + evmJSONRPC.WithBlock(block) + err := ob.ObserveTSSReceiveInBlock(blockNumber) + // error getting block is expected because the mock evmClient contains no receipt + require.ErrorContains(t, err, "error getting receipt") + }) + t.Run("should not observe on error posting vote", func(t *testing.T) { + ob := MockEVMClient(chain, evmClient, evmJSONRPC, zetaClient, tss, lastBlock, chainParam) + + // feed archived block and pause zeta bridge + evmJSONRPC.WithBlock(block) + evmClient.WithReceipt(receipt) + zetaClient.Pause() + err := ob.ObserveTSSReceiveInBlock(blockNumber) + // error posting vote is expected because the mock zetaClient is paused + require.ErrorContains(t, err, "error checking and voting") + }) +} diff --git a/zetaclient/interfaces/interfaces.go b/zetaclient/interfaces/interfaces.go index 422e36cc59..5d9a40fcc9 100644 --- a/zetaclient/interfaces/interfaces.go +++ b/zetaclient/interfaces/interfaces.go @@ -4,6 +4,7 @@ import ( "context" "math/big" + "github.com/onrik/ethrpc" "github.com/zeta-chain/zetacore/zetaclient/keys" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" @@ -137,3 +138,9 @@ type EVMRPCClient interface { TransactionReceipt(ctx context.Context, txHash ethcommon.Hash) (*ethtypes.Receipt, error) TransactionSender(ctx context.Context, tx *ethtypes.Transaction, block ethcommon.Hash, index uint) (ethcommon.Address, error) } + +// EVMJSONRPCClient is the interface for EVM JSON RPC client +type EVMJSONRPCClient interface { + EthGetBlockByNumber(number int, withTransactions bool) (*ethrpc.Block, error) + EthGetTransactionByHash(hash string) (*ethrpc.Transaction, error) +} diff --git a/zetaclient/metrics/metrics.go b/zetaclient/metrics/metrics.go index a99db9f760..3325d21156 100644 --- a/zetaclient/metrics/metrics.go +++ b/zetaclient/metrics/metrics.go @@ -16,36 +16,68 @@ type Metrics struct { s *http.Server } +const ZetaClientNamespace = "zetaclient" + var ( PendingTxsPerChain = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: "zetaclient", + Namespace: ZetaClientNamespace, Name: "pending_txs_total", Help: "Number of pending transactions per chain", }, []string{"chain"}) GetFilterLogsPerChain = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: "zetaclient", + Namespace: ZetaClientNamespace, Name: "rpc_getFilterLogs_count", Help: "Count of getLogs per chain", }, []string{"chain"}) GetBlockByNumberPerChain = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: "zetaclient", + Namespace: ZetaClientNamespace, Name: "rpc_getBlockByNumber_count", Help: "Count of getLogs per chain", }, []string{"chain"}) TssNodeBlamePerPubKey = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: "zetaclient", + Namespace: ZetaClientNamespace, Name: "tss_node_blame_count", Help: "Tss node blame counter per pubkey", }, []string{"pubkey"}) HotKeyBurnRate = promauto.NewGauge(prometheus.GaugeOpts{ - Namespace: "zetaclient", + Namespace: ZetaClientNamespace, Name: "hotkey_burn_rate", Help: "Fee burn rate of the hotkey", }) + + NumberOfUTXO = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: ZetaClientNamespace, + Name: "utxo_number", + Help: "Number of UTXOs", + }) + + LastScannedBlockNumber = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ZetaClientNamespace, + Name: "last_scanned_block_number", + Help: "Last scanned block number per chain", + }, []string{"chain"}) + + LastCoreBlockNumber = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: ZetaClientNamespace, + Name: "last_core_block_number", + Help: "Last core block number", + }) + + Info = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ZetaClientNamespace, + Name: "info", + Help: "Information about Zetaclient environment", + }, []string{"version"}) + + LastStartTime = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: ZetaClientNamespace, + Name: "last_start_timestamp_seconds", + Help: "Start time in Unix time", + }) ) func NewMetrics() (*Metrics, error) { diff --git a/zetaclient/metrics/telemetry.go b/zetaclient/metrics/telemetry.go index 07ca32ae35..209bf61fed 100644 --- a/zetaclient/metrics/telemetry.go +++ b/zetaclient/metrics/telemetry.go @@ -2,7 +2,6 @@ package metrics import ( "context" - "encoding/json" "errors" "fmt" "net/http" @@ -12,31 +11,23 @@ import ( "github.com/gorilla/mux" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/zeta-chain/zetacore/common" - "github.com/zeta-chain/zetacore/zetaclient/types" ) // TelemetryServer provide http endpoint for Tss server type TelemetryServer struct { - logger zerolog.Logger - s *http.Server - p2pid string - lastScannedBlockNumber map[int64]uint64 // chainid => block number - lastCoreBlockNumber int64 - mu sync.Mutex - lastStartTimestamp time.Time - status types.Status - ipAddress string - HotKeyBurnRate *BurnRate + logger zerolog.Logger + s *http.Server + p2pid string + mu sync.Mutex + ipAddress string + HotKeyBurnRate *BurnRate } // NewTelemetryServer should only listen to the loopback func NewTelemetryServer() *TelemetryServer { hs := &TelemetryServer{ - logger: log.With().Str("module", "http").Logger(), - lastScannedBlockNumber: make(map[int64]uint64), - lastStartTimestamp: time.Now(), - HotKeyBurnRate: NewBurnRate(100), + logger: log.With().Str("module", "http").Logger(), + HotKeyBurnRate: NewBurnRate(100), } s := &http.Server{ Addr: ":8123", @@ -48,12 +39,6 @@ func NewTelemetryServer() *TelemetryServer { return hs } -func (t *TelemetryServer) GetLastStartTimestamp() time.Time { - t.mu.Lock() - defer t.mu.Unlock() - return t.lastStartTimestamp -} - // setter/getter for p2pid func (t *TelemetryServer) SetP2PID(p2pid string) { t.mu.Lock() @@ -80,37 +65,6 @@ func (t *TelemetryServer) GetIPAddress() string { return t.ipAddress } -// setter for lastScanned block number -func (t *TelemetryServer) SetLastScannedBlockNumber(chainID int64, blockNumber uint64) { - t.mu.Lock() - t.lastScannedBlockNumber[chainID] = blockNumber - t.mu.Unlock() -} - -func (t *TelemetryServer) GetLastScannedBlockNumber(chainID int64) uint64 { - t.mu.Lock() - defer t.mu.Unlock() - return t.lastScannedBlockNumber[chainID] -} - -func (t *TelemetryServer) SetCoreBlockNumber(blockNumber int64) { - t.mu.Lock() - t.lastCoreBlockNumber = blockNumber - t.mu.Unlock() -} - -func (t *TelemetryServer) GetCoreBlockNumber() int64 { - t.mu.Lock() - defer t.mu.Unlock() - return t.lastCoreBlockNumber -} - -func (t *TelemetryServer) SetNumberOfUTXOs(numberOfUTXOs int) { - t.mu.Lock() - t.status.BTCNumberOfUTXOs = numberOfUTXOs - t.mu.Unlock() -} - func (t *TelemetryServer) AddFeeEntry(block int64, amount int64) { t.mu.Lock() err := t.HotKeyBurnRate.AddFee(amount, block) @@ -125,11 +79,6 @@ func (t *TelemetryServer) Handlers() http.Handler { router := mux.NewRouter() router.Handle("/ping", http.HandlerFunc(t.pingHandler)).Methods(http.MethodGet) router.Handle("/p2p", http.HandlerFunc(t.p2pHandler)).Methods(http.MethodGet) - router.Handle("/version", http.HandlerFunc(t.versionHandler)).Methods(http.MethodGet) - router.Handle("/lastscannedblock", http.HandlerFunc(t.lastScannedBlockHandler)).Methods(http.MethodGet) - router.Handle("/laststarttimestamp", http.HandlerFunc(t.lastStartTimestampHandler)).Methods(http.MethodGet) - router.Handle("/lastcoreblock", http.HandlerFunc(t.lastCoreBlockHandler)).Methods(http.MethodGet) - router.Handle("/status", http.HandlerFunc(t.statusHandler)).Methods(http.MethodGet) router.Handle("/ip", http.HandlerFunc(t.ipHandler)).Methods(http.MethodGet) router.Handle("/hotkeyburnrate", http.HandlerFunc(t.hotKeyFeeBurnRate)).Methods(http.MethodGet) @@ -198,54 +147,6 @@ func (t *TelemetryServer) ipHandler(w http.ResponseWriter, _ *http.Request) { fmt.Fprintf(w, "%s", t.ipAddress) } -func (t *TelemetryServer) lastScannedBlockHandler(w http.ResponseWriter, _ *http.Request) { - //w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/json") - - t.mu.Lock() - defer t.mu.Unlock() - // Convert map to JSON - jsonBytes, err := json.Marshal(t.lastScannedBlockNumber) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - _, err = w.Write(jsonBytes) - if err != nil { - t.logger.Error().Err(err).Msg("Failed to write response") - } -} - -func (t *TelemetryServer) lastCoreBlockHandler(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - t.mu.Lock() - defer t.mu.Unlock() - fmt.Fprintf(w, "%d", t.lastCoreBlockNumber) -} - -func (t *TelemetryServer) statusHandler(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - t.mu.Lock() - defer t.mu.Unlock() - s, err := json.MarshalIndent(t.status, "", "\t") - if err != nil { - t.logger.Error().Err(err).Msg("Failed to marshal status") - } - fmt.Fprintf(w, "%s", s) -} - -func (t *TelemetryServer) versionHandler(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "%s", common.Version) -} - -func (t *TelemetryServer) lastStartTimestampHandler(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - t.mu.Lock() - defer t.mu.Unlock() - fmt.Fprintf(w, "%s", t.lastStartTimestamp.Format(time.RFC3339)) -} - func (t *TelemetryServer) hotKeyFeeBurnRate(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) t.mu.Lock() diff --git a/zetaclient/outtxprocessor/out_tx_processor_manager_test.go b/zetaclient/outtxprocessor/out_tx_processor_manager_test.go new file mode 100644 index 0000000000..cc7ff3888c --- /dev/null +++ b/zetaclient/outtxprocessor/out_tx_processor_manager_test.go @@ -0,0 +1 @@ +package outtxprocessor_test diff --git a/zetaclient/testdata/evm/chain_1_block_ethrpc_trimmed_19330473.json b/zetaclient/testdata/evm/chain_1_block_ethrpc_trimmed_19330473.json new file mode 100644 index 0000000000..e90f23309d --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_block_ethrpc_trimmed_19330473.json @@ -0,0 +1,2049 @@ +{ + "Number": 19330473, + "Hash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "ParentHash": "0x267d191078b68fbd5be49e0274b6387943c892a4379bbfdedff9fcd45c0626a1", + "Nonce": "0x0000000000000000", + "Sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "LogsBloom": "0x05a54d18e50179d04c029178a3b01da3a2c34a8b5083fa792b2b04a0fc368432c5561425d383204ac138fba5654acb9c8209d0268d6274f01ea36423813eac648588157ac3cd9a7e6f01456f5ea8aaefc4491b11af4c59105c74db798864410c510c682c4e739369a9d157f620914ddd24991e755ab59706861188db541d821eaf0893ea2ced400a65713bec0fb01a761d09d4f10f908428461d154148f024688a87114a73a2bc529ecafcd49c2074bfeca41b2589f0498f87bba86629dd5361f7216c8345145249b246287701aad44931ea1e968758055ccbe5e23f125eff0c22d6ac09b214425a1c2c15acadf39eb0077d9c191178344b3b045b9239d8ac9a", + "TransactionsRoot": "0xd651daaca8c9a603fa2648d474233e8ddc912b9d831716f61f1faf0b4eddeac7", + "StateRoot": "0x29da2506610eee13a06d66c0fe881942da8e8b0413a0b8e19aa425efe6fd77df", + "Miner": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97", + "Difficulty": 0, + "TotalDifficulty": 58750003716598352816469, + "ExtraData": "0x546974616e2028746974616e6275696c6465722e78797a29", + "Size": 301417, + "GasLimit": 30000000, + "GasUsed": 18209718, + "Timestamp": 1709177027, + "Uncles": [], + "Transactions": [ + { + "Hash": "0x5bee6e0d1b2741badc5a490692a7e4ef118e3ad6736b206805ad6cfe69c18bdb", + "Nonce": 32, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 0, + "From": "0xe667c8621417c53ddea92375ab93d3f5efdeed18", + "To": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", + "Value": 1416723496930938000, + "Gas": 139195, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0x0c13995bfdfcff59aee550461de184ec3a3513d11a06318c816dde72dd15e402", + "Nonce": 865356, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 1, + "From": "0x91aae0aafd9d2d730111b395c6871f248d7bd728", + "To": "0x98c3d3183c4b8a650614ad179a1a98be0a8d6b8e", + "Value": 0, + "Gas": 300011, + "GasPrice": 47548888839, + "Input": "0x" + }, + { + "Hash": "0x341f44ef7a89db8bab64aceb26bf36160f24c123c9d89aaa648573c7389f2eb2", + "Nonce": 719917, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 2, + "From": "0x3527439923a63f8c13cf72b8fe80a77f6e572092", + "To": "0xa0425d71cb1d6fb80e65a5361a04096e0672de03", + "Value": 0, + "Gas": 8000000, + "GasPrice": 52548888839, + "Input": "0x" + }, + { + "Hash": "0x7ba81db5dd1235a979179a9db318262771ac566fe87b0649b0536987ac150657", + "Nonce": 1914, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 3, + "From": "0xd93d10900f53a4f445a88d62efff803edfe20980", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 0, + "Gas": 543760, + "GasPrice": 67558888839, + "Input": "0x" + }, + { + "Hash": "0x31ef5d0d7269b243280b7eb2263e2cdedbd3fa34c071759cf15e7c9447960710", + "Nonce": 77, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 4, + "From": "0xf1dfe5db0f757e6af9be2cc2e31725369e0088d5", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 0, + "Gas": 543760, + "GasPrice": 67558888839, + "Input": "0x" + }, + { + "Hash": "0x167eac47b20fe4d1c1aafd9d4d384c047a49a0cdb52b11ae595c1de6b57616a5", + "Nonce": 1287, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 5, + "From": "0x270f4ceead65439c717fdcab0b7d0be6aff90589", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 0, + "Gas": 543760, + "GasPrice": 67558888839, + "Input": "0x" + }, + { + "Hash": "0xeb5ad997da4b3ec54c0e32caf572f7fd177fc9491f898315bcd51698d3a8f95d", + "Nonce": 92, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 6, + "From": "0x8f640bd5a7e64743bb465fda991e8fdc0ceb4424", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 0, + "Gas": 514568, + "GasPrice": 63548888839, + "Input": "0x" + }, + { + "Hash": "0x7e02193f398e276599a5aaad7d4de81009db3cde92dafe5e4ad35e29cd153663", + "Nonce": 227, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 7, + "From": "0xba405e4c0a416881e866e3486538706dd741fcca", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 0, + "Gas": 514568, + "GasPrice": 63548888839, + "Input": "0x" + }, + { + "Hash": "0x2657838b6fe700fe08dd71761324e598106fcf8bc7929f6b3b92e433519c01c7", + "Nonce": 69120, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 8, + "From": "0x337a81113f934b8522459f0ca207eee7aa0f4545", + "To": "0xf8a16864d8de145a266a534174305f881ee2315e", + "Value": 0, + "Gas": 6000000, + "GasPrice": 50548888839, + "Input": "0x" + }, + { + "Hash": "0x252053df6a32c67a82d9298a65acd206ed3ef0be89b78c457bbb120d0de52186", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 9, + "From": "0xcad722685782b3d90470307ebbeca202d62c3b8a", + "To": "0xd523794c879d9ec028960a231f866758e405be34", + "Value": 980107655962837556, + "Gas": 248628, + "GasPrice": 60420315297, + "Input": "0x" + }, + { + "Hash": "0xa1a70772bf216b08883e2d5eedac69ddab262926d2a5dc51128d039eba802e77", + "Nonce": 48, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 10, + "From": "0x6c002142bd8ec3f20a7636e7973ca41888b42f7f", + "To": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", + "Value": 0, + "Gas": 194442, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0x5b9c8b8b86c5735e4f851471cc233a3dec798f56e9daca8dfc178619c9990913", + "Nonce": 8664, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 11, + "From": "0xee28beaa11e31d10581ee7cfdcb9e95c8d05c53c", + "To": "0xfbeedcfe378866dab6abbafd8b2986f5c1768737", + "Value": 19330473, + "Gas": 450000, + "GasPrice": 47548888839, + "Input": "0x" + }, + { + "Hash": "0x398e5ab7943bbdc985d4d0b40450f4f10688d140d23db996e54ad67fa2bbeef6", + "Nonce": 1776, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 12, + "From": "0x768f1621f176b2010647be124f1f252ca849d6a7", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 0, + "Gas": 587469, + "GasPrice": 57548888839, + "Input": "0x" + }, + { + "Hash": "0x9f7e0e7066c9cebb0523e698ddb26e063220f884b76d0f68fe07ff92fbaa1749", + "Nonce": 1091522, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 13, + "From": "0x6887246668a3b87f54deb3b94ba47a6f63f32985", + "To": "0xff00000000000000000000000000000000000010", + "Value": 0, + "Gas": 1695116, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x0f2a572995704d8d87419258f9d798c8ffd819550865412c21ab2a39fd070c79", + "Nonce": 76, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 14, + "From": "0x11ba386d08b622ca75f15d1ed6e716727aa27cdb", + "To": "0x6c42f24f2619d3de71a72a02a5fc2aa0cde667da", + "Value": 0, + "Gas": 679011, + "GasPrice": 51900000000, + "Input": "0x" + }, + { + "Hash": "0xa4ad716ce930d71f5c99c9fa0f26451f42cf93b16f1cb8e3509a56179bf20602", + "Nonce": 8381, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 15, + "From": "0x1e01eb96bafa7815d90b790329f1ceabed868181", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 0, + "Gas": 513906, + "GasPrice": 52548888839, + "Input": "0x" + }, + { + "Hash": "0xcdd852efd3dbf10190b567eded010bf3ac46555aac1b47aebee90ea828a132d4", + "Nonce": 2444, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 16, + "From": "0xc8d5cf84e1aa38ffa9e5e532fc97b2f6e1c4740c", + "To": "0x1111111254eeb25477b68fb85ed929f73a960582", + "Value": 0, + "Gas": 166301, + "GasPrice": 54324000000, + "Input": "0x" + }, + { + "Hash": "0x23d5604ef20576c43e63d3e1b07e7dbcf81b5629bd968498b5f6bd6f04bc6aee", + "Nonce": 36197, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 17, + "From": "0x051acf8d69a18d6a6e4dd063b39501e69704ac50", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 250000, + "GasPrice": 62000000000, + "Input": "0x" + }, + { + "Hash": "0x2c80a8a3bb3e48012a347c621bfb5edb4ab8f3b3f8dfa294c220b79d3907bca4", + "Nonce": 5951, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 18, + "From": "0x70ad6a24abd247fce9b87b2e85972049cc0170ad", + "To": "0xb62bdeda5d1e7a03a615b793e949c19d158c6b46", + "Value": 0, + "Gas": 425000, + "GasPrice": 50048888839, + "Input": "0x" + }, + { + "Hash": "0x9d9e3abb9b5bdb46b0518db5f46ff9dfd19bad2a78677de22286216582b85d76", + "Nonce": 43954, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 19, + "From": "0xc1e2a676c2cf7c8120e927396642324b4a3c0e75", + "To": "0x000000000dfde7deaf24138722987c9a6991e2d4", + "Value": 0, + "Gas": 600000, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0xe74f94622c7a2e46631394dc64777f6c8c65610202d7a81d9e0f5995ccd85354", + "Nonce": 122, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 20, + "From": "0xd54ec19313e39293dec6b86adad6713e4aaa11d0", + "To": "0x663dc15d3c1ac63ff12e45ab68fea3f0a883c251", + "Value": 1000000000000000, + "Gas": 361252, + "GasPrice": 49948888839, + "Input": "0x" + }, + { + "Hash": "0xc0b383d4e0735054415ee6e43c7b02d4f3daae50192dd8a3c3e2b2092cd3bddf", + "Nonce": 12005, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 21, + "From": "0x5acf4e865604ab620fb84acc047b990f2d2856fd", + "To": "0xf3f04555f8fda510bfc77820fd6eb8446f59e72d", + "Value": 0, + "Gas": 384855, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0xc9986543cd16efe7c8721a952c22ae682438092e0d695631de8d80e276997c74", + "Nonce": 82954, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 22, + "From": "0x2a4a7afa40a9d03b425752fb4cfd5f0ff5b3964c", + "To": "0x478238a1c8b862498c74d0647329aef9ea6819ed", + "Value": 0, + "Gas": 740000, + "GasPrice": 48912763532, + "Input": "0x" + }, + { + "Hash": "0xd01b0102d2aaebff16b0948286bff2e3e608527b1f9374d10b33cefd952db4cf", + "Nonce": 458436, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 23, + "From": "0x477b8d5ef7c2c42db84deb555419cd817c336b6f", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 96082, + "GasPrice": 54473432550, + "Input": "0x" + }, + { + "Hash": "0x0b25476c3d3ddec226669cfcad1569c5d16f8c3e44e16bba9462f01758acf168", + "Nonce": 151971, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 24, + "From": "0x1ab4973a48dc892cd9971ece8e01dcc7688f8f23", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 100000, + "GasPrice": 53336252238, + "Input": "0x" + }, + { + "Hash": "0x29a099996fd425797f48b4efbc521d23eb5b27baa8c1ed863d1089592839557e", + "Nonce": 9928, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 25, + "From": "0x147b12c06d9e8e3837280f783fd8070848d4412e", + "To": "0x5c7bcd6e7de5423a257d81b442095a1a6ced35c5", + "Value": 0, + "Gas": 115204, + "GasPrice": 50031355791, + "Input": "0x" + }, + { + "Hash": "0x49dbad8af7ea4dd6e05bbf0b1bdd4eef3c90f1876b0bf902cf4dfdfb35786488", + "Nonce": 266, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 26, + "From": "0x6f1e9f00c32d11a366405a1811389a088f7bc46b", + "To": "0x72c60bfffef18dca51db32b52b819a951b6ddbed", + "Value": 0, + "Gas": 120000, + "GasPrice": 52548888839, + "Input": "0x" + }, + { + "Hash": "0x1e29f0736e1e26d28373e05f0eeb3b7da5cb808372979693cbb26d9ba56942e2", + "Nonce": 43, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 27, + "From": "0xf319661f8a3d9055acd7465ef81583440194f91c", + "To": "0xa397a8c2086c554b531c02e29f3291c9704b00c7", + "Value": 0, + "Gas": 274471, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x5482b876b39ba649e1e762f015baacaa61d8278d63be31e894a23fbc3b189eab", + "Nonce": 2134, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 28, + "From": "0xc645677ce475e6bb1d14a42d0be45da24b636c99", + "To": "0x00000000000000adc04c56bf30ac9d3c0aaf14dc", + "Value": 350000000000000000, + "Gas": 210218, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xc8b9ad09aa7fc266bb7c25929f77e4df2afb9869f7a030e88a6309f7d5e1e808", + "Nonce": 24, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 29, + "From": "0x059f2909875a4d309e9750361eaae03e14fe3d58", + "To": "0x72c60bfffef18dca51db32b52b819a951b6ddbed", + "Value": 0, + "Gas": 56421, + "GasPrice": 50548888839, + "Input": "0x" + }, + { + "Hash": "0x63e93a33f662ba39aa5ee3caa63b9e56d6fa4b4b1e0fbbe5ceac0b74e7b6895e", + "Nonce": 2502215, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 30, + "From": "0xf89d7b9c864f589bbf53a82105107622b35eaa40", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 90000, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0x2ba11f551f8130d19869d1e749016e61d5487e9d66f67d52e39f7d84f7f88191", + "Nonce": 9077862, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 31, + "From": "0x28c6c06298d514db089934071355e5743bf21d60", + "To": "0x6de037ef9ad2725eb40118bb1702ebb27e4aeb24", + "Value": 0, + "Gas": 207128, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0xcc4413ee408069d6d9d50c8e1f17df8e5d483cda50de6128bc941c7942fcc99c", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 32, + "From": "0x341468f2b13155c4c0ff4c6f727189821e09f1cb", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 59533, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0x0db56ba3ff4ba73b12d7abae6c63add8ad47133094dd2c3950511586ccdfe67e", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 33, + "From": "0x14aff39623f752f9a190d6cf0140bbd7d8754e6b", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 59533, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0xbf6f3a77df707bfdeb7dbb40a41f8cc895609a2e053e593745d2c36ecd19a689", + "Nonce": 2, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 34, + "From": "0xdb8250f7be41362bd7245c2f8a200a62ebc2489a", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 59533, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0x5681005e4a6a3538761efa55dcbb7f2a3f82dfcb9bb597aba7ace344b81a88c0", + "Nonce": 1, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 35, + "From": "0xbcda3d2714c7a16752610a1a639c31024cc2eb77", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 59533, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0x24d7f9fe3de7e6ab5a812eaf9e0fbc8e9d8a52ca5546f27132ae288ae176b7ae", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 36, + "From": "0x457fefbde35d635f198cde14a9bc31ae1c62fa28", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 59533, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0x82d81463b5074deca126bff83beef3722d04afb5b310cfaded1509cf3927cc51", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 37, + "From": "0x3f8934cbb4584f4d1f0d723f532920d916da2384", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 59533, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0x7990fda5113774b18a34279fb025d50e97b6997b6f98b5756006625f21473688", + "Nonce": 1, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 38, + "From": "0xf3cb1448109eb769bbe3a254c47f6c5054fed057", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 84000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xe01c7a56342f619e8007904f15af64e67a1d29ad6877abadb2e8b888884e6bc7", + "Nonce": 29402, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 39, + "From": "0x13307b8854a95946b54a904100afd0767a7a577b", + "To": "0x06450dee7fd2fb8e39061434babcfc05599a6fb8", + "Value": 0, + "Gas": 50948, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0x5a82e744641c74badff4df9bceb1ca00c920af42a8ef011504a07b9bf8924532", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 40, + "From": "0x75a7d1cf30156157e2d68be10f3fc4479b3c8606", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 64000, + "GasPrice": 48336252238, + "Input": "0x" + }, + { + "Hash": "0xf421f19c805272d9b4395ebc5265d6da785d3f21610473738ee18572608b7ccc", + "Nonce": 18944, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 41, + "From": "0x22a82147a80747cfb1562e0f72f6be39f18b5f76", + "To": "0x40864568f679c10ac9e72211500096a5130770fa", + "Value": 0, + "Gas": 11000000, + "GasPrice": 47648888839, + "Input": "0x" + }, + { + "Hash": "0xacf927dd81243412a557e43be957841e9fe5d933331acfed537e67c17594385b", + "Nonce": 1441, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 42, + "From": "0x6fb3dbc55cf59a1e5eb71ae973900768ff83258d", + "To": "0x6b175474e89094c44da98b954eedeac495271d0f", + "Value": 0, + "Gas": 62196, + "GasPrice": 48000000000, + "Input": "0x" + }, + { + "Hash": "0x56a989c393b60418e8dcad21861fb173f26cbd9ace6c38fe37338548b1b530a0", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 43, + "From": "0xf99f7a261ace91f73d0c675c538420c680b5185a", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 46085, + "GasPrice": 48050284419, + "Input": "0x" + }, + { + "Hash": "0x977625bfecff53b416d41bbc45121f207c48083226c92515269a146005428560", + "Nonce": 1071, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 44, + "From": "0xe916778c679adda1d506d1783c7a5a3e49bc6056", + "To": "0x0df596ad12f927e41ec317af7dd666ca3574845f", + "Value": 0, + "Gas": 64000, + "GasPrice": 48336252238, + "Input": "0x" + }, + { + "Hash": "0xf8194e8f3b2a6b173e70490df4b4557e8d34d80440fd3b9c931fda269419378d", + "Nonce": 30, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 45, + "From": "0xd7ed1433ad6fcf2cd393fd5e72b63c4e5fc6f3aa", + "To": "0xd1d2eb1b1e90b638588728b4130137d262c87cae", + "Value": 0, + "Gas": 55000, + "GasPrice": 48000000000, + "Input": "0x" + }, + { + "Hash": "0x5ef4e7fe0c62cae78f2a05d859f2f028db31c62023e51110749f5089ac7a4e97", + "Nonce": 199, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 46, + "From": "0x7154b682e8fdb4207c035d2f037ad6448bbb8581", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 0, + "Gas": 356240, + "GasPrice": 47648888839, + "Input": "0x" + }, + { + "Hash": "0x9a1b2c12715ddc52fd81535f2bfa98a65b2774b0a9ef9753523968beeb33266b", + "Nonce": 2, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 47, + "From": "0xbad22e79a5b9309114a1ca3fcfc9cfe11474241a", + "To": "0x253553366da8546fc250f225fe3d25d0c782303b", + "Value": 1492833416759225, + "Gas": 588404, + "GasPrice": 47598888839, + "Input": "0x" + }, + { + "Hash": "0xf1d7bdc606994fd5fbc5c9b29b86080ae2cba8d0027797b5e78396edd9cd25f2", + "Nonce": 15, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 48, + "From": "0xd29297fbc7717ae056742bff56c76a231e232e05", + "To": "0x00000000ede6d8d217c60f93191c060747324bca", + "Value": 0, + "Gas": 1013974, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x8dc1cf3dfe14fb2594bce70cdc98ccbaf60a4cded5add1c050106a1908d340da", + "Nonce": 18, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 49, + "From": "0x17bbf977991336100f444d97e39cfb4f13a213f9", + "To": "0x68b429161ec09a6c1d65ba70727ab1faa5bc4026", + "Value": 0, + "Gas": 68026, + "GasPrice": 47628888839, + "Input": "0x" + }, + { + "Hash": "0x4e8460b89fd983c06e5303105f0a228be7f64a5c21ffb4c3bb15fbacabe71478", + "Nonce": 1, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 50, + "From": "0x36bb3e0a7823f39826b79a3f1f4d2bf1bb3c5767", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 50000, + "GasPrice": 47648888839, + "Input": "0x" + }, + { + "Hash": "0x59d0f7e63988e79724b45b673e52844ba7f5764a9301a9e2eae40e7df45790f2", + "Nonce": 6, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 51, + "From": "0x10ee730e8c4f19dce08d4548b3de8045e8b87e9f", + "To": "0x808507121b80c02388fad14726482e061b8da827", + "Value": 0, + "Gas": 60664, + "GasPrice": 47628888839, + "Input": "0x" + }, + { + "Hash": "0x09c2cab6cf4a951a55e1af620e7a1478520f60bc5ad9493da639247dbba80b92", + "Nonce": 6, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 52, + "From": "0x5cf88277ac14867a1e21e319db3f7a2691dab6d3", + "To": "0x13f4ea83d0bd40e75c8222255bc855a974568dd4", + "Value": 0, + "Gas": 271485, + "GasPrice": 47562888595, + "Input": "0x" + }, + { + "Hash": "0xfa5b3a5ca053e1c13afc665443f96b30a0709dbc3003f1a6e1bb862983c2fd48", + "Nonce": 25, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 53, + "From": "0xa34f5b8c3493e710bc27fccf071e6b021ca18a08", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 0, + "Gas": 258530, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x72439934df548b919204bc6eaee2955bda5ef72573163469f435eda159767762", + "Nonce": 18, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 54, + "From": "0xbde1f1dac1e001a272cb083962179d8a152c551b", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 85067, + "GasPrice": 47598888839, + "Input": "0x" + }, + { + "Hash": "0xe2ca844813c39450eb4d6a29f18e949a9d1410977e96a9a885f1de60b682e210", + "Nonce": 802, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 55, + "From": "0x334022d77bfc9e8aa5b34907873457c545d9faf2", + "To": "0x1a0ad011913a150f69f6a19df447a0cfd9551054", + "Value": 0, + "Gas": 958579, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x8a0bb204842e7ee03022614256e441ed333c6a74acbb1407672fb888c150ae33", + "Nonce": 326, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 56, + "From": "0x63126fdc6111f874ada547b9d7deb607503463a7", + "To": "0x500bfddf51e77da46ce7c8ca00ef153335420fb4", + "Value": 0, + "Gas": 44955, + "GasPrice": 47647888839, + "Input": "0x" + }, + { + "Hash": "0x67ae0da37ee873c8b58c6556b0a7c56fa93c2ba31b91b781c2712b9d86435962", + "Nonce": 43, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 57, + "From": "0x8d40a76ad48f05b8041f58430d3e903afb49dd44", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 0, + "Gas": 210315, + "GasPrice": 47562888595, + "Input": "0x" + }, + { + "Hash": "0x48136ab67c5d2a4e7a04168cceb9b32a35601b82e947bc94e276f9783a42d9b7", + "Nonce": 2199, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 58, + "From": "0xd605b8c680c34f15d7aaf597494fd839f75eba18", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 0, + "Gas": 226411, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xc31582514217131df5ecfa98f6a1c109f099d060d493699b6bc6e86297c602b3", + "Nonce": 4174, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 59, + "From": "0x7a6b049d83554da1175d65be3f6264a74a05ae17", + "To": "0x29469395eaf6f95920e59f858042f0e28d98a20b", + "Value": 0, + "Gas": 161687, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xc18b766e65a6ee620f377b7164dccd1510852d5027fc37f568ad8e59b459f5a6", + "Nonce": 1, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 60, + "From": "0x65ea5ecbe773e1ac344727d2534d9402e29cb8f5", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 250000000000000000, + "Gas": 208940, + "GasPrice": 47563634797, + "Input": "0x" + }, + { + "Hash": "0x1eaa0f39ba31d67be255e7d3af73b8697018cf3195afbf163b8eb8e4a964b70d", + "Nonce": 1500, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 61, + "From": "0x54c04155f5ffda3e6bf81ae76bf8fc6ebd6a2c05", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 6000000000000000000, + "Gas": 212673, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x9c37ea5b81385211d034f26714bee77380a3e014460a7bba2b935131fc392b5b", + "Nonce": 579, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 62, + "From": "0xa14368e5a732611adc565f3cad488f30d0023166", + "To": "0xb2ecfe4e4d61f8790bbb9de2d1259b9e2410cea5", + "Value": 0, + "Gas": 152191, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x88638b75a3078b51330670ba7ee25f6704fe8fb6366e6725c107632bd7358b33", + "Nonce": 94, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 63, + "From": "0xf2215488e721386d93d764cb63dae81ffa85989a", + "To": "0xec2432a227440139ddf1044c3fea7ae03203933e", + "Value": 0, + "Gas": 129878, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x2a84c9bbfc12a16d075ae9c0e7e7d5632c9af8114560eeaf5415e6b3830d1d8d", + "Nonce": 4091, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 64, + "From": "0xe14b4585795befea1b08a4317b46ca218000ac93", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 500000000000000000, + "Gas": 180666, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x9b60ea1aeb4a7eeda09663e9f4299ee6829fa22168c1bd17d7e67dbf56d005d3", + "Nonce": 1365, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 65, + "From": "0x0028109b8b6ca54e7e1c372cfc2a68b7e477cd43", + "To": "0x3bdca51226202fc2a64211aa35a8d95d61c6ca99", + "Value": 0, + "Gas": 206076, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xaf40f5ec304afc4e472dcfb0092e414f3e54b1f87c5a99b61a63efbe17da7399", + "Nonce": 9209, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 66, + "From": "0x24a9ee0ee55f08813924734cd703ab41c045c643", + "To": "0x1111111254eeb25477b68fb85ed929f73a960582", + "Value": 0, + "Gas": 157232, + "GasPrice": 47563634797, + "Input": "0x" + }, + { + "Hash": "0x899e838f75dd574d44f088db7bc0f118cdd7e7176b0f8031b6cab1c25d92be6f", + "Nonce": 9, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 67, + "From": "0xc16822f9109ea30a58d3572d02a6de2c80ac02d1", + "To": "0x5f6ae08b8aeb7078cf2f96afb089d7c9f51da47d", + "Value": 900000000000000000, + "Gas": 125810, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xde8f572e197f11e6837b5f6c6a5e673aa7251f7855a4848d79af10c7ca13127e", + "Nonce": 7273, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 68, + "From": "0xdda42f12b8b2ccc6717c053a2b772bad24b08cbd", + "To": "0x9d39a5de30e57443bff2a8307a4256c8797a3497", + "Value": 0, + "Gas": 145800, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x9719a79d81ab3a15bc7d88c96c4edf4f52d9dbea98351e259dbad342bec55594", + "Nonce": 53, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 69, + "From": "0xdf0f1ef01bb8b994efbadec67f1a4eab05651fe7", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 120000000000000000, + "Gas": 3000000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x57f11c80b1e1f06c483f61f107005bbc9f09e8404e2c7954668f4a39c69303b0", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 70, + "From": "0x306c113ba7c60121bec46d170628796607ca2410", + "To": "0xe03f81bcb9f7876a1a64f9ece379df1e189ebdc8", + "Value": 7322726194273998, + "Gas": 21000, + "GasPrice": 56582707735, + "Input": "0x" + }, + { + "Hash": "0x03636251efc508996a2ebd590ed8736e3bbe0a3ff9c499b8c2ab924a8ed66350", + "Nonce": 8, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 71, + "From": "0xb83a8df4b68d18e3d85e6d708244f3d09e995899", + "To": "0xa5fc7bc086a0c07688f09ffea3917052b83d3a2e", + "Value": 48456575324369561, + "Gas": 25200, + "GasPrice": 50375597873, + "Input": "0x" + }, + { + "Hash": "0x689e68d62fd6645b107189e439e7539e2babce2d69deeb595b83dedda9cf1ee4", + "Nonce": 15, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 72, + "From": "0xecaa84f671320b817832d69004f9408ee46642d1", + "To": "0x808d0aee8db7e7c74faf4b264333afe8c9ccdba4", + "Value": 998959473334381000, + "Gas": 21000, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0x5ab8572fda8661d6627ac76e05a46808aa2efb922e6061c750c0488f9154d31b", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 73, + "From": "0x20e7fb230e12f1a1e51e986fb9b7c613c3904494", + "To": "0x808d0aee8db7e7c74faf4b264333afe8c9ccdba4", + "Value": 55670053334381000, + "Gas": 21000, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0x2da537de7f841c6bc9dd185d522a1160090147a51ce434b36bac5dbd1f8869ce", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 74, + "From": "0x38d43560972227d51c5c6394234a7f24c7b10b42", + "To": "0x808d0aee8db7e7c74faf4b264333afe8c9ccdba4", + "Value": 11890263334381000, + "Gas": 21000, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0x1efdc55822942555bdac56c955f6b5689a72d89b8d0c7f144c0c3caff341a1a6", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 75, + "From": "0xac7459107f545d0fc131199f360f4635af3a79df", + "To": "0x808d0aee8db7e7c74faf4b264333afe8c9ccdba4", + "Value": 83809303334381000, + "Gas": 21000, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0xb9012da1b2c7df70c2511ea0326eacd38e5ee98114ef0ac4173b165fe6b6e82c", + "Nonce": 6231082, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 76, + "From": "0x56eddb7aa87536c09ccc2793473599fd21a8b17f", + "To": "0x9b935c8336b825970a9434c3f78df91f6b5f8d50", + "Value": 1768895550000000000, + "Gas": 207128, + "GasPrice": 49548888839, + "Input": "0x" + }, + { + "Hash": "0xa86f930e7a2a63bd1243db9af08c34dc0f509b23fe250a1530bde1d6b1eb1d48", + "Nonce": 1864, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 77, + "From": "0xb6f30f6fff1fbc6597c6e32a8865b1bccfe84c5d", + "To": "0xb0999731f7c2581844658a9d2ced1be0077b7397", + "Value": 10000000000000000, + "Gas": 53793, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0x60d2cbe80d21bfa0cac4f5c79b484c33c42f4f46fd0529575cd70431d04851ee", + "Nonce": 24223, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 78, + "From": "0xe795d3de925c970cbef7c691f9c39505202afaca", + "To": "0x76da499494e8f6abcf0f664447b4ad28b17d535a", + "Value": 50270000000000000, + "Gas": 21000, + "GasPrice": 49000000000, + "Input": "0x" + }, + { + "Hash": "0x61f05f0c8a97fd5e17767c5d253a844a239f066e86f695d274ebcb43a103bee6", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 79, + "From": "0x0c521549dda8e9f615061a300314f9327ff10a75", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 462768287051891733, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x7756d1e8d4b884047d190758c52f75a91b1e11ba3ee272897be0414e7e05cc21", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 80, + "From": "0xed5d47f643dbcd64096e4399b486a5448af8a9c9", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 776842068302016208, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x8e2bd179ee3bdd484342b4ba3eb4780e448bbcd2b7ca8f351f5000f7273250a5", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 81, + "From": "0x01aa8a859d6b3dfdfc3f63d7062dfb3f338569c9", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 3707370000000000, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x792a13933220d317b3426eff47b61fdef525610ac865c9379ba6668140db805b", + "Nonce": 1132, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 82, + "From": "0xec129edf84da5b5f08646287094382a1ba2be81d", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 2691841796684382, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x479efae546660b04b64931e63049a1f5df7fa7bcc4225a03a19bf272ede60e7e", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 83, + "From": "0x66a7ce4255ec75a3df27a5158ab4107dc42902fa", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 12058320359425533, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x74b6d1968d9c98f20a6e8b148f11d21bc3ea85f27481bdfd51c2f35d697ad250", + "Nonce": 35, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 84, + "From": "0x03c8bd4e8b328d351d2683e20f1ac4ad40722837", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 2198094609990875520, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x84c46064dfeb21a91b7a56ace478a15c5a0dd43f8ff803b56bf2315f2763e6a7", + "Nonce": 2, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 85, + "From": "0x50085d3cc268a7054ae1ded2e956ec5193d261ec", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 2150976788762877156, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x21b0e4dc76491ca3c31f9d926b8b6afaaa84f2370fba645a6884a602b8b46efc", + "Nonce": 4, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 86, + "From": "0xce237f7fb5aebf96972780f6e79a0db11f50fbfc", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 16578017150183975, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x515db3b4bab7fa0788df13f8eb913a4034cb0fcb37f2d4f42d28907666b694cf", + "Nonce": 22, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 87, + "From": "0x86ef12fbece63a18d80f2eabd81a4706d7e35071", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 42529768051405192, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xe0ecbb0f7c5b2fcfca8c91a5a3346a3aad979f79c5ebf40d842cc1960dec42f5", + "Nonce": 83, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 88, + "From": "0x2b992826fe89d44fc7f07b9f8c00ed8044232a47", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 8411773680572080, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xb1c99449c28e8ec77d2b702867c1eb9a5ab14dca6b33101a412c0e935a241bdc", + "Nonce": 1, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 89, + "From": "0x9122ab4d401274136117d6d5636d40edbb235b4f", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 3999077726144250130, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xbe84ac778a8918852741f26b0254c280c7a0f7fc2e5ebaebebccf0b8c0ae71ac", + "Nonce": 12, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 90, + "From": "0xc87785da0dd6226809e6b715d70710436b9013b5", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 21742364570290170, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xc27253ed1ca9495b9e4057c7b6cdc0ee60306776601d465d2faa0785216cd49a", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 91, + "From": "0xff06ab0d2ff3d7fb0a9bb2fed17404ba90734568", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 26972374121195485, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x0e41f1276c56987ad0b66361614bd066d878de214bb7f08bee818542b5514609", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 92, + "From": "0x30a1344d200e4c0e048d6dba93cc786a867514f8", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 7634516628410000, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xfe945134cad8513e751db4dc712c187f696bdbd2a39ea97f4f79b70cf55379cb", + "Nonce": 1, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 93, + "From": "0xb1d24bf0f6d54b8527231a1a1fe812f92523505f", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 7524752371580930, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xe466533d07d274fcbc1205ec840a52a1bed23a9bb66a55e9acca53055b250673", + "Nonce": 6, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 94, + "From": "0xb3568635218c1efac483a1bdec41aed08fc15de5", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 498828636624236675, + "Gas": 23300, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x215e1cd032cbec84e5549cd955e6d8cf44f483df4f3668f450b00e384068580e", + "Nonce": 960462, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 95, + "From": "0x6081258689a75d253d87ce902a8de3887239fe80", + "To": "0x25a8e4634ab19a6c49d3cf1a0ff1b5e20e3e8834", + "Value": 1760689100000000, + "Gas": 21000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xd5ebb2dbe96fe6d97d344bd70355a756f691f518f14493b33e92738778a7d350", + "Nonce": 6882, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 96, + "From": "0x01b7a710bab5fe81564feed292b631896c7f0a53", + "To": "0x108ab2f19995b00dbc7f279296dd14448da8328d", + "Value": 3573262641172520, + "Gas": 21000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x5b59d52bcd79e4632c22e74b911bc61285d9afe54418563f7b199dafa238c626", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 97, + "From": "0x56fec52ef29ce01b7d29698b96c94e5906a6c69c", + "To": "0x95a9bd206ae52c4ba8eecfc93d18eacdd41c88cc", + "Value": 569988216421831016, + "Gas": 21000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0x2a1113bd1a869f52ec4d1bbcf811b94581a038430b6806e75b8c09f5d14581fd", + "Nonce": 1, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 98, + "From": "0x821c0e818ab6fb03f9f54d536c705f554f07b340", + "To": "0x95a9bd206ae52c4ba8eecfc93d18eacdd41c88cc", + "Value": 34633538809596195, + "Gas": 21000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xf64cd1a3890cbf0edcc5f732fd34ff9896d2334282f5f943c5ff4a3d67c853d4", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 99, + "From": "0x2e7bb229a8174f705aefd619fcf4f09f5e32a015", + "To": "0x95a9bd206ae52c4ba8eecfc93d18eacdd41c88cc", + "Value": 5757192435004000, + "Gas": 21000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xc6c44bd646f6882b3921cb1a9480abb30b61e7d0f27be8847cf0e286a4631f7b", + "Nonce": 8, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 100, + "From": "0xf2d3600dbc36a3d5bb8edb497b9d1b5a72040f8b", + "To": "0x95a9bd206ae52c4ba8eecfc93d18eacdd41c88cc", + "Value": 379756371914635899, + "Gas": 21000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xe2cffea80d923c4f28dcc4a8e3e0ed1ea8e9a4f06ab4d6f83df0e51cc82fd2bc", + "Nonce": 12, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 101, + "From": "0x5038ee2d5fd2f0882d93de981e0d1948e925d41f", + "To": "0x95a9bd206ae52c4ba8eecfc93d18eacdd41c88cc", + "Value": 55855263972735000, + "Gas": 21000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xbcd296d46802e367ea89cc8325387cb478e21a6d41e68c3ed71e562a1aa17ef9", + "Nonce": 4, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 102, + "From": "0x2a392e4d9612b2dd13bcdead01553bfb2da31e07", + "To": "0x95a9bd206ae52c4ba8eecfc93d18eacdd41c88cc", + "Value": 22022413995598520, + "Gas": 21000, + "GasPrice": 48548888839, + "Input": "0x" + }, + { + "Hash": "0xff4d706971881b6d796b08fc9b483ae0fe7c062a822c365ddd900b5a054e6ac4", + "Nonce": 13, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 103, + "From": "0x6ec5c96b2d142dbe089e0695d855a139129e9ee7", + "To": "0x5a7d87c42bc19993cde05fe9283431af7cb60735", + "Value": 150000000000000000, + "Gas": 21000, + "GasPrice": 48048888839, + "Input": "0x" + }, + { + "Hash": "0x30a61ee639a59645616969912d9a301df1d7d949d9b83d9f1e4cc0bb46088c81", + "Nonce": 39, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 104, + "From": "0x3168c5cb525998fca7780efa73c5769495faa813", + "To": "0x4e69e175f8a21a3ab9e1fa99f36c45734cf8600b", + "Value": 5000000000000000, + "Gas": 21000, + "GasPrice": 48048888839, + "Input": "0x" + }, + { + "Hash": "0x0d7495e7ce3b09aeb56d157e98899bc655f1813a1cd3831870aacbfbff4a04c9", + "Nonce": 15, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 105, + "From": "0x8418a8ed7fa1ded5882cf94aff001c7904a7be5c", + "To": "0xaf5323a542651a844be3b2c0d52e4f0f0dfe086d", + "Value": 1616741584510527773, + "Gas": 21000, + "GasPrice": 48048888839, + "Input": "0x" + }, + { + "Hash": "0x3c005eeaa9e7984f552dab48edcee960bab3a7ad43ce99bd31e935ca7914dc56", + "Nonce": 1013320, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 106, + "From": "0xa4e5961b58dbe487639929643dcb1dc3848daf5e", + "To": "0x61ee5aaced0063c06ded69ce4325af5c571ae752", + "Value": 5424350000000000, + "Gas": 22000, + "GasPrice": 48000000000, + "Input": "0x" + }, + { + "Hash": "0xd28184c4005790955d76746ba263f058e9cfc1dc25b166befd7d67ce23832a10", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 107, + "From": "0x254493aedff08427c23f1d1b836c6eae8a744461", + "To": "0x077d360f11d220e4d5d831430c81c26c9be7c4a4", + "Value": 28329090000000000, + "Gas": 22000, + "GasPrice": 48000000000, + "Input": "0x" + }, + { + "Hash": "0xfad63a744af9d33038825461d0f6e84640cbdbfd11cab5b1ef1eb9a88a7f71dc", + "Nonce": 1043002, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 108, + "From": "0x077d360f11d220e4d5d831430c81c26c9be7c4a4", + "To": "0xee9fda27eab28306ac66685a4d4c3a1208d35653", + "Value": 166210860000000000, + "Gas": 60000, + "GasPrice": 48000000000, + "Input": "0x" + }, + { + "Hash": "0x8d39bd123d74eaccde632fbe78138b4e7f8fe0448a9d980a2513bc94b800035c", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 109, + "From": "0x432b2637e3bbe8333e90e03191664d7547f29d80", + "To": "0x077d360f11d220e4d5d831430c81c26c9be7c4a4", + "Value": 28943990000000000, + "Gas": 22000, + "GasPrice": 48000000000, + "Input": "0x" + }, + { + "Hash": "0xce377e372d440c9363e481e447afd74afb4fe20a58e6943ceab8d7e944e48e48", + "Nonce": 2004683, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 110, + "From": "0xa7efae728d2936e78bda97dc267687568dd593f3", + "To": "0x68210ab6751e22e6da1ef0f2775e0cf24605f2c0", + "Value": 10300000000000000, + "Gas": 210000, + "GasPrice": 47648888839, + "Input": "0x" + }, + { + "Hash": "0x6060290431252299f0b1d6c5cea973963695dbb11f39ceb84f77026c47ca638f", + "Nonce": 1049017, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 111, + "From": "0xf7858da8a6617f7c6d0ff2bcafdb6d2eedf64840", + "To": "0x5615c4420a748dfab816444525c42e712f7c4d21", + "Value": 942231000000000000, + "Gas": 210000, + "GasPrice": 47648888839, + "Input": "0x" + }, + { + "Hash": "0x4617b64e1978ae7971848ddab6e96f235345622bd259521d09ee99ca744a127d", + "Nonce": 2, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 112, + "From": "0x5f7feb96ff866ddcf7d8ee66b28871f1471f40cd", + "To": "0x5f6ae08b8aeb7078cf2f96afb089d7c9f51da47d", + "Value": 20000000000000000, + "Gas": 102242, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xfbf3a065ec8501850a1a3e4621c430db75f0546badff8ff2d82330864323eec0", + "Nonce": 889, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 113, + "From": "0x40e4587954c57ade62f3753a970e26eec6330831", + "To": "0xa0c68c638235ee32657e8f720a23cec1bfc77c77", + "Value": 185874839935350428, + "Gas": 111592, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x756305e0f8bbb11ba297dd031ee5cd081cfd09466cb5b9b8c6f2b8e603f50b0e", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 114, + "From": "0x9b05eaf9bcf2140bd28ecdfb9110c541cc7dbf69", + "To": "0xa7606281f7808167b5faf89f4d898685a8b87549", + "Value": 0, + "Gas": 47659, + "GasPrice": 47564242815, + "Input": "0x" + }, + { + "Hash": "0xd1496bdf94edb7b1f6792c9af5505dffeae43906dfc298480df9b88b98770714", + "Nonce": 13, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 115, + "From": "0x40594aee4f3bad820236dfca99b6e1dafa66e45d", + "To": "0xd9a442856c234a39a81a089c06451ebaa4306a72", + "Value": 0, + "Gas": 51760, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x7075039ec476e9be1623f356c2fc4545428e01da851749804b7e74d4c73a53d7", + "Nonce": 128, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 116, + "From": "0x0847cc113a7899eca102964a6be39f3874fdc84e", + "To": "0xff3f8227c3b0241848e1420ec77d61aee2ad5f67", + "Value": 0, + "Gas": 46546, + "GasPrice": 47563634797, + "Input": "0x" + }, + { + "Hash": "0x5c80539bf17c55d82179ce4501ff61215422067c4ba1424c087dfc167e72c1fe", + "Nonce": 3, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 117, + "From": "0x1e601320e64e0edde73e136911fffd04767bddd8", + "To": "0x4c9edd5852cd905f086c759e8383e09bff1e68b3", + "Value": 0, + "Gas": 70464, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x8d897b787c1f419bef334c0947d32c86960bdf6775460faecee751bb38ca5caf", + "Nonce": 807, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 118, + "From": "0x1bccea07ae47ae3c5f8207e253fcf733d19412ed", + "To": "0xfaba6f8e4a5e8ab82f62fe7c39859fa577269be3", + "Value": 0, + "Gas": 46976, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x898b8023a05933eb967f4caeb56b78ff12c162231c0a7368ba8e7bce0580ecf0", + "Nonce": 38, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 119, + "From": "0x7f1652e05388a237a4ff2125c2a5e497ed83ecc4", + "To": "0x72c60bfffef18dca51db32b52b819a951b6ddbed", + "Value": 0, + "Gas": 46957, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x832d821aee6b370c7f7f236d02bdf51301ae3658e21ec85339a7be748d6c4582", + "Nonce": 907, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 120, + "From": "0x0db858b5ea8ce6cb34344d3db780e7bcbc9aa2bf", + "To": "0xfc6ccc11c91577842c4843a335eac4a586ba6ae0", + "Value": 0, + "Gas": 46318, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xec34e6ba055c320d73ef36362fadf8db7842fc2a056673502d863380e026456b", + "Nonce": 238, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 121, + "From": "0x0e19f9d69c46a9f49389d6a427e04c97097f992c", + "To": "0x72c60bfffef18dca51db32b52b819a951b6ddbed", + "Value": 0, + "Gas": 47017, + "GasPrice": 47558888839, + "Input": "0x" + }, + { + "Hash": "0x35e665990950b0355d79be7290c719aeff7cd092dbd64efc5fec49152bb78927", + "Nonce": 123, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 122, + "From": "0x108613c231792bb14f041719ecbf7b18789926ca", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 0, + "Gas": 426890, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0x370d7de27634e11a9365ca1acb5738cbdff2bacc85bc0bbe01493c1952201816", + "Nonce": 38, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 123, + "From": "0x4095e294a12a0d8d98fc602fcd3e0fb3b96c477a", + "To": "0x5e809a85aa182a9921edd10a4163745bb3e36284", + "Value": 10150000000000002, + "Gas": 21000, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x446a45102acd665e95de4a498feb9e71a31f843143a687de8878b440878d3a6f", + "Nonce": 224, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 124, + "From": "0xce0135aef6c807f822d5de6ca3ffd6bcc170d941", + "To": "0x58e2cacfa2322acaa56682eeba09a47a028840c8", + "Value": 200000000000000000, + "Gas": 21000, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xcee22c01572d92df51e6a3b5022f92a0b458ee4a4f339b7f8e8bb3da3e365265", + "Nonce": 271, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 125, + "From": "0xd435aa3217020db4b9fa3a6ddd7edaadee9cadca", + "To": "0xb2692e4d0a5333d20b08182aefc9a2e956a719c2", + "Value": 29372543721031329, + "Gas": 21000, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xb9e70e0882d81f80e9d4f3dd031081598822f77e23d5f18562520b5b11981eb4", + "Nonce": 10379, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 126, + "From": "0x006f0537ed493f7f84340dc6c417a8381c6b1035", + "To": "0x7e8139f246776bcfa2cc9a77111ed98d3a08560e", + "Value": 11407000000000000, + "Gas": 21000, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x2d33465c8fd4ea2ce1ce4c0a35a0331e5152f8301a06079e1c2dd9d8e537943d", + "Nonce": 2, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 127, + "From": "0xe06d48db6fe0f6fe53cf6a5ac4705afe27c44489", + "To": "0x2501111b3a5bb30608a06b1b8c182e777f7e6c1a", + "Value": 46739280024215673, + "Gas": 21000, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x3991d63319b2e541058ceca5038e755237663cce54979a057354dbd2b97df6b8", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 128, + "From": "0xc318c7f0060447674584ec3c013da9387f6f8f8e", + "To": "0x6507337007e9779cdd49b369108f722cfe5190e5", + "Value": 1000000000000000000, + "Gas": 21000, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0x00c7d3846f5d9d1e6e862bcf837107096dd4ab1a43db63082f203437ee24a0a1", + "Nonce": 66, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 129, + "From": "0x2c7a652f55dc40983e914e0fd638479ace6a1f75", + "To": "0x7ef265e7c167891fe20b1bffd0e5076d8484b264", + "Value": 50000000000000000, + "Gas": 21000, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532", + "Nonce": 18, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 130, + "From": "0xf829fa7069680b8c37a8086b37d4a24697e5003b", + "To": "0x70e967acfcc17c3941e87562161406d41676fd83", + "Value": 4000000000000000, + "Gas": 21000, + "GasPrice": 47562901998, + "Input": "0x" + }, + { + "Hash": "0xa32fa55ad570ee943de6a69681f85809cff9611bb489dc59620c6c9634278860", + "Nonce": 6, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 131, + "From": "0x1a301f7e9c7f4372cb886dafc32c03505ae8a347", + "To": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", + "Value": 5853241671568000, + "Gas": 225269, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0x68498ef50ae339f64f587508686d2070beb59a4fd704824abefbb3b846692bfe", + "Nonce": 36, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 132, + "From": "0xd9026bf9bccf9865c1b7ef3890e88141adc96b04", + "To": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", + "Value": 58663498837720000, + "Gas": 153186, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0xac5f5cbe533cd6d42f4562f08aa306ce0433ab4e6df5ea4356d09eef65efdc78", + "Nonce": 95, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 133, + "From": "0xbf0ecd6003e6fe86254b4e432578e47fbffd0c79", + "To": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", + "Value": 24052034523465200, + "Gas": 156789, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0x29bd93d2ee05bd9e2abfe0b4fd52e5374516e64665b4560966b013495f335bd5", + "Nonce": 46, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 134, + "From": "0x65327701f0fde489341e7825d42ab727ba24a141", + "To": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", + "Value": 14665874709430000, + "Gas": 151857, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0xcd576979151ddf22017c3ed40169c41b08057ee34853e790c78e0503599ffb66", + "Nonce": 27, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 135, + "From": "0xd437ab1447edfe0cd8d3b6427efd7b29216c557d", + "To": "0x5946aeaab44e65eb370ffaa6a7ef2218cff9b47d", + "Value": 0, + "Gas": 76658, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0x2352fddc3824473acdc0cb61256fb56c32db0fd1db41244b42b4ad1bb7d7dadc", + "Nonce": 17332, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 136, + "From": "0xa0d55532fa467db182780b6adaf34817ee99054d", + "To": "0x5db209f7f4cbd7011b486b8d99e5ae616947a138", + "Value": 0, + "Gas": 181540, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0x9ae881cb9e534096347f0a937a16342fb6626885dd385f55f64f461a9913b3cb", + "Nonce": 349, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 137, + "From": "0xeb85679cdf6182f3531e6bd260acaabc84945837", + "To": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "Value": 0, + "Gas": 39792, + "GasPrice": 47549888842, + "Input": "0x" + }, + { + "Hash": "0x1ef9ed16e2e0b41ab18042a4bdaeaf726a71776687a0c242577146e21c62514c", + "Nonce": 22337, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 138, + "From": "0x42f5e0a60685a75b4a63bb2bb971193fd23f5869", + "To": "0x42f5e0a60685a75b4a63bb2bb971193fd23f5869", + "Value": 0, + "Gas": 400000, + "GasPrice": 47549998839, + "Input": "0x" + }, + { + "Hash": "0x24177294b901a1b673a6df33837e2b0006190bff20f9392c836b8ca46e26d052", + "Nonce": 0, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 139, + "From": "0xba8e558bd0677ac300489420138785cc3a37a223", + "To": "0x13e0a14da2ff4e2aa3c911c485ef56d29cbd7e1c", + "Value": 5866349883772000, + "Gas": 21000, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0xfb04f84490fd405f0150eccda7080425d9196a65d71a46574aa88cebe235f146", + "Nonce": 1, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 140, + "From": "0x31524adb04b2482291a0ffe376ff5c60dc16ab7e", + "To": "0x4908fd52a3fd7344675abf143f3922bb7b26a77c", + "Value": 1975540117349000, + "Gas": 21000, + "GasPrice": 47549888839, + "Input": "0x" + }, + { + "Hash": "0x18ad5408e52be20570b1f4ec929425f3f09d7ea8231e5dda5407defa11916997", + "Nonce": 29403, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 141, + "From": "0x13307b8854a95946b54a904100afd0767a7a577b", + "To": "0x0800394f6e23dd539929c8b77a3d45c96f76aefc", + "Value": 0, + "Gas": 50456, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0x8b21c64aa243aeaaabcc0810a837099c0ba65b33cffa0e7d232191717c094898", + "Nonce": 29404, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 142, + "From": "0x13307b8854a95946b54a904100afd0767a7a577b", + "To": "0x0c572544a4ee47904d54aaa6a970af96b6f00e1b", + "Value": 0, + "Gas": 59302, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0xe6a766bff4149b232e7b86cdb49d21d81d5dee485642f241f1315be86e19adf1", + "Nonce": 29405, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 143, + "From": "0x13307b8854a95946b54a904100afd0767a7a577b", + "To": "0x26c8afbbfe1ebaca03c2bb082e69d0476bffe099", + "Value": 0, + "Gas": 50847, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0xe482f176072f558851718fb8c53e8165d2e5b7f37da8bd38d9d7066e4850d531", + "Nonce": 29406, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 144, + "From": "0x13307b8854a95946b54a904100afd0767a7a577b", + "To": "0x34be5b8c30ee4fde069dc878989686abe9884470", + "Value": 0, + "Gas": 50683, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0x92295ce76ba313185935046487d1a324c5e35ee35e4b3130ccb499b0f31ef57e", + "Nonce": 29407, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 145, + "From": "0x13307b8854a95946b54a904100afd0767a7a577b", + "To": "0x38e382f74dfb84608f3c1f10187f6bef5951de93", + "Value": 0, + "Gas": 50456, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0x26adefddd3ce81f98b531a485703b22111945b95947d45f5ba4b578c595aa0be", + "Nonce": 29408, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 146, + "From": "0x13307b8854a95946b54a904100afd0767a7a577b", + "To": "0x41545f8b9472d758bb669ed8eaeeecd7a9c4ec29", + "Value": 0, + "Gas": 62306, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0x9fc91528d534ce6d97fd06216334f91c4555ccbb39fe1dfcbe3dfaf1ad76caaa", + "Nonce": 29409, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 147, + "From": "0x13307b8854a95946b54a904100afd0767a7a577b", + "To": "0x42baf1f659d765c65ade5bb7e08eb2c680360d9d", + "Value": 0, + "Gas": 74354, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0xe7825e407a5e4bac4ccbfee2650b1c7450fb2e52b14a7763a442123a48ee1ce7", + "Nonce": 29410, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 148, + "From": "0x13307b8854a95946b54a904100afd0767a7a577b", + "To": "0x476c5e26a75bd202a9683ffd34359c0cc15be0ff", + "Value": 0, + "Gas": 52932, + "GasPrice": 49048888839, + "Input": "0x" + }, + { + "Hash": "0x6f58d988f08ab702a26a9956a42d6ad13292f6a806ad457b5446ac8efef565f9", + "Nonce": 1043003, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 149, + "From": "0x077d360f11d220e4d5d831430c81c26c9be7c4a4", + "To": "0xefa63e9b5177fbafb2e587ab9d12fdf37870922a", + "Value": 164375120000000000, + "Gas": 60000, + "GasPrice": 49000000000, + "Input": "0x" + }, + { + "Hash": "0x17c01eaedf2642baa1ca4350378d56fbbc7a7315171d10a2cefb72bba8848ec8", + "Nonce": 1043004, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 150, + "From": "0x077d360f11d220e4d5d831430c81c26c9be7c4a4", + "To": "0x21d688bf078f68ee2f9e6b52817d9e2a8469998f", + "Value": 38413030000000000, + "Gas": 60000, + "GasPrice": 49000000000, + "Input": "0x" + }, + { + "Hash": "0xfe58a80dd77a06efdf25fbb4529ce7c6f4343e200704a88d6e288b832f526d5c", + "Nonce": 1013321, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 151, + "From": "0xa4e5961b58dbe487639929643dcb1dc3848daf5e", + "To": "0x839a35b9468007d8cb3a5bc9af31ead7242b916e", + "Value": 5424350000000000, + "Gas": 22000, + "GasPrice": 48000000000, + "Input": "0x" + }, + { + "Hash": "0x2e0ac6c2af639e879774284835b815b051de19c36cfaa3153e558e88bcf5db95", + "Nonce": 19, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 152, + "From": "0xbde1f1dac1e001a272cb083962179d8a152c551b", + "To": "0x1111111254eeb25477b68fb85ed929f73a960582", + "Value": 0, + "Gas": 275732, + "GasPrice": 47598888839, + "Input": "0x" + }, + { + "Hash": "0xb8a3727333b506b64769aed2e9b0e7c29637a8c333744e8f159abae458e33d3c", + "Nonce": 18945, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 153, + "From": "0x22a82147a80747cfb1562e0f72f6be39f18b5f76", + "To": "0x40864568f679c10ac9e72211500096a5130770fa", + "Value": 0, + "Gas": 11000000, + "GasPrice": 47648888839, + "Input": "0x" + }, + { + "Hash": "0x076b0ef1e4482f2b85f7f6c36152686281041191099bb1597e692ddfe6477b05", + "Nonce": 7, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 154, + "From": "0x10ee730e8c4f19dce08d4548b3de8045e8b87e9f", + "To": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", + "Value": 0, + "Gas": 161459, + "GasPrice": 47628888839, + "Input": "0x" + }, + { + "Hash": "0xef85516883c377de4a7671d372b0e5d7721af7a9bb6e42ba97a0c88d3db58720", + "Nonce": 224600, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 155, + "From": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97", + "To": "0x388c818ca8b9251b393131c08a736a67ccb19297", + "Value": 81116746339497983, + "Gas": 22111, + "GasPrice": 47548888839, + "Input": "0x" + } + ] +} diff --git a/zetaclient/testutils/stub/core_bridge.go b/zetaclient/testutils/stub/core_bridge.go index 1bf06be84b..d9172636c3 100644 --- a/zetaclient/testutils/stub/core_bridge.go +++ b/zetaclient/testutils/stub/core_bridge.go @@ -1,6 +1,7 @@ package stub import ( + "errors" "math/big" "cosmossdk.io/math" @@ -14,118 +15,189 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/testutils" ) -var _ interfaces.ZetaCoreBridger = &ZetaCoreBridge{} +const ErrMsgPaused = "zeta core bridge is paused" -type ZetaCoreBridge struct { +var _ interfaces.ZetaCoreBridger = &MockZetaCoreBridge{} + +type MockZetaCoreBridge struct { + paused bool zetaChain common.Chain } -func (z ZetaCoreBridge) PostVoteInbound(_, _ uint64, _ *cctxtypes.MsgVoteOnObservedInboundTx) (string, string, error) { +func NewMockZetaCoreBridge() *MockZetaCoreBridge { + zetaChain, err := common.ZetaChainFromChainID("zetachain_7000-1") + if err != nil { + panic(err) + } + return &MockZetaCoreBridge{ + paused: false, + zetaChain: zetaChain, + } +} + +func (z *MockZetaCoreBridge) PostVoteInbound(_, _ uint64, _ *cctxtypes.MsgVoteOnObservedInboundTx) (string, string, error) { + if z.paused { + return "", "", errors.New(ErrMsgPaused) + } return "", "", nil } -func (z ZetaCoreBridge) PostVoteOutbound(_ string, _ string, _ uint64, _ uint64, _ *big.Int, _ uint64, _ *big.Int, _ common.ReceiveStatus, _ common.Chain, _ uint64, _ common.CoinType) (string, string, error) { +func (z *MockZetaCoreBridge) PostVoteOutbound(_ string, _ string, _ uint64, _ uint64, _ *big.Int, _ uint64, _ *big.Int, _ common.ReceiveStatus, _ common.Chain, _ uint64, _ common.CoinType) (string, string, error) { + if z.paused { + return "", "", errors.New(ErrMsgPaused) + } return "", "", nil } -func (z ZetaCoreBridge) PostGasPrice(_ common.Chain, _ uint64, _ string, _ uint64) (string, error) { +func (z *MockZetaCoreBridge) PostGasPrice(_ common.Chain, _ uint64, _ string, _ uint64) (string, error) { + if z.paused { + return "", errors.New(ErrMsgPaused) + } return "", nil } -func (z ZetaCoreBridge) PostAddBlockHeader(_ int64, _ []byte, _ int64, _ common.HeaderData) (string, error) { +func (z *MockZetaCoreBridge) PostAddBlockHeader(_ int64, _ []byte, _ int64, _ common.HeaderData) (string, error) { + if z.paused { + return "", errors.New(ErrMsgPaused) + } return "", nil } -func (z ZetaCoreBridge) GetBlockHeaderStateByChain(_ int64) (observerTypes.QueryGetBlockHeaderStateResponse, error) { +func (z *MockZetaCoreBridge) GetBlockHeaderStateByChain(_ int64) (observerTypes.QueryGetBlockHeaderStateResponse, error) { + if z.paused { + return observerTypes.QueryGetBlockHeaderStateResponse{}, errors.New(ErrMsgPaused) + } return observerTypes.QueryGetBlockHeaderStateResponse{}, nil } -func (z ZetaCoreBridge) PostBlameData(_ *blame.Blame, _ int64, _ string) (string, error) { +func (z *MockZetaCoreBridge) PostBlameData(_ *blame.Blame, _ int64, _ string) (string, error) { + if z.paused { + return "", errors.New(ErrMsgPaused) + } return "", nil } -func (z ZetaCoreBridge) AddTxHashToOutTxTracker(_ int64, _ uint64, _ string, _ *common.Proof, _ string, _ int64) (string, error) { +func (z *MockZetaCoreBridge) AddTxHashToOutTxTracker(_ int64, _ uint64, _ string, _ *common.Proof, _ string, _ int64) (string, error) { + if z.paused { + return "", errors.New(ErrMsgPaused) + } return "", nil } -func (z ZetaCoreBridge) GetKeys() *keys.Keys { +func (z *MockZetaCoreBridge) GetKeys() *keys.Keys { return &keys.Keys{} } -func (z ZetaCoreBridge) GetBlockHeight() (int64, error) { +func (z *MockZetaCoreBridge) GetBlockHeight() (int64, error) { + if z.paused { + return 0, errors.New(ErrMsgPaused) + } return 0, nil } -func (z ZetaCoreBridge) GetZetaBlockHeight() (int64, error) { +func (z *MockZetaCoreBridge) GetZetaBlockHeight() (int64, error) { + if z.paused { + return 0, errors.New(ErrMsgPaused) + } return 0, nil } -func (z ZetaCoreBridge) GetLastBlockHeightByChain(_ common.Chain) (*cctxtypes.LastBlockHeight, error) { +func (z *MockZetaCoreBridge) GetLastBlockHeightByChain(_ common.Chain) (*cctxtypes.LastBlockHeight, error) { + if z.paused { + return nil, errors.New(ErrMsgPaused) + } return &cctxtypes.LastBlockHeight{}, nil } -func (z ZetaCoreBridge) ListPendingCctx(_ int64) ([]*cctxtypes.CrossChainTx, uint64, error) { +func (z *MockZetaCoreBridge) ListPendingCctx(_ int64) ([]*cctxtypes.CrossChainTx, uint64, error) { + if z.paused { + return nil, 0, errors.New(ErrMsgPaused) + } return []*cctxtypes.CrossChainTx{}, 0, nil } -func (z ZetaCoreBridge) GetPendingNoncesByChain(_ int64) (observerTypes.PendingNonces, error) { +func (z *MockZetaCoreBridge) GetPendingNoncesByChain(_ int64) (observerTypes.PendingNonces, error) { + if z.paused { + return observerTypes.PendingNonces{}, errors.New(ErrMsgPaused) + } return observerTypes.PendingNonces{}, nil } -func (z ZetaCoreBridge) GetCctxByNonce(_ int64, _ uint64) (*cctxtypes.CrossChainTx, error) { +func (z *MockZetaCoreBridge) GetCctxByNonce(_ int64, _ uint64) (*cctxtypes.CrossChainTx, error) { + if z.paused { + return nil, errors.New(ErrMsgPaused) + } return &cctxtypes.CrossChainTx{}, nil } -func (z ZetaCoreBridge) GetOutTxTracker(_ common.Chain, _ uint64) (*cctxtypes.OutTxTracker, error) { +func (z *MockZetaCoreBridge) GetOutTxTracker(_ common.Chain, _ uint64) (*cctxtypes.OutTxTracker, error) { + if z.paused { + return nil, errors.New(ErrMsgPaused) + } return &cctxtypes.OutTxTracker{}, nil } -func (z ZetaCoreBridge) GetAllOutTxTrackerByChain(_ int64, _ interfaces.Order) ([]cctxtypes.OutTxTracker, error) { +func (z *MockZetaCoreBridge) GetAllOutTxTrackerByChain(_ int64, _ interfaces.Order) ([]cctxtypes.OutTxTracker, error) { + if z.paused { + return nil, errors.New(ErrMsgPaused) + } return []cctxtypes.OutTxTracker{}, nil } -func (z ZetaCoreBridge) GetCrosschainFlags() (observerTypes.CrosschainFlags, error) { +func (z *MockZetaCoreBridge) GetCrosschainFlags() (observerTypes.CrosschainFlags, error) { + if z.paused { + return observerTypes.CrosschainFlags{}, errors.New(ErrMsgPaused) + } return observerTypes.CrosschainFlags{}, nil } -func (z ZetaCoreBridge) GetObserverList() ([]string, error) { +func (z *MockZetaCoreBridge) GetObserverList() ([]string, error) { + if z.paused { + return nil, errors.New(ErrMsgPaused) + } return []string{}, nil } -func (z ZetaCoreBridge) GetKeyGen() (*observerTypes.Keygen, error) { +func (z *MockZetaCoreBridge) GetKeyGen() (*observerTypes.Keygen, error) { + if z.paused { + return nil, errors.New(ErrMsgPaused) + } return &observerTypes.Keygen{}, nil } -func (z ZetaCoreBridge) GetBtcTssAddress(_ int64) (string, error) { +func (z *MockZetaCoreBridge) GetBtcTssAddress(_ int64) (string, error) { + if z.paused { + return "", errors.New(ErrMsgPaused) + } return testutils.TSSAddressBTCMainnet, nil } -func (z ZetaCoreBridge) GetInboundTrackersForChain(_ int64) ([]cctxtypes.InTxTracker, error) { +func (z *MockZetaCoreBridge) GetInboundTrackersForChain(_ int64) ([]cctxtypes.InTxTracker, error) { + if z.paused { + return nil, errors.New(ErrMsgPaused) + } return []cctxtypes.InTxTracker{}, nil } -func (z ZetaCoreBridge) GetLogger() *zerolog.Logger { +func (z *MockZetaCoreBridge) GetLogger() *zerolog.Logger { return nil } -func (z ZetaCoreBridge) ZetaChain() common.Chain { +func (z *MockZetaCoreBridge) ZetaChain() common.Chain { return z.zetaChain } -func (z ZetaCoreBridge) Pause() { -} - -func (z ZetaCoreBridge) Unpause() { +func (z *MockZetaCoreBridge) Pause() { + z.paused = true } -func (z ZetaCoreBridge) GetZetaHotKeyBalance() (math.Int, error) { - return math.NewInt(0), nil +func (z *MockZetaCoreBridge) Unpause() { + z.paused = false } -func NewZetaCoreBridge() *ZetaCoreBridge { - zetaChain, err := common.ZetaChainFromChainID("zetachain_7000-1") - if err != nil { - panic(err) +func (z *MockZetaCoreBridge) GetZetaHotKeyBalance() (math.Int, error) { + if z.paused { + return math.NewInt(0), errors.New(ErrMsgPaused) } - return &ZetaCoreBridge{zetaChain: zetaChain} + return math.NewInt(0), nil } diff --git a/zetaclient/testutils/stub/evm_json_rpc.go b/zetaclient/testutils/stub/evm_json_rpc.go new file mode 100644 index 0000000000..cd80966660 --- /dev/null +++ b/zetaclient/testutils/stub/evm_json_rpc.go @@ -0,0 +1,75 @@ +package stub + +import ( + "errors" + + "github.com/onrik/ethrpc" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" +) + +// EvmClient interface +var _ interfaces.EVMJSONRPCClient = &MockJSONRPCClient{} + +// MockJSONRPCClient is a mock implementation of the EVMJSONRPCClient interface +type MockJSONRPCClient struct { + Blocks []*ethrpc.Block + Transactions []*ethrpc.Transaction +} + +// NewMockJSONRPCClient creates a new mock JSON RPC client +func NewMockJSONRPCClient() *MockJSONRPCClient { + client := &MockJSONRPCClient{} + return client.Reset() +} + +// EthGetBlockByNumber returns a pre-loaded block or nil +func (e *MockJSONRPCClient) EthGetBlockByNumber(_ int, _ bool) (*ethrpc.Block, error) { + // pop a block from the list + if len(e.Blocks) > 0 { + block := e.Blocks[len(e.Blocks)-1] + e.Blocks = e.Blocks[:len(e.Blocks)-1] + return block, nil + } + return nil, errors.New("no block found") +} + +// EthGetTransactionByHash returns a pre-loaded transaction or nil +func (e *MockJSONRPCClient) EthGetTransactionByHash(_ string) (*ethrpc.Transaction, error) { + // pop a transaction from the list + if len(e.Transactions) > 0 { + tx := e.Transactions[len(e.Transactions)-1] + e.Transactions = e.Transactions[:len(e.Transactions)-1] + return tx, nil + } + return nil, errors.New("no transaction found") +} + +// Reset clears the mock data +func (e *MockJSONRPCClient) Reset() *MockJSONRPCClient { + e.Blocks = []*ethrpc.Block{} + e.Transactions = []*ethrpc.Transaction{} + return e +} + +// ---------------------------------------------------------------------------- +// Feed data to the mock JSON RPC client for testing +// ---------------------------------------------------------------------------- +func (e *MockJSONRPCClient) WithBlock(block *ethrpc.Block) *MockJSONRPCClient { + e.Blocks = append(e.Blocks, block) + return e +} + +func (e *MockJSONRPCClient) WithBlocks(blocks []*ethrpc.Block) *MockJSONRPCClient { + e.Blocks = append(e.Blocks, blocks...) + return e +} + +func (e *MockJSONRPCClient) WithTransaction(tx *ethrpc.Transaction) *MockJSONRPCClient { + e.Transactions = append(e.Transactions, tx) + return e +} + +func (e *MockJSONRPCClient) WithTransactions(txs []*ethrpc.Transaction) *MockJSONRPCClient { + e.Transactions = append(e.Transactions, txs...) + return e +} diff --git a/zetaclient/testutils/stub/evm_rpc.go b/zetaclient/testutils/stub/evm_rpc.go index 05f14fc59b..dd1fd5fedf 100644 --- a/zetaclient/testutils/stub/evm_rpc.go +++ b/zetaclient/testutils/stub/evm_rpc.go @@ -1,6 +1,7 @@ package stub import ( + "errors" "math/big" "github.com/ethereum/go-ethereum" @@ -26,73 +27,103 @@ func (s subscription) Err() <-chan error { } // EvmClient interface -var _ interfaces.EVMRPCClient = EvmClient{} +var _ interfaces.EVMRPCClient = &MockEvmClient{} -type EvmClient struct { +type MockEvmClient struct { + Receipts []*ethtypes.Receipt } -func (e EvmClient) SubscribeFilterLogs(_ context.Context, _ ethereum.FilterQuery, _ chan<- ethtypes.Log) (ethereum.Subscription, error) { +func NewMockEvmClient() *MockEvmClient { + client := &MockEvmClient{} + return client.Reset() +} + +func (e *MockEvmClient) SubscribeFilterLogs(_ context.Context, _ ethereum.FilterQuery, _ chan<- ethtypes.Log) (ethereum.Subscription, error) { return subscription{}, nil } -func (e EvmClient) CodeAt(_ context.Context, _ ethcommon.Address, _ *big.Int) ([]byte, error) { +func (e *MockEvmClient) CodeAt(_ context.Context, _ ethcommon.Address, _ *big.Int) ([]byte, error) { return []byte{}, nil } -func (e EvmClient) CallContract(_ context.Context, _ ethereum.CallMsg, _ *big.Int) ([]byte, error) { +func (e *MockEvmClient) CallContract(_ context.Context, _ ethereum.CallMsg, _ *big.Int) ([]byte, error) { return []byte{}, nil } -func (e EvmClient) HeaderByNumber(_ context.Context, _ *big.Int) (*ethtypes.Header, error) { +func (e *MockEvmClient) HeaderByNumber(_ context.Context, _ *big.Int) (*ethtypes.Header, error) { return ðtypes.Header{}, nil } -func (e EvmClient) PendingCodeAt(_ context.Context, _ ethcommon.Address) ([]byte, error) { +func (e *MockEvmClient) PendingCodeAt(_ context.Context, _ ethcommon.Address) ([]byte, error) { return []byte{}, nil } -func (e EvmClient) PendingNonceAt(_ context.Context, _ ethcommon.Address) (uint64, error) { +func (e *MockEvmClient) PendingNonceAt(_ context.Context, _ ethcommon.Address) (uint64, error) { return 0, nil } -func (e EvmClient) SuggestGasPrice(_ context.Context) (*big.Int, error) { +func (e *MockEvmClient) SuggestGasPrice(_ context.Context) (*big.Int, error) { return big.NewInt(0), nil } -func (e EvmClient) SuggestGasTipCap(_ context.Context) (*big.Int, error) { +func (e *MockEvmClient) SuggestGasTipCap(_ context.Context) (*big.Int, error) { return big.NewInt(0), nil } -func (e EvmClient) EstimateGas(_ context.Context, _ ethereum.CallMsg) (gas uint64, err error) { +func (e *MockEvmClient) EstimateGas(_ context.Context, _ ethereum.CallMsg) (gas uint64, err error) { gas = 0 err = nil return } -func (e EvmClient) SendTransaction(_ context.Context, _ *ethtypes.Transaction) error { +func (e *MockEvmClient) SendTransaction(_ context.Context, _ *ethtypes.Transaction) error { return nil } -func (e EvmClient) FilterLogs(_ context.Context, _ ethereum.FilterQuery) ([]ethtypes.Log, error) { +func (e *MockEvmClient) FilterLogs(_ context.Context, _ ethereum.FilterQuery) ([]ethtypes.Log, error) { return []ethtypes.Log{}, nil } -func (e EvmClient) BlockNumber(_ context.Context) (uint64, error) { +func (e *MockEvmClient) BlockNumber(_ context.Context) (uint64, error) { return 0, nil } -func (e EvmClient) BlockByNumber(_ context.Context, _ *big.Int) (*ethtypes.Block, error) { +func (e *MockEvmClient) BlockByNumber(_ context.Context, _ *big.Int) (*ethtypes.Block, error) { return ðtypes.Block{}, nil } -func (e EvmClient) TransactionByHash(_ context.Context, _ ethcommon.Hash) (tx *ethtypes.Transaction, isPending bool, err error) { +func (e *MockEvmClient) TransactionByHash(_ context.Context, _ ethcommon.Hash) (tx *ethtypes.Transaction, isPending bool, err error) { return ðtypes.Transaction{}, false, nil } -func (e EvmClient) TransactionReceipt(_ context.Context, _ ethcommon.Hash) (*ethtypes.Receipt, error) { - return ðtypes.Receipt{}, nil +func (e *MockEvmClient) TransactionReceipt(_ context.Context, _ ethcommon.Hash) (*ethtypes.Receipt, error) { + // pop a receipt from the list + if len(e.Receipts) > 0 { + receipt := e.Receipts[len(e.Receipts)-1] + e.Receipts = e.Receipts[:len(e.Receipts)-1] + return receipt, nil + } + return nil, errors.New("no receipt found") } -func (e EvmClient) TransactionSender(_ context.Context, _ *ethtypes.Transaction, _ ethcommon.Hash, _ uint) (ethcommon.Address, error) { +func (e *MockEvmClient) TransactionSender(_ context.Context, _ *ethtypes.Transaction, _ ethcommon.Hash, _ uint) (ethcommon.Address, error) { return ethcommon.Address{}, nil } + +func (e *MockEvmClient) Reset() *MockEvmClient { + e.Receipts = []*ethtypes.Receipt{} + return e +} + +// ---------------------------------------------------------------------------- +// Feed data to the mock evm client for testing +// ---------------------------------------------------------------------------- +func (e *MockEvmClient) WithReceipt(receipt *ethtypes.Receipt) *MockEvmClient { + e.Receipts = append(e.Receipts, receipt) + return e +} + +func (e *MockEvmClient) WithReceipts(receipts []*ethtypes.Receipt) *MockEvmClient { + e.Receipts = append(e.Receipts, receipts...) + return e +} diff --git a/zetaclient/types/ethish_test.go b/zetaclient/types/ethish_test.go new file mode 100644 index 0000000000..d932b46501 --- /dev/null +++ b/zetaclient/types/ethish_test.go @@ -0,0 +1 @@ +package types_test diff --git a/zetaclient/zetabridge/zetacore_bridge.go b/zetaclient/zetabridge/zetacore_bridge.go index fbe5187088..babdc505bf 100644 --- a/zetaclient/zetabridge/zetacore_bridge.go +++ b/zetaclient/zetabridge/zetacore_bridge.go @@ -221,7 +221,7 @@ func (b *ZetaCoreBridge) UpdateZetaCoreContext(coreContext *corecontext.ZetaCore // check and update chain params for each chain for _, chainParam := range chainParams { - err := config.ValidateChainParams(chainParam) + err := observertypes.ValidateChainParams(chainParam) if err != nil { b.logger.Warn().Err(err).Msgf("Invalid chain params for chain %d", chainParam.ChainId) continue diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index 6d3320c746..429c209287 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -168,7 +168,7 @@ func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { } // update last processed block number lastBlockNum = bn - co.ts.SetCoreBlockNumber(lastBlockNum) + metrics.LastCoreBlockNumber.Set(float64(lastBlockNum)) } } }