From 757289ca39130a2a47e8d60e56504d419bd3ef58 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Thu, 20 Aug 2026 13:34:19 +0530 Subject: [PATCH 1/2] fix: remove x/group module and drop wasm stargate capability Both are generic nested-message dispatchers that reach the message router after the ante handler has run, letting an MsgEthereumTx skip the EVM ante (F-2026-18197). Neither is used by Push. Adds a remove-group upgrade handler that prunes the group store. --- app/app.go | 20 --- app/nested_dispatch_test.go | 75 ++++++++++ app/txpolicy/gasless_test.go | 54 ++++++++ app/upgrades.go | 5 +- app/upgrades/remove-group/upgrade.go | 63 +++++++++ app/wasm.go | 11 +- .../uexecutor/gasless_module_sender_test.go | 130 ++++++++++++++++++ 7 files changed, 334 insertions(+), 24 deletions(-) create mode 100644 app/nested_dispatch_test.go create mode 100644 app/txpolicy/gasless_test.go create mode 100644 app/upgrades/remove-group/upgrade.go create mode 100644 test/integration/uexecutor/gasless_module_sender_test.go diff --git a/app/app.go b/app/app.go index 19f459b9f..881a79cfd 100644 --- a/app/app.go +++ b/app/app.go @@ -91,9 +91,6 @@ import ( govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - "github.com/cosmos/cosmos-sdk/x/group" - groupkeeper "github.com/cosmos/cosmos-sdk/x/group/keeper" - groupmodule "github.com/cosmos/cosmos-sdk/x/group/module" "github.com/cosmos/cosmos-sdk/x/mint" mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" @@ -195,7 +192,6 @@ var ( capabilities = []string{ "iterator", "staking", - "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3", "cosmwasm_1_4", "token_factory", } @@ -312,7 +308,6 @@ type ChainApp struct { AuthzKeeper authzkeeper.Keeper EvidenceKeeper evidencekeeper.Keeper FeeGrantKeeper feegrantkeeper.Keeper - GroupKeeper groupkeeper.Keeper NFTKeeper nftkeeper.Keeper ConsensusParamsKeeper consensusparamkeeper.Keeper CircuitKeeper circuitkeeper.Keeper @@ -433,7 +428,6 @@ func NewChainApp( circuittypes.StoreKey, authzkeeper.StoreKey, nftkeeper.StoreKey, - group.StoreKey, // non sdk store keys ibcexported.StoreKey, ibctransfertypes.StoreKey, @@ -589,17 +583,6 @@ func NewChainApp( app.AccountKeeper, ) - groupConfig := group.DefaultConfig() - groupConfig.MaxMetadataLen = 10000 - app.GroupKeeper = groupkeeper.NewKeeper( - keys[group.StoreKey], - // runtime.NewKVStoreService(keys[group.StoreKey]), - appCodec, - app.MsgServiceRouter(), - app.AccountKeeper, - groupConfig, - ) - // get skipUpgradeHeights from the app options skipUpgradeHeights := map[int64]bool{} for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { @@ -1026,7 +1009,6 @@ func NewChainApp( evidence.NewAppModule(app.EvidenceKeeper), params.NewAppModule(app.ParamsKeeper), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), - groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), nftmodule.NewAppModule(appCodec, app.NFTKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), circuit.NewAppModule(appCodec, app.CircuitKeeper), @@ -1111,7 +1093,6 @@ func NewChainApp( stakingtypes.ModuleName, genutiltypes.ModuleName, feegrant.ModuleName, - group.ModuleName, // additional non simd modules evmtypes.ModuleName, erc20types.ModuleName, feemarkettypes.ModuleName, ibctransfertypes.ModuleName, @@ -1157,7 +1138,6 @@ func NewChainApp( authz.ModuleName, feegrant.ModuleName, nft.ModuleName, - group.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName, diff --git a/app/nested_dispatch_test.go b/app/nested_dispatch_test.go new file mode 100644 index 000000000..0ac59d9a1 --- /dev/null +++ b/app/nested_dispatch_test.go @@ -0,0 +1,75 @@ +package app + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// Regression tests for F-2026-18197 (nested message dispatch bypasses the EVM ante). +// +// Ethereum signature, nonce and gas checks live only in the EVM ante handler; +// x/vm's Keeper.EthereumTx assumes the ante already ran. Any module that unpacks +// and re-dispatches an embedded sdk.Msg therefore reaches the EVM executor with +// none of those checks applied. Push had two such dispatchers wired: x/group +// (MsgSubmitProposal/MsgExec) and the CosmWasm "stargate" capability +// (CosmosMsg::Any). Both are removed; these tests keep them removed. + +// groupMsgTypeURLs are the x/group entry points that unpack and dispatch a +// nested sdk.Msg. They must not resolve or route. +var groupMsgTypeURLs = []string{ + "/cosmos.group.v1.MsgSubmitProposal", + "/cosmos.group.v1.MsgExec", + "/cosmos.group.v1.MsgCreateGroup", + "/cosmos.group.v1.MsgCreateGroupWithPolicy", + "/cosmos.group.v1.MsgCreateGroupPolicy", +} + +// TestGroupModuleNotWired asserts x/group is gone from every wiring point: the +// module manager, the store keys, the message router and the interface registry. +func TestGroupModuleNotWired(t *testing.T) { + // setup() constructs the app without InitChain, which is all these + // assertions need: the module manager, store keys, message routes and + // interface registry are populated by then. Setup() is avoided on purpose - + // it passes the "testing" chain ID and only works once another test has + // already initialised the global EVM configurator. + gapp, _ := setup(t, ChainID, false, 0) + + t.Run("not in module manager", func(t *testing.T) { + _, ok := gapp.ModuleManager.Modules["group"] + require.False(t, ok, "x/group must not be registered in the module manager") + }) + + t.Run("no store key", func(t *testing.T) { + require.Nil(t, gapp.GetKey("group"), "x/group must not have a KV store key") + }) + + t.Run("msgs unroutable", func(t *testing.T) { + for _, typeURL := range groupMsgTypeURLs { + require.Nil(t, gapp.MsgServiceRouter().HandlerByTypeURL(typeURL), + "%s must have no handler on the msg service router", typeURL) + } + }) + + t.Run("msgs unresolvable", func(t *testing.T) { + for _, typeURL := range groupMsgTypeURLs { + _, err := gapp.InterfaceRegistry().Resolve(typeURL) + require.Error(t, err, + "%s must not resolve in the interface registry (tx decoding must fail)", typeURL) + } + }) +} + +// TestWasmStargateCapabilityDisabled asserts the "stargate" wasmvm capability is +// off for both wasm VMs. With it enabled, an uploaded contract may emit an +// arbitrary encoded sdk.Msg (CosmosMsg::Any / Stargate) that the wasm message +// handler forwards straight to the message router, after ante has already run. +func TestWasmStargateCapabilityDisabled(t *testing.T) { + t.Run("x/wasm", func(t *testing.T) { + require.NotContains(t, AllCapabilities(), "stargate") + }) + + t.Run("08-wasm light client", func(t *testing.T) { + require.NotContains(t, capabilities, "stargate") + }) +} diff --git a/app/txpolicy/gasless_test.go b/app/txpolicy/gasless_test.go new file mode 100644 index 000000000..78f2c57e0 --- /dev/null +++ b/app/txpolicy/gasless_test.go @@ -0,0 +1,54 @@ +package txpolicy_test + +import ( + "testing" + + protov2 "google.golang.org/protobuf/proto" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app/txpolicy" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// msgsOnlyTx is the minimal sdk.Tx IsGaslessTx needs. +type msgsOnlyTx struct{ msgs []sdk.Msg } + +func (t msgsOnlyTx) GetMsgs() []sdk.Msg { return t.msgs } +func (t msgsOnlyTx) GetMsgsV2() ([]protov2.Message, error) { return nil, nil } + +// TestGaslessMsgTypesExcludeEthereumTx is one half of the F-2026-18197 invariant +// guard (see test/integration/uexecutor/gasless_module_sender_test.go for the +// other half). +// +// x/vm's Keeper.EthereumTx now rejects any MsgEthereumTx whose From is not the +// ECDSA signer of the raw transaction. A module account is derived from a name +// and has no key pair, so a module-signed MsgEthereumTx could never pass that +// check. Push's gasless flows are safe precisely because none of them is a +// MsgEthereumTx - they reach the EVM through CallEVM / DerivedEVMCall, which +// call ApplyMessageWithConfig directly. If a MsgEthereumTx were ever added to +// the gasless set, that flow would break 100% of the time; this test fails first. +func TestGaslessMsgTypesExcludeEthereumTx(t *testing.T) { + t.Run("MsgEthereumTx is not gasless", func(t *testing.T) { + tx := msgsOnlyTx{msgs: []sdk.Msg{&evmtypes.MsgEthereumTx{}}} + require.False(t, txpolicy.IsGaslessTx(tx), + "MsgEthereumTx must never be a gasless message type") + }) + + t.Run("MsgEthereumTx nested in authz is not gasless", func(t *testing.T) { + inner, err := codectypes.NewAnyWithValue(&evmtypes.MsgEthereumTx{}) + require.NoError(t, err) + tx := msgsOnlyTx{msgs: []sdk.Msg{&authz.MsgExec{Msgs: []*codectypes.Any{inner}}}} + require.False(t, txpolicy.IsGaslessTx(tx), + "MsgEthereumTx nested in authz.MsgExec must never be a gasless message type") + }) + + t.Run("MsgExecutePayload stays gasless", func(t *testing.T) { + tx := msgsOnlyTx{msgs: []sdk.Msg{&uexecutortypes.MsgExecutePayload{}}} + require.True(t, txpolicy.IsGaslessTx(tx)) + }) +} diff --git a/app/upgrades.go b/app/upgrades.go index beb0452d8..c3e8af185 100755 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -7,10 +7,13 @@ import ( "github.com/pushchain/push-chain-node/app/upgrades" "github.com/pushchain/push-chain-node/app/upgrades/noop" + removegroup "github.com/pushchain/push-chain-node/app/upgrades/remove-group" ) // Upgrades list of chain upgrades -var Upgrades = []upgrades.Upgrade{} +var Upgrades = []upgrades.Upgrade{ + removegroup.NewUpgrade(), +} // RegisterUpgradeHandlers registers the chain upgrade handlers func (app *ChainApp) RegisterUpgradeHandlers() { diff --git a/app/upgrades/remove-group/upgrade.go b/app/upgrades/remove-group/upgrade.go new file mode 100644 index 000000000..aae9ae50b --- /dev/null +++ b/app/upgrades/remove-group/upgrade.go @@ -0,0 +1,63 @@ +package removegroup + +import ( + "context" + "fmt" + + storetypes "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/group" + + "github.com/pushchain/push-chain-node/app/upgrades" +) + +const UpgradeName = "remove-group" + +// NewUpgrade removes the x/group module from the chain. +// +// x/group is unused on Push (no group, no group policy and no proposal is ever +// created by the chain, the universal client or any user-facing flow) but it is +// a generic message dispatcher: MsgSubmitProposal/MsgExec unpack and route +// arbitrary nested sdk.Msgs straight to the message router, after the ante +// handler has already run. An MsgEthereumTx nested that way therefore skips the +// whole EVM ante chain - signature, nonce and gas checks (F-2026-18197). +// +// Removing the module deletes the dispatcher instead of trying to enumerate the +// message shapes it can carry. The store is dropped via StoreUpgrades.Deleted. +func NewUpgrade() upgrades.Upgrade { + return upgrades.Upgrade{ + UpgradeName: UpgradeName, + CreateUpgradeHandler: CreateUpgradeHandler, + StoreUpgrades: storetypes.StoreUpgrades{ + Added: []string{}, + Deleted: []string{group.StoreKey}, + }, + } +} + +func CreateUpgradeHandler( + mm upgrades.ModuleManager, + configurator module.Configurator, + ak *upgrades.AppKeepers, +) upgradetypes.UpgradeHandler { + return func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + logger := sdk.UnwrapSDKContext(ctx).Logger().With("upgrade", UpgradeName) + logger.Info("Starting upgrade handler") + + // Drop x/group from the consensus version map so RunMigrations does not + // try to migrate a module that is no longer registered. The KV store + // itself is pruned by StoreUpgrades.Deleted above. + delete(fromVM, group.ModuleName) + + versionMap, err := mm.RunMigrations(ctx, configurator, fromVM) + if err != nil { + return nil, fmt.Errorf("RunMigrations: %w", err) + } + + logger.Info("Upgrade complete", "removed_module", group.ModuleName) + return versionMap, nil + } +} diff --git a/app/wasm.go b/app/wasm.go index 70f811bc1..1facc0d0b 100755 --- a/app/wasm.go +++ b/app/wasm.go @@ -1,13 +1,18 @@ package app -// AllCapabilities returns all capabilities available with the current wasmvm +// AllCapabilities returns the wasmvm capabilities enabled on this chain. // See https://github.com/CosmWasm/cosmwasm/blob/main/docs/CAPABILITIES-BUILT-IN.md -// This functionality is going to be moved upstream: https://github.com/CosmWasm/wasmvm/issues/425 +// +// NOTE: "stargate" is deliberately NOT enabled. It lets a contract emit an +// arbitrary encoded sdk.Msg (CosmosMsg::Any / Stargate), which reaches the +// message router without the tx ever passing through the ante handler. That is +// the nested-dispatch vector reported as F-2026-18197: an MsgEthereumTx routed +// that way skips the EVM ante entirely (signature, nonce and gas checks). No +// contract deployed on Push requires it. func AllCapabilities() []string { return []string{ "iterator", "staking", - "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3", diff --git a/test/integration/uexecutor/gasless_module_sender_test.go b/test/integration/uexecutor/gasless_module_sender_test.go new file mode 100644 index 000000000..0cbe3078a --- /dev/null +++ b/test/integration/uexecutor/gasless_module_sender_test.go @@ -0,0 +1,130 @@ +package integrationtest + +import ( + "testing" + + "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + utils "github.com/pushchain/push-chain-node/test/utils" + "github.com/pushchain/push-chain-node/types" + uexecutorkeeper "github.com/pushchain/push-chain-node/x/uexecutor/keeper" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" +) + +// TestGaslessExecutePayloadWithModuleSender is the invariant guard for +// F-2026-18197. +// +// The fix hardens x/vm's Keeper.EthereumTx to require that msg.From is the +// ECDSA signer of the raw transaction, so that an MsgEthereumTx smuggled in via +// a nested-message dispatcher can no longer execute as somebody else. Push's +// gasless / module-sender flows must be completely unaffected by that, and they +// are - because they never reach that msg server. MsgExecutePayload runs the +// payload through CallEVM / DerivedEVMCall, which go straight to +// ApplyMessageWithConfig; no MsgEthereumTx is ever constructed. +// +// This test pins that down end to end: a gasless MsgExecutePayload, whose EVM +// caller is the uexecutor module account, still executes successfully. +func TestGaslessExecutePayloadWithModuleSender(t *testing.T) { + app, ctx, _ := utils.SetAppWithValidators(t) + + // The uexecutor module account is derived from a name, not from a key pair. + // It can never produce an ECDSA signature, so if a module operation ever + // routed through MsgEthereumTx the new VerifySender check would reject it + // 100% of the time. That is why module-driven EVM calls must keep using the + // ApplyMessage* path, and why this test exists. + moduleAcc := app.AccountKeeper.GetModuleAccount(ctx, uexecutortypes.ModuleName) + require.NotNil(t, moduleAcc) + require.Nil(t, moduleAcc.GetPubKey(), + "the uexecutor module account must have no public key - it cannot sign an MsgEthereumTx") + + app.UregistryKeeper.AddChainConfig(ctx, &uregistrytypes.ChainConfig{ + Chain: "eip155:11155111", + VmType: uregistrytypes.VmType_EVM, + PublicRpcUrl: "https://sepolia.drpc.org", + GatewayAddress: "0x28E0F09bE2321c1420Dc60Ee146aACbD68B335Fe", + BlockConfirmation: &uregistrytypes.BlockConfirmation{ + FastInbound: 5, + StandardInbound: 12, + }, + GatewayMethods: []*uregistrytypes.GatewayMethods{{ + Name: "addFunds", + Identifier: "", + EventIdentifier: "0xb28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd", + }}, + Enabled: &uregistrytypes.ChainEnabled{ + IsInboundEnabled: true, + IsOutboundEnabled: true, + }, + }) + + params := app.FeeMarketKeeper.GetParams(ctx) + params.BaseFee = math.LegacyNewDec(1000000000) + app.FeeMarketKeeper.SetParams(ctx, params) + + ms := uexecutorkeeper.NewMsgServerImpl(app.UexecutorKeeper) + + universalAccount := &uexecutortypes.UniversalAccountId{ + ChainNamespace: "eip155", + ChainId: "11155111", + Owner: "0x778d3206374f8ac265728e18e3fe2ae6b93e4ce4", + } + payload := &uexecutortypes.UniversalPayload{ + To: "0x527F3692F5C53CfA83F7689885995606F93b6164", + Value: "0", + Data: "0x2ba2ed980000000000000000000000000000000000000000000000000000000000000312", + GasLimit: "21000000", + MaxFeePerGas: "1000000000", + MaxPriorityFeePerGas: "200000000", + Nonce: "1", + Deadline: "0", + VType: uexecutortypes.VerificationType(0), + } + + evmFrom := common.HexToAddress("0x1000000000000000000000000000000000000001") + + err := app.BankKeeper.MintCoins( + ctx, + uexecutortypes.ModuleName, + sdk.NewCoins(sdk.NewCoin(types.BaseDenom, sdkmath.NewInt(2_000_000_000_000_000))), + ) + require.NoError(t, err) + + err = app.BankKeeper.SendCoinsFromModuleToAccount( + ctx, + uexecutortypes.ModuleName, + sdk.AccAddress(evmFrom.Bytes()), + sdk.NewCoins(sdk.NewCoin(types.BaseDenom, sdkmath.NewInt(1_000_000_000_000_000))), + ) + require.NoError(t, err) + + _, err = app.UexecutorKeeper.DeployUEAV2(ctx, evmFrom, universalAccount) + require.NoError(t, err) + + ueaAddr, _, err := app.UexecutorKeeper.CallFactoryToGetUEAAddressForOrigin( + ctx, evmFrom, utils.GetDefaultAddresses().FactoryAddr, universalAccount, + ) + require.NoError(t, err) + + err = app.BankKeeper.SendCoinsFromModuleToAccount( + ctx, + uexecutortypes.ModuleName, + sdk.AccAddress(ueaAddr.Bytes()), + sdk.NewCoins(sdk.NewCoin(types.BaseDenom, sdkmath.NewInt(1_000_000_000_000_000))), + ) + require.NoError(t, err) + + // The gasless message itself: signer is a relayer, the EVM caller is the + // uexecutor module. This must still succeed after the x/vm change. + _, err = ms.ExecutePayload(ctx, &uexecutortypes.MsgExecutePayload{ + Signer: "cosmos1xpurwdecvsenyvpkxvmnge3cv93nyd34xuersef38pjnxen9xfsk2dnz8yek2drrv56qmn2ak9", + UniversalAccountId: universalAccount, + UniversalPayload: payload, + VerificationData: "0x91987784d56359fa91c3e3e0332f4f0cffedf9c081eb12874a63b41d5b5e5c660dc827947c2ae26e658d0551ad4b2d2aa073d62691429a0ae239d2cc58055bf11c", + }) + require.NoError(t, err, "gasless module-sender MsgExecutePayload must still execute end to end") +} From b2f97fdb9687ba851ed113e901b7aba84bde6b43 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Mon, 24 Aug 2026 07:49:28 +0530 Subject: [PATCH 2/2] chore: drop the remove-group upgrade handler audit-fixes targets mainnet, which starts from a fresh genesis where x/group is never mounted, so no store deletion is required. The StoreUpgrades handler belongs on the testnet branch, where donut has an existing group store. --- app/upgrades.go | 5 +-- app/upgrades/remove-group/upgrade.go | 63 ---------------------------- 2 files changed, 1 insertion(+), 67 deletions(-) delete mode 100644 app/upgrades/remove-group/upgrade.go diff --git a/app/upgrades.go b/app/upgrades.go index c3e8af185..beb0452d8 100755 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -7,13 +7,10 @@ import ( "github.com/pushchain/push-chain-node/app/upgrades" "github.com/pushchain/push-chain-node/app/upgrades/noop" - removegroup "github.com/pushchain/push-chain-node/app/upgrades/remove-group" ) // Upgrades list of chain upgrades -var Upgrades = []upgrades.Upgrade{ - removegroup.NewUpgrade(), -} +var Upgrades = []upgrades.Upgrade{} // RegisterUpgradeHandlers registers the chain upgrade handlers func (app *ChainApp) RegisterUpgradeHandlers() { diff --git a/app/upgrades/remove-group/upgrade.go b/app/upgrades/remove-group/upgrade.go deleted file mode 100644 index aae9ae50b..000000000 --- a/app/upgrades/remove-group/upgrade.go +++ /dev/null @@ -1,63 +0,0 @@ -package removegroup - -import ( - "context" - "fmt" - - storetypes "cosmossdk.io/store/types" - upgradetypes "cosmossdk.io/x/upgrade/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/x/group" - - "github.com/pushchain/push-chain-node/app/upgrades" -) - -const UpgradeName = "remove-group" - -// NewUpgrade removes the x/group module from the chain. -// -// x/group is unused on Push (no group, no group policy and no proposal is ever -// created by the chain, the universal client or any user-facing flow) but it is -// a generic message dispatcher: MsgSubmitProposal/MsgExec unpack and route -// arbitrary nested sdk.Msgs straight to the message router, after the ante -// handler has already run. An MsgEthereumTx nested that way therefore skips the -// whole EVM ante chain - signature, nonce and gas checks (F-2026-18197). -// -// Removing the module deletes the dispatcher instead of trying to enumerate the -// message shapes it can carry. The store is dropped via StoreUpgrades.Deleted. -func NewUpgrade() upgrades.Upgrade { - return upgrades.Upgrade{ - UpgradeName: UpgradeName, - CreateUpgradeHandler: CreateUpgradeHandler, - StoreUpgrades: storetypes.StoreUpgrades{ - Added: []string{}, - Deleted: []string{group.StoreKey}, - }, - } -} - -func CreateUpgradeHandler( - mm upgrades.ModuleManager, - configurator module.Configurator, - ak *upgrades.AppKeepers, -) upgradetypes.UpgradeHandler { - return func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { - logger := sdk.UnwrapSDKContext(ctx).Logger().With("upgrade", UpgradeName) - logger.Info("Starting upgrade handler") - - // Drop x/group from the consensus version map so RunMigrations does not - // try to migrate a module that is no longer registered. The KV store - // itself is pruned by StoreUpgrades.Deleted above. - delete(fromVM, group.ModuleName) - - versionMap, err := mm.RunMigrations(ctx, configurator, fromVM) - if err != nil { - return nil, fmt.Errorf("RunMigrations: %w", err) - } - - logger.Info("Upgrade complete", "removed_module", group.ModuleName) - return versionMap, nil - } -}