diff --git a/app/upgrade_test.go b/app/upgrade_test.go index 312dfc8d..f6d846df 100644 --- a/app/upgrade_test.go +++ b/app/upgrade_test.go @@ -6,13 +6,17 @@ import ( "testing" "time" + sdkmath "cosmossdk.io/math" dbm "github.com/cometbft/cometbft-db" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmtime "github.com/cometbft/cometbft/types/time" "github.com/cosmos/cosmos-sdk/baseapp" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,7 +33,7 @@ func Test_UpgradeAndMigrate(t *testing.T) { fxtypes.SetConfig(true) home := filepath.Join(os.Getenv("HOME"), "tmp") - chainId := fxtypes.TestnetChainId // The upgrade test is not related to chainId, do not modify it + chainId := fxtypes.MainnetChainId fxtypes.SetChainId(chainId) db, err := dbm.NewDB("application", dbm.GoLevelDBBackend, filepath.Join(home, "data")) @@ -42,8 +46,7 @@ func Test_UpgradeAndMigrate(t *testing.T) { myApp.SetStoreLoader(upgradetypes.UpgradeStoreLoader(myApp.LastBlockHeight()+1, nextversion.Upgrade.StoreUpgrades())) require.NoError(t, myApp.LoadLatestVersion()) - ctx := newContext(t, myApp, chainId) - + ctx := newContext(t, myApp, chainId, false) require.NoError(t, myApp.UpgradeKeeper.ScheduleUpgrade(ctx, upgradetypes.Plan{ Name: nextversion.Upgrade.UpgradeName, Height: ctx.BlockHeight() + 1, @@ -51,7 +54,7 @@ func Test_UpgradeAndMigrate(t *testing.T) { header := ctx.BlockHeader() header.Height = header.Height + 1 - header.Time = time.Now().UTC() + require.NotPanics(t, func() { myApp.BeginBlock(abci.RequestBeginBlock{ Header: header, @@ -62,13 +65,37 @@ func Test_UpgradeAndMigrate(t *testing.T) { Height: header.Height, }) }) + + ctx = newContext(t, myApp, chainId, true) + ingProposalIds := govDepositAndVote(t, ctx, myApp) + + header.Time = tmtime.Now().Add(21 * 24 * time.Hour) // exec proposal + require.NotPanics(t, func() { + myApp.BeginBlock(abci.RequestBeginBlock{ + Header: header, + }) + }) + require.NotPanics(t, func() { + myApp.EndBlock(abci.RequestEndBlock{ + Height: header.Height, + }) + }) + + checkProposalPassed(t, ctx, myApp, ingProposalIds) } -func newContext(t *testing.T, myApp *app.App, chainId string) sdk.Context { - ctx := myApp.NewUncachedContext(false, tmproto.Header{ +func newContext(t *testing.T, myApp *app.App, chainId string, deliveState bool) sdk.Context { + header := tmproto.Header{ ChainID: chainId, Height: myApp.LastBlockHeight(), - }) + Time: tmtime.Now(), + } + var ctx sdk.Context + if deliveState { + ctx = myApp.NewContext(false, header) + } else { + ctx = myApp.NewUncachedContext(false, header) + } // set the first validator to proposer validators := myApp.StakingKeeper.GetAllValidators(ctx) assert.True(t, len(validators) > 0) @@ -77,3 +104,73 @@ func newContext(t *testing.T, myApp *app.App, chainId string) sdk.Context { ctx = ctx.WithProposer(pubKey.Address().Bytes()) return ctx } + +func govDepositAndVote(t *testing.T, ctx sdk.Context, myApp *app.App) []uint64 { + var ingProposalIds []uint64 + amount := sdk.NewCoins(sdk.NewCoin(fxtypes.DefaultDenom, sdkmath.NewIntWithDecimal(1_000_000_000, 18))) + require.NoError(t, myApp.BankKeeper.MintCoins(ctx, minttypes.ModuleName, amount)) + accAddr := helpers.GenAccAddress() + require.NoError(t, myApp.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, accAddr, amount)) + + endTime := tmtime.Now().Add(21 * 24 * time.Hour) + myApp.GovKeeper.IterateInactiveProposalsQueue(ctx, endTime, func(proposal v1.Proposal) (stop bool) { + minDeposit := myApp.GovKeeper.NeedMinDeposit(ctx, proposal) + if sdk.NewCoins(proposal.TotalDeposit...).IsAllGTE(minDeposit) { + return false + } + insufficientCoins := sdk.NewCoins(minDeposit...).Sub(proposal.TotalDeposit...) + deposit, err := myApp.GovKeeper.AddDeposit(ctx, proposal.Id, accAddr, insufficientCoins) + require.NoError(t, err) + require.True(t, deposit) + return false + }) + + myApp.GovKeeper.IterateActiveProposalsQueue(ctx, endTime, func(proposal v1.Proposal) (stop bool) { + if isUpgradeProposal(t, proposal) { + ctx.Logger().Info("skip upgrade proposal", "id", proposal.Id) + return false + } + ingProposalIds = append(ingProposalIds, proposal.Id) + powerStoreIterator := myApp.StakingKeeper.ValidatorsPowerStoreIterator(ctx) + defer powerStoreIterator.Close() + voteOptions := v1.WeightedVoteOptions{&v1.WeightedVoteOption{Option: v1.VoteOption_VOTE_OPTION_YES, Weight: "1"}} + for ; powerStoreIterator.Valid(); powerStoreIterator.Next() { + err := myApp.GovKeeper.AddVote(ctx, proposal.Id, powerStoreIterator.Value(), voteOptions, "") + require.NoError(t, err) + } + return false + }) + return ingProposalIds +} + +func isUpgradeProposal(t *testing.T, proposal v1.Proposal) bool { + msgs, err := proposal.GetMsgs() + require.NoError(t, err) + + if len(msgs) != 1 { + return false + } + msg := msgs[0] + if _, ok := msg.(*upgradetypes.MsgSoftwareUpgrade); ok { + return true + } + legacyMsg, ok := msg.(*v1.MsgExecLegacyContent) + if !ok { + return false + } + content, err := v1.LegacyContentFromMessage(legacyMsg) + require.NoError(t, err) + if _, ok = content.(*upgradetypes.SoftwareUpgradeProposal); ok { // nolint:staticcheck + return true + } + return false +} + +func checkProposalPassed(t *testing.T, ctx sdk.Context, myApp *app.App, ids []uint64) { + for _, id := range ids { + proposal, found := myApp.GovKeeper.GetProposal(ctx, id) + require.True(t, found) + require.Equal(t, proposal.Status, v1.ProposalStatus_PROPOSAL_STATUS_PASSED) + t.Logf("proposal id:%d, status:%d, title:%s, msgType:%s", id, proposal.Status, proposal.Title, proposal.Messages[0].TypeUrl) + } +} diff --git a/app/upgrades/v7/upgrade.go b/app/upgrades/v7/upgrade.go index b535adb2..86abaf90 100644 --- a/app/upgrades/v7/upgrade.go +++ b/app/upgrades/v7/upgrade.go @@ -1,10 +1,15 @@ package v7 import ( + "time" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - autytypes "github.com/cosmos/cosmos-sdk/x/auth/types" + sdktx "github.com/cosmos/cosmos-sdk/types/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "github.com/functionx/fx-core/v7/app/keepers" @@ -12,12 +17,13 @@ import ( fxtypes "github.com/functionx/fx-core/v7/types" crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" fxevmkeeper "github.com/functionx/fx-core/v7/x/evm/keeper" + fxgovkeeper "github.com/functionx/fx-core/v7/x/gov/keeper" ) func CreateUpgradeHandler(mm *module.Manager, configurator module.Configurator, app *keepers.AppKeepers) upgradetypes.UpgradeHandler { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { // Testnet skip - if fxtypes.ChainId() == fxtypes.TestnetChainId { + if ctx.ChainID() == fxtypes.TestnetChainId { return fromVM, nil } // Migrate Tendermint consensus parameters from x/params module to a dedicated x/consensus module. @@ -37,11 +43,13 @@ func CreateUpgradeHandler(mm *module.Manager, configurator module.Configurator, UpdateWFXLogicCode(cacheCtx, app.EvmKeeper) UpdateFIP20LogicCode(cacheCtx, app.EvmKeeper) - crosschainBridgeCallFrom := autytypes.NewModuleAddress(crosschaintypes.ModuleName) + crosschainBridgeCallFrom := authtypes.NewModuleAddress(crosschaintypes.ModuleName) if account := app.AccountKeeper.GetAccount(ctx, crosschainBridgeCallFrom); account == nil { app.AccountKeeper.SetAccount(ctx, app.AccountKeeper.NewAccountWithAddress(ctx, crosschainBridgeCallFrom)) } + MigrateCommunityPoolSpendProposals(cacheCtx, app.GovKeeper) + commit() ctx.Logger().Info("upgrade complete", "module", "upgrade") return toVM, nil @@ -65,3 +73,60 @@ func UpdateFIP20LogicCode(ctx sdk.Context, keeper *fxevmkeeper.Keeper) { ctx.Logger().Info("update FIP20 contract", "module", "upgrade", "codeHash", fip20.CodeHash()) } } + +func MigrateCommunityPoolSpendProposals(ctx sdk.Context, keeper *fxgovkeeper.Keeper) { + // migrate inactive CommunityPoolSpendProposal + maxEndTime := time.Hour * 24 * 14 + keeper.IterateInactiveProposalsQueue(ctx, ctx.BlockHeader().Time.Add(maxEndTime), func(proposal v1.Proposal) (stop bool) { + ConvertCommunityPoolSpendProposal(ctx, keeper, proposal) + return false + }) + + // migrate active CommunityPoolSpendProposal + keeper.IterateActiveProposalsQueue(ctx, ctx.BlockHeader().Time.Add(maxEndTime), func(proposal v1.Proposal) (stop bool) { + ConvertCommunityPoolSpendProposal(ctx, keeper, proposal) + return false + }) +} + +func ConvertCommunityPoolSpendProposal(ctx sdk.Context, keeper *fxgovkeeper.Keeper, proposal v1.Proposal) { + msgs, err := proposal.GetMsgs() + if err != nil { + panic(err) + } + haveSpendMsg := false + newMsgs := make([]sdk.Msg, 0, len(msgs)) + for _, msg := range msgs { + legacyMsg, ok := msg.(*v1.MsgExecLegacyContent) + if !ok { + newMsgs = append(newMsgs, msg) + continue + } + content, err := v1.LegacyContentFromMessage(legacyMsg) + if err != nil { + panic(err) + } + cpsp, ok := content.(*distrtypes.CommunityPoolSpendProposal) // nolint:staticcheck + if !ok { + newMsgs = append(newMsgs, msg) + continue + } + haveSpendMsg = true + spendMsg := &distrtypes.MsgCommunityPoolSpend{ + Authority: keeper.GetAuthority(), + Recipient: cpsp.Recipient, + Amount: cpsp.Amount, + } + newMsgs = append(newMsgs, spendMsg) + } + if !haveSpendMsg { + return + } + ctx.Logger().Info("migrate community pool spend proposal,", "id", proposal.Id) + anyMsgs, err := sdktx.SetMsgs(newMsgs) + if err != nil { + panic(err) + } + proposal.Messages = anyMsgs + keeper.SetProposal(ctx, proposal) +}