diff --git a/CHANGELOG.md b/CHANGELOG.md index fb7130477578..5ae71195b83e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## UNRELEASED +This patch introduces a state machine breaking change in `x/feegrant`. Revoking a grant previously attempted to delete expiration entries under the wrong store key (`prefix|grantee|granter` instead of `prefix|granter|grantee`), leaving stale queues behind. Chains must execute a software upgrade to this release so future revocations clean up the queue correctly; no additional migration scripts or manual sweeping of existing queues are required. + ### Features * (crypto/ledger) [#25435](https://github.com/cosmos/cosmos-sdk/pull/25435) Add SetDERConversion to reset skipDERConversion and App name for ledger. @@ -45,6 +47,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Bug Fixes * (cli) [#25485](https://github.com/cosmos/cosmos-sdk/pull/25485) Avoid failed to convert address field in `withdraw-validator-commission` cmd. +* (x/feegrant) [#25540](https://github.com/cosmos/cosmos-sdk/pull/25540) Fix revocation cleanup by deleting expiration entries with the correct `prefix|granter|grantee` store key. ## [v0.53.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.3) - 2025-07-25 diff --git a/x/feegrant/keeper/keeper.go b/x/feegrant/keeper/keeper.go index 0368d598ed2b..a76c279dbc20 100644 --- a/x/feegrant/keeper/keeper.go +++ b/x/feegrant/keeper/keeper.go @@ -174,7 +174,7 @@ func (k Keeper) revokeAllowance(ctx context.Context, granter, grantee sdk.AccAdd } if exp != nil { - if err := store.Delete(feegrant.FeeAllowancePrefixQueue(exp, feegrant.FeeAllowanceKey(grantee, granter)[1:])); err != nil { + if err := store.Delete(feegrant.FeeAllowancePrefixQueue(exp, key[1:])); err != nil { return err } } diff --git a/x/feegrant/keeper/keeper_test.go b/x/feegrant/keeper/keeper_test.go index ad515e63a04d..7bad7b7e63a5 100644 --- a/x/feegrant/keeper/keeper_test.go +++ b/x/feegrant/keeper/keeper_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "testing" + "time" "github.com/stretchr/testify/suite" "go.uber.org/mock/gomock" @@ -32,6 +33,7 @@ type KeeperTestSuite struct { feegrantKeeper keeper.Keeper accountKeeper *feegranttestutil.MockAccountKeeper bankKeeper *feegranttestutil.MockBankKeeper + storeKey storetypes.StoreKey } func TestKeeperTestSuite(t *testing.T) { @@ -41,6 +43,7 @@ func TestKeeperTestSuite(t *testing.T) { func (suite *KeeperTestSuite) SetupTest() { suite.addrs = simtestutil.CreateIncrementalAccounts(20) key := storetypes.NewKVStoreKey(feegrant.StoreKey) + suite.storeKey = key testCtx := testutil.DefaultContextWithDB(suite.T(), key, storetypes.NewTransientStoreKey("transient_test")) encCfg := moduletestutil.MakeTestEncodingConfig(module.AppModuleBasic{}) @@ -186,6 +189,34 @@ func (suite *KeeperTestSuite) TestKeeperCrud() { suite.Require().NoError(err) } +func (suite *KeeperTestSuite) TestRevokeAllowanceClearsQueue() { + expiration := suite.ctx.BlockTime().Add(24 * time.Hour) + allowance := &feegrant.BasicAllowance{ + SpendLimit: suite.atom, + Expiration: &expiration, + } + err := suite.feegrantKeeper.GrantAllowance(suite.ctx, suite.addrs[0], suite.addrs[1], allowance) + suite.Require().NoError(err) + suite.Require().Equal(1, suite.feeAllowanceQueueEntryCount()) + _, err = suite.msgSrvr.RevokeAllowance(suite.ctx, &feegrant.MsgRevokeAllowance{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + }) + suite.Require().NoError(err) + suite.Require().Equal(0, suite.feeAllowanceQueueEntryCount()) +} + +func (suite *KeeperTestSuite) feeAllowanceQueueEntryCount() int { + store := suite.ctx.KVStore(suite.storeKey) + iterator := storetypes.KVStorePrefixIterator(store, feegrant.FeeAllowanceQueueKeyPrefix) + defer iterator.Close() + var count int + for ; iterator.Valid(); iterator.Next() { + count++ + } + return count +} + func (suite *KeeperTestSuite) TestUseGrantedFee() { eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 123)) blockTime := suite.ctx.BlockTime()