refactor(gov)!: use collections for deposit state management (#16127)

Co-authored-by: unknown unknown <unknown@unknown>
This commit is contained in:
testinginprod 2023-05-15 17:58:56 +02:00 committed by GitHub
parent b8e15a7930
commit 71468d2d5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 144 additions and 399 deletions

View File

@ -196,6 +196,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (types/math) [#16040](https://github.com/cosmos/cosmos-sdk/pull/16040) Remove unused aliases in math.go
* (x/gov) [#16106](https://github.com/cosmos/cosmos-sdk/pull/16106) Remove gRPC query methods from Keeper
* (x/gov) [#16118](https://github.com/cosmos/cosmos-sdk/pull/16118/) Use collections for constituion and params state management.
* (x/gov) [](https://github.com/cosmos/cosmos-sdk/pull/16127) Use collections for deposit management:
- The following methods are removed from the gov keeper: `GetDeposit`, `GetAllDeposits`, `IterateAllDeposits`.
- The following functions are removed from the gov types: `DepositKey`, `DepositsKey`.
### Client Breaking Changes

View File

@ -35,6 +35,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [#16074](https://github.com/cosmos/cosmos-sdk/pull/16074) makes the generic Collection interface public, still highly unstable.
### API Breaking
* [#16127](https://github.com/cosmos/cosmos-sdk/pull/16127) In the `Walk` method the call back function being passed is allowed to error.
## [v0.1.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.1.0)
Collections `v0.1.0` is released! Check out the [docs](https://docs.cosmos.network/main/packages/collections) to know how to use the APIs.

View File

@ -93,7 +93,7 @@ func (m *IndexedMap[PrimaryKey, Value, Idx]) Remove(ctx context.Context, pk Prim
}
// Walk applies the same semantics as Map.Walk.
func (m *IndexedMap[PrimaryKey, Value, Idx]) Walk(ctx context.Context, ranger Ranger[PrimaryKey], walkFunc func(key PrimaryKey, value Value) bool) error {
func (m *IndexedMap[PrimaryKey, Value, Idx]) Walk(ctx context.Context, ranger Ranger[PrimaryKey], walkFunc func(key PrimaryKey, value Value) (stop bool, err error)) error {
return m.m.Walk(ctx, ranger, walkFunc)
}

View File

@ -82,9 +82,9 @@ func (m *Multi[ReferenceKey, PrimaryKey, Value]) Iterate(ctx context.Context, ra
func (m *Multi[ReferenceKey, PrimaryKey, Value]) Walk(
ctx context.Context,
ranger collections.Ranger[collections.Pair[ReferenceKey, PrimaryKey]],
walkFunc func(indexingKey ReferenceKey, indexedKey PrimaryKey) bool,
walkFunc func(indexingKey ReferenceKey, indexedKey PrimaryKey) (stop bool, err error),
) error {
return m.refKeys.Walk(ctx, ranger, func(key collections.Pair[ReferenceKey, PrimaryKey]) bool {
return m.refKeys.Walk(ctx, ranger, func(key collections.Pair[ReferenceKey, PrimaryKey]) (bool, error) {
return walkFunc(key.K1(), key.K2())
})
}

View File

@ -67,9 +67,9 @@ func (i *ReversePair[K1, K2, Value]) Unreference(ctx context.Context, pk collect
func (i *ReversePair[K1, K2, Value]) Walk(
ctx context.Context,
ranger collections.Ranger[collections.Pair[K2, K1]],
walkFunc func(indexingKey K2, indexedKey K1) bool,
walkFunc func(indexingKey K2, indexedKey K1) (stop bool, err error),
) error {
return i.refKeys.Walk(ctx, ranger, func(key collections.Pair[K2, K1]) bool {
return i.refKeys.Walk(ctx, ranger, func(key collections.Pair[K2, K1]) (bool, error) {
return walkFunc(key.K1(), key.K2())
})
}

View File

@ -90,7 +90,7 @@ func (i *Unique[ReferenceKey, PrimaryKey, Value]) Iterate(ctx context.Context, r
func (i *Unique[ReferenceKey, PrimaryKey, Value]) Walk(
ctx context.Context,
ranger collections.Ranger[ReferenceKey],
walkFunc func(indexingKey ReferenceKey, indexedKey PrimaryKey) bool,
walkFunc func(indexingKey ReferenceKey, indexedKey PrimaryKey) (stop bool, err error),
) error {
return i.refKeys.Walk(ctx, ranger, walkFunc)
}

View File

@ -175,14 +175,24 @@ func TestWalk(t *testing.T) {
}
u := uint64(0)
err = m.Walk(ctx, nil, func(key, value uint64) bool {
err = m.Walk(ctx, nil, func(key, value uint64) (bool, error) {
if key == 5 {
return true
return true, nil
}
require.Equal(t, u, key)
require.Equal(t, u, value)
u++
return false
return false, nil
})
require.NoError(t, err)
sentinelErr := fmt.Errorf("sentinel error")
err = m.Walk(ctx, nil, func(key, value uint64) (stop bool, err error) {
require.LessOrEqual(t, key, uint64(3)) // asserts that after the number three we stop
if key == 3 {
return false, sentinelErr
}
return false, nil
})
require.ErrorIs(t, err, sentinelErr) // asserts correct error propagation
}

View File

@ -53,8 +53,8 @@ func (k KeySet[K]) IterateRaw(ctx context.Context, start, end []byte, order Orde
// Walk provides the same functionality as Map.Walk, but callbacks the walk
// function only with the key.
func (k KeySet[K]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key K) bool) error {
return (Map[K, NoValue])(k).Walk(ctx, ranger, func(key K, value NoValue) bool { return walkFunc(key) })
func (k KeySet[K]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key K) (stop bool, err error)) error {
return (Map[K, NoValue])(k).Walk(ctx, ranger, func(key K, value NoValue) (bool, error) { return walkFunc(key) })
}
func (k KeySet[K]) KeyCodec() codec.KeyCodec[K] { return (Map[K, NoValue])(k).KeyCodec() }

View File

@ -127,7 +127,7 @@ func (m Map[K, V]) Iterate(ctx context.Context, ranger Ranger[K]) (Iterator[K, V
// walk function with the decoded key and value. If the callback function
// returns true then the walking is stopped.
// A nil ranger equals to walking over the entire key and value set.
func (m Map[K, V]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(K, V) bool) error {
func (m Map[K, V]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key K, value V) (stop bool, err error)) error {
iter, err := m.Iterate(ctx, ranger)
if err != nil {
return err
@ -139,7 +139,11 @@ func (m Map[K, V]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(K,
if err != nil {
return err
}
if walkFunc(kv.Key, kv.Value) {
stop, err := walkFunc(kv.Key, kv.Value)
if err != nil {
return err
}
if stop {
return nil
}
}

View File

@ -155,8 +155,6 @@ require (
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace cosmossdk.io/collections => ../../collections
// Fix upstream GHSA-h395-qcrw-5vmq and GHSA-3vp4-m3rf-835h vulnerabilities.
// TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409
// TODO investigate if we can outright delete this dependency, otherwise go install won't work :(

View File

@ -37,6 +37,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cosmossdk.io/api v0.4.1 h1:0ikaYM6GyxTYYcfBiyR8YnLCfhNnhKpEFnaSepCTmqg=
cosmossdk.io/api v0.4.1/go.mod h1:jR7k5ok90LxW2lFUXvd8Vpo/dr4PpiyVegxdm7b1ZdE=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/core v0.7.0 h1:GFss3qt2P9p23Cz24NnqLkslzb8n+B75A24x1JgJJp0=
cosmossdk.io/core v0.7.0/go.mod h1:36hP0ZH/8ipsjzfcp0yKU4bqQXUGhS0/m1krWFCtwCc=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=

View File

@ -2,8 +2,11 @@ package keeper
import (
"context"
"errors"
"fmt"
"cosmossdk.io/collections"
"cosmossdk.io/core/store"
"cosmossdk.io/log"
"cosmossdk.io/math"
@ -256,9 +259,12 @@ func (k BaseKeeper) GetAllDenomMetaData(ctx context.Context) []types.Metadata {
// provides the metadata to a callback. If true is returned from the
// callback, iteration is halted.
func (k BaseKeeper) IterateAllDenomMetaData(ctx context.Context, cb func(types.Metadata) bool) {
_ = k.BaseViewKeeper.DenomMetadata.Walk(ctx, nil, func(_ string, metadata types.Metadata) bool {
return cb(metadata)
err := k.BaseViewKeeper.DenomMetadata.Walk(ctx, nil, func(_ string, metadata types.Metadata) (stop bool, err error) {
return cb(metadata), nil
})
if err != nil && !errors.Is(err, collections.ErrInvalidIterator) {
panic(err)
}
}
// SetDenomMetaData sets the denominations metadata
@ -474,7 +480,10 @@ func (k BaseKeeper) trackUndelegation(ctx context.Context, addr sdk.AccAddress,
// with the balance of each coin.
// The iteration stops if the callback returns true.
func (k BaseViewKeeper) IterateTotalSupply(ctx context.Context, cb func(sdk.Coin) bool) {
_ = k.Supply.Walk(ctx, nil, func(s string, m math.Int) bool {
return cb(sdk.NewCoin(s, m))
err := k.Supply.Walk(ctx, nil, func(s string, m math.Int) (bool, error) {
return cb(sdk.NewCoin(s, m)), nil
})
if err != nil && !errors.Is(err, collections.ErrInvalidIterator) {
panic(err)
}
}

View File

@ -376,7 +376,12 @@ func (k BaseSendKeeper) DeleteSendEnabled(ctx context.Context, denoms ...string)
// IterateSendEnabledEntries iterates over all the SendEnabled entries.
func (k BaseSendKeeper) IterateSendEnabledEntries(ctx context.Context, cb func(denom string, sendEnabled bool) bool) {
_ = k.SendEnabled.Walk(ctx, nil, cb)
err := k.SendEnabled.Walk(ctx, nil, func(key string, value bool) (stop bool, err error) {
return cb(key, value), nil
})
if err != nil && !errorsmod.IsOf(err, collections.ErrInvalidIterator) {
panic(err)
}
}
// GetAllSendEnabledEntries gets all the SendEnabled entries that are stored.

View File

@ -157,10 +157,10 @@ func (k BaseViewKeeper) GetBalance(ctx context.Context, addr sdk.AccAddress, den
// provides the token balance to a callback. If true is returned from the
// callback, iteration is halted.
func (k BaseViewKeeper) IterateAccountBalances(ctx context.Context, addr sdk.AccAddress, cb func(sdk.Coin) bool) {
err := k.Balances.Walk(ctx, collections.NewPrefixedPairRange[sdk.AccAddress, string](addr), func(key collections.Pair[sdk.AccAddress, string], value math.Int) bool {
return cb(sdk.NewCoin(key.K2(), value))
err := k.Balances.Walk(ctx, collections.NewPrefixedPairRange[sdk.AccAddress, string](addr), func(key collections.Pair[sdk.AccAddress, string], value math.Int) (stop bool, err error) {
return cb(sdk.NewCoin(key.K2(), value)), nil
})
if err != nil && !errors.Is(err, collections.ErrInvalidIterator) { // TODO(tip): is this the correct strategy
if err != nil && !errors.Is(err, collections.ErrInvalidIterator) {
panic(err)
}
}
@ -169,10 +169,10 @@ func (k BaseViewKeeper) IterateAccountBalances(ctx context.Context, addr sdk.Acc
// denominations that are provided to a callback. If true is returned from the
// callback, iteration is halted.
func (k BaseViewKeeper) IterateAllBalances(ctx context.Context, cb func(sdk.AccAddress, sdk.Coin) bool) {
err := k.Balances.Walk(ctx, nil, func(key collections.Pair[sdk.AccAddress, string], value math.Int) bool {
return cb(key.K1(), sdk.NewCoin(key.K2(), value))
err := k.Balances.Walk(ctx, nil, func(key collections.Pair[sdk.AccAddress, string], value math.Int) (stop bool, err error) {
return cb(key.K1(), sdk.NewCoin(key.K2(), value)), nil
})
if err != nil {
if err != nil && !errors.Is(err, collections.ErrInvalidIterator) {
panic(err)
}
}

View File

@ -34,7 +34,10 @@ func InitGenesis(ctx sdk.Context, ak types.AccountKeeper, bk types.BankKeeper, k
var totalDeposits sdk.Coins
for _, deposit := range data.Deposits {
k.SetDeposit(ctx, *deposit)
err := k.SetDeposit(ctx, *deposit)
if err != nil {
panic(err)
}
totalDeposits = totalDeposits.Add(deposit.Amount...)
}

View File

@ -4,147 +4,55 @@ import (
"context"
"fmt"
"cosmossdk.io/collections"
"cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"
disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/gov/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
)
// GetDeposit gets the deposit of a specific depositor on a specific proposal
func (keeper Keeper) GetDeposit(ctx context.Context, proposalID uint64, depositorAddr sdk.AccAddress) (deposit v1.Deposit, err error) {
store := keeper.storeService.OpenKVStore(ctx)
bz, err := store.Get(types.DepositKey(proposalID, depositorAddr))
if err != nil {
return deposit, err
}
if bz == nil {
return deposit, types.ErrDepositNotFound
}
err = keeper.cdc.Unmarshal(bz, &deposit)
if err != nil {
return deposit, err
}
return deposit, nil
}
// SetDeposit sets a Deposit to the gov store
func (keeper Keeper) SetDeposit(ctx context.Context, deposit v1.Deposit) error {
store := keeper.storeService.OpenKVStore(ctx)
bz, err := keeper.cdc.Marshal(&deposit)
if err != nil {
return err
}
depositor, err := keeper.authKeeper.StringToBytes(deposit.Depositor)
if err != nil {
return err
}
return store.Set(types.DepositKey(deposit.ProposalId, depositor), bz)
}
// GetAllDeposits returns all the deposits from the store
func (keeper Keeper) GetAllDeposits(ctx context.Context) (deposits v1.Deposits, err error) {
err = keeper.IterateAllDeposits(ctx, func(deposit v1.Deposit) error {
deposits = append(deposits, &deposit)
return nil
})
return
return keeper.Deposits.Set(ctx, collections.Join(deposit.ProposalId, sdk.AccAddress(depositor)), deposit)
}
// GetDeposits returns all the deposits of a proposal
func (keeper Keeper) GetDeposits(ctx context.Context, proposalID uint64) (deposits v1.Deposits, err error) {
err = keeper.IterateDeposits(ctx, proposalID, func(deposit v1.Deposit) error {
err = keeper.IterateDeposits(ctx, proposalID, func(_ collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (bool, error) {
deposits = append(deposits, &deposit)
return nil
return false, nil
})
return
return deposits, err
}
// DeleteAndBurnDeposits deletes and burns all the deposits on a specific proposal.
func (keeper Keeper) DeleteAndBurnDeposits(ctx context.Context, proposalID uint64) error {
store := keeper.storeService.OpenKVStore(ctx)
err := keeper.IterateDeposits(ctx, proposalID, func(deposit v1.Deposit) error {
err := keeper.bankKeeper.BurnCoins(ctx, types.ModuleName, deposit.Amount)
if err != nil {
return err
}
depositor, err := keeper.authKeeper.StringToBytes(deposit.Depositor)
if err != nil {
return err
}
err = store.Delete(types.DepositKey(proposalID, depositor))
if err != nil {
return err
}
return nil
coinsToBurn := sdk.NewCoins()
err := keeper.IterateDeposits(ctx, proposalID, func(key collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (stop bool, err error) {
coinsToBurn = coinsToBurn.Add(deposit.Amount...)
return false, keeper.Deposits.Remove(ctx, key)
})
return err
}
// IterateAllDeposits iterates over all the stored deposits and performs a callback function.
func (keeper Keeper) IterateAllDeposits(ctx context.Context, cb func(deposit v1.Deposit) error) error {
store := keeper.storeService.OpenKVStore(ctx)
iterator := storetypes.KVStorePrefixIterator(runtime.KVStoreAdapter(store), types.DepositsKeyPrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var deposit v1.Deposit
err := keeper.cdc.Unmarshal(iterator.Value(), &deposit)
if err != nil {
return err
}
err = cb(deposit)
// exit early without error if cb returns ErrStopIterating
if errors.IsOf(err, errors.ErrStopIterating) {
return nil
} else if err != nil {
return err
}
if err != nil {
return err
}
return nil
return keeper.bankKeeper.BurnCoins(ctx, types.ModuleName, coinsToBurn)
}
// IterateDeposits iterates over all the proposals deposits and performs a callback function
func (keeper Keeper) IterateDeposits(ctx context.Context, proposalID uint64, cb func(deposit v1.Deposit) error) error {
store := keeper.storeService.OpenKVStore(ctx)
iterator := storetypes.KVStorePrefixIterator(runtime.KVStoreAdapter(store), types.DepositsKey(proposalID))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var deposit v1.Deposit
err := keeper.cdc.Unmarshal(iterator.Value(), &deposit)
if err != nil {
return err
}
err = cb(deposit)
// exit early without error if cb returns ErrStopIterating
if errors.IsOf(err, errors.ErrStopIterating) {
return nil
} else if err != nil {
return err
}
func (keeper Keeper) IterateDeposits(ctx context.Context, proposalID uint64, cb func(key collections.Pair[uint64, sdk.AccAddress], value v1.Deposit) (bool, error)) error {
rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposalID)
err := keeper.Deposits.Walk(ctx, rng, cb)
if err != nil && !errors.IsOf(err, collections.ErrInvalidIterator) {
return err
}
return nil
}
@ -196,12 +104,12 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito
}
// Add or update deposit object
deposit, err := keeper.GetDeposit(ctx, proposalID, depositorAddr)
deposit, err := keeper.Deposits.Get(ctx, collections.Join(proposalID, depositorAddr))
switch {
case err == nil:
// deposit exists
deposit.Amount = sdk.NewCoins(deposit.Amount...).Add(depositAmount...)
case errors.IsOf(err, types.ErrDepositNotFound):
case errors.IsOf(err, collections.ErrNotFound):
// deposit doesn't exist
deposit = v1.NewDeposit(proposalID, depositorAddr, depositAmount)
default:
@ -233,7 +141,6 @@ func (keeper Keeper) AddDeposit(ctx context.Context, proposalID uint64, deposito
// send to a destAddress if defined or burn otherwise.
// Remaining funds are send back to the depositor.
func (keeper Keeper) ChargeDeposit(ctx context.Context, proposalID uint64, destAddress, proposalCancelRate string) error {
store := keeper.storeService.OpenKVStore(ctx)
rate := sdkmath.LegacyMustNewDecFromStr(proposalCancelRate)
var cancellationCharges sdk.Coins
@ -275,8 +182,7 @@ func (keeper Keeper) ChargeDeposit(ctx context.Context, proposalID uint64, destA
return err
}
}
err = store.Delete(types.DepositKey(deposit.ProposalId, depositerAddress))
err = keeper.Deposits.Remove(ctx, collections.Join(deposit.ProposalId, sdk.AccAddress(depositerAddress)))
if err != nil {
return err
}
@ -317,27 +223,15 @@ func (keeper Keeper) ChargeDeposit(ctx context.Context, proposalID uint64, destA
// RefundAndDeleteDeposits refunds and deletes all the deposits on a specific proposal.
func (keeper Keeper) RefundAndDeleteDeposits(ctx context.Context, proposalID uint64) error {
store := keeper.storeService.OpenKVStore(ctx)
err := keeper.IterateDeposits(ctx, proposalID, func(deposit v1.Deposit) error {
depositor, err := keeper.authKeeper.StringToBytes(deposit.Depositor)
return keeper.IterateDeposits(ctx, proposalID, func(key collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (bool, error) {
depositor := key.K2()
err := keeper.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, depositor, deposit.Amount)
if err != nil {
return err
return false, err
}
err = keeper.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, depositor, deposit.Amount)
if err != nil {
return err
}
err = store.Delete(types.DepositKey(proposalID, depositor))
if err != nil {
return err
}
return nil
err = keeper.Deposits.Remove(ctx, key)
return false, err
})
return err
}
// validateInitialDeposit validates if initial deposit is greater than or equal to the minimum

View File

@ -4,6 +4,8 @@ import (
"fmt"
"testing"
"cosmossdk.io/collections"
sdkmath "cosmossdk.io/math"
"github.com/stretchr/testify/require"
@ -12,7 +14,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/gov/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
)
@ -68,8 +69,8 @@ func TestDeposits(t *testing.T) {
require.True(t, sdk.NewCoins(proposal.TotalDeposit...).Equal(sdk.NewCoins()))
// Check no deposits at beginning
_, err = govKeeper.GetDeposit(ctx, proposalID, TestAddrs[1])
require.ErrorIs(t, err, types.ErrDepositNotFound)
_, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[1]))
require.ErrorIs(t, err, collections.ErrNotFound)
proposal, err = govKeeper.GetProposal(ctx, proposalID)
require.Nil(t, err)
require.Nil(t, proposal.VotingStartTime)
@ -78,7 +79,7 @@ func TestDeposits(t *testing.T) {
votingStarted, err := govKeeper.AddDeposit(ctx, proposalID, TestAddrs[0], fourStake)
require.NoError(t, err)
require.False(t, votingStarted)
deposit, err := govKeeper.GetDeposit(ctx, proposalID, TestAddrs[0])
deposit, err := govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[0]))
require.Nil(t, err)
require.Equal(t, fourStake, sdk.NewCoins(deposit.Amount...))
require.Equal(t, TestAddrs[0].String(), deposit.Depositor)
@ -91,7 +92,7 @@ func TestDeposits(t *testing.T) {
votingStarted, err = govKeeper.AddDeposit(ctx, proposalID, TestAddrs[0], fiveStake)
require.NoError(t, err)
require.False(t, votingStarted)
deposit, err = govKeeper.GetDeposit(ctx, proposalID, TestAddrs[0])
deposit, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[0]))
require.Nil(t, err)
require.Equal(t, fourStake.Add(fiveStake...), sdk.NewCoins(deposit.Amount...))
require.Equal(t, TestAddrs[0].String(), deposit.Depositor)
@ -104,7 +105,7 @@ func TestDeposits(t *testing.T) {
votingStarted, err = govKeeper.AddDeposit(ctx, proposalID, TestAddrs[1], fourStake)
require.NoError(t, err)
require.True(t, votingStarted)
deposit, err = govKeeper.GetDeposit(ctx, proposalID, TestAddrs[1])
deposit, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[1]))
require.Nil(t, err)
require.Equal(t, TestAddrs[1].String(), deposit.Depositor)
require.Equal(t, fourStake, sdk.NewCoins(deposit.Amount...))
@ -120,7 +121,12 @@ func TestDeposits(t *testing.T) {
// Test deposit iterator
// NOTE order of deposits is determined by the addresses
deposits, _ := govKeeper.GetAllDeposits(ctx)
var deposits v1.Deposits
err = govKeeper.Deposits.Walk(ctx, nil, func(_ collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (bool, error) {
deposits = append(deposits, &deposit)
return false, nil
})
require.NoError(t, err)
require.Len(t, deposits, 2)
propDeposits, _ := govKeeper.GetDeposits(ctx, proposalID)
require.Equal(t, deposits, propDeposits)
@ -130,12 +136,12 @@ func TestDeposits(t *testing.T) {
require.Equal(t, fourStake, sdk.NewCoins(deposits[1].Amount...))
// Test Refund Deposits
deposit, err = govKeeper.GetDeposit(ctx, proposalID, TestAddrs[1])
deposit, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[1]))
require.Nil(t, err)
require.Equal(t, fourStake, sdk.NewCoins(deposit.Amount...))
govKeeper.RefundAndDeleteDeposits(ctx, proposalID)
deposit, err = govKeeper.GetDeposit(ctx, proposalID, TestAddrs[1])
require.ErrorIs(t, err, types.ErrDepositNotFound)
deposit, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[1]))
require.ErrorIs(t, err, collections.ErrNotFound)
require.Equal(t, addr0Initial, bankKeeper.GetAllBalances(ctx, TestAddrs[0]))
require.Equal(t, addr1Initial, bankKeeper.GetAllBalances(ctx, TestAddrs[1]))

View File

@ -3,6 +3,8 @@ package keeper
import (
"context"
"cosmossdk.io/collections"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -91,9 +93,9 @@ func (q queryServer) Proposals(ctx context.Context, req *v1.QueryProposalsReques
if err != nil {
return nil, err
}
_, err = q.k.GetDeposit(ctx, p.Id, depositor)
has, err := q.k.Deposits.Has(ctx, collections.Join(p.Id, sdk.AccAddress(depositor)))
// if no error, deposit found, matchDepositor = true
matchDepositor = err == nil
matchDepositor = err == nil && has
}
if matchVoter && matchDepositor && matchStatus {
@ -225,13 +227,9 @@ func (q queryServer) Deposit(ctx context.Context, req *v1.QueryDepositRequest) (
if err != nil {
return nil, err
}
deposit, err := q.k.GetDeposit(ctx, req.ProposalId, depositor)
deposit, err := q.k.Deposits.Get(ctx, collections.Join(req.ProposalId, sdk.AccAddress(depositor)))
if err != nil {
if errors.IsOf(err, types.ErrDepositNotFound) {
return nil, status.Errorf(codes.InvalidArgument,
"depositer: %v not found for proposal: %v", req.Depositor, req.ProposalId)
}
return nil, status.Error(codes.Internal, err.Error())
return nil, status.Error(codes.NotFound, err.Error())
}
return &v1.QueryDepositResponse{Deposit: &deposit}, nil
@ -248,19 +246,10 @@ func (q queryServer) Deposits(ctx context.Context, req *v1.QueryDepositsRequest)
}
var deposits []*v1.Deposit
store := q.k.storeService.OpenKVStore(ctx)
depositStore := prefix.NewStore(runtime.KVStoreAdapter(store), types.DepositsKey(req.ProposalId))
pageRes, err := query.Paginate(depositStore, req.Pagination, func(key, value []byte) error {
var deposit v1.Deposit
if err := q.k.cdc.Unmarshal(value, &deposit); err != nil {
return err
}
_, pageRes, err := query.CollectionFilteredPaginate(ctx, q.k.Deposits, req.Pagination, func(_ collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (bool, error) {
deposits = append(deposits, &deposit)
return nil
})
return false, nil // we don't include results as they're being appended to the slice above.
}, query.WithCollectionPaginationPairPrefix[uint64, sdk.AccAddress](req.ProposalId))
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

View File

@ -1,8 +1,11 @@
package keeper
import (
"errors"
"fmt"
"cosmossdk.io/collections"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
@ -13,23 +16,19 @@ func RegisterInvariants(ir sdk.InvariantRegistry, keeper *Keeper, bk types.BankK
ir.RegisterRoute(types.ModuleName, "module-account", ModuleAccountInvariant(keeper, bk))
}
// AllInvariants runs all invariants of the governance module
func AllInvariants(keeper *Keeper, bk types.BankKeeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
return ModuleAccountInvariant(keeper, bk)(ctx)
}
}
// ModuleAccountInvariant checks that the module account coins reflects the sum of
// deposit amounts held on store.
func ModuleAccountInvariant(keeper *Keeper, bk types.BankKeeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
var expectedDeposits sdk.Coins
keeper.IterateAllDeposits(ctx, func(deposit v1.Deposit) error {
expectedDeposits = expectedDeposits.Add(deposit.Amount...)
return nil
err := keeper.Deposits.Walk(ctx, nil, func(key collections.Pair[uint64, sdk.AccAddress], value v1.Deposit) (stop bool, err error) {
expectedDeposits = expectedDeposits.Add(value.Amount...)
return false, nil
})
if err != nil && !errors.Is(err, collections.ErrInvalidIterator) {
panic(err)
}
macc := keeper.GetGovernanceAccount(ctx)
balances := bk.GetAllBalances(ctx, macc.GetAddress())

View File

@ -53,6 +53,7 @@ type Keeper struct {
Schema collections.Schema
Constitution collections.Item[string]
Params collections.Item[v1.Params]
Deposits collections.Map[collections.Pair[uint64, sdk.AccAddress], v1.Deposit]
}
// GetAuthority returns the x/gov module's authority.
@ -99,6 +100,7 @@ func NewKeeper(
authority: authority,
Constitution: collections.NewItem(sb, types.ConstitutionKey, "constitution", collections.StringValue),
Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[v1.Params](cdc)),
Deposits: collections.NewMap(sb, types.DepositsKeyPrefix, "deposits", collections.PairKeyCodec(collections.Uint64Key, sdk.AddressKeyAsIndexKey(sdk.AccAddressKey)), codec.CollValue[v1.Deposit](cdc)), //nolint: staticcheck // Needed to retain state compatibility
}
schema, err := sb.Build()
if err != nil {

View File

@ -6,6 +6,8 @@ import (
"fmt"
"time"
"cosmossdk.io/collections"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
@ -323,7 +325,7 @@ func (keeper Keeper) GetProposalsFiltered(ctx context.Context, params v1.QueryPr
// match depositor (if supplied)
if len(params.Depositor) > 0 {
_, err = keeper.GetDeposit(ctx, p.Id, params.Depositor)
_, err = keeper.Deposits.Get(ctx, collections.Join(p.Id, params.Depositor))
// if no error, deposit found, matchDepositor = true
matchDepositor = err == nil
}

View File

@ -5,6 +5,9 @@ import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"cosmossdk.io/math"
"github.com/stretchr/testify/require"
@ -64,7 +67,7 @@ func TestMigrateStore(t *testing.T) {
{
"DepositKey",
v1.DepositKey(proposalID, addr1), dummyValue,
types.DepositKey(proposalID, addr1), dummyValue,
depositKey(proposalID, addr1), dummyValue,
},
{
"VotesKeyPrefix",
@ -94,3 +97,9 @@ func TestMigrateStore(t *testing.T) {
})
}
}
// depositKey key of a specific deposit from the store.
// NOTE(tip): legacy, eventually remove me.
func depositKey(proposalID uint64, depositorAddr sdk.AccAddress) []byte {
return append(append(types.DepositsKeyPrefix, sdk.Uint64ToBigEndian(proposalID)...), address.MustLengthPrefix(depositorAddr.Bytes())...)
}

View File

@ -353,7 +353,7 @@ func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.Weight
// RegisterStoreDecoder registers a decoder for gov module's types
func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) {
sdr[govtypes.StoreKey] = simulation.NewDecodeStore(am.cdc)
sdr[govtypes.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.Schema)
}
// WeightedOperations returns the all the gov module operations with their respective weights.

View File

@ -1,68 +0,0 @@
package simulation
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/types/kv"
"github.com/cosmos/cosmos-sdk/x/gov/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
)
// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's
// Value to the corresponding gov type.
func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string {
return func(kvA, kvB kv.Pair) string {
switch {
case bytes.Equal(kvA.Key[:1], types.ProposalsKeyPrefix):
var (
proposalA v1beta1.Proposal
proposalB v1beta1.Proposal
proposalD v1.Proposal
proposalC v1.Proposal
)
if err := cdc.Unmarshal(kvA.Value, &proposalC); err != nil {
cdc.MustUnmarshal(kvA.Value, &proposalA)
}
if err := cdc.Unmarshal(kvB.Value, &proposalD); err != nil {
cdc.MustUnmarshal(kvB.Value, &proposalB)
}
// this is to check if the proposal has been unmarshalled as v1 correctly (and not v1beta1)
if proposalC.Title != "" || proposalD.Title != "" {
return fmt.Sprintf("%v\n%v", proposalC, proposalD)
}
return fmt.Sprintf("%v\n%v", proposalA, proposalB)
case bytes.Equal(kvA.Key[:1], types.ActiveProposalQueuePrefix),
bytes.Equal(kvA.Key[:1], types.InactiveProposalQueuePrefix),
bytes.Equal(kvA.Key[:1], types.ProposalIDKey):
proposalIDA := binary.LittleEndian.Uint64(kvA.Value)
proposalIDB := binary.LittleEndian.Uint64(kvB.Value)
return fmt.Sprintf("proposalIDA: %d\nProposalIDB: %d", proposalIDA, proposalIDB)
case bytes.Equal(kvA.Key[:1], types.DepositsKeyPrefix):
var depositA, depositB v1beta1.Deposit
cdc.MustUnmarshal(kvA.Value, &depositA)
cdc.MustUnmarshal(kvB.Value, &depositB)
return fmt.Sprintf("%v\n%v", depositA, depositB)
case bytes.Equal(kvA.Key[:1], types.VotesKeyPrefix):
var voteA, voteB v1beta1.Vote
cdc.MustUnmarshal(kvA.Value, &voteA)
cdc.MustUnmarshal(kvB.Value, &voteB)
return fmt.Sprintf("%v\n%v", voteA, voteB)
case bytes.Equal(kvA.Key[:1], types.VotingPeriodProposalKeyPrefix):
return fmt.Sprintf("%v\n%v", kvA.Value, kvB.Value)
default:
panic(fmt.Sprintf("invalid governance key prefix %X", kvA.Key[:1]))
}
}
}

View File

@ -1,103 +0,0 @@
package simulation_test
import (
"encoding/binary"
"fmt"
"testing"
"time"
"cosmossdk.io/math"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/kv"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/gov/simulation"
"github.com/cosmos/cosmos-sdk/x/gov/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
)
var (
delPk1 = ed25519.GenPrivKey().PubKey()
delAddr1 = sdk.AccAddress(delPk1.Address())
)
func TestDecodeStore(t *testing.T) {
cdc := moduletestutil.MakeTestEncodingConfig(gov.AppModuleBasic{}).Codec
dec := simulation.NewDecodeStore(cdc)
endTime := time.Now().UTC()
content, ok := v1beta1.ContentFromProposalType("test", "test", v1beta1.ProposalTypeText)
require.True(t, ok)
proposalA, err := v1beta1.NewProposal(content, 1, endTime, endTime.Add(24*time.Hour))
require.NoError(t, err)
proposalB, err := v1beta1.NewProposal(content, 2, endTime, endTime.Add(24*time.Hour))
require.NoError(t, err)
proposalC, err := v1.NewProposal([]sdk.Msg{}, 3, endTime, endTime.Add(24*time.Hour), "metadata", "title", "summary", delAddr1, false)
require.NoError(t, err)
proposalD, err := v1.NewProposal([]sdk.Msg{}, 4, endTime, endTime.Add(24*time.Hour), "metadata", "title", "summary", delAddr1, true)
require.NoError(t, err)
proposalIDBz := make([]byte, 8)
binary.LittleEndian.PutUint64(proposalIDBz, 1)
deposit := v1beta1.NewDeposit(1, delAddr1, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, math.OneInt())))
vote := v1beta1.NewVote(1, delAddr1, v1beta1.NewNonSplitVoteOption(v1beta1.OptionYes))
tests := []struct {
name string
kvA, kvB kv.Pair
expectedLog string
wantPanic bool
}{
{
"proposals v1beta",
kv.Pair{Key: types.ProposalKey(1), Value: cdc.MustMarshal(&proposalA)},
kv.Pair{Key: types.ProposalKey(2), Value: cdc.MustMarshal(&proposalB)},
fmt.Sprintf("%v\n%v", proposalA, proposalB), false,
},
{
"proposals v1",
kv.Pair{Key: types.ProposalKey(3), Value: cdc.MustMarshal(&proposalC)},
kv.Pair{Key: types.ProposalKey(4), Value: cdc.MustMarshal(&proposalD)},
fmt.Sprintf("%v\n%v", proposalC, proposalD), false,
},
{
"proposal IDs",
kv.Pair{Key: types.InactiveProposalQueueKey(1, endTime), Value: proposalIDBz},
kv.Pair{Key: types.InactiveProposalQueueKey(1, endTime), Value: proposalIDBz},
"proposalIDA: 1\nProposalIDB: 1", false,
},
{
"deposits",
kv.Pair{Key: types.DepositKey(1, delAddr1), Value: cdc.MustMarshal(&deposit)},
kv.Pair{Key: types.DepositKey(1, delAddr1), Value: cdc.MustMarshal(&deposit)},
fmt.Sprintf("%v\n%v", deposit, deposit), false,
},
{
"votes",
kv.Pair{Key: types.VoteKey(1, delAddr1), Value: cdc.MustMarshal(&vote)},
kv.Pair{Key: types.VoteKey(1, delAddr1), Value: cdc.MustMarshal(&vote)},
fmt.Sprintf("%v\n%v", vote, vote), false,
},
{
"other",
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
"", true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
if tt.wantPanic {
require.Panics(t, func() { dec(tt.kvA, tt.kvB) }, tt.name)
} else {
require.Equal(t, tt.expectedLog, dec(tt.kvA, tt.kvB), tt.name)
}
})
}
}

View File

@ -27,6 +27,7 @@ import (
_ "github.com/cosmos/cosmos-sdk/x/consensus"
_ "github.com/cosmos/cosmos-sdk/x/distribution"
dk "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
_ "github.com/cosmos/cosmos-sdk/x/gov"
govcodec "github.com/cosmos/cosmos-sdk/x/gov/codec"
"github.com/cosmos/cosmos-sdk/x/gov/keeper"
"github.com/cosmos/cosmos-sdk/x/gov/simulation"

View File

@ -19,14 +19,11 @@ var (
ErrNoProposalMsgs = errors.Register(ModuleName, 11, "no messages proposed")
ErrInvalidProposalMsg = errors.Register(ModuleName, 12, "invalid proposal message")
ErrInvalidSigner = errors.Register(ModuleName, 13, "expected gov account as only signer for proposal message")
ErrInvalidSignalMsg = errors.Register(ModuleName, 14, "signal message is invalid")
ErrMetadataTooLong = errors.Register(ModuleName, 15, "metadata too long")
ErrMinDepositTooSmall = errors.Register(ModuleName, 16, "minimum deposit is too small")
ErrProposalNotFound = errors.Register(ModuleName, 17, "proposal is not found")
ErrInvalidProposer = errors.Register(ModuleName, 18, "invalid proposer")
ErrNoDeposits = errors.Register(ModuleName, 19, "no deposits found")
ErrVotingPeriodEnded = errors.Register(ModuleName, 20, "voting period already ended")
ErrInvalidProposal = errors.Register(ModuleName, 21, "invalid proposal")
ErrDepositNotFound = errors.Register(ModuleName, 22, "deposit is not found")
ErrVoteNotFound = errors.Register(ModuleName, 23, "vote is not found")
)

View File

@ -47,7 +47,7 @@ var (
ProposalIDKey = []byte{0x03}
VotingPeriodProposalKeyPrefix = []byte{0x04}
DepositsKeyPrefix = []byte{0x10}
DepositsKeyPrefix = collections.NewPrefix(16)
VotesKeyPrefix = []byte{0x20}
@ -102,16 +102,6 @@ func InactiveProposalQueueKey(proposalID uint64, endTime time.Time) []byte {
return append(InactiveProposalByTimeKey(endTime), GetProposalIDBytes(proposalID)...)
}
// DepositsKey gets the first part of the deposits key based on the proposalID
func DepositsKey(proposalID uint64) []byte {
return append(DepositsKeyPrefix, GetProposalIDBytes(proposalID)...)
}
// DepositKey key of a specific deposit from the store
func DepositKey(proposalID uint64, depositorAddr sdk.AccAddress) []byte {
return append(DepositsKey(proposalID), address.MustLengthPrefix(depositorAddr.Bytes())...)
}
// VotesKey gets the first part of the votes key based on the proposalID
func VotesKey(proposalID uint64) []byte {
return append(VotesKeyPrefix, GetProposalIDBytes(proposalID)...)

View File

@ -36,17 +36,6 @@ func TestProposalKeys(t *testing.T) {
require.Panics(t, func() { SplitInactiveProposalQueueKey([]byte("test")) })
}
func TestDepositKeys(t *testing.T) {
key := DepositsKey(2)
proposalID := SplitProposalKey(key)
require.Equal(t, int(proposalID), 2)
key = DepositKey(2, addr)
proposalID, depositorAddr := SplitKeyDeposit(key)
require.Equal(t, int(proposalID), 2)
require.Equal(t, addr, depositorAddr)
}
func TestVoteKeys(t *testing.T) {
key := VotesKey(2)
proposalID := SplitProposalKey(key)