diff --git a/app/app.go b/app/app.go index caea548446..d48c0c251f 100644 --- a/app/app.go +++ b/app/app.go @@ -890,7 +890,7 @@ func New( encodingConfig.TxConfig, ), auth.NewAppModule(appCodec, app.AccountKeeper, nil), - vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper, app.UpgradeKeeper), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), capability.NewAppModule(appCodec, *app.CapabilityKeeper), feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), diff --git a/cmd/seid/cmd/genaccounts.go b/cmd/seid/cmd/genaccounts.go index 7a55402598..800f0ef1bf 100644 --- a/cmd/seid/cmd/genaccounts.go +++ b/cmd/seid/cmd/genaccounts.go @@ -204,6 +204,10 @@ The association between the sei address and the eth address will also be created cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts") cmd.Flags().Int64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts") cmd.Flags().Int64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts") + deprecationNotice := "the vesting module is deprecated; vesting parameters are kept only for legacy genesis tooling" + _ = cmd.Flags().MarkDeprecated(flagVestingAmt, deprecationNotice) + _ = cmd.Flags().MarkDeprecated(flagVestingStart, deprecationNotice) + _ = cmd.Flags().MarkDeprecated(flagVestingEnd, deprecationNotice) flags.AddQueryFlagsToCmd(cmd) return cmd diff --git a/sei-cosmos/x/auth/vesting/client/cli/tx.go b/sei-cosmos/x/auth/vesting/client/cli/tx.go index c55422715c..9ec31a5185 100644 --- a/sei-cosmos/x/auth/vesting/client/cli/tx.go +++ b/sei-cosmos/x/auth/vesting/client/cli/tx.go @@ -22,7 +22,7 @@ const ( func GetTxCmd() *cobra.Command { txCmd := &cobra.Command{ Use: types.ModuleName, - Short: "Vesting transaction subcommands", + Short: "Vesting transaction subcommands (deprecated)", DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, @@ -46,7 +46,8 @@ account can either be a delayed or continuous vesting account, which is determin by the '--delayed' flag. All vesting accouts created will have their start time set by the committed block's time. The end_time must be provided as a UNIX epoch timestamp. You can also optionally configure the 'admin' field using the flag '--admin {addr}. This admin will be able to perform some administrative actions on the vesting account if set.`, - Args: cobra.ExactArgs(3), + Deprecated: "the vesting module is deprecated; the chain rejects this message, so new vesting accounts can no longer be created.", + Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { diff --git a/sei-cosmos/x/auth/vesting/client/testutil/cli_test.go b/sei-cosmos/x/auth/vesting/client/testutil/cli_test.go index 9126c139f8..ac86f359c6 100644 --- a/sei-cosmos/x/auth/vesting/client/testutil/cli_test.go +++ b/sei-cosmos/x/auth/vesting/client/testutil/cli_test.go @@ -12,7 +12,7 @@ import ( ) func TestIntegrationTestSuite(t *testing.T) { - cfg := network.DefaultConfig() + cfg := network.DefaultConfig(t) cfg.NumValidators = 1 suite.Run(t, NewIntegrationTestSuite(cfg)) } diff --git a/sei-cosmos/x/auth/vesting/client/testutil/suite.go b/sei-cosmos/x/auth/vesting/client/testutil/suite.go index 08d2d26e96..963a329eba 100644 --- a/sei-cosmos/x/auth/vesting/client/testutil/suite.go +++ b/sei-cosmos/x/auth/vesting/client/testutil/suite.go @@ -1,6 +1,7 @@ package testutil import ( + "bytes" "fmt" "github.com/gogo/protobuf/proto" @@ -11,6 +12,7 @@ import ( "github.com/sei-protocol/sei-chain/sei-cosmos/testutil/network" sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/vesting/client/cli" + "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/vesting/types" ) type IntegrationTestSuite struct { @@ -47,7 +49,7 @@ func (s *IntegrationTestSuite) TestNewMsgCreateVestingAccountCmd() { expectedCode uint32 respType proto.Message }{ - "create a continuous vesting account": { + "create a continuous vesting account is rejected as deprecated": { args: []string{ sdk.AccAddress("addr2_______________").String(), sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String(), @@ -55,13 +57,13 @@ func (s *IntegrationTestSuite) TestNewMsgCreateVestingAccountCmd() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(2000))).String()), }, expectErr: false, - expectedCode: 0, + expectedCode: types.ErrVestingDeprecated.ABCICode(), respType: &sdk.TxResponse{}, }, - "create a delayed vesting account": { + "create a delayed vesting account is rejected as deprecated": { args: []string{ sdk.AccAddress("addr3_______________").String(), sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String(), @@ -70,10 +72,10 @@ func (s *IntegrationTestSuite) TestNewMsgCreateVestingAccountCmd() { fmt.Sprintf("--%s=true", cli.FlagDelayed), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(2000))).String()), }, expectErr: false, - expectedCode: 0, + expectedCode: types.ErrVestingDeprecated.ABCICode(), respType: &sdk.TxResponse{}, }, "invalid address": { @@ -112,8 +114,6 @@ func (s *IntegrationTestSuite) TestNewMsgCreateVestingAccountCmd() { } for name, tc := range testCases { - tc := tc - s.Run(name, func() { clientCtx := val.ClientCtx @@ -122,7 +122,13 @@ func (s *IntegrationTestSuite) TestNewMsgCreateVestingAccountCmd() { s.Require().Error(err) } else { s.Require().NoError(err) - s.Require().NoError(clientCtx.Codec.UnmarshalAsJSON(bw.Bytes(), tc.respType), bw.String()) + // the mock IO buffer captures cobra's deprecation notice ahead + // of the JSON response; skip past it before unmarshalling + out := bw.Bytes() + if i := bytes.IndexByte(out, '{'); i > 0 { + out = out[i:] + } + s.Require().NoError(clientCtx.Codec.UnmarshalAsJSON(out, tc.respType), bw.String()) txResp := tc.respType.(*sdk.TxResponse) s.Require().Equal(tc.expectedCode, txResp.Code) diff --git a/sei-cosmos/x/auth/vesting/handler.go b/sei-cosmos/x/auth/vesting/handler.go index 18033be66a..5b44543b4b 100644 --- a/sei-cosmos/x/auth/vesting/handler.go +++ b/sei-cosmos/x/auth/vesting/handler.go @@ -7,9 +7,11 @@ import ( "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/vesting/types" ) -// NewHandler returns a handler for x/auth message types. -func NewHandler(ak keeper.AccountKeeper, bk types.BankKeeper) sdk.Handler { - msgServer := NewMsgServerImpl(ak, bk) +// NewHandler returns a handler for x/auth message types. The vesting module is +// deprecated: once the deprecation gate is active, every message is rejected +// with types.ErrVestingDeprecated. +func NewHandler(ak keeper.AccountKeeper, bk types.BankKeeper, uk types.UpgradeKeeper) sdk.Handler { + msgServer := NewMsgServerImpl(ak, bk, uk) return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) diff --git a/sei-cosmos/x/auth/vesting/handler_test.go b/sei-cosmos/x/auth/vesting/handler_test.go index f0694c8a0f..0527da80bd 100644 --- a/sei-cosmos/x/auth/vesting/handler_test.go +++ b/sei-cosmos/x/auth/vesting/handler_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/suite" sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" + authtypes "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/types" "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/vesting" "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/vesting/types" ) @@ -24,15 +25,86 @@ func (suite *HandlerTestSuite) SetupTest() { checkTx := false app := app.Setup(suite.T(), checkTx, false, false) - suite.handler = vesting.NewHandler(app.AccountKeeper, app.BankKeeper) + suite.handler = vesting.NewHandler(app.AccountKeeper, app.BankKeeper, app.UpgradeKeeper) suite.app = app } -func (suite *HandlerTestSuite) TestMsgCreateVestingAccount() { - ctx := suite.app.BaseApp.NewContext(false, tmproto.Header{Height: suite.app.LastBlockHeight() + 1}) +func (suite *HandlerTestSuite) fundedContext(chainID string) (sdk.Context, sdk.AccAddress, sdk.Coins) { + ctx := suite.app.BaseApp.NewContext(false, tmproto.Header{Height: suite.app.LastBlockHeight() + 1, ChainID: chainID}) balances := sdk.NewCoins(sdk.NewInt64Coin("test", 1000)) - addr1 := sdk.AccAddress([]byte("addr1_______________")) + funder := sdk.AccAddress([]byte("addr1_______________")) + acc := suite.app.AccountKeeper.NewAccountWithAddress(ctx, funder) + suite.app.AccountKeeper.SetAccount(ctx, acc) + suite.Require().NoError(apptesting.FundAccount(suite.app.BankKeeper, ctx, funder, balances)) + + return ctx, funder, balances +} + +// TestMsgCreateVestingAccountDeprecated verifies that on chains without +// pre-deprecation history (any chain-id outside the allowlist) the deprecated +// vesting module rejects account creation without mutating any state. +func (suite *HandlerTestSuite) TestMsgCreateVestingAccountDeprecated() { + ctx, addr1, balances := suite.fundedContext("") + addr2 := sdk.AccAddress([]byte("addr2_______________")) + + testCases := []struct { + name string + msg *types.MsgCreateVestingAccount + }{ + { + name: "delayed vesting account", + msg: types.NewMsgCreateVestingAccount(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("test", 100)), ctx.BlockTime().Unix()+10000, true, nil), + }, + { + name: "continuous vesting account", + msg: types.NewMsgCreateVestingAccount(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("test", 100)), ctx.BlockTime().Unix()+10000, false, nil), + }, + { + name: "delayed vesting account with admin", + msg: types.NewMsgCreateVestingAccount(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("test", 100)), ctx.BlockTime().Unix()+10000, true, addr1), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + res, err := suite.handler(ctx, tc.msg) + suite.Require().ErrorIs(err, types.ErrVestingDeprecated) + suite.Require().Nil(res) + + // no account is created and no funds move + suite.Require().Nil(suite.app.AccountKeeper.GetAccount(ctx, addr2)) + suite.Require().Equal(balances, suite.app.BankKeeper.GetAllBalances(ctx, addr1)) + }) + } +} + +// TestMsgCreateVestingAccountPostUpgrade verifies that on chains with +// pre-deprecation history the gate activates once the deprecation upgrade has +// executed. +func (suite *HandlerTestSuite) TestMsgCreateVestingAccountPostUpgrade() { + ctx, addr1, balances := suite.fundedContext("pacific-1") + addr2 := sdk.AccAddress([]byte("addr2_______________")) + + suite.app.UpgradeKeeper.SetDone(ctx, vesting.DeprecationUpgradeName) + + msg := types.NewMsgCreateVestingAccount(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("test", 100)), ctx.BlockTime().Unix()+10000, true, nil) + res, err := suite.handler(ctx, msg) + suite.Require().ErrorIs(err, types.ErrVestingDeprecated) + suite.Require().Nil(res) + + // no account is created and no funds move + suite.Require().Nil(suite.app.AccountKeeper.GetAccount(ctx, addr2)) + suite.Require().Equal(balances, suite.app.BankKeeper.GetAllBalances(ctx, addr1)) +} + +// TestMsgCreateVestingAccountPreUpgradeHistory verifies that on chains with +// pre-deprecation history the original behavior is preserved below the +// deprecation upgrade height, so replaying historical blocks yields identical +// state. +func (suite *HandlerTestSuite) TestMsgCreateVestingAccountPreUpgradeHistory() { + ctx, addr1, _ := suite.fundedContext("pacific-1") + addr2 := sdk.AccAddress([]byte("addr2_______________")) addr3 := sdk.AccAddress([]byte("addr3_______________")) addr4 := sdk.AccAddress([]byte("addr4_______________")) @@ -41,9 +113,7 @@ func (suite *HandlerTestSuite) TestMsgCreateVestingAccount() { addr7 := sdk.AccAddress([]byte("addr7_______________")) addr8 := sdk.AccAddress([]byte("addr8_______________")) - acc1 := suite.app.AccountKeeper.NewAccountWithAddress(ctx, addr1) - suite.app.AccountKeeper.SetAccount(ctx, acc1) - suite.Require().NoError(apptesting.FundAccount(suite.app.BankKeeper, ctx, addr1, balances)) + balances := sdk.NewCoins(sdk.NewInt64Coin("test", 1000)) acc4 := suite.app.AccountKeeper.NewAccountWithAddress(ctx, addr4) suite.app.AccountKeeper.SetAccount(ctx, acc4) suite.Require().NoError(apptesting.FundAccount(suite.app.BankKeeper, ctx, addr4, balances)) @@ -86,8 +156,6 @@ func (suite *HandlerTestSuite) TestMsgCreateVestingAccount() { } for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { res, err := suite.handler(ctx, tc.msg) if tc.expectErr { @@ -115,6 +183,32 @@ func (suite *HandlerTestSuite) TestMsgCreateVestingAccount() { } } +// TestExistingVestingAccountsStillSupported verifies that vesting accounts +// already in state remain decodable and keep vesting after the module's +// deprecation. +func (suite *HandlerTestSuite) TestExistingVestingAccountsStillSupported() { + ctx := suite.app.BaseApp.NewContext(false, tmproto.Header{Height: suite.app.LastBlockHeight() + 1}) + + addr := sdk.AccAddress([]byte("vestacct____________")) + origVesting := sdk.NewCoins(sdk.NewInt64Coin("test", 100)) + endTime := ctx.BlockTime().Unix() + 10000 + + baseAcc, ok := suite.app.AccountKeeper.NewAccountWithAddress(ctx, addr).(*authtypes.BaseAccount) + suite.Require().True(ok) + + vestingAcc := types.NewDelayedVestingAccountRaw(types.NewBaseVestingAccount(baseAcc, origVesting, endTime, nil)) + suite.app.AccountKeeper.SetAccount(ctx, vestingAcc) + suite.Require().NoError(apptesting.FundAccount(suite.app.BankKeeper, ctx, addr, origVesting)) + + accI := suite.app.AccountKeeper.GetAccount(ctx, addr) + suite.Require().NotNil(accI) + + acc, ok := accI.(*types.DelayedVestingAccount) + suite.Require().True(ok) + suite.Require().Equal(origVesting, acc.GetVestingCoins(ctx.BlockTime())) + suite.Require().True(suite.app.BankKeeper.SpendableCoins(ctx, addr).IsZero()) +} + func TestHandlerTestSuite(t *testing.T) { suite.Run(t, new(HandlerTestSuite)) } diff --git a/sei-cosmos/x/auth/vesting/metrics.go b/sei-cosmos/x/auth/vesting/metrics.go deleted file mode 100644 index 6e7bc0c748..0000000000 --- a/sei-cosmos/x/auth/vesting/metrics.go +++ /dev/null @@ -1,33 +0,0 @@ -package vesting - -import ( - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/metric" -) - -var ( - meter = otel.Meter("seicosmos_x_auth_vesting") - - vestingMetrics = struct { - newAccount metric.Int64Counter - accountAmount metric.Int64Gauge - }{ - newAccount: must(meter.Int64Counter( - "new_account", - metric.WithDescription("Number of new vesting accounts created"), - metric.WithUnit("{count}"), - )), - accountAmount: must(meter.Int64Gauge( - "account_amount", - metric.WithDescription("Amount funded into the last new vesting account by denomination"), - metric.WithUnit("{utoken}"), - )), - } -) - -func must[V any](v V, err error) V { - if err != nil { - panic(err) - } - return v -} diff --git a/sei-cosmos/x/auth/vesting/module.go b/sei-cosmos/x/auth/vesting/module.go index a9e8dea87f..f0af1bca1b 100644 --- a/sei-cosmos/x/auth/vesting/module.go +++ b/sei-cosmos/x/auth/vesting/module.go @@ -26,6 +26,14 @@ var ( // AppModuleBasic defines the basic application module used by the sub-vesting // module. The module itself contain no special logic or state other than message // handling. +// +// The vesting module is deprecated: once the deprecation gate is active (the +// DeprecationUpgradeName upgrade on chains with pre-deprecation history, +// genesis everywhere else), its message handlers reject all messages, so new +// vesting accounts can no longer be created. The module must remain wired into +// the app so its codec and interface registrations stay in place: they are +// required to decode existing vesting accounts in the auth store and +// historical transactions. type AppModuleBasic struct{} // Name returns the module's name. @@ -83,18 +91,23 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { // AppModule extends the AppModuleBasic implementation by implementing the // AppModule interface. +// +// The vesting module is deprecated; see AppModuleBasic. Once the deprecation +// gate is active, all message handlers return types.ErrVestingDeprecated. type AppModule struct { AppModuleBasic accountKeeper keeper.AccountKeeper bankKeeper types.BankKeeper + upgradeKeeper types.UpgradeKeeper } -func NewAppModule(ak keeper.AccountKeeper, bk types.BankKeeper) AppModule { +func NewAppModule(ak keeper.AccountKeeper, bk types.BankKeeper, uk types.UpgradeKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, accountKeeper: ak, bankKeeper: bk, + upgradeKeeper: uk, } } @@ -103,7 +116,7 @@ func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // Route returns the module's message router and handler. func (am AppModule) Route() sdk.Route { - return sdk.NewRoute(types.RouterKey, NewHandler(am.accountKeeper, am.bankKeeper)) + return sdk.NewRoute(types.RouterKey, NewHandler(am.accountKeeper, am.bankKeeper, am.upgradeKeeper)) } // QuerierRoute returns an empty string as the module contains no query @@ -112,7 +125,7 @@ func (AppModule) QuerierRoute() string { return "" } // RegisterServices registers module services. func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterMsgServer(cfg.MsgServer(), NewMsgServerImpl(am.accountKeeper, am.bankKeeper)) + types.RegisterMsgServer(cfg.MsgServer(), NewMsgServerImpl(am.accountKeeper, am.bankKeeper, am.upgradeKeeper)) } // LegacyQuerierHandler performs a no-op. diff --git a/sei-cosmos/x/auth/vesting/msg_server.go b/sei-cosmos/x/auth/vesting/msg_server.go index 1bd1994d27..09393d1cb5 100644 --- a/sei-cosmos/x/auth/vesting/msg_server.go +++ b/sei-cosmos/x/auth/vesting/msg_server.go @@ -3,34 +3,76 @@ package vesting import ( "context" - authtypes "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/types" - - "github.com/armon/go-metrics" - - "github.com/sei-protocol/sei-chain/sei-cosmos/telemetry" sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors" "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/keeper" + authtypes "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/types" "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/vesting/types" - "go.opentelemetry.io/otel/attribute" - otelmetric "go.opentelemetry.io/otel/metric" ) +// DeprecationUpgradeName is the upgrade plan that activates the vesting +// module's deprecation on chains with pre-deprecation history. It must match +// the plan name of the release that ships the deprecation; if this change +// slips to a later release, bump this constant to that release's name. +const DeprecationUpgradeName = "v6.7" + +// chainsWithVestingHistory are the public chains whose history may contain +// successful MsgCreateVestingAccount transactions. On these chains the +// deprecation activates at the height the DeprecationUpgradeName upgrade +// executes, so replaying historical blocks produces identical state, gas, and +// app hashes. On every other chain (fresh networks, tests) the deprecation is +// active from genesis. +var chainsWithVestingHistory = map[string]struct{}{ + "pacific-1": {}, // mainnet + "atlantic-2": {}, // testnet + "arctic-1": {}, // devnet +} + type msgServer struct { keeper.AccountKeeper types.BankKeeper + upgradeKeeper types.UpgradeKeeper } -// NewMsgServerImpl returns an implementation of the vesting MsgServer interface, -// wrapping the corresponding AccountKeeper and BankKeeper. -func NewMsgServerImpl(k keeper.AccountKeeper, bk types.BankKeeper) types.MsgServer { - return &msgServer{AccountKeeper: k, BankKeeper: bk} +// NewMsgServerImpl returns an implementation of the vesting MsgServer +// interface, wrapping the corresponding AccountKeeper and BankKeeper. +// +// The vesting module is deprecated: once the deprecation gate is active (see +// creationDeprecated), every handler rejects its message with +// types.ErrVestingDeprecated. Existing vesting accounts in state remain fully +// supported. +func NewMsgServerImpl(k keeper.AccountKeeper, bk types.BankKeeper, uk types.UpgradeKeeper) types.MsgServer { + return &msgServer{AccountKeeper: k, BankKeeper: bk, upgradeKeeper: uk} } var _ types.MsgServer = msgServer{} +// creationDeprecated reports whether vesting account creation is disabled at +// the current block. Chains with pre-deprecation history keep the original +// behavior below the deprecation upgrade height so historical replay is +// unchanged; all other chains reject immediately. +func (s msgServer) creationDeprecated(ctx sdk.Context) bool { + if _, ok := chainsWithVestingHistory[ctx.ChainID()]; !ok { + return true + } + // The done-height lookup must not consume gas: the pre-deprecation handler + // performed no store reads before its first bank check, so charging gas + // here would alter gas usage of historical transactions during replay. + gasFreeCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter(1, 1)) + return s.upgradeKeeper.IsUpgradeActiveAtHeight(gasFreeCtx, DeprecationUpgradeName, ctx.BlockHeight()) +} + +// CreateVestingAccount is deprecated and returns types.ErrVestingDeprecated +// once the deprecation gate is active; the original behavior is preserved +// below the gate so chains with pre-deprecation history replay identically. +// Existing vesting accounts remain in state and continue to vest according to +// their schedules regardless of the gate. func (s msgServer) CreateVestingAccount(goCtx context.Context, msg *types.MsgCreateVestingAccount) (*types.MsgCreateVestingAccountResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + if s.creationDeprecated(ctx) { + return nil, types.ErrVestingDeprecated + } + ak := s.AccountKeeper bk := s.BankKeeper @@ -83,24 +125,6 @@ func (s msgServer) CreateVestingAccount(goCtx context.Context, msg *types.MsgCre ak.SetAccount(ctx, acc) - defer func() { - vestingMetrics.newAccount.Add(goCtx, 1) - // TODO(PLT-353): remove once vesting_new_account verified - telemetry.IncrCounter(1, "new", "account") - - for _, a := range msg.Amount { - if a.Amount.IsInt64() { - vestingMetrics.accountAmount.Record(goCtx, a.Amount.Int64(), otelmetric.WithAttributes(attribute.String("denom_class", telemetry.DenomClass(a.Denom)))) - // TODO(PLT-353): remove once vesting_account_amount verified - telemetry.SetGaugeWithLabels( - []string{"tx", "msg", "create_vesting_account"}, - float32(a.Amount.Int64()), - []metrics.Label{telemetry.NewLabel("denom", a.Denom)}, - ) - } - } - }() - err = bk.SendCoins(ctx, from, to, msg.Amount) if err != nil { return nil, err diff --git a/sei-cosmos/x/auth/vesting/types/errors.go b/sei-cosmos/x/auth/vesting/types/errors.go new file mode 100644 index 0000000000..16635a6ab5 --- /dev/null +++ b/sei-cosmos/x/auth/vesting/types/errors.go @@ -0,0 +1,11 @@ +package types + +import ( + sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors" +) + +// ErrVestingDeprecated is returned by every vesting message handler now that +// the module is deprecated. Existing vesting accounts remain in state and +// continue to vest according to their schedules; only the creation of new +// vesting accounts is disabled. +var ErrVestingDeprecated = sdkerrors.Register(ModuleName, 2, "vesting module is deprecated; creating new vesting accounts is disabled") diff --git a/sei-cosmos/x/auth/vesting/types/expected_keepers.go b/sei-cosmos/x/auth/vesting/types/expected_keepers.go index ee77cd747d..c3bfe37ace 100644 --- a/sei-cosmos/x/auth/vesting/types/expected_keepers.go +++ b/sei-cosmos/x/auth/vesting/types/expected_keepers.go @@ -11,3 +11,9 @@ type BankKeeper interface { SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error BlockedAddr(addr sdk.AccAddress) bool } + +// UpgradeKeeper defines the expected interface contract the vesting module +// requires for checking whether the deprecation upgrade has executed. +type UpgradeKeeper interface { + IsUpgradeActiveAtHeight(ctx sdk.Context, name string, height int64) bool +} diff --git a/sei-ibc-go/testing/simapp/app.go b/sei-ibc-go/testing/simapp/app.go index b0e881ecc9..ee98f1b8e0 100644 --- a/sei-ibc-go/testing/simapp/app.go +++ b/sei-ibc-go/testing/simapp/app.go @@ -380,7 +380,7 @@ func NewSimApp( encodingConfig.TxConfig, ), auth.NewAppModule(appCodec, app.AccountKeeper, nil), - vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper, app.UpgradeKeeper), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), capability.NewAppModule(appCodec, *app.CapabilityKeeper), feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), diff --git a/sei-wasmd/app/app.go b/sei-wasmd/app/app.go index 3d0b15fb17..505c5a0ca8 100644 --- a/sei-wasmd/app/app.go +++ b/sei-wasmd/app/app.go @@ -535,7 +535,7 @@ func NewWasmApp( encodingConfig.TxConfig, ), auth.NewAppModule(appCodec, app.accountKeeper, nil), - vesting.NewAppModule(app.accountKeeper, app.bankKeeper), + vesting.NewAppModule(app.accountKeeper, app.bankKeeper, app.upgradeKeeper), bank.NewAppModule(appCodec, app.bankKeeper, app.accountKeeper), capability.NewAppModule(appCodec, *app.capabilityKeeper), gov.NewAppModule(appCodec, app.govKeeper, app.accountKeeper, app.bankKeeper), diff --git a/x/evm/module_test.go b/x/evm/module_test.go index 149570f70f..868771a2f1 100644 --- a/x/evm/module_test.go +++ b/x/evm/module_test.go @@ -128,10 +128,10 @@ func TestABCI(t *testing.T) { require.Equal(t, receipt.BlockNumber, uint64(ctx.BlockHeight())) require.Equal(t, receipt.VmError, "test error") - // disallow creating vesting account for coinbase address + // creating vesting accounts (including for coinbase addresses) is rejected: the module is deprecated k.BeginBlock(ctx) coinbase := state.GetCoinbaseAddress(2) - vms := vesting.NewMsgServerImpl(*k.AccountKeeper(), k.BankKeeper()) + vms := vesting.NewMsgServerImpl(*k.AccountKeeper(), k.BankKeeper(), k.UpgradeKeeper()) _, err = vms.CreateVestingAccount(sdk.WrapSDKContext(ctx), &vestingtypes.MsgCreateVestingAccount{ FromAddress: sdk.AccAddress(evmAddr1[:]).String(), ToAddress: coinbase.String(),