From b65c38c2cd6bb124a3493c627eafcd786dfe5e1e Mon Sep 17 00:00:00 2001 From: Tanmay Date: Wed, 11 Dec 2024 10:42:38 -0500 Subject: [PATCH 1/7] test: initialize simulation tests for custom zetachain modules (#3095) * add crosschain decoders * decoders test * add oeprations * add fungible deploy contracts * add fungible deploy contracts * add simulation for observer module * add comments for crosschain operations * add comments for observer and fungible operations * generate files * debug import export test * fix import export tests * fix import export tests * fix app determinism test * update codecov.yml * reduce weight for DeployedSystemContracts operation * add new function to generate eth address from provided randomness * Update x/fungible/simulation/decoders.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update testutil/sample/sample.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * add validations * add validations * add randmoness to tss * make requested changes 1 * update state.go file * update state.go file * change chains.IsEVMChain to chains.IsEthereumChain in deposit tests * add decoder for tss history * remove unnecessary todo * remove unnecessary todo * remove unused keys * Update simulation/simulation_test.go Co-authored-by: skosito * fix comments and remove unused functions * fix comments and remove unused functions * improve comment wording for operation weights * improve comment wording for operation weights * rebase develop --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: skosito --- Makefile | 2 +- app/app.go | 2 + app/modules.go | 3 + changelog.md | 1 + codecov.yml | 2 +- contrib/docker-scripts/start.sh | 2 +- server/start.go | 16 +- simulation/simulation_test.go | 74 ++- simulation/state.go | 345 +++++++----- testutil/keeper/crosschain.go | 2 + testutil/keeper/emissions.go | 2 + testutil/keeper/fungible.go | 2 + testutil/keeper/ibccrosschain.go | 2 + testutil/keeper/keeper.go | 2 + testutil/keeper/mocks/crosschain/account.go | 20 + testutil/keeper/mocks/crosschain/bank.go | 20 + testutil/keeper/mocks/fungible/authority.go | 32 +- testutil/keeper/mocks/fungible/bank.go | 20 + testutil/keeper/mocks/observer/authority.go | 28 + testutil/keeper/observer.go | 10 + testutil/sample/crosschain.go | 25 + testutil/sample/crypto.go | 4 + testutil/sample/observer.go | 21 + testutil/sample/sample.go | 6 + .../cctx_orchestrator_validate_inbound.go | 1 - .../keeper/msg_server_vote_inbound_tx.go | 3 +- .../keeper/msg_server_vote_inbound_tx_test.go | 7 +- x/crosschain/module_simulation.go | 23 +- x/crosschain/simulation/decoders.go | 62 +++ x/crosschain/simulation/decoders_test.go | 60 +++ x/crosschain/simulation/operations.go | 492 ++++++++++++++++++ x/crosschain/simulation/simap.go | 15 - x/crosschain/types/expected_keepers.go | 2 + x/fungible/keeper/evm.go | 1 + x/fungible/keeper/keeper.go | 8 +- ...msg_server_update_gateway_contract_test.go | 3 +- x/fungible/module.go | 4 +- x/fungible/module_simulation.go | 23 +- x/fungible/simulation/decoders.go | 35 ++ x/fungible/simulation/decoders_test.go | 43 ++ x/fungible/simulation/operations.go | 116 +++++ x/fungible/simulation/simap.go | 15 - x/fungible/types/expected_keepers.go | 3 + x/observer/keeper/keeper.go | 20 +- .../keeper/msg_server_disable_cctx_flags.go | 1 - x/observer/module.go | 4 +- x/observer/module_simulation.go | 34 +- x/observer/simulation/decoders.go | 97 ++++ x/observer/simulation/decoders_test.go | 88 ++++ x/observer/simulation/operations.go | 107 ++++ x/observer/simulation/simap.go | 15 - x/observer/types/expected_keepers.go | 11 + x/observer/types/keys.go | 21 +- zetaclient/testutils/mocks/zetacore_client.go | 2 +- 54 files changed, 1687 insertions(+), 272 deletions(-) create mode 100644 x/crosschain/simulation/decoders.go create mode 100644 x/crosschain/simulation/decoders_test.go create mode 100644 x/crosschain/simulation/operations.go delete mode 100644 x/crosschain/simulation/simap.go create mode 100644 x/fungible/simulation/decoders.go create mode 100644 x/fungible/simulation/decoders_test.go create mode 100644 x/fungible/simulation/operations.go delete mode 100644 x/fungible/simulation/simap.go create mode 100644 x/observer/simulation/decoders.go create mode 100644 x/observer/simulation/decoders_test.go create mode 100644 x/observer/simulation/operations.go delete mode 100644 x/observer/simulation/simap.go diff --git a/Makefile b/Makefile index 82e9c89a0a..e2d34c31f4 100644 --- a/Makefile +++ b/Makefile @@ -409,7 +409,7 @@ test-sim-fullappsimulation: $(call run-sim-test,"TestFullAppSimulation",TestFullAppSimulation,100,200,30m) test-sim-import-export: - $(call run-sim-test,"test-import-export",TestAppImportExport,100,200,30m) + $(call run-sim-test,"test-import-export",TestAppImportExport,50,100,30m) test-sim-after-import: $(call run-sim-test,"test-sim-after-import",TestAppSimulationAfterImport,100,200,30m) diff --git a/app/app.go b/app/app.go index 90bbe8576b..c79552bc74 100644 --- a/app/app.go +++ b/app/app.go @@ -481,6 +481,8 @@ func New( app.SlashingKeeper, app.AuthorityKeeper, app.LightclientKeeper, + app.BankKeeper, + app.AccountKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/app/modules.go b/app/modules.go index 6f164a9409..0feeb7cb9a 100644 --- a/app/modules.go +++ b/app/modules.go @@ -178,5 +178,8 @@ func simulationModules( evm.NewAppModule(app.EvmKeeper, app.AccountKeeper, app.GetSubspace(evmtypes.ModuleName)), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + crosschainmodule.NewAppModule(appCodec, app.CrosschainKeeper), + observermodule.NewAppModule(appCodec, *app.ObserverKeeper), + fungiblemodule.NewAppModule(appCodec, app.FungibleKeeper), } } diff --git a/changelog.md b/changelog.md index 45eb187472..791af078fe 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ * [3205](https://github.com/zeta-chain/node/issues/3205) - move Bitcoin revert address test to advanced group to avoid upgrade test failure * [3254](https://github.com/zeta-chain/node/pull/3254) - rename v2 E2E tests as evm tests and rename old evm tests as legacy +* [3095](https://github.com/zeta-chain/node/pull/3095) - initialize simulation tests for custom zetachain modules ## Refactor diff --git a/codecov.yml b/codecov.yml index da90e44bd9..092dd4baa4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -52,7 +52,7 @@ ignore: - "x/**/events.go" - "x/**/migrator.go" - "x/**/module_simulation.go" - - "x/**/simulation/**/*" + - "x/**/simulation/*.go" - "**/*.proto" - "**/*.md" - "**/*.yml" diff --git a/contrib/docker-scripts/start.sh b/contrib/docker-scripts/start.sh index 6d79effa57..bb151a49fe 100644 --- a/contrib/docker-scripts/start.sh +++ b/contrib/docker-scripts/start.sh @@ -332,4 +332,4 @@ else logt "Start Network" start_network -fi +fi \ No newline at end of file diff --git a/server/start.go b/server/start.go index 14c64482f8..9e2711d265 100644 --- a/server/start.go +++ b/server/start.go @@ -172,7 +172,7 @@ which accepts a path for the resulting pprof file. cmd.Flags(). String(server.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photon;0.0001stake)") - //nolint:lll + //nolint:lll cmd.Flags(). IntSlice(server.FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary") cmd.Flags(). @@ -189,7 +189,7 @@ which accepts a path for the resulting pprof file. cmd.Flags(). Uint64(server.FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')") - //nolint:lll + //nolint:lll cmd.Flags().Uint(server.FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks") cmd.Flags(). Uint64(server.FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks") @@ -217,11 +217,11 @@ which accepts a path for the resulting pprof file. cmd.Flags(). Uint64(srvflags.JSONRPCGasCap, config.DefaultGasCap, "Sets a cap on gas that can be used in eth_call/estimateGas unit is aphoton (0=infinite)") - //nolint:lll + //nolint:lll cmd.Flags(). Float64(srvflags.JSONRPCTxFeeCap, config.DefaultTxFeeCap, "Sets a cap on transaction fee that can be sent via the RPC APIs (1 = default 1 photon)") - //nolint:lll + //nolint:lll cmd.Flags(). Int32(srvflags.JSONRPCFilterCap, config.DefaultFilterCap, "Sets the global cap for total number of filters that can be created") cmd.Flags(). @@ -233,7 +233,7 @@ which accepts a path for the resulting pprof file. cmd.Flags(). Bool(srvflags.JSONRPCAllowUnprotectedTxs, config.DefaultAllowUnprotectedTxs, "Allow for unprotected (non EIP155 signed) transactions to be submitted via the node's RPC when the global parameter is disabled") - //nolint:lll + //nolint:lll cmd.Flags(). Int32(srvflags.JSONRPCLogsCap, config.DefaultLogsCap, "Sets the max number of results can be returned from single `eth_getLogs` query") cmd.Flags(). @@ -241,18 +241,18 @@ which accepts a path for the resulting pprof file. cmd.Flags(). Int(srvflags.JSONRPCMaxOpenConnections, config.DefaultMaxOpenConnections, "Sets the maximum number of simultaneous connections for the server listener") - //nolint:lll + //nolint:lll cmd.Flags().Bool(srvflags.JSONRPCEnableIndexer, false, "Enable the custom tx indexer for json-rpc") cmd.Flags().Bool(srvflags.JSONRPCEnableMetrics, false, "Define if EVM rpc metrics server should be enabled") cmd.Flags(). String(srvflags.EVMTracer, config.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)") - //nolint:lll + //nolint:lll cmd.Flags(). Uint64(srvflags.EVMMaxTxGasWanted, config.DefaultMaxTxGasWanted, "the gas wanted for each eth tx returned in ante handler in check tx mode") - //nolint:lll + //nolint:lll cmd.Flags().String(srvflags.TLSCertPath, "", "the cert.pem file path for the server TLS configuration") cmd.Flags().String(srvflags.TLSKeyPath, "", "the key.pem file path for the server TLS configuration") diff --git a/simulation/simulation_test.go b/simulation/simulation_test.go index 3f39c77fa1..818250cbaf 100644 --- a/simulation/simulation_test.go +++ b/simulation/simulation_test.go @@ -10,29 +10,30 @@ import ( abci "github.com/cometbft/cometbft/abci/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" + cosmossimutils "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + cosmossim "github.com/cosmos/cosmos-sdk/types/simulation" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + cosmossimcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" "github.com/zeta-chain/node/app" zetasimulation "github.com/zeta-chain/node/simulation" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/server" - cosmossimutils "github.com/cosmos/cosmos-sdk/testutil/sims" - cosmossim "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" - cosmossimcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" + observertypes "github.com/zeta-chain/node/x/observer/types" ) // AppChainID hardcoded chainID for simulation @@ -41,10 +42,12 @@ func init() { zetasimulation.GetSimulatorFlags() } +// StoreKeysPrefixes defines a struct used in comparing two keys for two different stores +// SkipPrefixes is used to skip certain prefixes when comparing the stores type StoreKeysPrefixes struct { - A storetypes.StoreKey - B storetypes.StoreKey - Prefixes [][]byte + A storetypes.StoreKey + B storetypes.StoreKey + SkipPrefixes [][]byte } const ( @@ -133,9 +136,11 @@ func TestAppStateDeterminism(t *testing.T) { os.Stdout, simApp.BaseApp, zetasimulation.AppStateFn( + t, simApp.AppCodec(), simApp.SimulationManager(), simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + nil, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), @@ -222,9 +227,11 @@ func TestFullAppSimulation(t *testing.T) { os.Stdout, simApp.BaseApp, zetasimulation.AppStateFn( + t, simApp.AppCodec(), simApp.SimulationManager(), simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + nil, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), @@ -292,9 +299,11 @@ func TestAppImportExport(t *testing.T) { os.Stdout, simApp.BaseApp, zetasimulation.AppStateFn( + t, simApp.AppCodec(), simApp.SimulationManager(), simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + nil, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), @@ -314,7 +323,6 @@ func TestAppImportExport(t *testing.T) { exported, err := simApp.ExportAppStateAndValidators(false, []string{}, []string{}) require.NoError(t, err) - t.Log("importing genesis") newDB, newDir, _, _, err := cosmossimutils.SetupSimulation( config, SimDBBackend+"_new", @@ -360,20 +368,29 @@ func TestAppImportExport(t *testing.T) { ctxSimApp := simApp.NewContext(true, tmproto.Header{ Height: simApp.LastBlockHeight(), ChainID: SimAppChainID, - }).WithChainID(SimAppChainID) + }) + ctxNewSimApp := newSimApp.NewContext(true, tmproto.Header{ Height: simApp.LastBlockHeight(), ChainID: SimAppChainID, - }).WithChainID(SimAppChainID) + }) + t.Log("initializing genesis for the new app using exported genesis state") // Use genesis state from the first app to initialize the second app newSimApp.ModuleManager().InitGenesis(ctxNewSimApp, newSimApp.AppCodec(), genesisState) newSimApp.StoreConsensusParams(ctxNewSimApp, exported.ConsensusParams) t.Log("comparing stores") + // The ordering of the keys is not important, we compare the same prefix for both simulations storeKeysPrefixes := []StoreKeysPrefixes{ - {simApp.GetKey(authtypes.StoreKey), newSimApp.GetKey(authtypes.StoreKey), [][]byte{}}, + // Interaction with EVM module, + // such as deploying contracts or interacting with them such as setting gas price, + // causes the state for the auth module to change on export.The order of keys within the store is modified. + // We will need to explore this further to find a definitive answer + // TODO:https://github.com/zeta-chain/node/issues/3263 + + // {simApp.GetKey(authtypes.StoreKey), newSimApp.GetKey(authtypes.StoreKey), [][]byte{}}, { simApp.GetKey(stakingtypes.StoreKey), newSimApp.GetKey(stakingtypes.StoreKey), [][]byte{ @@ -388,13 +405,23 @@ func TestAppImportExport(t *testing.T) { {simApp.GetKey(govtypes.StoreKey), newSimApp.GetKey(govtypes.StoreKey), [][]byte{}}, {simApp.GetKey(evidencetypes.StoreKey), newSimApp.GetKey(evidencetypes.StoreKey), [][]byte{}}, {simApp.GetKey(evmtypes.StoreKey), newSimApp.GetKey(evmtypes.StoreKey), [][]byte{}}, + {simApp.GetKey(crosschaintypes.StoreKey), newSimApp.GetKey(crosschaintypes.StoreKey), [][]byte{ + // We update the timestamp for cctx when importing the genesis state which results in a different value + crosschaintypes.KeyPrefix(crosschaintypes.CCTXKey), + }}, + + {simApp.GetKey(observertypes.StoreKey), newSimApp.GetKey(observertypes.StoreKey), [][]byte{ + // The order of ballots when importing is not preserved which causes the value to be different. + observertypes.KeyPrefix(observertypes.BallotListKey), + }}, + {simApp.GetKey(fungibletypes.StoreKey), newSimApp.GetKey(fungibletypes.StoreKey), [][]byte{}}, } for _, skp := range storeKeysPrefixes { storeA := ctxSimApp.KVStore(skp.A) storeB := ctxNewSimApp.KVStore(skp.B) - failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) + failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.SkipPrefixes) require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") t.Logf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) @@ -459,9 +486,11 @@ func TestAppSimulationAfterImport(t *testing.T) { os.Stdout, simApp.BaseApp, zetasimulation.AppStateFn( + t, simApp.AppCodec(), simApp.SimulationManager(), simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + nil, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(simApp, simApp.AppCodec(), config), @@ -487,8 +516,6 @@ func TestAppSimulationAfterImport(t *testing.T) { exported, err := simApp.ExportAppStateAndValidators(true, []string{}, []string{}) require.NoError(t, err) - t.Log("importing genesis") - newDB, newDir, _, _, err := cosmossimutils.SetupSimulation( config, SimDBBackend+"_new", @@ -517,6 +544,7 @@ func TestAppSimulationAfterImport(t *testing.T) { ) require.NoError(t, err) + t.Log("Importing genesis into the new app") newSimApp.InitChain(abci.RequestInitChain{ ChainId: SimAppChainID, AppStateBytes: exported.AppState, @@ -527,9 +555,11 @@ func TestAppSimulationAfterImport(t *testing.T) { os.Stdout, newSimApp.BaseApp, zetasimulation.AppStateFn( - simApp.AppCodec(), - simApp.SimulationManager(), - simApp.BasicManager().DefaultGenesis(simApp.AppCodec()), + t, + nil, + nil, + nil, + exported.AppState, ), cosmossim.RandomAccounts, cosmossimutils.SimulationOperations(newSimApp, newSimApp.AppCodec(), config), diff --git a/simulation/state.go b/simulation/state.go index 540cd0aca7..9bf2c24949 100644 --- a/simulation/state.go +++ b/simulation/state.go @@ -6,6 +6,7 @@ import ( "io" "math/rand" "os" + "testing" "time" "cosmossdk.io/math" @@ -19,24 +20,228 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" zetaapp "github.com/zeta-chain/node/app" + "github.com/zeta-chain/node/testutil/sample" + authoritytypes "github.com/zeta-chain/node/x/authority/types" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" + observertypes "github.com/zeta-chain/node/x/observer/types" ) -// Simulation parameter constants +// simulation parameter constants const ( StakePerAccount = "stake_per_account" InitiallyBondedValidators = "initially_bonded_validators" ) +func updateBankState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + notBondedCoins sdk.Coin, +) *banktypes.GenesisState { + bankStateBz, ok := rawState[banktypes.ModuleName] + require.True(t, ok, "bank genesis state is missing") + + bankState := new(banktypes.GenesisState) + err := cdc.UnmarshalJSON(bankStateBz, bankState) + require.NoError(t, err) + + stakingAddr := authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String() + var found bool + for _, balance := range bankState.Balances { + if balance.Address == stakingAddr { + found = true + break + } + } + if !found { + bankState.Balances = append(bankState.Balances, banktypes.Balance{ + Address: stakingAddr, + Coins: sdk.NewCoins(notBondedCoins), + }) + } + + return bankState +} + +func updateEVMState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + bondDenom string, +) *evmtypes.GenesisState { + evmStateBz, ok := rawState[evmtypes.ModuleName] + require.True(t, ok, "evm genesis state is missing") + + evmState := new(evmtypes.GenesisState) + cdc.MustUnmarshalJSON(evmStateBz, evmState) + + // replace the EvmDenom with BondDenom + evmState.Params.EvmDenom = bondDenom + + return evmState +} + +func updateStakingState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, +) (*stakingtypes.GenesisState, sdk.Coin) { + stakingStateBz, ok := rawState[stakingtypes.ModuleName] + require.True(t, ok, "staking genesis state is missing") + + stakingState := new(stakingtypes.GenesisState) + err := cdc.UnmarshalJSON(stakingStateBz, stakingState) + if err != nil { + panic(err) + } + + // compute not bonded balance + notBondedTokens := math.ZeroInt() + for _, val := range stakingState.Validators { + if val.Status != stakingtypes.Unbonded { + continue + } + notBondedTokens = notBondedTokens.Add(val.GetTokens()) + } + notBondedCoins := sdk.NewCoin(stakingState.Params.BondDenom, notBondedTokens) + + return stakingState, notBondedCoins +} + +func updateObserverState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + r *rand.Rand, + validators stakingtypes.Validators, +) *observertypes.GenesisState { + observerStateBz, ok := rawState[observertypes.ModuleName] + require.True(t, ok, "observer genesis state is missing") + + observerState := new(observertypes.GenesisState) + cdc.MustUnmarshalJSON(observerStateBz, observerState) + + observers := make([]string, 0) + for _, validator := range validators { + accAddress, err := observertypes.GetAccAddressFromOperatorAddress(validator.OperatorAddress) + if err != nil { + continue + } + observers = append(observers, accAddress.String()) + } + + r.Shuffle(len(observers), func(i, j int) { + observers[i], observers[j] = observers[j], observers[i] + }) + + numObservers := r.Intn(11) + 5 + if numObservers > len(observers) { + numObservers = len(observers) + } + observers = observers[:numObservers] + + observerState.Observers.ObserverList = observers + observerState.CrosschainFlags.IsInboundEnabled = true + observerState.CrosschainFlags.IsOutboundEnabled = true + + tss := sample.TSSFromRand(t, r) + tss.OperatorAddressList = observers + observerState.Tss = &tss + + return observerState +} + +func updateAuthorityState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + r *rand.Rand, + accs []simtypes.Account, +) *authoritytypes.GenesisState { + authorityStateBz, ok := rawState[authoritytypes.ModuleName] + require.True(t, ok, "authority genesis state is missing") + + authorityState := new(authoritytypes.GenesisState) + cdc.MustUnmarshalJSON(authorityStateBz, authorityState) + + randomAccount := accs[r.Intn(len(accs))] + policies := authoritytypes.Policies{ + Items: []*authoritytypes.Policy{ + { + Address: randomAccount.Address.String(), + PolicyType: authoritytypes.PolicyType_groupEmergency, + }, + { + Address: randomAccount.Address.String(), + PolicyType: authoritytypes.PolicyType_groupAdmin, + }, + { + Address: randomAccount.Address.String(), + PolicyType: authoritytypes.PolicyType_groupOperational, + }, + }, + } + authorityState.Policies = policies + + return authorityState +} + +func updateFungibleState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + r *rand.Rand, +) *fungibletypes.GenesisState { + fungibleStateBz, ok := rawState[fungibletypes.ModuleName] + require.True(t, ok, "fungible genesis state is missing") + + fungibleState := new(fungibletypes.GenesisState) + cdc.MustUnmarshalJSON(fungibleStateBz, fungibleState) + fungibleState.SystemContract = &fungibletypes.SystemContract{ + SystemContract: sample.EthAddressFromRand(r).String(), + ConnectorZevm: sample.EthAddressFromRand(r).String(), + Gateway: sample.EthAddressFromRand(r).String(), + } + + return fungibleState +} + +func updateRawState( + t *testing.T, + rawState map[string]json.RawMessage, + cdc codec.Codec, + r *rand.Rand, + accs []simtypes.Account, +) { + stakingState, notBondedCoins := updateStakingState(t, rawState, cdc) + bankState := updateBankState(t, rawState, cdc, notBondedCoins) + evmState := updateEVMState(t, rawState, cdc, stakingState.Params.BondDenom) + observerState := updateObserverState(t, rawState, cdc, r, stakingState.Validators) + authorityState := updateAuthorityState(t, rawState, cdc, r, accs) + fungibleState := updateFungibleState(t, rawState, cdc, r) + + rawState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingState) + rawState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankState) + rawState[evmtypes.ModuleName] = cdc.MustMarshalJSON(evmState) + rawState[observertypes.ModuleName] = cdc.MustMarshalJSON(observerState) + rawState[authoritytypes.ModuleName] = cdc.MustMarshalJSON(authorityState) + rawState[fungibletypes.ModuleName] = cdc.MustMarshalJSON(fungibleState) +} + // AppStateFn returns the initial application state using a genesis or the simulation parameters. // It panics if the user provides files for both of them. // If a file is not given for the genesis or the sim params, it creates a randomized one. +// All modifications to the genesis state should be done in this function. func AppStateFn( + t *testing.T, cdc codec.Codec, simManager *module.SimulationManager, genesisState map[string]json.RawMessage, + exportedState json.RawMessage, ) simtypes.AppStateFn { return func(r *rand.Rand, accs []simtypes.Account, config simtypes.Config, ) (appState json.RawMessage, simAccs []simtypes.Account, chainID string, genesisTimestamp time.Time) { @@ -47,135 +252,35 @@ func AppStateFn( } chainID = config.ChainID - switch { - case config.ParamsFile != "" && config.GenesisFile != "": - panic("cannot provide both a genesis file and a params file") - - case config.GenesisFile != "": - // override the default chain-id from simapp to set it later to the config - genesisDoc, accounts, err := AppStateFromGenesisFileFn(r, cdc, config.GenesisFile) - if err != nil { - panic(err) - } - - if FlagGenesisTimeValue == 0 { - // use genesis timestamp if no custom timestamp is provided (i.e no random timestamp) - genesisTimestamp = genesisDoc.GenesisTime - } - - appState = genesisDoc.AppState - chainID = genesisDoc.ChainID - simAccs = accounts - - case config.ParamsFile != "": - appParams := make(simtypes.AppParams) - bz, err := os.ReadFile(config.ParamsFile) - if err != nil { - panic(err) - } - - err = json.Unmarshal(bz, &appParams) - if err != nil { - panic(err) - } - appState, simAccs = AppStateRandomizedFn( - simManager, - r, - cdc, - accs, - genesisTimestamp, - appParams, - genesisState, - ) - - default: - appParams := make(simtypes.AppParams) - appState, simAccs = AppStateRandomizedFn( - simManager, - r, - cdc, - accs, - genesisTimestamp, - appParams, - genesisState, - ) - } - rawState := make(map[string]json.RawMessage) - err := json.Unmarshal(appState, &rawState) - if err != nil { - panic(err) + // if exported state is provided then use it + if exportedState != nil { + return exportedState, accs, chainID, genesisTimestamp } - stakingStateBz, ok := rawState[stakingtypes.ModuleName] - if !ok { - panic("staking genesis state is missing") - } + appParams := make(simtypes.AppParams) + appState, simAccs = AppStateRandomizedFn( + simManager, + r, + cdc, + accs, + genesisTimestamp, + appParams, + genesisState, + ) - stakingState := new(stakingtypes.GenesisState) - err = cdc.UnmarshalJSON(stakingStateBz, stakingState) - if err != nil { - panic(err) - } - - // compute not bonded balance - notBondedTokens := math.ZeroInt() - for _, val := range stakingState.Validators { - if val.Status != stakingtypes.Unbonded { - continue - } - notBondedTokens = notBondedTokens.Add(val.GetTokens()) - } - notBondedCoins := sdk.NewCoin(stakingState.Params.BondDenom, notBondedTokens) - - // edit bank state to make it have the not bonded pool tokens - bankStateBz, ok := rawState[banktypes.ModuleName] - if !ok { - panic("bank genesis state is missing") - } - bankState := new(banktypes.GenesisState) - err = cdc.UnmarshalJSON(bankStateBz, bankState) + rawState := make(map[string]json.RawMessage) + err := json.Unmarshal(appState, &rawState) if err != nil { panic(err) } - stakingAddr := authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String() - var found bool - for _, balance := range bankState.Balances { - if balance.Address == stakingAddr { - found = true - break - } - } - if !found { - bankState.Balances = append(bankState.Balances, banktypes.Balance{ - Address: stakingAddr, - Coins: sdk.NewCoins(notBondedCoins), - }) - } - - // Set the bond denom in the EVM genesis state - evmStateBz, ok := rawState[evmtypes.ModuleName] - if !ok { - panic("evm genesis state is missing") - } - - evmState := new(evmtypes.GenesisState) - cdc.MustUnmarshalJSON(evmStateBz, evmState) - - // we should replace the EvmDenom with BondDenom - evmState.Params.EvmDenom = stakingState.Params.BondDenom - - // change appState back - rawState[evmtypes.ModuleName] = cdc.MustMarshalJSON(evmState) - rawState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingState) - rawState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankState) + updateRawState(t, rawState, cdc, r, simAccs) // replace appstate appState, err = json.Marshal(rawState) - if err != nil { - panic(err) - } + require.NoError(t, err) + return appState, simAccs, chainID, genesisTimestamp } } @@ -208,7 +313,7 @@ func AppStateRandomizedFn( numInitiallyBonded = numAccs } - // Set the default power reduction to be one less than the initial stake so that all randomised validators are part of the validator set + // set the default power reduction to be one less than the initial stake so that all randomised validators are part of the validator set sdk.DefaultPowerReduction = initialStake.Sub(sdk.OneInt()) fmt.Printf( diff --git a/testutil/keeper/crosschain.go b/testutil/keeper/crosschain.go index 8418bbdaaf..d0f7d0220c 100644 --- a/testutil/keeper/crosschain.go +++ b/testutil/keeper/crosschain.go @@ -129,6 +129,8 @@ func CrosschainKeeperWithMocks( sdkKeepers.SlashingKeeper, authorityKeeperTmp, lightclientKeeperTmp, + sdkKeepers.BankKeeper, + sdkKeepers.AuthKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testutil/keeper/emissions.go b/testutil/keeper/emissions.go index b2ce7a004c..bfbe2813d9 100644 --- a/testutil/keeper/emissions.go +++ b/testutil/keeper/emissions.go @@ -54,6 +54,8 @@ func EmissionKeeperWithMockOptions( sdkKeepers.StakingKeeper, sdkKeepers.SlashingKeeper, authorityKeeper, + sdkKeepers.BankKeeper, + sdkKeepers.AuthKeeper, initLightclientKeeper(cdc, stateStore, authorityKeeper), ) diff --git a/testutil/keeper/fungible.go b/testutil/keeper/fungible.go index aa0e975c97..5b51d08934 100644 --- a/testutil/keeper/fungible.go +++ b/testutil/keeper/fungible.go @@ -116,6 +116,8 @@ func FungibleKeeperWithMocks( sdkKeepers.SlashingKeeper, authorityKeeperTmp, lightclientKeeperTmp, + sdkKeepers.BankKeeper, + sdkKeepers.AuthKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testutil/keeper/ibccrosschain.go b/testutil/keeper/ibccrosschain.go index 95cf7559e7..5bd222141d 100644 --- a/testutil/keeper/ibccrosschain.go +++ b/testutil/keeper/ibccrosschain.go @@ -77,6 +77,8 @@ func IBCCrosschainKeeperWithMocks( sdkKeepers.StakingKeeper, sdkKeepers.SlashingKeeper, authorityKeeper, + sdkKeepers.BankKeeper, + sdkKeepers.AuthKeeper, lightclientKeeper, ) fungibleKeeper := initFungibleKeeper( diff --git a/testutil/keeper/keeper.go b/testutil/keeper/keeper.go index 5819c6b74b..1342cdfac8 100644 --- a/testutil/keeper/keeper.go +++ b/testutil/keeper/keeper.go @@ -525,6 +525,8 @@ func NewSDKKeepersWithKeys( slashingKeeper, authorityKeeper, lightclientKeeper, + bankKeeper, + authKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) emissionsKeeper := emissionskeeper.NewKeeper( diff --git a/testutil/keeper/mocks/crosschain/account.go b/testutil/keeper/mocks/crosschain/account.go index fbd7c0377b..3ff30725e6 100644 --- a/testutil/keeper/mocks/crosschain/account.go +++ b/testutil/keeper/mocks/crosschain/account.go @@ -14,6 +14,26 @@ type CrosschainAccountKeeper struct { mock.Mock } +// GetAccount provides a mock function with given fields: ctx, addr +func (_m *CrosschainAccountKeeper) GetAccount(ctx types.Context, addr types.AccAddress) authtypes.AccountI { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for GetAccount") + } + + var r0 authtypes.AccountI + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress) authtypes.AccountI); ok { + r0 = rf(ctx, addr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(authtypes.AccountI) + } + } + + return r0 +} + // GetModuleAccount provides a mock function with given fields: ctx, name func (_m *CrosschainAccountKeeper) GetModuleAccount(ctx types.Context, name string) authtypes.ModuleAccountI { ret := _m.Called(ctx, name) diff --git a/testutil/keeper/mocks/crosschain/bank.go b/testutil/keeper/mocks/crosschain/bank.go index 90f4e17e29..7e6a3ae058 100644 --- a/testutil/keeper/mocks/crosschain/bank.go +++ b/testutil/keeper/mocks/crosschain/bank.go @@ -49,6 +49,26 @@ func (_m *CrosschainBankKeeper) MintCoins(ctx types.Context, moduleName string, return r0 } +// SpendableCoins provides a mock function with given fields: ctx, addr +func (_m *CrosschainBankKeeper) SpendableCoins(ctx types.Context, addr types.AccAddress) types.Coins { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for SpendableCoins") + } + + var r0 types.Coins + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress) types.Coins); ok { + r0 = rf(ctx, addr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Coins) + } + } + + return r0 +} + // NewCrosschainBankKeeper creates a new instance of CrosschainBankKeeper. 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 NewCrosschainBankKeeper(t interface { diff --git a/testutil/keeper/mocks/fungible/authority.go b/testutil/keeper/mocks/fungible/authority.go index 214834ac86..ccf891e5f7 100644 --- a/testutil/keeper/mocks/fungible/authority.go +++ b/testutil/keeper/mocks/fungible/authority.go @@ -3,8 +3,10 @@ package mocks import ( - mock "github.com/stretchr/testify/mock" chains "github.com/zeta-chain/node/pkg/chains" + authoritytypes "github.com/zeta-chain/node/x/authority/types" + + mock "github.com/stretchr/testify/mock" types "github.com/cosmos/cosmos-sdk/types" ) @@ -52,6 +54,34 @@ func (_m *FungibleAuthorityKeeper) GetAdditionalChainList(ctx types.Context) []c return r0 } +// GetPolicies provides a mock function with given fields: ctx +func (_m *FungibleAuthorityKeeper) GetPolicies(ctx types.Context) (authoritytypes.Policies, bool) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetPolicies") + } + + var r0 authoritytypes.Policies + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context) (authoritytypes.Policies, bool)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(types.Context) authoritytypes.Policies); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(authoritytypes.Policies) + } + + if rf, ok := ret.Get(1).(func(types.Context) bool); ok { + r1 = rf(ctx) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // NewFungibleAuthorityKeeper creates a new instance of FungibleAuthorityKeeper. 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 NewFungibleAuthorityKeeper(t interface { diff --git a/testutil/keeper/mocks/fungible/bank.go b/testutil/keeper/mocks/fungible/bank.go index 1c46b35688..26c07bb9ad 100644 --- a/testutil/keeper/mocks/fungible/bank.go +++ b/testutil/keeper/mocks/fungible/bank.go @@ -67,6 +67,26 @@ func (_m *FungibleBankKeeper) SendCoinsFromModuleToAccount(ctx types.Context, se return r0 } +// SpendableCoins provides a mock function with given fields: ctx, addr +func (_m *FungibleBankKeeper) SpendableCoins(ctx types.Context, addr types.AccAddress) types.Coins { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for SpendableCoins") + } + + var r0 types.Coins + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress) types.Coins); ok { + r0 = rf(ctx, addr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Coins) + } + } + + return r0 +} + // NewFungibleBankKeeper creates a new instance of FungibleBankKeeper. 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 NewFungibleBankKeeper(t interface { diff --git a/testutil/keeper/mocks/observer/authority.go b/testutil/keeper/mocks/observer/authority.go index 24a105fb4d..0bfc08f9fe 100644 --- a/testutil/keeper/mocks/observer/authority.go +++ b/testutil/keeper/mocks/observer/authority.go @@ -54,6 +54,34 @@ func (_m *ObserverAuthorityKeeper) GetAdditionalChainList(ctx types.Context) []c return r0 } +// GetPolicies provides a mock function with given fields: ctx +func (_m *ObserverAuthorityKeeper) GetPolicies(ctx types.Context) (authoritytypes.Policies, bool) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetPolicies") + } + + var r0 authoritytypes.Policies + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context) (authoritytypes.Policies, bool)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(types.Context) authoritytypes.Policies); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(authoritytypes.Policies) + } + + if rf, ok := ret.Get(1).(func(types.Context) bool); ok { + r1 = rf(ctx) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // SetPolicies provides a mock function with given fields: ctx, policies func (_m *ObserverAuthorityKeeper) SetPolicies(ctx types.Context, policies authoritytypes.Policies) { _m.Called(ctx, policies) diff --git a/testutil/keeper/observer.go b/testutil/keeper/observer.go index d84f722b0c..39b6796c7f 100644 --- a/testutil/keeper/observer.go +++ b/testutil/keeper/observer.go @@ -10,7 +10,9 @@ import ( "github.com/cosmos/cosmos-sdk/store/rootmulti" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" @@ -47,6 +49,8 @@ func initObserverKeeper( stakingKeeper stakingkeeper.Keeper, slashingKeeper slashingkeeper.Keeper, authorityKeeper types.AuthorityKeeper, + bankKeeper bankkeeper.Keeper, + authKeeper authkeeper.AccountKeeper, lightclientKeeper types.LightclientKeeper, ) *keeper.Keeper { storeKey := sdk.NewKVStoreKey(types.StoreKey) @@ -62,6 +66,8 @@ func initObserverKeeper( slashingKeeper, authorityKeeper, lightclientKeeper, + bankKeeper, + authKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) } @@ -103,6 +109,8 @@ func ObserverKeeperWithMocks( var slashingKeeper types.SlashingKeeper = sdkKeepers.SlashingKeeper var authorityKeeper types.AuthorityKeeper = authorityKeeperTmp var lightclientKeeper types.LightclientKeeper = lightclientKeeperTmp + var bankKeeper types.BankKeeper = sdkKeepers.BankKeeper + var authKeeper types.AccountKeeper = sdkKeepers.AuthKeeper if mockOptions.UseStakingMock { stakingKeeper = observermocks.NewObserverStakingKeeper(t) } @@ -124,6 +132,8 @@ func ObserverKeeperWithMocks( slashingKeeper, authorityKeeper, lightclientKeeper, + bankKeeper, + authKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index e58c457073..82e683604a 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -11,6 +11,7 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -290,6 +291,7 @@ func ZetaAccounting(t *testing.T, index string) types.ZetaAccounting { } } +// InboundVote creates a sample inbound vote message func InboundVote(coinType coin.CoinType, from, to int64) types.MsgVoteInbound { return types.MsgVoteInbound{ Creator: Bech32AccAddress().String(), @@ -311,6 +313,29 @@ func InboundVote(coinType coin.CoinType, from, to int64) types.MsgVoteInbound { } } +// InboundVoteFromRand creates a simulated inbound vote message. This function uses the provided source of randomness to generate the vote +func InboundVoteFromRand(coinType coin.CoinType, from, to int64, r *rand.Rand) types.MsgVoteInbound { + EthAddress() + return types.MsgVoteInbound{ + Creator: "", + Sender: EthAddressFromRand(r).String(), + SenderChainId: from, + Receiver: EthAddressFromRand(r).String(), + ReceiverChain: to, + Amount: math.NewUint(r.Uint64()), + Message: base64.StdEncoding.EncodeToString(RandomBytes(r)), + InboundBlockHeight: r.Uint64(), + CallOptions: &types.CallOptions{ + GasLimit: 1000000000, + }, + InboundHash: ethcommon.BytesToHash(RandomBytes(r)).String(), + CoinType: coinType, + TxOrigin: EthAddressFromRand(r).String(), + Asset: StringRandom(r, 32), + EventIndex: r.Uint64(), + } +} + func ZRC20Withdrawal(to []byte, value *big.Int) *zrc20.ZRC20Withdrawal { return &zrc20.ZRC20Withdrawal{ From: EthAddress(), diff --git a/testutil/sample/crypto.go b/testutil/sample/crypto.go index 7cc565936a..9e643fa123 100644 --- a/testutil/sample/crypto.go +++ b/testutil/sample/crypto.go @@ -60,6 +60,10 @@ func EthAddress() ethcommon.Address { return ethcommon.BytesToAddress(sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).Bytes()) } +func EthAddressFromRand(r *rand.Rand) ethcommon.Address { + return ethcommon.BytesToAddress(sdk.AccAddress(PubKey(r).Address()).Bytes()) +} + // BtcAddressP2WPKH returns a sample btc P2WPKH address func BtcAddressP2WPKH(t *testing.T, net *chaincfg.Params) string { privateKey, err := btcec.NewPrivateKey() diff --git a/testutil/sample/observer.go b/testutil/sample/observer.go index 8d0877ce9b..e9e8eb7cd7 100644 --- a/testutil/sample/observer.go +++ b/testutil/sample/observer.go @@ -2,12 +2,14 @@ package sample import ( "fmt" + "math/rand" "testing" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/cosmos" @@ -119,6 +121,25 @@ func ChainParamsList() (cpl types.ChainParamsList) { return } +// TSSFromRand returns a random TSS,it uses the randomness provided as a parameter +func TSSFromRand(t *testing.T, r *rand.Rand) types.TSS { + pubKey := PubKey(r) + spk, err := cosmos.Bech32ifyPubKey(cosmos.Bech32PubKeyTypeAccPub, pubKey) + require.NoError(t, err) + pk, err := zetacrypto.NewPubKey(spk) + require.NoError(t, err) + pubkeyString := pk.String() + return types.TSS{ + TssPubkey: pubkeyString, + TssParticipantList: []string{}, + OperatorAddressList: []string{}, + FinalizedZetaHeight: r.Int63(), + KeyGenZetaHeight: r.Int63(), + } +} + +// TODO: rename to TSS +// https://github.com/zeta-chain/node/issues/3098 func Tss() types.TSS { _, pubKey, _ := testdata.KeyTestPubAddr() spk, err := cosmos.Bech32ifyPubKey(cosmos.Bech32PubKeyTypeAccPub, pubKey) diff --git a/testutil/sample/sample.go b/testutil/sample/sample.go index 365d1c7e85..3f0390ddad 100644 --- a/testutil/sample/sample.go +++ b/testutil/sample/sample.go @@ -61,6 +61,12 @@ func Bytes() []byte { return []byte("sample") } +func RandomBytes(r *rand.Rand) []byte { + b := make([]byte, 10) + _, _ = r.Read(b) + return b +} + // String returns a sample string func String() string { return "sample" diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go index d4764d9637..3ae74fe45c 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -20,7 +20,6 @@ func (k Keeper) ValidateInbound( if !tssFound { return nil, types.ErrCannotFindTSSKeys } - err := k.CheckIfTSSMigrationTransfer(ctx, msg) if err != nil { return nil, err diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index a34e75c026..263b7b23bc 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -93,6 +93,7 @@ func (k msgServer) VoteInbound( } } commit() + // If the ballot is not finalized return nil here to add vote to commit state if !finalized { return &types.MsgVoteInboundResponse{}, nil @@ -102,9 +103,9 @@ func (k msgServer) VoteInbound( if err != nil { return nil, sdkerrors.Wrap(err, voteInboundID) } - // Save the inbound CCTX to the store. This is called irrespective of the status of the CCTX or the outcome of the process function. k.SaveObservedInboundInformation(ctx, cctx, msg.EventIndex) + return &types.MsgVoteInboundResponse{}, nil } diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go index 27f5a8ad98..7fc5c9be99 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go @@ -51,19 +51,20 @@ func TestKeeper_VoteInbound(t *testing.T) { k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t) msgServer := keeper.NewMsgServerImpl(*k) validatorList := setObservers(t, k, ctx, zk) + to, from := int64(1337), int64(101) supportedChains := zk.ObserverKeeper.GetSupportedChains(ctx) for _, chain := range supportedChains { - if chains.IsEVMChain(chain.ChainId, []chains.Chain{}) { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { from = chain.ChainId } if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { to = chain.ChainId } } - zk.ObserverKeeper.SetTSS(ctx, sample.Tss()) msg := sample.InboundVote(0, from, to) + zk.ObserverKeeper.SetTSS(ctx, sample.Tss()) err := sdkk.EvmKeeper.SetAccount(ctx, ethcommon.HexToAddress(msg.Receiver), statedb.Account{ Nonce: 0, @@ -210,7 +211,7 @@ func TestKeeper_VoteInbound(t *testing.T) { to, from := int64(1337), int64(101) supportedChains := zk.ObserverKeeper.GetSupportedChains(ctx) for _, chain := range supportedChains { - if chains.IsEVMChain(chain.ChainId, []chains.Chain{}) { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { from = chain.ChainId } if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { diff --git a/x/crosschain/module_simulation.go b/x/crosschain/module_simulation.go index 38ff067fbf..1399b27cdb 100644 --- a/x/crosschain/module_simulation.go +++ b/x/crosschain/module_simulation.go @@ -5,17 +5,14 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/zeta-chain/node/x/crosschain/simulation" "github.com/zeta-chain/node/x/crosschain/types" ) -// GenerateGenesisState creates a randomized GenState of the module +// GenerateGenesisState creates a GenState of the module used to initialize the simulation runs func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - crosschainGenesis := types.GenesisState{} - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&crosschainGenesis) + crosschainGenesis := types.DefaultGenesis() + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(crosschainGenesis) } // ProposalContents doesn't return any content functions for governance proposals @@ -28,11 +25,13 @@ func (AppModule) ProposalMsgs(_ module.SimulationState) []simtypes.WeightedPropo } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} // WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.keeper, + ) } diff --git a/x/crosschain/simulation/decoders.go b/x/crosschain/simulation/decoders.go new file mode 100644 index 0000000000..dd2c6c4e07 --- /dev/null +++ b/x/crosschain/simulation/decoders.go @@ -0,0 +1,62 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/zeta-chain/node/x/crosschain/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding crosschain types. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key, types.KeyPrefix(types.CCTXKey)): + var cctxA, cctxB types.CrossChainTx + cdc.MustUnmarshal(kvA.Value, &cctxA) + cdc.MustUnmarshal(kvB.Value, &cctxB) + return fmt.Sprintf("%v\n%v", cctxA, cctxB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.LastBlockHeightKey)): + var lastBlockHeightA, lastBlockHeightB types.LastBlockHeight + cdc.MustUnmarshal(kvA.Value, &lastBlockHeightA) + cdc.MustUnmarshal(kvB.Value, &lastBlockHeightB) + return fmt.Sprintf("%v\n%v", lastBlockHeightA, lastBlockHeightB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.FinalizedInboundsKey)): + var finalizedInboundsA, finalizedInboundsB []byte + finalizedInboundsA = kvA.Value + finalizedInboundsB = kvB.Value + return fmt.Sprintf("%v\n%v", finalizedInboundsA, finalizedInboundsB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.GasPriceKey)): + var gasPriceA, gasPriceB types.GasPrice + cdc.MustUnmarshal(kvA.Value, &gasPriceA) + cdc.MustUnmarshal(kvB.Value, &gasPriceB) + return fmt.Sprintf("%v\n%v", gasPriceA, gasPriceB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.OutboundTrackerKeyPrefix)): + var outboundTrackerA, outboundTrackerB types.OutboundTracker + cdc.MustUnmarshal(kvA.Value, &outboundTrackerA) + cdc.MustUnmarshal(kvB.Value, &outboundTrackerB) + return fmt.Sprintf("%v\n%v", outboundTrackerA, outboundTrackerB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.InboundTrackerKeyPrefix)): + var inboundTrackerA, inboundTrackerB types.InboundTracker + cdc.MustUnmarshal(kvA.Value, &inboundTrackerA) + cdc.MustUnmarshal(kvB.Value, &inboundTrackerB) + return fmt.Sprintf("%v\n%v", inboundTrackerA, inboundTrackerB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ZetaAccountingKey)): + var zetaAccountingA, zetaAccountingB types.ZetaAccounting + cdc.MustUnmarshal(kvA.Value, &zetaAccountingA) + cdc.MustUnmarshal(kvB.Value, &zetaAccountingB) + return fmt.Sprintf("%v\n%v", zetaAccountingA, zetaAccountingB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.RateLimiterFlagsKey)): + var rateLimiterFlagsA, rateLimiterFlagsB types.RateLimiterFlags + cdc.MustUnmarshal(kvA.Value, &rateLimiterFlagsA) + cdc.MustUnmarshal(kvB.Value, &rateLimiterFlagsB) + return fmt.Sprintf("%v\n%v", rateLimiterFlagsA, rateLimiterFlagsB) + default: + panic(fmt.Sprintf("invalid crosschain key prefix %X", kvA.Key[:1])) + } + } +} diff --git a/x/crosschain/simulation/decoders_test.go b/x/crosschain/simulation/decoders_test.go new file mode 100644 index 0000000000..9765aca43d --- /dev/null +++ b/x/crosschain/simulation/decoders_test.go @@ -0,0 +1,60 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/stretchr/testify/require" + keepertest "github.com/zeta-chain/node/testutil/keeper" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/crosschain/simulation" + "github.com/zeta-chain/node/x/crosschain/types" +) + +func TestDecodeStore(t *testing.T) { + k, _, _, _ := keepertest.CrosschainKeeper(t) + cdc := k.GetCodec() + dec := simulation.NewDecodeStore(cdc) + cctx := sample.CrossChainTx(t, "sample") + lastBlockHeight := sample.LastBlockHeight(t, "sample") + gasPrice := sample.GasPrice(t, "sample") + outboundTracker := sample.OutboundTracker(t, "sample") + inboundTracker := sample.InboundTracker(t, "sample") + zetaAccounting := sample.ZetaAccounting(t, "sample") + rateLimiterFlags := sample.RateLimiterFlags() + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.KeyPrefix(types.CCTXKey), Value: cdc.MustMarshal(cctx)}, + {Key: types.KeyPrefix(types.LastBlockHeightKey), Value: cdc.MustMarshal(lastBlockHeight)}, + {Key: types.KeyPrefix(types.GasPriceKey), Value: cdc.MustMarshal(gasPrice)}, + {Key: types.KeyPrefix(types.OutboundTrackerKeyPrefix), Value: cdc.MustMarshal(&outboundTracker)}, + {Key: types.KeyPrefix(types.InboundTrackerKeyPrefix), Value: cdc.MustMarshal(&inboundTracker)}, + {Key: types.KeyPrefix(types.ZetaAccountingKey), Value: cdc.MustMarshal(&zetaAccounting)}, + {Key: types.KeyPrefix(types.RateLimiterFlagsKey), Value: cdc.MustMarshal(&rateLimiterFlags)}, + {Key: types.KeyPrefix(types.FinalizedInboundsKey), Value: []byte{1}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"CrossChainTx", fmt.Sprintf("%v\n%v", *cctx, *cctx)}, + {"LastBlockHeight", fmt.Sprintf("%v\n%v", *lastBlockHeight, *lastBlockHeight)}, + {"GasPrice", fmt.Sprintf("%v\n%v", *gasPrice, *gasPrice)}, + {"OutboundTracker", fmt.Sprintf("%v\n%v", outboundTracker, outboundTracker)}, + {"InboundTracker", fmt.Sprintf("%v\n%v", inboundTracker, inboundTracker)}, + {"ZetaAccounting", fmt.Sprintf("%v\n%v", zetaAccounting, zetaAccounting)}, + {"RateLimiterFlags", fmt.Sprintf("%v\n%v", rateLimiterFlags, rateLimiterFlags)}, + {"FinalizedInbounds", fmt.Sprintf("%v\n%v", []byte{1}, []byte{1})}, + } + + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i])) + }) + } +} diff --git a/x/crosschain/simulation/operations.go b/x/crosschain/simulation/operations.go new file mode 100644 index 0000000000..96b04970b9 --- /dev/null +++ b/x/crosschain/simulation/operations.go @@ -0,0 +1,492 @@ +package simulation + +import ( + "fmt" + "math" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/zeta-chain/node/pkg/authz" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/crosschain/keeper" + "github.com/zeta-chain/node/x/crosschain/types" + observerTypes "github.com/zeta-chain/node/x/observer/types" +) + +// Simulation operation weights constants +// Operation weights are used by the `SimulateFromSeed` +// function to pick a random operation based on the weights.The functions with higher weights are more likely to be picked. + +// Therefore, this decides the percentage of a certain operation that is part of a block. + +// Based on the weights assigned in the cosmos sdk modules, +// 100 seems to the max weight used,and we should use relative weights +// to signify the number of each operation in a block. + +// TODO Add more details to comment based on what the number represents in terms of percentage of operations in a block +// https://github.com/zeta-chain/node/issues/3100 +const ( + DefaultWeightMsgAddOutboundTracker = 50 + DefaultWeightAddInboundTracker = 50 + DefaultWeightRemoveOutboundTracker = 5 + DefaultWeightVoteGasPrice = 100 + DefaultWeightVoteOutbound = 50 + DefaultWeightVoteInbound = 100 + DefaultWeightWhitelistERC20 = 1 + DefaultWeightMigrateTssFunds = 1 + DefaultWeightUpdateTssAddress = 1 + DefaultWeightAbortStuckCCTX = 10 + DefaultWeightUpdateRateLimiterFlags = 1 + + OpWeightMsgAddOutboundTracker = "op_weight_msg_add_outbound_tracker" // #nosec G101 not a hardcoded credential + OpWeightAddInboundTracker = "op_weight_msg_add_inbound_tracker" // #nosec G101 not a hardcoded credential + OpWeightRemoveOutboundTracker = "op_weight_msg_remove_outbound_tracker" // #nosec G101 not a hardcoded credential + OpWeightVoteGasPrice = "op_weight_msg_vote_gas_price" // #nosec G101 not a hardcoded credential + OpWeightVoteOutbound = "op_weight_msg_vote_outbound" // #nosec G101 not a hardcoded credential + OpWeightVoteInbound = "op_weight_msg_vote_inbound" // #nosec G101 not a hardcoded credential + OpWeightWhitelistERC20 = "op_weight_msg_whitelist_erc20" // #nosec G101 not a hardcoded credential + OpWeightMigrateTssFunds = "op_weight_msg_migrate_tss_funds" // #nosec G101 not a hardcoded credential + OpWeightUpdateTssAddress = "op_weight_msg_update_tss_address" // #nosec G101 not a hardcoded credential + OpWeightAbortStuckCCTX = "op_weight_msg_abort_stuck_cctx" // #nosec G101 not a hardcoded credential + OpWeightUpdateRateLimiterFlags = "op_weight_msg_update_rate_limiter_flags" // #nosec G101 not a hardcoded credential + +) + +func WeightedOperations( + appParams simtypes.AppParams, cdc codec.JSONCodec, k keeper.Keeper) simulation.WeightedOperations { + var ( + weightMsgAddOutboundTracker int + weightAddInboundTracker int + weightRemoveOutboundTracker int + weightVoteGasPrice int + weightVoteOutbound int + weightVoteInbound int + weightWhitelistERC20 int + weightMigrateTssFunds int + weightUpdateTssAddress int + weightAbortStuckCCTX int + weightUpdateRateLimiterFlags int + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgAddOutboundTracker, &weightMsgAddOutboundTracker, nil, + func(_ *rand.Rand) { + weightMsgAddOutboundTracker = DefaultWeightMsgAddOutboundTracker + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightAddInboundTracker, &weightAddInboundTracker, nil, + func(_ *rand.Rand) { + weightAddInboundTracker = DefaultWeightAddInboundTracker + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightRemoveOutboundTracker, &weightRemoveOutboundTracker, nil, + func(_ *rand.Rand) { + weightRemoveOutboundTracker = DefaultWeightRemoveOutboundTracker + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightVoteGasPrice, &weightVoteGasPrice, nil, + func(_ *rand.Rand) { + weightVoteGasPrice = DefaultWeightVoteGasPrice + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightVoteOutbound, &weightVoteOutbound, nil, + func(_ *rand.Rand) { + weightVoteOutbound = DefaultWeightVoteOutbound + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightVoteInbound, &weightVoteInbound, nil, + func(_ *rand.Rand) { + weightVoteInbound = DefaultWeightVoteInbound + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightWhitelistERC20, &weightWhitelistERC20, nil, + func(_ *rand.Rand) { + weightWhitelistERC20 = DefaultWeightWhitelistERC20 + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMigrateTssFunds, &weightMigrateTssFunds, nil, + func(_ *rand.Rand) { + weightMigrateTssFunds = DefaultWeightMigrateTssFunds + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightUpdateTssAddress, &weightUpdateTssAddress, nil, + func(_ *rand.Rand) { + weightUpdateTssAddress = DefaultWeightUpdateTssAddress + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightAbortStuckCCTX, &weightAbortStuckCCTX, nil, + func(_ *rand.Rand) { + weightAbortStuckCCTX = DefaultWeightAbortStuckCCTX + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightUpdateRateLimiterFlags, &weightUpdateRateLimiterFlags, nil, + func(_ *rand.Rand) { + weightUpdateRateLimiterFlags = DefaultWeightUpdateRateLimiterFlags + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightVoteGasPrice, + SimulateMsgVoteGasPrice(k), + ), + simulation.NewWeightedOperation( + weightVoteInbound, + SimulateVoteInbound(k), + ), + } +} + +// operationSimulateVoteInbound generates a MsgVoteInbound with a random vote and delivers it. +func operationSimulateVoteInbound( + k keeper.Keeper, + msg types.MsgVoteInbound, + simAccount simtypes.Account, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, _ []simtypes.Account, _ string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + // Fetch the account from the auth keeper which can then be used to fetch spendable coins + authAccount := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + // Generate a transaction with a random fee and deliver it + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + // Generate and deliver the transaction using the function defined by us instead of using the default function provided by the cosmos-sdk + // The main difference between the two functions is that the one defined by us does not error out if the vote fails. + // We need this behaviour as the votes are assigned to future operations, i.e., they are scheduled to be executed in a future block. We do not know at the time of scheduling if the vote will be successful or not. + // There might be multiple reasons for a vote to fail , like the observer not being present in the observer set, the observer not being an observer, etc. + return GenAndDeliverTxWithRandFees(txCtx) + } +} + +func SimulateVoteInbound(k keeper.Keeper) simtypes.Operation { + // The states are: + // column 1: All observers vote + // column 2: 90% vote + // column 3: 75% vote + // column 4: 40% vote + // column 5: 15% vote + // column 6: noone votes + // All columns sum to 100 for simplicity, but this is arbitrary and can be changed + numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ + {20, 10, 0, 0, 0, 0}, + {55, 50, 20, 10, 0, 0}, + {25, 25, 30, 25, 30, 15}, + {0, 15, 30, 25, 30, 30}, + {0, 0, 20, 30, 30, 30}, + {0, 0, 0, 10, 10, 25}, + }) + + statePercentageArray := []float64{1, .9, .75, .4, .15, 0} + curNumVotesState := 1 + + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // TODO : randomize these values + // Right now we use a constant value for cctx creation , this is the same as the one used in unit tests for the successful condition. + // TestKeeper_VoteInbound/successfully vote on evm deposit + // But this can improved by adding more randomization + + to, from := int64(1337), int64(101) + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + for _, chain := range supportedChains { + if chains.IsEthereumChain(chain.ChainId, []chains.Chain{}) { + from = chain.ChainId + } + if chains.IsZetaChain(chain.ChainId, []chains.Chain{}) { + to = chain.ChainId + } + } + + msg := sample.InboundVoteFromRand(0, from, to, r) + + // Pick a random observer to create the ballot + // If this returns an error, it is likely that the entire observer set has been removed + simAccount, firstVoter, err := GetRandomAccountAndObserver(r, ctx, k, accs) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig + account := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + firstMsg := msg + firstMsg.Creator = firstVoter + + err = firstMsg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate first inbound vote"), nil, err + } + + tx, err := simtestutil.GenSignedMockTx( + r, + txGen, + []sdk.Msg{&firstMsg}, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, + simtestutil.DefaultGenTxGas, + chainID, + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + simAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + } + + // We can return error here as we can guarantee that the first vote will be successful. + // Since we query the observer set before adding votes + _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + opMsg := simtypes.NewOperationMsg(&msg, true, "", nil) + + // Add subsequent votes + observerSet, found := k.GetObserverKeeper().GetObserverSet(ctx) + if !found { + return simtypes.NoOpMsg(types.ModuleName, authz.InboundVoter.String(), "observer set not found"), nil, nil + } + + // 1) Schedule operations for votes + // 1.1) first pick a number of people to vote. + curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) + numVotes := int(math.Ceil(float64(len(observerSet.ObserverList)) * statePercentageArray[curNumVotesState])) + + // 1.2) select who votes + whoVotes := r.Perm(len(observerSet.ObserverList)) + whoVotes = whoVotes[:numVotes] + + var fops []simtypes.FutureOperation + + for _, observerIdx := range whoVotes { + observerAddress := observerSet.ObserverList[observerIdx] + // firstVoter has already voted. + if observerAddress == firstVoter { + continue + } + observerAccount, err := GetObserverAccount(observerAddress, accs) + if err != nil { + continue + } + // 1.3) schedule the vote + votingMsg := msg + votingMsg.Creator = observerAddress + + e := votingMsg.ValidateBasic() + if e != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate voting msg"), nil, e + } + + fops = append(fops, simtypes.FutureOperation{ + // Submit all subsequent votes in the next block. + // We can consider adding a random block height between 1 and ballot maturity blocks in the future. + BlockHeight: int(ctx.BlockHeight() + 1), + Op: operationSimulateVoteInbound(k, votingMsg, observerAccount), + }) + } + return opMsg, fops, nil + } +} + +// SimulateMsgVoteGasPrice generates a MsgVoteGasPrice and delivers it +func SimulateMsgVoteGasPrice(k keeper.Keeper) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + // Get a random account and observer + // If this returns an error, it is likely that the entire observer set has been removed + simAccount, randomObserver, err := GetRandomAccountAndObserver(r, ctx, k, accounts) + if err != nil { + return simtypes.OperationMsg{}, nil, nil + } + authAccount := k.GetAuthKeeper().GetAccount(ctx, simAccount.Address) + spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress()) + + supportedChains := k.GetObserverKeeper().GetSupportedChains(ctx) + if len(supportedChains) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + authz.GasPriceVoter.String(), + "no supported chains found", + ), nil, nil + } + randomChainID := GetRandomChainID(r, supportedChains) + + // Vote for random gas price. Gas prices do not use a ballot system, so we can vote directly without having to schedule future operations. + // The random nature of the price might create weird gas prices for the chain, but it is fine for now. We can remove the randomness if needed + msg := types.MsgVoteGasPrice{ + Creator: randomObserver, + ChainId: randomChainID, + Price: r.Uint64(), + PriorityFee: r.Uint64(), + BlockNumber: r.Uint64(), + Supply: fmt.Sprintf("%d", r.Int63()), + } + + // System contracts are deployed on the first block, so we cannot vote on gas prices before that + if ctx.BlockHeight() <= 1 { + return simtypes.NewOperationMsg(&msg, true, "block height less than 1", nil), nil, nil + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to validate vote gas price msg"), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +func GetRandomObserver(r *rand.Rand, observerList []string) string { + idx := r.Intn(len(observerList)) + return observerList[idx] +} + +func GetRandomChainID(r *rand.Rand, chains []chains.Chain) int64 { + idx := r.Intn(len(chains)) + return chains[idx].ChainId +} + +// GetRandomAccountAndObserver returns a random account and the associated observer address +func GetRandomAccountAndObserver( + r *rand.Rand, + ctx sdk.Context, + k keeper.Keeper, + accounts []simtypes.Account, +) (simtypes.Account, string, error) { + observers, found := k.GetObserverKeeper().GetObserverSet(ctx) + if !found { + return simtypes.Account{}, "", fmt.Errorf("observer set not found") + } + + if len(observers.ObserverList) == 0 { + return simtypes.Account{}, "", fmt.Errorf("no observers present in observer set found") + } + + randomObserver := GetRandomObserver(r, observers.ObserverList) + simAccount, err := GetObserverAccount(randomObserver, accounts) + if err != nil { + return simtypes.Account{}, "", err + } + return simAccount, randomObserver, nil +} + +// GetObserverAccount returns the account associated with the observer address from the list of accounts provided +// GetObserverAccount can fail if all the observers are removed from the observer set ,this can happen +//if the other modules create transactions which affect the validator +//and triggers any of the staking hooks defined in the observer modules + +func GetObserverAccount(observerAddress string, accounts []simtypes.Account) (simtypes.Account, error) { + operatorAddress, err := observerTypes.GetOperatorAddressFromAccAddress(observerAddress) + if err != nil { + return simtypes.Account{}, fmt.Errorf("validator not found for observer ") + } + + simAccount, found := simtypes.FindAccount(accounts, operatorAddress) + if !found { + return simtypes.Account{}, fmt.Errorf("operator account not found") + } + return simAccount, nil +} + +// GenAndDeliverTxWithRandFees generates a transaction with a random fee and delivers it. +func GenAndDeliverTxWithRandFees( + txCtx simulation.OperationInput, +) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + spendable := txCtx.Bankkeeper.SpendableCoins(txCtx.Context, account.GetAddress()) + + var fees sdk.Coins + var err error + + coins, hasNeg := spendable.SafeSub(txCtx.CoinsSpentInMsg...) + if hasNeg { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "message doesn't leave room for fees"), nil, err + } + + fees, err = simtypes.RandomFees(txCtx.R, txCtx.Context, coins) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate fees"), nil, err + } + return GenAndDeliverTx(txCtx, fees) +} + +// GenAndDeliverTx generates a transactions and delivers it with the provided fees. +// This function does not return an error if the transaction fails to deliver. +func GenAndDeliverTx( + txCtx simulation.OperationInput, + fees sdk.Coins, +) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + tx, err := simtestutil.GenSignedMockTx( + txCtx.R, + txCtx.TxGen, + []sdk.Msg{txCtx.Msg}, + fees, + simtestutil.DefaultGenTxGas, + txCtx.Context.ChainID(), + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + txCtx.SimAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate mock tx"), nil, err + } + + _, _, err = txCtx.App.SimDeliver(txCtx.TxGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to deliver tx"), nil, nil + } + + return simtypes.NewOperationMsg(txCtx.Msg, true, "", txCtx.Cdc), nil, nil +} diff --git a/x/crosschain/simulation/simap.go b/x/crosschain/simulation/simap.go deleted file mode 100644 index 92c437c0d1..0000000000 --- a/x/crosschain/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 0f496c13d3..2fee9cb31a 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -24,12 +24,14 @@ type StakingKeeper interface { // AccountKeeper defines the expected account keeper (noalias) type AccountKeeper interface { GetModuleAccount(ctx sdk.Context, name string) types.ModuleAccountI + GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI } // BankKeeper defines the expected interface needed to retrieve account balances. type BankKeeper interface { BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins } type ObserverKeeper interface { diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index 95b1b05dae..6abd4444d9 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -322,6 +322,7 @@ func (k Keeper) DepositZRC20AndCallContract(ctx sdk.Context, if !found { return nil, cosmoserrors.Wrapf(types.ErrContractNotFound, "GetSystemContract address not found") } + systemAddress := common.HexToAddress(system.SystemContract) sysConABI, err := systemcontract.SystemContractMetaData.GetAbi() diff --git a/x/fungible/keeper/keeper.go b/x/fungible/keeper/keeper.go index ce1e9d5fdc..6e4af7f194 100644 --- a/x/fungible/keeper/keeper.go +++ b/x/fungible/keeper/keeper.go @@ -13,7 +13,7 @@ import ( type ( Keeper struct { - cdc codec.BinaryCodec + cdc codec.Codec storeKey storetypes.StoreKey memKey storetypes.StoreKey authKeeper types.AccountKeeper @@ -25,7 +25,7 @@ type ( ) func NewKeeper( - cdc codec.BinaryCodec, + cdc codec.Codec, storeKey, memKey storetypes.StoreKey, authKeeper types.AccountKeeper, @@ -54,6 +54,10 @@ func (k Keeper) GetAuthKeeper() types.AccountKeeper { return k.authKeeper } +func (k Keeper) GetCodec() codec.Codec { + return k.cdc +} + func (k Keeper) GetEVMKeeper() types.EVMKeeper { return k.evmKeeper } diff --git a/x/fungible/keeper/msg_server_update_gateway_contract_test.go b/x/fungible/keeper/msg_server_update_gateway_contract_test.go index b1ab5a7fbf..ba838b3674 100644 --- a/x/fungible/keeper/msg_server_update_gateway_contract_test.go +++ b/x/fungible/keeper/msg_server_update_gateway_contract_test.go @@ -1,10 +1,11 @@ package keeper_test import ( + "testing" + "github.com/ethereum/go-ethereum/common" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" - "testing" "github.com/stretchr/testify/require" keepertest "github.com/zeta-chain/node/testutil/keeper" diff --git a/x/fungible/module.go b/x/fungible/module.go index fcec27c9d4..8e87fdbd43 100644 --- a/x/fungible/module.go +++ b/x/fungible/module.go @@ -31,10 +31,10 @@ var ( // AppModuleBasic implements the AppModuleBasic interface for the fungible module. type AppModuleBasic struct { - cdc codec.BinaryCodec + cdc codec.Codec } -func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { +func NewAppModuleBasic(cdc codec.Codec) AppModuleBasic { return AppModuleBasic{cdc: cdc} } diff --git a/x/fungible/module_simulation.go b/x/fungible/module_simulation.go index e502102502..120f9158f4 100644 --- a/x/fungible/module_simulation.go +++ b/x/fungible/module_simulation.go @@ -5,17 +5,14 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/zeta-chain/node/x/fungible/simulation" "github.com/zeta-chain/node/x/fungible/types" ) -// GenerateGenesisState creates a randomized GenState of the module +// GenerateGenesisState creates a GenState of the module used to initialize the simulation runs func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - fungibleGenesis := types.GenesisState{} - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&fungibleGenesis) + fungibleGenesis := types.DefaultGenesis() + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(fungibleGenesis) } // ProposalContents doesn't return any content functions for governance proposals @@ -28,11 +25,13 @@ func (AppModule) ProposalMsgs(_ module.SimulationState) []simtypes.WeightedPropo } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} // WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.keeper, + ) } diff --git a/x/fungible/simulation/decoders.go b/x/fungible/simulation/decoders.go new file mode 100644 index 0000000000..04be66f781 --- /dev/null +++ b/x/fungible/simulation/decoders.go @@ -0,0 +1,35 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/zeta-chain/node/x/fungible/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding fungible types. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + if !bytes.Equal(kvA.Key[:1], kvB.Key[:1]) { + return fmt.Sprintf("key prefixes do not match. A: %X, B: %X", kvA.Key[:1], kvB.Key[:1]) + } + switch { + case bytes.Equal(kvA.Key, types.KeyPrefix(types.SystemContractKey)): + var systemContractA, systemContractB types.SystemContract + cdc.MustUnmarshal(kvA.Value, &systemContractA) + cdc.MustUnmarshal(kvB.Value, &systemContractB) + return fmt.Sprintf("%v\n%v", systemContractA, systemContractB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ForeignCoinsKeyPrefix)): + var foreignCoinsA, foreignCoinsB types.ForeignCoins + cdc.MustUnmarshal(kvA.Value, &foreignCoinsA) + cdc.MustUnmarshal(kvB.Value, &foreignCoinsB) + return fmt.Sprintf("%v\n%v", foreignCoinsA, foreignCoinsB) + default: + return fmt.Sprintf("invalid fungible key prefix %X", kvA.Key[:1]) + } + } +} diff --git a/x/fungible/simulation/decoders_test.go b/x/fungible/simulation/decoders_test.go new file mode 100644 index 0000000000..c2e4cdfd44 --- /dev/null +++ b/x/fungible/simulation/decoders_test.go @@ -0,0 +1,43 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/stretchr/testify/require" + keepertest "github.com/zeta-chain/node/testutil/keeper" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/fungible/simulation" + "github.com/zeta-chain/node/x/fungible/types" +) + +func TestDecodeStore(t *testing.T) { + k, _, _, _ := keepertest.FungibleKeeper(t) + cdc := k.GetCodec() + dec := simulation.NewDecodeStore(cdc) + systemContract := sample.SystemContract() + foreignCoins := sample.ForeignCoins(t, sample.EthAddress().String()) + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: []byte(types.SystemContractKey), Value: cdc.MustMarshal(systemContract)}, + {Key: []byte(types.ForeignCoinsKeyPrefix), Value: cdc.MustMarshal(&foreignCoins)}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"SystemContract", fmt.Sprintf("%v\n%v", *systemContract, *systemContract)}, + {"ForeignCoins", fmt.Sprintf("%v\n%v", foreignCoins, foreignCoins)}, + } + + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i])) + }) + } +} diff --git a/x/fungible/simulation/operations.go b/x/fungible/simulation/operations.go new file mode 100644 index 0000000000..2a14713979 --- /dev/null +++ b/x/fungible/simulation/operations.go @@ -0,0 +1,116 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/zeta-chain/node/x/fungible/keeper" + "github.com/zeta-chain/node/x/fungible/types" + observerTypes "github.com/zeta-chain/node/x/observer/types" +) + +// Simulation operation weights constants +// Operation weights are used by the `SimulateFromSeed` +// function to pick a random operation based on the weights.The functions with higher weights are more likely to be picked. + +// Therefore, this decides the percentage of a certain operation that is part of a block. + +// Based on the weights assigned in the cosmos sdk modules, +// 100 seems to the max weight used,and we should use relative weights +// to signify the number of each operation in a block. + +// TODO Add more details to comment based on what the number represents in terms of percentage of operations in a block +// https://github.com/zeta-chain/node/issues/3100 +const ( + // #nosec G101 not a hardcoded credential + OpWeightMsgDeploySystemContracts = "op_weight_msg_deploy_system_contracts" + DefaultWeightMsgDeploySystemContracts = 5 +) + +// DeployedSystemContracts Use a flag to ensure that the system contracts are deployed only once +// https://github.com/zeta-chain/node/issues/3102 +func WeightedOperations( + appParams simtypes.AppParams, cdc codec.JSONCodec, k keeper.Keeper) simulation.WeightedOperations { + var weightMsgDeploySystemContracts int + + appParams.GetOrGenerate(cdc, OpWeightMsgDeploySystemContracts, &weightMsgDeploySystemContracts, nil, + func(_ *rand.Rand) { + weightMsgDeploySystemContracts = DefaultWeightMsgDeploySystemContracts + }) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgDeploySystemContracts, + SimulateMsgDeploySystemContracts(k), + ), + } +} + +// SimulateMsgDeploySystemContracts deploy system contracts.It is run only once in first block. +func SimulateMsgDeploySystemContracts(k keeper.Keeper) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + policies, found := k.GetAuthorityKeeper().GetPolicies(ctx) + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgDeploySystemContracts, + "policies object not found", + ), nil, nil + } + if len(policies.Items) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgDeploySystemContracts, + "no policies found", + ), nil, nil + } + admin := policies.Items[0].Address + + address, err := observerTypes.GetOperatorAddressFromAccAddress(admin) + if err != nil { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgDeploySystemContracts, + "unable to get operator address", + ), nil, err + } + simAccount, found := simtypes.FindAccount(accounts, address) + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + types.TypeMsgDeploySystemContracts, + "sim account for admin address not found", + ), nil, nil + } + + msg := types.MsgDeploySystemContracts{Creator: admin} + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "failed to validate basic msg"), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/fungible/simulation/simap.go b/x/fungible/simulation/simap.go deleted file mode 100644 index 92c437c0d1..0000000000 --- a/x/fungible/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/fungible/types/expected_keepers.go b/x/fungible/types/expected_keepers.go index 442f625c10..a0ee828878 100644 --- a/x/fungible/types/expected_keepers.go +++ b/x/fungible/types/expected_keepers.go @@ -13,6 +13,7 @@ import ( evmtypes "github.com/zeta-chain/ethermint/x/evm/types" "github.com/zeta-chain/node/pkg/chains" + authoritytypes "github.com/zeta-chain/node/x/authority/types" ) // AccountKeeper defines the expected account keeper used for simulations (noalias) @@ -33,6 +34,7 @@ type BankKeeper interface { amt sdk.Coins, ) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins GetSupply(ctx sdk.Context, denom string) sdk.Coin } @@ -62,4 +64,5 @@ type EVMKeeper interface { type AuthorityKeeper interface { CheckAuthorization(ctx sdk.Context, msg sdk.Msg) error GetAdditionalChainList(ctx sdk.Context) (list []chains.Chain) + GetPolicies(ctx sdk.Context) (val authoritytypes.Policies, found bool) } diff --git a/x/observer/keeper/keeper.go b/x/observer/keeper/keeper.go index ad7fa984c9..b9fdc0f97d 100644 --- a/x/observer/keeper/keeper.go +++ b/x/observer/keeper/keeper.go @@ -13,25 +13,29 @@ import ( type ( Keeper struct { - cdc codec.BinaryCodec + cdc codec.Codec storeKey storetypes.StoreKey memKey storetypes.StoreKey stakingKeeper types.StakingKeeper slashingKeeper types.SlashingKeeper authorityKeeper types.AuthorityKeeper lightclientKeeper types.LightclientKeeper + bankKeeper types.BankKeeper + authKeeper types.AccountKeeper authority string } ) func NewKeeper( - cdc codec.BinaryCodec, + cdc codec.Codec, storeKey, memKey storetypes.StoreKey, stakingKeeper types.StakingKeeper, slashinKeeper types.SlashingKeeper, authorityKeeper types.AuthorityKeeper, lightclientKeeper types.LightclientKeeper, + bankKeeper types.BankKeeper, + authKeeper types.AccountKeeper, authority string, ) *Keeper { if _, err := sdk.AccAddressFromBech32(authority); err != nil { @@ -46,6 +50,8 @@ func NewKeeper( slashingKeeper: slashinKeeper, authorityKeeper: authorityKeeper, lightclientKeeper: lightclientKeeper, + bankKeeper: bankKeeper, + authKeeper: authKeeper, authority: authority, } } @@ -62,6 +68,14 @@ func (k Keeper) GetAuthorityKeeper() types.AuthorityKeeper { return k.authorityKeeper } +func (k Keeper) GetBankKeeper() types.BankKeeper { + return k.bankKeeper +} + +func (k Keeper) GetAuthKeeper() types.AccountKeeper { + return k.authKeeper +} + func (k Keeper) GetLightclientKeeper() types.LightclientKeeper { return k.lightclientKeeper } @@ -74,7 +88,7 @@ func (k Keeper) StoreKey() storetypes.StoreKey { return k.storeKey } -func (k Keeper) Codec() codec.BinaryCodec { +func (k Keeper) Codec() codec.Codec { return k.cdc } diff --git a/x/observer/keeper/msg_server_disable_cctx_flags.go b/x/observer/keeper/msg_server_disable_cctx_flags.go index bd5a0a9f17..70a77849cb 100644 --- a/x/observer/keeper/msg_server_disable_cctx_flags.go +++ b/x/observer/keeper/msg_server_disable_cctx_flags.go @@ -37,7 +37,6 @@ func (k msgServer) DisableCCTX( if msg.DisableOutbound { flags.IsOutboundEnabled = false } - k.SetCrosschainFlags(ctx, flags) err = ctx.EventManager().EmitTypedEvents(&types.EventCCTXDisabled{ diff --git a/x/observer/module.go b/x/observer/module.go index 41724bc8b1..1136be6ecd 100644 --- a/x/observer/module.go +++ b/x/observer/module.go @@ -31,10 +31,10 @@ var ( // AppModuleBasic implements the AppModuleBasic interface for the observer module. type AppModuleBasic struct { - cdc codec.BinaryCodec + cdc codec.Codec } -func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { +func NewAppModuleBasic(cdc codec.Codec) AppModuleBasic { return AppModuleBasic{cdc: cdc} } diff --git a/x/observer/module_simulation.go b/x/observer/module_simulation.go index 555532c090..373682019c 100644 --- a/x/observer/module_simulation.go +++ b/x/observer/module_simulation.go @@ -1,29 +1,18 @@ package observer import ( - "math/rand" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/zeta-chain/node/x/observer/simulation" "github.com/zeta-chain/node/x/observer/types" ) -const ( - // #nosec G101 not a hardcoded credential - opWeightMsgUpdateClientParams = "op_weight_msg_update_client_params" - defaultWeightMsgUpdateClientParams int = 100 -) - -// GenerateGenesisState creates a randomized GenState of the module +// GenerateGenesisState creates a GenState of the module used to initialize the simulation runs func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - observerGenesis := types.GenesisState{} - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&observerGenesis) + observerGenesis := types.DefaultGenesis() + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(observerGenesis) } // ProposalContents doesn't return any content functions for governance proposals @@ -36,18 +25,13 @@ func (AppModule) ProposalMsgs(_ module.SimulationState) []simtypes.WeightedPropo } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - var weightMsgUpdateClientParams int - simState.AppParams.GetOrGenerate(simState.Cdc, opWeightMsgUpdateClientParams, &weightMsgUpdateClientParams, nil, - func(_ *rand.Rand) { - weightMsgUpdateClientParams = defaultWeightMsgUpdateClientParams - }, + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.keeper, ) - - return operations } diff --git a/x/observer/simulation/decoders.go b/x/observer/simulation/decoders.go new file mode 100644 index 0000000000..fd8eae7535 --- /dev/null +++ b/x/observer/simulation/decoders.go @@ -0,0 +1,97 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/zeta-chain/node/x/observer/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding observer types. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key, types.KeyPrefix(types.CrosschainFlagsKey)): + var crosschainFlagsA, crosschainFlagsB types.CrosschainFlags + cdc.MustUnmarshal(kvA.Value, &crosschainFlagsA) + cdc.MustUnmarshal(kvB.Value, &crosschainFlagsB) + return fmt.Sprintf("%v\n%v", crosschainFlagsA, crosschainFlagsB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.LastBlockObserverCountKey)): + var lastBlockObserverCountA, lastBlockObserverCountB types.LastObserverCount + cdc.MustUnmarshal(kvA.Value, &lastBlockObserverCountA) + cdc.MustUnmarshal(kvB.Value, &lastBlockObserverCountB) + return fmt.Sprintf("%v\n%v", lastBlockObserverCountA, lastBlockObserverCountB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.NodeAccountKey)): + var nodeAccountA, nodeAccountB types.NodeAccount + cdc.MustUnmarshal(kvA.Value, &nodeAccountA) + cdc.MustUnmarshal(kvB.Value, &nodeAccountB) + return fmt.Sprintf("%v\n%v", nodeAccountA, nodeAccountB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.KeygenKey)): + var keygenA, keygenB types.Keygen + cdc.MustUnmarshal(kvA.Value, &keygenA) + cdc.MustUnmarshal(kvB.Value, &keygenB) + return fmt.Sprintf("%v\n%v", keygenA, keygenB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.BallotListKey)): + var ballotListA, ballotListB types.BallotListForHeight + cdc.MustUnmarshal(kvA.Value, &ballotListA) + cdc.MustUnmarshal(kvB.Value, &ballotListB) + return fmt.Sprintf("%v\n%v", ballotListA, ballotListB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.VoterKey)): + var voterA, voterB types.Ballot + cdc.MustUnmarshal(kvA.Value, &voterA) + cdc.MustUnmarshal(kvB.Value, &voterB) + return fmt.Sprintf("%v\n%v", voterA, voterB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.TSSKey)): + var tssA, tssB types.TSS + cdc.MustUnmarshal(kvA.Value, &tssA) + cdc.MustUnmarshal(kvB.Value, &tssB) + return fmt.Sprintf("%v\n%v", tssA, tssB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ObserverSetKey)): + var observerSetA, observerSetB types.ObserverSet + cdc.MustUnmarshal(kvA.Value, &observerSetA) + cdc.MustUnmarshal(kvB.Value, &observerSetB) + return fmt.Sprintf("%v\n%v", observerSetA, observerSetB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.AllChainParamsKey)): + var allChainParamsA, allChainParamsB types.ChainParamsList + cdc.MustUnmarshal(kvA.Value, &allChainParamsA) + cdc.MustUnmarshal(kvB.Value, &allChainParamsB) + return fmt.Sprintf("%v\n%v", allChainParamsA, allChainParamsB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.TSSHistoryKey)): + var tssHistoryA, tssHistoryB types.TSS + cdc.MustUnmarshal(kvA.Value, &tssHistoryA) + cdc.MustUnmarshal(kvB.Value, &tssHistoryB) + return fmt.Sprintf("%v\n%v", tssHistoryA, tssHistoryB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.TssFundMigratorKey)): + var tssFundMigratorA, tssFundMigratorB types.TssFundMigratorInfo + cdc.MustUnmarshal(kvA.Value, &tssFundMigratorA) + cdc.MustUnmarshal(kvB.Value, &tssFundMigratorB) + return fmt.Sprintf("%v\n%v", tssFundMigratorA, tssFundMigratorB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.PendingNoncesKeyPrefix)): + var pendingNoncesA, pendingNoncesB types.PendingNonces + cdc.MustUnmarshal(kvA.Value, &pendingNoncesA) + cdc.MustUnmarshal(kvB.Value, &pendingNoncesB) + return fmt.Sprintf("%v\n%v", pendingNoncesA, pendingNoncesB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ChainNoncesKey)): + var chainNoncesA, chainNoncesB types.ChainNonces + cdc.MustUnmarshal(kvA.Value, &chainNoncesA) + cdc.MustUnmarshal(kvB.Value, &chainNoncesB) + return fmt.Sprintf("%v\n%v", chainNoncesA, chainNoncesB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.NonceToCctxKeyPrefix)): + var nonceToCctxA, nonceToCctxB types.NonceToCctx + cdc.MustUnmarshal(kvA.Value, &nonceToCctxA) + cdc.MustUnmarshal(kvB.Value, &nonceToCctxB) + return fmt.Sprintf("%v\n%v", nonceToCctxA, nonceToCctxB) + case bytes.Equal(kvA.Key, types.KeyPrefix(types.ParamsKey)): + var paramsA, paramsB types.Params + cdc.MustUnmarshal(kvA.Value, ¶msA) + cdc.MustUnmarshal(kvB.Value, ¶msB) + return fmt.Sprintf("%v\n%v", paramsA, paramsB) + default: + panic(fmt.Sprintf("invalid observer key prefix %X", kvA.Key)) + } + } +} diff --git a/x/observer/simulation/decoders_test.go b/x/observer/simulation/decoders_test.go new file mode 100644 index 0000000000..616994a664 --- /dev/null +++ b/x/observer/simulation/decoders_test.go @@ -0,0 +1,88 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/pkg/chains" + keepertest "github.com/zeta-chain/node/testutil/keeper" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/observer/simulation" + "github.com/zeta-chain/node/x/observer/types" +) + +func TestNewDecodeStore(t *testing.T) { + k, _, _, _ := keepertest.ObserverKeeper(t) + cdc := k.Codec() + dec := simulation.NewDecodeStore(cdc) + crosschainFlags := sample.CrosschainFlags() + lastBlockObserverCount := sample.LastObserverCount(10) + nodeAccount := sample.NodeAccount() + keygen := sample.Keygen(t) + + ballotList := types.BallotListForHeight{ + Height: 10, + BallotsIndexList: []string{sample.ZetaIndex(t)}, + } + + ballot := sample.Ballot(t, "sample") + tss := sample.Tss() + observerSet := sample.ObserverSet(10) + chainParamsList := sample.ChainParamsList() + //tssHistory := sample.TssList(10) + tssFundMigrator := sample.TssFundsMigrator(chains.Ethereum.ChainId) + pendingNonce := sample.PendingNoncesList(t, "index", 10)[0] + chainNonces := sample.ChainNonces(chains.Ethereum.ChainId) + nonceToCctx := sample.NonceToCCTX(t, "index") + params := types.Params{BallotMaturityBlocks: 100} + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.KeyPrefix(types.CrosschainFlagsKey), Value: cdc.MustMarshal(crosschainFlags)}, + {Key: types.KeyPrefix(types.LastBlockObserverCountKey), Value: cdc.MustMarshal(lastBlockObserverCount)}, + {Key: types.KeyPrefix(types.NodeAccountKey), Value: cdc.MustMarshal(nodeAccount)}, + {Key: types.KeyPrefix(types.KeygenKey), Value: cdc.MustMarshal(keygen)}, + {Key: types.KeyPrefix(types.BallotListKey), Value: cdc.MustMarshal(&ballotList)}, + {Key: types.KeyPrefix(types.VoterKey), Value: cdc.MustMarshal(ballot)}, + {Key: types.KeyPrefix(types.TSSKey), Value: cdc.MustMarshal(&tss)}, + {Key: types.KeyPrefix(types.TSSHistoryKey), Value: cdc.MustMarshal(&tss)}, + {Key: types.KeyPrefix(types.ObserverSetKey), Value: cdc.MustMarshal(&observerSet)}, + {Key: types.KeyPrefix(types.AllChainParamsKey), Value: cdc.MustMarshal(&chainParamsList)}, + {Key: types.KeyPrefix(types.TssFundMigratorKey), Value: cdc.MustMarshal(&tssFundMigrator)}, + {Key: types.KeyPrefix(types.PendingNoncesKeyPrefix), Value: cdc.MustMarshal(&pendingNonce)}, + {Key: types.KeyPrefix(types.ChainNoncesKey), Value: cdc.MustMarshal(&chainNonces)}, + {Key: types.KeyPrefix(types.NonceToCctxKeyPrefix), Value: cdc.MustMarshal(&nonceToCctx)}, + {Key: types.KeyPrefix(types.ParamsKey), Value: cdc.MustMarshal(¶ms)}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"CrosschainFlags", fmt.Sprintf("%v\n%v", *crosschainFlags, *crosschainFlags)}, + {"LastBlockObserverCount", fmt.Sprintf("%v\n%v", *lastBlockObserverCount, *lastBlockObserverCount)}, + {"NodeAccount", fmt.Sprintf("%v\n%v", *nodeAccount, *nodeAccount)}, + {"Keygen", fmt.Sprintf("%v\n%v", *keygen, *keygen)}, + {"BallotList", fmt.Sprintf("%v\n%v", ballotList, ballotList)}, + {"Ballot", fmt.Sprintf("%v\n%v", *ballot, *ballot)}, + {"TSS", fmt.Sprintf("%v\n%v", tss, tss)}, + {"TSSHistory", fmt.Sprintf("%v\n%v", tss, tss)}, + {"ObserverSet", fmt.Sprintf("%v\n%v", observerSet, observerSet)}, + {"ChainParamsList", fmt.Sprintf("%v\n%v", chainParamsList, chainParamsList)}, + {"TssFundMigrator", fmt.Sprintf("%v\n%v", tssFundMigrator, tssFundMigrator)}, + {"PendingNonces", fmt.Sprintf("%v\n%v", pendingNonce, pendingNonce)}, + {"ChainNonces", fmt.Sprintf("%v\n%v", chainNonces, chainNonces)}, + {"NonceToCctx", fmt.Sprintf("%v\n%v", nonceToCctx, nonceToCctx)}, + {"Params", fmt.Sprintf("%v\n%v", params, params)}, + } + + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i])) + }) + } +} diff --git a/x/observer/simulation/operations.go b/x/observer/simulation/operations.go new file mode 100644 index 0000000000..7c12ced3c0 --- /dev/null +++ b/x/observer/simulation/operations.go @@ -0,0 +1,107 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/zeta-chain/node/x/observer/keeper" + "github.com/zeta-chain/node/x/observer/types" +) + +// Simulation operation weights constants +// Operation weights are used by the `SimulateFromSeed` +// function to pick a random operation based on the weights.The functions with higher weights are more likely to be picked. + +// Therefore, this decides the percentage of a certain operation that is part of a block. + +// Based on the weights assigned in the cosmos sdk modules, +// 100 seems to the max weight used,and we should use relative weights +// to signify the number of each operation in a block. + +// TODO Add more details to comment based on what the number represents in terms of percentage of operations in a block +// https://github.com/zeta-chain/node/issues/3100 +const ( + // #nosec G101 not a hardcoded credential + OpWeightMsgTypeMsgEnableCCTX = "op_weight_msg_enable_crosschain_flags" + // DefaultWeightMsgTypeMsgEnableCCTX We ues a high weight for this operation + // to ensure that it is present in the block more number of times than any operation that changes the validator set + + // Arrived at this number based on the weights used in the cosmos sdk staking module and through some trial and error + DefaultWeightMsgTypeMsgEnableCCTX = 3650 +) + +// WeightedOperations for observer module +func WeightedOperations( + appParams simtypes.AppParams, cdc codec.JSONCodec, k keeper.Keeper, +) simulation.WeightedOperations { + var weightMsgTypeMsgEnableCCTX int + + appParams.GetOrGenerate(cdc, OpWeightMsgTypeMsgEnableCCTX, &weightMsgTypeMsgEnableCCTX, nil, + func(_ *rand.Rand) { + weightMsgTypeMsgEnableCCTX = DefaultWeightMsgTypeMsgEnableCCTX + }) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgTypeMsgEnableCCTX, + SimulateMsgTypeMsgEnableCCTX(k), + ), + } +} + +// SimulateMsgTypeMsgEnableCCTX generates a MsgEnableCCTX and delivers it. +func SimulateMsgTypeMsgEnableCCTX(k keeper.Keeper) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, _ string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + policies, found := k.GetAuthorityKeeper().GetPolicies(ctx) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEnableCCTX, "policies object not found"), nil, nil + } + if len(policies.Items) == 0 { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEnableCCTX, "no policies found"), nil, nil + } + + admin := policies.Items[0].Address + address, err := types.GetOperatorAddressFromAccAddress(admin) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEnableCCTX, err.Error()), nil, err + } + simAccount, found := simtypes.FindAccount(accounts, address) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEnableCCTX, "admin account not found"), nil, nil + } + + msg := types.MsgEnableCCTX{ + Creator: simAccount.Address.String(), + EnableInbound: true, + EnableOutbound: false, + } + + err = msg.ValidateBasic() + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), err.Error()), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: k.GetAuthKeeper(), + Bankkeeper: k.GetBankKeeper(), + ModuleName: types.ModuleName, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/observer/simulation/simap.go b/x/observer/simulation/simap.go deleted file mode 100644 index 92c437c0d1..0000000000 --- a/x/observer/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/observer/types/expected_keepers.go b/x/observer/types/expected_keepers.go index 2cf2b9ac75..2788187c94 100644 --- a/x/observer/types/expected_keepers.go +++ b/x/observer/types/expected_keepers.go @@ -2,6 +2,7 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -38,6 +39,7 @@ type AuthorityKeeper interface { // SetPolicies is solely used for the migration of policies from observer to authority SetPolicies(ctx sdk.Context, policies authoritytypes.Policies) + GetPolicies(ctx sdk.Context) (val authoritytypes.Policies, found bool) } type LightclientKeeper interface { @@ -57,3 +59,12 @@ type LightclientKeeper interface { parentHash []byte, ) } + +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins +} + +// AccountKeeper defines the expected account keeper used for simulations (noalias) +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI +} diff --git a/x/observer/types/keys.go b/x/observer/types/keys.go index 539eab83c0..abdb2543d0 100644 --- a/x/observer/types/keys.go +++ b/x/observer/types/keys.go @@ -49,13 +49,15 @@ func ChainNoncesKeyPrefix(chainID int64) []byte { return []byte(strconv.FormatInt(chainID, 10)) } +// Address TODOS for name changes: https://github.com/zeta-chain/node/issues/3098 const ( BlameKey = "Blame-" - // TODO change identifier for VoterKey to something more descriptive + // TODO change identifier for VoterKey to BallotKey VoterKey = "Voter-value-" // AllChainParamsKey is the ke prefix for all chain params // NOTE: CoreParams is old name for AllChainParams we keep it as key value for backward compatibility + // TODO rename to ChainParamsListKey AllChainParamsKey = "CoreParams" ObserverSetKey = "ObserverSet-value-" @@ -64,20 +66,25 @@ const ( // NOTE: PermissionFlags is old name for CrosschainFlags we keep it as key value for backward compatibility CrosschainFlagsKey = "PermissionFlags-value-" + // TODO rename to LastObserverCountKey LastBlockObserverCountKey = "ObserverCount-value-" NodeAccountKey = "NodeAccount-value-" KeygenKey = "Keygen-value-" - BlockHeaderKey = "BlockHeader-value-" - BlockHeaderStateKey = "BlockHeaderState-value-" - BallotListKey = "BallotList-value-" - TSSKey = "TSS-value-" - TSSHistoryKey = "TSS-History-value-" + // TODO rename to BallotListForHeightKey + BallotListKey = "BallotList-value-" + TSSKey = "TSS-value-" + TSSHistoryKey = "TSS-History-value-" + + // TODO rename to TssFundMigratorInfoKey TssFundMigratorKey = "FundsMigrator-value-" + // TODO Rename to PendingNoncesListKey PendingNoncesKeyPrefix = "PendingNonces-value-" ChainNoncesKey = "ChainNonces-value-" - NonceToCctxKeyPrefix = "NonceToCctx-value-" + + // TODO rename to NonceToCctxListKey + NonceToCctxKeyPrefix = "NonceToCctx-value-" ParamsKey = "Params-value-" ) diff --git a/zetaclient/testutils/mocks/zetacore_client.go b/zetaclient/testutils/mocks/zetacore_client.go index fa5b34486b..fefd653874 100644 --- a/zetaclient/testutils/mocks/zetacore_client.go +++ b/zetaclient/testutils/mocks/zetacore_client.go @@ -22,8 +22,8 @@ import ( upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - zerolog "github.com/rs/zerolog" cometbfttypes "github.com/cometbft/cometbft/types" + zerolog "github.com/rs/zerolog" ) // ZetacoreClient is an autogenerated mock type for the ZetacoreClient type From 66adf1dcb353eb0c9f6fc746b5a29220e59f91b7 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Wed, 11 Dec 2024 07:43:07 -0800 Subject: [PATCH 2/7] chore: use v23 as base in upgrade tests (#3272) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e2d34c31f4..9d399507e6 100644 --- a/Makefile +++ b/Makefile @@ -327,7 +327,7 @@ ifdef UPGRADE_TEST_FROM_SOURCE zetanode-upgrade: e2e-images @echo "Building zetanode-upgrade from source" $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime-source \ - --build-arg OLD_VERSION='release/v22' \ + --build-arg OLD_VERSION='release/v23' \ --build-arg NODE_VERSION=$(NODE_VERSION) \ --build-arg NODE_COMMIT=$(NODE_COMMIT) . @@ -336,7 +336,7 @@ else zetanode-upgrade: e2e-images @echo "Building zetanode-upgrade from binaries" $(DOCKER) build -t zetanode:old -f Dockerfile-localnet --target old-runtime \ - --build-arg OLD_VERSION='https://github.com/zeta-chain/node/releases/download/v22.1.1' \ + --build-arg OLD_VERSION='https://github.com/zeta-chain/node/releases/download/v23.1.5' \ --build-arg NODE_VERSION=$(NODE_VERSION) \ --build-arg NODE_COMMIT=$(NODE_COMMIT) \ . From be8783b1b74e023d3cbf2aa773ad6045f64c2e15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 08:37:20 -0800 Subject: [PATCH 3/7] chore(deps): bump cosmossdk.io/math from 1.3.0 to 1.4.0 (#3269) Bumps [cosmossdk.io/math](https://github.com/cosmos/cosmos-sdk) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/cosmos/cosmos-sdk/releases) - [Changelog](https://github.com/cosmos/cosmos-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/cosmos/cosmos-sdk/compare/log/v1.3.0...log/v1.4.0) --- updated-dependencies: - dependency-name: cosmossdk.io/math dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: Alex Gartner --- go.mod | 2 +- go.sum | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 05a7166c93..e2eac8fdac 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.8 require ( cosmossdk.io/errors v1.0.1 - cosmossdk.io/math v1.3.0 + cosmossdk.io/math v1.4.0 cosmossdk.io/tools/rosetta v0.2.1 // indirect github.com/99designs/keyring v1.2.1 github.com/btcsuite/btcd v0.24.2 diff --git a/go.sum b/go.sum index 5862cca9f4..9cd653fcf0 100644 --- a/go.sum +++ b/go.sum @@ -195,8 +195,8 @@ cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0= cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U= cosmossdk.io/log v1.4.1 h1:wKdjfDRbDyZRuWa8M+9nuvpVYxrEOwbD/CA8hvhU8QM= cosmossdk.io/log v1.4.1/go.mod h1:k08v0Pyq+gCP6phvdI6RCGhLf/r425UT6Rk/m+o74rU= -cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= -cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k= +cosmossdk.io/math v1.4.0 h1:XbgExXFnXmF/CccPPEto40gOO7FpWu9yWNAZPN3nkNQ= +cosmossdk.io/math v1.4.0/go.mod h1:O5PkD4apz2jZs4zqFdTr16e1dcaQCc5z6lkEnrrppuk= cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -597,8 +597,6 @@ github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7 github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= -github.com/gagliardetto/solana-go v1.10.0 h1:lDuHGC+XLxw9j8fCHBZM9tv4trI0PVhev1m9NAMaIdM= -github.com/gagliardetto/solana-go v1.10.0/go.mod h1:afBEcIRrDLJst3lvAahTr63m6W2Ns6dajZxe2irF7Jg= github.com/gagliardetto/solana-go v1.12.0 h1:rzsbilDPj6p+/DOPXBMLhwMZeBgeRuXjm5zQFCoXgsg= github.com/gagliardetto/solana-go v1.12.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k= github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= @@ -1462,7 +1460,6 @@ github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= @@ -1502,9 +1499,7 @@ github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -1547,8 +1542,6 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 h1:qxen9oVGzDdIRP6ejyAJc760RwW4SnVDiTYTzwnXuxo= go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5/go.mod h1:eW0HG9/oHQhvRCvb1/pIXW4cOvtDqeQK+XSi3TnwaXY= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE= -go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws= go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= go.nhat.io/aferomock v0.4.0 h1:gs3nJzIqAezglUuaPfautAmZwulwRWLcfSSzdK4YCC0= From 6bbe9ea97f32bb2f7d34e9e9b498b14cd5df7cc0 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Thu, 12 Dec 2024 08:19:31 +0100 Subject: [PATCH 4/7] fix: enforce checksum format for asset address in ZRC20 (#3278) * add checksum check * changelog * add migration script * add logs * Update pkg/crypto/evm_address.go Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> * remove duplicated logs --------- Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> --- changelog.md | 1 + pkg/crypto/address.go | 12 -- pkg/crypto/address_test.go | 37 ----- pkg/crypto/evm_address.go | 31 ++++ pkg/crypto/evm_address_test.go | 156 ++++++++++++++++++ testutil/sample/fungible.go | 1 + x/crosschain/types/message_whitelist_erc20.go | 22 ++- .../types/message_whitelist_erc20_test.go | 30 +++- x/fungible/keeper/migrator.go | 24 +++ x/fungible/migrations/v3/migrate.go | 30 ++++ x/fungible/migrations/v3/migrate_test.go | 76 +++++++++ x/fungible/module.go | 6 +- 12 files changed, 371 insertions(+), 55 deletions(-) delete mode 100644 pkg/crypto/address.go delete mode 100644 pkg/crypto/address_test.go create mode 100644 pkg/crypto/evm_address.go create mode 100644 pkg/crypto/evm_address_test.go create mode 100644 x/fungible/keeper/migrator.go create mode 100644 x/fungible/migrations/v3/migrate.go create mode 100644 x/fungible/migrations/v3/migrate_test.go diff --git a/changelog.md b/changelog.md index 791af078fe..14baffeb92 100644 --- a/changelog.md +++ b/changelog.md @@ -24,6 +24,7 @@ * [3225](https://github.com/zeta-chain/node/pull/3225) - use separate database file names for btc signet and testnet4 * [3242](https://github.com/zeta-chain/node/pull/3242) - set the `Receiver` of `MsgVoteInbound` to the address pulled from solana memo * [3253](https://github.com/zeta-chain/node/pull/3253) - fix solana inbound version 0 queries and move tss keysign prior to relayer key checking +* [3278](https://github.com/zeta-chain/node/pull/3278) - enforce checksum format for asset address in ZRC20 ## v23.0.0 diff --git a/pkg/crypto/address.go b/pkg/crypto/address.go deleted file mode 100644 index 90876a790f..0000000000 --- a/pkg/crypto/address.go +++ /dev/null @@ -1,12 +0,0 @@ -package crypto - -import ( - "github.com/ethereum/go-ethereum/common" - - "github.com/zeta-chain/node/pkg/constant" -) - -// IsEmptyAddress returns true if the address is empty -func IsEmptyAddress(address common.Address) bool { - return address == (common.Address{}) || address.Hex() == constant.EVMZeroAddress -} diff --git a/pkg/crypto/address_test.go b/pkg/crypto/address_test.go deleted file mode 100644 index db10c5c7cf..0000000000 --- a/pkg/crypto/address_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package crypto - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - "github.com/zeta-chain/node/pkg/constant" - "testing" -) - -func TestIsEmptyAddress(t *testing.T) { - tests := []struct { - name string - address common.Address - want bool - }{ - { - name: "empty address", - address: common.Address{}, - want: true, - }, - { - name: "zero address", - address: common.HexToAddress(constant.EVMZeroAddress), - want: true, - }, - { - name: "non empty address", - address: common.HexToAddress("0x1"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.EqualValues(t, tt.want, IsEmptyAddress(tt.address)) - }) - } -} diff --git a/pkg/crypto/evm_address.go b/pkg/crypto/evm_address.go new file mode 100644 index 0000000000..803bfca8fd --- /dev/null +++ b/pkg/crypto/evm_address.go @@ -0,0 +1,31 @@ +package crypto + +import ( + "strings" + + "github.com/ethereum/go-ethereum/common" + + "github.com/zeta-chain/node/pkg/constant" +) + +// IsEmptyAddress returns true if the address is empty +func IsEmptyAddress(address common.Address) bool { + return address == (common.Address{}) || address.Hex() == constant.EVMZeroAddress +} + +// IsEVMAddress returns true if the string is an EVM address +// independently of the checksum format +func IsEVMAddress(address string) bool { + return len(address) == 42 && strings.HasPrefix(address, "0x") && common.IsHexAddress(address) +} + +// IsChecksumAddress returns true if the EVM address string is a valid checksum address +// See https://eips.ethereum.org/EIPS/eip-55 +func IsChecksumAddress(address string) bool { + return address == common.HexToAddress(address).Hex() +} + +// ToChecksumAddress returns the checksum address of the given EVM address +func ToChecksumAddress(address string) string { + return common.HexToAddress(address).Hex() +} diff --git a/pkg/crypto/evm_address_test.go b/pkg/crypto/evm_address_test.go new file mode 100644 index 0000000000..307bdc6866 --- /dev/null +++ b/pkg/crypto/evm_address_test.go @@ -0,0 +1,156 @@ +package crypto + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/pkg/constant" + "testing" +) + +func TestIsEmptyAddress(t *testing.T) { + tests := []struct { + name string + address common.Address + want bool + }{ + { + name: "empty address", + address: common.Address{}, + want: true, + }, + { + name: "zero address", + address: common.HexToAddress(constant.EVMZeroAddress), + want: true, + }, + { + name: "non empty address", + address: common.HexToAddress("0x1"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, IsEmptyAddress(tt.address)) + }) + } +} + +func TestIsEVMAddress(t *testing.T) { + tests := []struct { + name string + address string + want bool + }{ + { + name: "EVM address", + address: "0x5a4f260A7D716c859A2736151cB38b9c58C32c64", + want: true, + }, + { + name: "EVM address with invalid checksum", + address: "0x5a4f260a7D716c859A2736151CB38b9c58C32c64", + want: true, + }, + { + name: "EVM address all lowercase", + address: "0x5a4f260a7d716c859a2736151cb38b9c58c32c64", + want: true, + }, + { + name: "EVM address all uppercase", + address: "0x5A4F260A7D716C859A2736151CB38B9C58C32C64", + want: true, + }, + { + name: "invalid EVM address", + address: "5a4f260A7D716c859A2736151cB38b9c58C32c64", + }, + { + name: "empty address", + address: "", + }, + { + name: "non EVM address", + address: "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, IsEVMAddress(tt.address)) + }) + } +} + +func TestIsChecksumAddress(t *testing.T) { + tests := []struct { + name string + address string + want bool + }{ + { + name: "checksum address", + address: "0x5a4f260A7D716c859A2736151cB38b9c58C32c64", + want: true, + }, + { + name: "invalid checksum address", + address: "0x5a4f260a7D716c859A2736151CB38b9c58C32c64", + }, + { + name: "all lowercase", + address: "0x5a4f260a7d716c859a2736151cb38b9c58c32c64", + }, + { + name: "all uppercase", + address: "0x5A4F260A7D716C859A2736151CB38B9C58C32C64", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, IsChecksumAddress(tt.address)) + }) + } +} + +func TestToChecksumAddress(t *testing.T) { + tests := []struct { + name string + address string + want string + }{ + { + name: "checksum address", + address: "0x5a4f260A7D716c859A2736151cB38b9c58C32c64", + want: "0x5a4f260A7D716c859A2736151cB38b9c58C32c64", + }, + { + name: "all lowercase", + address: "0x5a4f260a7d716c859a2736151cb38b9c58c32c64", + want: "0x5a4f260A7D716c859A2736151cB38b9c58C32c64", + }, + { + name: "all uppercase", + address: "0x5A4F260A7D716C859A2736151CB38B9C58C32C64", + want: "0x5a4f260A7D716c859A2736151cB38b9c58C32c64", + }, + { + name: "empty address returns null address", + address: "", + want: "0x0000000000000000000000000000000000000000", + }, + { + name: "non evm address returns null address", + address: "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + want: "0x0000000000000000000000000000000000000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, ToChecksumAddress(tt.address)) + }) + } +} diff --git a/testutil/sample/fungible.go b/testutil/sample/fungible.go index 7d4ae17577..4aecea486b 100644 --- a/testutil/sample/fungible.go +++ b/testutil/sample/fungible.go @@ -20,6 +20,7 @@ func ForeignCoins(t *testing.T, address string) types.ForeignCoins { Symbol: StringRandom(r, 32), CoinType: coin.CoinType_ERC20, GasLimit: r.Uint64(), + LiquidityCap: UintInRange(0, 10000000000), } } diff --git a/x/crosschain/types/message_whitelist_erc20.go b/x/crosschain/types/message_whitelist_erc20.go index d27492d7a5..be5ca0e9ca 100644 --- a/x/crosschain/types/message_whitelist_erc20.go +++ b/x/crosschain/types/message_whitelist_erc20.go @@ -4,7 +4,9 @@ import ( cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/pkg/errors" + "github.com/zeta-chain/node/pkg/crypto" "github.com/zeta-chain/node/x/fungible/types" ) @@ -50,10 +52,10 @@ func (msg *MsgWhitelistERC20) GetSignBytes() []byte { func (msg *MsgWhitelistERC20) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err.Error()) } - if msg.Erc20Address == "" { - return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "empty asset address") + if err := validateAssetAddress(msg.Erc20Address); err != nil { + return cosmoserrors.Wrapf(types.ErrInvalidAddress, "invalid asset address (%s)", err.Error()) } if msg.Decimals > 128 { return cosmoserrors.Wrapf(types.ErrInvalidDecimals, "invalid decimals (%d)", msg.Decimals) @@ -63,3 +65,17 @@ func (msg *MsgWhitelistERC20) ValidateBasic() error { } return nil } + +func validateAssetAddress(address string) error { + if address == "" { + return errors.New("empty asset address") + } + + // if the address is an evm address, check if it is in checksum format + if crypto.IsEVMAddress(address) && !crypto.IsChecksumAddress(address) { + return errors.New("evm address is not in checksum format") + } + + // currently no specific check is implemented for other address format + return nil +} diff --git a/x/crosschain/types/message_whitelist_erc20_test.go b/x/crosschain/types/message_whitelist_erc20_test.go index d5bf845272..4af25c215f 100644 --- a/x/crosschain/types/message_whitelist_erc20_test.go +++ b/x/crosschain/types/message_whitelist_erc20_test.go @@ -71,10 +71,36 @@ func TestMsgWhitelistERC20_ValidateBasic(t *testing.T) { error: true, }, { - name: "valid", + name: "evm asset address with invalid checksum format", msg: types.NewMsgWhitelistERC20( sample.AccAddress(), - sample.EthAddress().Hex(), + "0x5a4f260a7d716c859a2736151cb38b9c58c32c64", + 1, + "name", + "symbol", + 6, + 10, + ), + error: true, + }, + { + name: "valid message with evm asset address", + msg: types.NewMsgWhitelistERC20( + sample.AccAddress(), + "0x5a4f260A7D716c859A2736151cB38b9c58C32c64", + 1, + "name", + "symbol", + 6, + 10, + ), + error: false, + }, + { + name: "valid message with solana asset address", + msg: types.NewMsgWhitelistERC20( + sample.AccAddress(), + "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", 1, "name", "symbol", diff --git a/x/fungible/keeper/migrator.go b/x/fungible/keeper/migrator.go new file mode 100644 index 0000000000..011b284074 --- /dev/null +++ b/x/fungible/keeper/migrator.go @@ -0,0 +1,24 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + v3 "github.com/zeta-chain/node/x/fungible/migrations/v3" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + fungibleKeeper Keeper +} + +// NewMigrator returns a new Migrator. +func NewMigrator(keeper Keeper) Migrator { + return Migrator{ + fungibleKeeper: keeper, + } +} + +// Migrate2to3 migrates the store from consensus version 2 to 3 +func (m Migrator) Migrate2to3(ctx sdk.Context) error { + return v3.MigrateStore(ctx, m.fungibleKeeper) +} diff --git a/x/fungible/migrations/v3/migrate.go b/x/fungible/migrations/v3/migrate.go new file mode 100644 index 0000000000..f67ec43d99 --- /dev/null +++ b/x/fungible/migrations/v3/migrate.go @@ -0,0 +1,30 @@ +package v3 + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/zeta-chain/node/pkg/crypto" + "github.com/zeta-chain/node/x/fungible/types" +) + +type fungibleKeeper interface { + GetAllForeignCoins(ctx sdk.Context) (list []types.ForeignCoins) + SetForeignCoins(ctx sdk.Context, foreignCoins types.ForeignCoins) +} + +// MigrateStore migrates the x/fungible module state from the consensus version 2 to 3 +// It updates all existing address in ForeignCoin to use checksum format if the address is EVM type +func MigrateStore(ctx sdk.Context, fungibleKeeper fungibleKeeper) error { + fcs := fungibleKeeper.GetAllForeignCoins(ctx) + for _, fc := range fcs { + if fc.Asset != "" && crypto.IsEVMAddress(fc.Asset) && !crypto.IsChecksumAddress(fc.Asset) { + checksumAddress := crypto.ToChecksumAddress(fc.Asset) + ctx.Logger().Info("Patching zrc20 asset", "zrc20", fc.Symbol, "old", fc.Asset, "new", checksumAddress) + + fc.Asset = checksumAddress + fungibleKeeper.SetForeignCoins(ctx, fc) + } + } + + return nil +} diff --git a/x/fungible/migrations/v3/migrate_test.go b/x/fungible/migrations/v3/migrate_test.go new file mode 100644 index 0000000000..767c9d7eb2 --- /dev/null +++ b/x/fungible/migrations/v3/migrate_test.go @@ -0,0 +1,76 @@ +package v3_test + +import ( + "github.com/stretchr/testify/require" + keepertest "github.com/zeta-chain/node/testutil/keeper" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/fungible/migrations/v3" + "github.com/zeta-chain/node/x/fungible/types" + "testing" +) + +func TestMigrateStore(t *testing.T) { + tests := []struct { + name string + assetList []string + expectedList []string + }{ + { + name: "no asset to update", + assetList: []string{}, + expectedList: []string{}, + }, + { + name: "assets to update", + assetList: []string{ + "", + "0x5a4f260a7d716c859a2736151cb38b9c58c32c64", // lowercase + "", + "0xc0ffee254729296a45a3885639AC7E10F9d54979", // checksum + "", + "", + "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + "BrS9iNMC3y8J4QTmCz8VrGrYepdoxXYvKxcDMiixwLn5", + "0x999999CF1046E68E36E1AA2E0E07105EDDD1F08E", // uppercase + }, + expectedList: []string{ + "", + "0x5a4f260A7D716c859A2736151cB38b9c58C32c64", + "", + "0xc0ffee254729296a45a3885639AC7E10F9d54979", + "", + "", + "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + "BrS9iNMC3y8J4QTmCz8VrGrYepdoxXYvKxcDMiixwLn5", + "0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E", + }, + }, + } + + for _, tt := range tests { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + // Arrange + + // set sample foreign coins + expectedForeignCoins := make([]types.ForeignCoins, len(tt.assetList)) + for i, asset := range tt.assetList { + expectedForeignCoins[i] = sample.ForeignCoins(t, sample.EthAddress().Hex()) + expectedForeignCoins[i].Asset = asset + k.SetForeignCoins(ctx, expectedForeignCoins[i]) + } + + // update for expected list + for i := range tt.assetList { + expectedForeignCoins[i].Asset = tt.expectedList[i] + } + + // Act + err := v3.MigrateStore(ctx, k) + require.NoError(t, err) + + // Assert + actualForeignCoins := k.GetAllForeignCoins(ctx) + require.ElementsMatch(t, expectedForeignCoins, actualForeignCoins) + } + +} diff --git a/x/fungible/module.go b/x/fungible/module.go index 8e87fdbd43..13b7e59e40 100644 --- a/x/fungible/module.go +++ b/x/fungible/module.go @@ -123,6 +123,10 @@ func (am AppModule) Name() string { func (am AppModule) RegisterServices(cfg module.Configurator) { types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) types.RegisterQueryServer(cfg.QueryServer(), am.keeper) + m := keeper.NewMigrator(am.keeper) + if err := cfg.RegisterMigration(types.ModuleName, 2, m.Migrate2to3); err != nil { + panic(err) + } } // RegisterInvariants registers the fungible module's invariants. @@ -153,7 +157,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw } // ConsensusVersion implements ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 2 } +func (AppModule) ConsensusVersion() uint64 { return 3 } // BeginBlock executes all ABCI BeginBlock logic respective to the fungible module. func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} From cdb54c253fa9598714b1c658ffcac8be687bfcfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:34:33 -0800 Subject: [PATCH 5/7] chore(deps): bump golang.org/x/crypto from 0.23.0 to 0.31.0 (#3281) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.23.0 to 0.31.0. - [Commits](https://github.com/golang/crypto/compare/v0.23.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index e2eac8fdac..7b3cf1b668 100644 --- a/go.mod +++ b/go.mod @@ -59,10 +59,10 @@ require ( github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c gitlab.com/thorchain/tss/go-tss v1.6.5 go.nhat.io/grpcmock v0.25.0 - golang.org/x/crypto v0.23.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/net v0.25.0 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.10.0 google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 google.golang.org/grpc v1.62.1 google.golang.org/protobuf v1.33.0 @@ -297,9 +297,9 @@ require ( go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.17.0 golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.20.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.155.0 // indirect diff --git a/go.sum b/go.sum index 9cd653fcf0..6e3e894a3e 100644 --- a/go.sum +++ b/go.sum @@ -1645,8 +1645,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1812,8 +1812,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1939,8 +1939,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1954,8 +1954,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1974,8 +1974,8 @@ golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From f63cccf2b996daab10cf808ca2d692fe60a80c7b Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Thu, 12 Dec 2024 09:00:02 -0800 Subject: [PATCH 6/7] refactor(e2e): use address_book.seed rather than bootstrap peer (#3277) * refactor(e2e): use seed address book rather than bootstrap peer * review updates * move to local * feedback --- cmd/zetae2e/local/get_zetaclient_bootstrap.go | 104 ++++++++++++++++++ cmd/zetae2e/local/local.go | 2 + contrib/localnet/scripts/start-zetaclientd.sh | 14 +-- 3 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 cmd/zetae2e/local/get_zetaclient_bootstrap.go diff --git a/cmd/zetae2e/local/get_zetaclient_bootstrap.go b/cmd/zetae2e/local/get_zetaclient_bootstrap.go new file mode 100644 index 0000000000..16cbdcf10c --- /dev/null +++ b/cmd/zetae2e/local/get_zetaclient_bootstrap.go @@ -0,0 +1,104 @@ +package local + +import ( + "fmt" + "net" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/spf13/cobra" + "gitlab.com/thorchain/tss/go-tss/conversion" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/zeta-chain/node/pkg/rpc" + "github.com/zeta-chain/node/pkg/sdkconfig" + observertypes "github.com/zeta-chain/node/x/observer/types" +) + +const grpcURLFlag = "grpc-url" + +func NewGetZetaclientBootstrap() *cobra.Command { + var cmd = &cobra.Command{ + Use: "get-zetaclient-bootstrap", + Short: "get bootstrap address book entries for zetaclient", + RunE: getZetaclientBootstrap, + } + + cmd.Flags(). + String(grpcURLFlag, "zetacore0:9090", "--grpc-url zetacore0:9090") + + return cmd +} + +func getZetaclientBootstrap(cmd *cobra.Command, _ []string) error { + sdkconfig.SetDefault(true) + grpcURL, _ := cmd.Flags().GetString(grpcURLFlag) + rpcClient, err := rpc.NewGRPCClients( + grpcURL, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), + ) + if err != nil { + return fmt.Errorf("get zetacore rpc client: %w", err) + } + var res *observertypes.QueryAllNodeAccountResponse + for { + res, err = rpcClient.Observer.NodeAccountAll(cmd.Context(), &observertypes.QueryAllNodeAccountRequest{}) + if err != nil { + return fmt.Errorf("get all node accounts: %w", err) + } + if len(res.NodeAccount) > 1 { + break + } + fmt.Fprintln(cmd.OutOrStderr(), "waiting for node accounts") + } + + // note that we deliberately do not filter ourselfs/localhost + // to mirror the production configuration + for _, account := range res.NodeAccount { + accAddr, err := sdk.AccAddressFromBech32(account.Operator) + if err != nil { + return err + } + valAddr := sdk.ValAddress(accAddr).String() + validatorRes, err := rpcClient.Staking.Validator(cmd.Context(), &stakingtypes.QueryValidatorRequest{ + ValidatorAddr: valAddr, + }) + if err != nil { + return fmt.Errorf("getting validator info for %s: %w", account.Operator, err) + } + // in localnet, moniker is also the hostname + moniker := validatorRes.Validator.Description.Moniker + + peerID, err := conversion.Bech32PubkeyToPeerID(account.GranteePubkey.Secp256k1.String()) + if err != nil { + return fmt.Errorf("converting pubkey to peerID: %w", err) + } + zetaclientHostname := strings.ReplaceAll(moniker, "zetacore", "zetaclient") + + // resolve the hostname + // something in libp2p/go-tss requires /ip4/ and doesn't tolerate /dns4/ + ipAddresses, err := net.LookupIP(zetaclientHostname) + if err != nil { + return fmt.Errorf("failed to resolve hostname %s: %w", zetaclientHostname, err) + } + if len(ipAddresses) == 0 { + return fmt.Errorf("no IP addresses found for hostname %s", zetaclientHostname) + } + ipv4Address := "" + for _, ip := range ipAddresses { + if ip.To4() != nil { + ipv4Address = ip.String() + break + } + } + if ipv4Address == "" { + return fmt.Errorf("no IPv4 address found for hostname %s", zetaclientHostname) + } + fmt.Printf("/ip4/%s/tcp/6668/p2p/%s\n", ipv4Address, peerID.String()) + } + + return nil +} diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 0ebb6244f9..e625b4174a 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -83,6 +83,8 @@ func NewLocalCmd() *cobra.Command { cmd.Flags(). Bool(flagUpgradeContracts, false, "set to true to upgrade Gateways and ERC20Custody contracts during setup for ZEVM and EVM") + cmd.AddCommand(NewGetZetaclientBootstrap()) + return cmd } diff --git a/contrib/localnet/scripts/start-zetaclientd.sh b/contrib/localnet/scripts/start-zetaclientd.sh index 93fa5c2af6..c994f1d2df 100755 --- a/contrib/localnet/scripts/start-zetaclientd.sh +++ b/contrib/localnet/scripts/start-zetaclientd.sh @@ -68,11 +68,15 @@ echo "operatorAddress: $operatorAddress" RELAYER_KEY_PATH="$HOME/.zetacored/relayer-keys" mkdir -p "${RELAYER_KEY_PATH}" +mkdir -p "$HOME/.tss/" +zetae2e local get-zetaclient-bootstrap > "$HOME/.tss/address_book.seed" + +MYIP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1) + echo "Start zetaclientd" # skip initialization if the config file already exists (zetaclientd init has already been run) if [[ $HOSTNAME == "zetaclient0" && ! -f ~/.zetacored/config/zetaclient_config.json ]] then - MYIP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1) zetaclientd init --zetacore-url zetacore0 --chain-id athens_101-1 --operator "$operatorAddress" --log-format=text --public-ip "$MYIP" --keyring-backend "$BACKEND" --pre-params "$PREPARAMS_PATH" # import relayer private key for zetaclient0 @@ -90,13 +94,7 @@ if [[ $HOSTNAME != "zetaclient0" && ! -f ~/.zetacored/config/zetaclient_config.j then num=$(echo $HOSTNAME | tr -dc '0-9') node="zetacore$num" - MYIP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1) - SEED="" - while [ -z "$SEED" ] - do - SEED=$(curl --retry 30 --retry-delay 1 --max-time 1 --retry-connrefused -s zetaclient0:8123/p2p) - done - zetaclientd init --peer "/ip4/172.20.0.21/tcp/6668/p2p/${SEED}" --zetacore-url "$node" --chain-id athens_101-1 --operator "$operatorAddress" --log-format=text --public-ip "$MYIP" --log-level 1 --keyring-backend "$BACKEND" --pre-params "$PREPARAMS_PATH" + zetaclientd init --zetacore-url "$node" --chain-id athens_101-1 --operator "$operatorAddress" --log-format=text --public-ip "$MYIP" --log-level 1 --keyring-backend "$BACKEND" --pre-params "$PREPARAMS_PATH" # import relayer private key for zetaclient{$num} import_relayer_key "${num}" From 462fcea8b068b0c8dd3506b177e3c86131d0f759 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Thu, 12 Dec 2024 18:27:54 +0100 Subject: [PATCH 7/7] docs: add a guide for E2E tests to run (#3285) * add e2e test guide * rename * sol deposit and call --- e2e/TESTING_GUIDE.md | 92 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 e2e/TESTING_GUIDE.md diff --git a/e2e/TESTING_GUIDE.md b/e2e/TESTING_GUIDE.md new file mode 100644 index 0000000000..58555bae44 --- /dev/null +++ b/e2e/TESTING_GUIDE.md @@ -0,0 +1,92 @@ +# Regular E2E tests + +This page lists the regular E2E tests to run when testing the network, in case of upgrade, etc.. +These snippets are aimed to be copy-pasted in the input in the E2E CI tool. + +## Inbounds and outbounds observation + +When we only want to verify the network correctly observe cross-chain transactions, simple deposits and withdraws are sufficient. + +The amount provided represent `0.0001` unit for coin with 18 decimals. + +``` +eth_deposit:100000000000000 eth_withdraw:100000000000000 +``` + +## ERC20 observation + +When we want to verify the network correctly observe cross-chain transactions for ERC20 tokens. + +The amount is set to a small value so it can be used for most ERC20s regardless of the decimals. + +``` +erc20_deposit:1000 erc20_withdraw:1000 +``` + +## Gateway basic workflow + +When we want to verify the gateway basic workflow, the happy path where cross-chain calls succeed. + +The amount is arbitrarily set to a small value, currently the tokens sent to the test contracts are lost. + +``` +eth_deposit_and_call:1000 eth_withdraw_and_call:1000 erc20_deposit_and_call:1000 erc20_withdraw_and_call:1000 zevm_to_evm_call evm_to_zevm_call +``` + +## Solana + +When it is necessary to test the Solana workflows, SOL and SPL tokens. + +``` +solana_deposit:1000 solana_withdraw:1000 solana_deposit_and_call:1000 spl_deposit:1000 spl_withdraw:1000 spl_deposit_and_call:1000 solana_deposit_and_call_revert:20000 +``` + +## Gateway revert workflow + +When we want to verify the gateway revert workflow, the unhappy path where cross-chain calls fail + +### WithdrawAndCall + +The `withdrawAndCall` tests doesn't depend on the provided amount, this list can be used across all networks + +``` +eth_withdraw_and_call_revert:1000 eth_withdraw_and_call_revert_with_call:1000 erc20_withdraw_and_call_revert:1000 erc20_withdraw_and_call_revert_with_call:1000 +``` + +### DepositAndCall + +The amount for reverting `depositAndCall` must depend on the chain as the value in the CCTX is used to pay for the revert fee. + +Note: these are estimated required values for mainnet based on the current gas price, the actual value might be different and fine-tuned. The values for ERC20 tests are set for USDC token. + +Ethereum: `0.0007ETH` and `3USDC` + +``` +eth_deposit_and_call_revert:700000000000000 eth_deposit_and_call_revert_with_call:700000000000000 erc20_deposit_and_call_revert:3000000 erc20_deposit_and_call_revert_with_call:3000000 +``` + +BSC: `0.0008BNB` and `0.5USDC` + +``` +eth_deposit_and_call_revert:800000000000000 eth_deposit_and_call_revert_with_call:800000000000000 erc20_deposit_and_call_revert:500000 erc20_deposit_and_call_revert_with_call:500000 +``` + +Polygon: `0.008POL` and `0.01USDC` + +``` +eth_deposit_and_call_revert:8000000000000000 eth_deposit_and_call_revert_with_call:8000000000000000 erc20_deposit_and_call_revert:10000 erc20_deposit_and_call_revert_with_call:10000 +``` + +Base: `0.000005ETH` and `0.02USDC` + +``` +eth_deposit_and_call_revert:5000000000000 eth_deposit_and_call_revert_with_call:5000000000000 erc20_deposit_and_call_revert:20000 erc20_deposit_and_call_revert_with_call:20000 +``` + +## Gateway arbitrary calls + +Arbitrary calls feature is an experimental and niche use case for now, these tests are not necessary for regular testing. + +``` +eth_withdraw_and_arbitrary_call:1000 erc20_withdraw_and_arbitrary_call:1000 +```