diff --git a/x/bank/v2/keeper/keeper.go b/x/bank/v2/keeper/keeper.go index 88799b4ac18b..f16982f168c4 100644 --- a/x/bank/v2/keeper/keeper.go +++ b/x/bank/v2/keeper/keeper.go @@ -1,12 +1,20 @@ package keeper import ( + "context" + "cosmossdk.io/collections" + "cosmossdk.io/collections/indexes" "cosmossdk.io/core/address" appmodulev2 "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/event" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" "cosmossdk.io/x/bank/v2/types" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // Keeper defines the bank/v2 module keeper. @@ -18,6 +26,8 @@ type Keeper struct { addressCodec address.Codec schema collections.Schema params collections.Item[types.Params] + balances *collections.IndexedMap[collections.Pair[[]byte, string], math.Int, BalancesIndexes] + supply collections.Map[string, math.Int] } func NewKeeper(authority []byte, addressCodec address.Codec, env appmodulev2.Environment, cdc codec.BinaryCodec) *Keeper { @@ -28,6 +38,8 @@ func NewKeeper(authority []byte, addressCodec address.Codec, env appmodulev2.Env authority: authority, addressCodec: addressCodec, // TODO(@julienrbrt): Should we add address codec to the environment? params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)), + balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.PairKeyCodec(collections.BytesKey, collections.StringKey), sdk.IntValue, newBalancesIndexes(sb)), + supply: collections.NewMap(sb, types.SupplyKey, "supply", collections.StringKey, sdk.IntValue), } schema, err := sb.Build() @@ -38,3 +50,205 @@ func NewKeeper(authority []byte, addressCodec address.Codec, env appmodulev2.Env return k } + +// MintCoins creates new coins from thin air and adds it to the module account. +// An error is returned if the module account does not exist or is unauthorized. +func (k Keeper) MintCoins(ctx context.Context, addr []byte, amounts sdk.Coins) error { + // TODO: Mint restriction & permission + + if !amounts.IsValid() { + return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, amounts.String()) + } + + err := k.addCoins(ctx, addr, amounts) + if err != nil { + return err + } + + for _, amount := range amounts { + supply := k.GetSupply(ctx, amount.GetDenom()) + supply = supply.Add(amount) + k.setSupply(ctx, supply) + } + + addrStr, err := k.addressCodec.BytesToString(addr) + if err != nil { + return err + } + + // emit mint event + return k.EventService.EventManager(ctx).EmitKV( + types.EventTypeCoinMint, + event.NewAttribute(types.AttributeKeyMinter, addrStr), + event.NewAttribute(sdk.AttributeKeyAmount, amounts.String()), + ) +} + +// SendCoins transfers amt coins from a sending account to a receiving account. +// Function take sender & receipient as []byte. +// They can be sdk address or module name. +// An error is returned upon failure. +func (k Keeper) SendCoins(ctx context.Context, from, to []byte, amt sdk.Coins) error { + if !amt.IsValid() { + return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) + } + + var err error + // TODO: Send restriction + + err = k.subUnlockedCoins(ctx, from, amt) + if err != nil { + return err + } + + err = k.addCoins(ctx, to, amt) + if err != nil { + return err + } + + fromAddrString, err := k.addressCodec.BytesToString(from) + if err != nil { + return err + } + toAddrString, err := k.addressCodec.BytesToString(to) + if err != nil { + return err + } + + return k.EventService.EventManager(ctx).EmitKV( + types.EventTypeTransfer, + event.NewAttribute(types.AttributeKeyRecipient, toAddrString), + event.NewAttribute(types.AttributeKeySender, fromAddrString), + event.NewAttribute(sdk.AttributeKeyAmount, amt.String()), + ) +} + +// GetSupply retrieves the Supply from store +func (k Keeper) GetSupply(ctx context.Context, denom string) sdk.Coin { + amt, err := k.supply.Get(ctx, denom) + if err != nil { + return sdk.NewCoin(denom, math.ZeroInt()) + } + return sdk.NewCoin(denom, amt) +} + +// GetBalance returns the balance of a specific denomination for a given account +// by address. +func (k Keeper) GetBalance(ctx context.Context, addr []byte, denom string) sdk.Coin { + amt, err := k.balances.Get(ctx, collections.Join(addr, denom)) + if err != nil { + return sdk.NewCoin(denom, math.ZeroInt()) + } + return sdk.NewCoin(denom, amt) +} + +// subUnlockedCoins removes the unlocked amt coins of the given account. +// An error is returned if the resulting balance is negative. +// +// CONTRACT: The provided amount (amt) must be valid, non-negative coins. +// +// A coin_spent event is emitted after the operation. +func (k Keeper) subUnlockedCoins(ctx context.Context, addr []byte, amt sdk.Coins) error { + for _, coin := range amt { + balance := k.GetBalance(ctx, addr, coin.Denom) + spendable := sdk.Coins{balance} + + _, hasNeg := spendable.SafeSub(coin) + if hasNeg { + if len(spendable) == 0 { + spendable = sdk.Coins{sdk.Coin{Denom: coin.Denom, Amount: math.ZeroInt()}} + } + return errorsmod.Wrapf( + sdkerrors.ErrInsufficientFunds, + "spendable balance %s is smaller than %s", + spendable, coin, + ) + } + + newBalance := balance.Sub(coin) + + if err := k.setBalance(ctx, addr, newBalance); err != nil { + return err + } + } + + addrStr, err := k.addressCodec.BytesToString(addr) + if err != nil { + return err + } + + return k.EventService.EventManager(ctx).EmitKV( + types.EventTypeCoinSpent, + event.NewAttribute(types.AttributeKeySpender, addrStr), + event.NewAttribute(sdk.AttributeKeyAmount, amt.String()), + ) +} + +// addCoins increases the balance of the given address by the specified amount. +// +// CONTRACT: The provided amount (amt) must be valid, non-negative coins. +// +// It emits a coin_received event after the operation. +func (k Keeper) addCoins(ctx context.Context, addr []byte, amt sdk.Coins) error { + for _, coin := range amt { + balance := k.GetBalance(ctx, addr, coin.Denom) + newBalance := balance.Add(coin) + + err := k.setBalance(ctx, addr, newBalance) + if err != nil { + return err + } + } + + addrStr, err := k.addressCodec.BytesToString(addr) + if err != nil { + return err + } + + return k.EventService.EventManager(ctx).EmitKV( + types.EventTypeCoinReceived, + event.NewAttribute(types.AttributeKeyReceiver, addrStr), + event.NewAttribute(sdk.AttributeKeyAmount, amt.String()), + ) +} + +// setSupply sets the supply for the given coin +func (k Keeper) setSupply(ctx context.Context, coin sdk.Coin) { + // Bank invariants and IBC requires to remove zero coins. + if coin.IsZero() { + _ = k.supply.Remove(ctx, coin.Denom) + } else { + _ = k.supply.Set(ctx, coin.Denom, coin.Amount) + } +} + +// setBalance sets the coin balance for an account by address. +func (k Keeper) setBalance(ctx context.Context, addr []byte, balance sdk.Coin) error { + if !balance.IsValid() { + return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, balance.String()) + } + + // x/bank invariants prohibit persistence of zero balances + if balance.IsZero() { + err := k.balances.Remove(ctx, collections.Join(addr, balance.Denom)) + if err != nil { + return err + } + return nil + } + return k.balances.Set(ctx, collections.Join(addr, balance.Denom), balance.Amount) +} + +func newBalancesIndexes(sb *collections.SchemaBuilder) BalancesIndexes { + return BalancesIndexes{ + Denom: indexes.NewReversePair[math.Int]( + sb, types.DenomAddressPrefix, "address_by_denom_index", + collections.PairKeyCodec(collections.BytesKey, collections.StringKey), + indexes.WithReversePairUncheckedValue(), // denom to address indexes were stored as Key: Join(denom, address) Value: []byte{0}, this will migrate the value to []byte{} in a lazy way. + ), + } +} + +type BalancesIndexes struct { + Denom *indexes.ReversePair[[]byte, string, math.Int] +} diff --git a/x/bank/v2/keeper/keeper_test.go b/x/bank/v2/keeper/keeper_test.go new file mode 100644 index 000000000000..32ccbac4d2ef --- /dev/null +++ b/x/bank/v2/keeper/keeper_test.go @@ -0,0 +1,186 @@ +package keeper_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + "cosmossdk.io/core/address" + "cosmossdk.io/core/header" + coretesting "cosmossdk.io/core/testing" + "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/bank/v2/keeper" + banktestutil "cosmossdk.io/x/bank/v2/testutil" + banktypes "cosmossdk.io/x/bank/v2/types" + + codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +const ( + fooDenom = "foo" + barDenom = "bar" +) + +var ( + burnerAcc = authtypes.NewEmptyModuleAccount(authtypes.Burner, authtypes.Burner, authtypes.Staking) + mintAcc = authtypes.NewEmptyModuleAccount(banktypes.MintModuleName, authtypes.Minter) + + accAddrs = []sdk.AccAddress{ + sdk.AccAddress([]byte("addr1_______________")), + sdk.AccAddress([]byte("addr2_______________")), + sdk.AccAddress([]byte("addr3_______________")), + sdk.AccAddress([]byte("addr4_______________")), + sdk.AccAddress([]byte("addr5_______________")), + } +) + +func newFooCoin(amt int64) sdk.Coin { + return sdk.NewInt64Coin(fooDenom, amt) +} + +func newBarCoin(amt int64) sdk.Coin { + return sdk.NewInt64Coin(barDenom, amt) +} + +type KeeperTestSuite struct { + suite.Suite + + ctx context.Context + bankKeeper keeper.Keeper + addressCodec address.Codec +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) SetupTest() { + key := storetypes.NewKVStoreKey(banktypes.StoreKey) + testCtx := testutil.DefaultContextWithDB(suite.T(), key, storetypes.NewTransientStoreKey("transient_test")) + ctx := testCtx.Ctx.WithHeaderInfo(header.Info{Time: time.Now()}) + encCfg := moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}) + + env := runtime.NewEnvironment(runtime.NewKVStoreService(key), coretesting.NewNopLogger()) + + ac := codectestutil.CodecOptions{}.GetAddressCodec() + authority := authtypes.NewModuleAddress("gov") + + suite.ctx = ctx + suite.bankKeeper = *keeper.NewKeeper( + authority, + ac, + env, + encCfg.Codec, + ) + suite.addressCodec = ac +} + +func (suite *KeeperTestSuite) TestSendCoins_Acount_To_Account() { + ctx := suite.ctx + require := suite.Require() + balances := sdk.NewCoins(newFooCoin(100), newBarCoin(50)) + sendAmt := sdk.NewCoins(newFooCoin(10), newBarCoin(10)) + + // Try send with empty balances + err := suite.bankKeeper.SendCoins(ctx, accAddrs[0], accAddrs[1], sendAmt) + require.Error(err) + + // Set balances for acc0 and then try send to acc1 + require.NoError(banktestutil.FundAccount(ctx, suite.bankKeeper, accAddrs[0], balances)) + require.NoError(suite.bankKeeper.SendCoins(ctx, accAddrs[0], accAddrs[1], sendAmt)) + + // Check balances + acc0FooBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[0], fooDenom) + require.Equal(acc0FooBalance.Amount, math.NewInt(90)) + acc0BarBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[0], barDenom) + require.Equal(acc0BarBalance.Amount, math.NewInt(40)) + acc1FooBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[1], fooDenom) + require.Equal(acc1FooBalance.Amount, math.NewInt(10)) + acc1BarBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[1], barDenom) + require.Equal(acc1BarBalance.Amount, math.NewInt(10)) +} + +func (suite *KeeperTestSuite) TestSendCoins_Acount_To_Module() { + ctx := suite.ctx + require := suite.Require() + balances := sdk.NewCoins(newFooCoin(100), newBarCoin(50)) + sendAmt := sdk.NewCoins(newFooCoin(10), newBarCoin(10)) + + // Try send with empty balances + err := suite.bankKeeper.SendCoins(ctx, accAddrs[0], burnerAcc.GetAddress(), sendAmt) + require.Error(err) + + // Set balances for acc0 and then try send to acc1 + require.NoError(banktestutil.FundAccount(ctx, suite.bankKeeper, accAddrs[0], balances)) + require.NoError(suite.bankKeeper.SendCoins(ctx, accAddrs[0], burnerAcc.GetAddress(), sendAmt)) + + // Check balances + acc0FooBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[0], fooDenom) + require.Equal(acc0FooBalance.Amount, math.NewInt(90)) + acc0BarBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[0], barDenom) + require.Equal(acc0BarBalance.Amount, math.NewInt(40)) + burnerFooBalance := suite.bankKeeper.GetBalance(ctx, burnerAcc.GetAddress(), fooDenom) + require.Equal(burnerFooBalance.Amount, math.NewInt(10)) + burnerBarBalance := suite.bankKeeper.GetBalance(ctx, burnerAcc.GetAddress(), barDenom) + require.Equal(burnerBarBalance.Amount, math.NewInt(10)) +} + +func (suite *KeeperTestSuite) TestSendCoins_Module_To_Account() { + ctx := suite.ctx + require := suite.Require() + balances := sdk.NewCoins(newFooCoin(100), newBarCoin(50)) + + require.NoError(suite.bankKeeper.MintCoins(ctx, mintAcc.GetAddress(), balances)) + + // Try send from burner module + err := suite.bankKeeper.SendCoins(ctx, burnerAcc.GetAddress(), accAddrs[4], balances) + require.Error(err) + + // Send from mint module + err = suite.bankKeeper.SendCoins(ctx, mintAcc.GetAddress(), accAddrs[4], balances) + require.NoError(err) + + // Check balances + acc4FooBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[4], fooDenom) + require.Equal(acc4FooBalance.Amount, math.NewInt(100)) + acc4BarBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[4], barDenom) + require.Equal(acc4BarBalance.Amount, math.NewInt(50)) + mintFooBalance := suite.bankKeeper.GetBalance(ctx, mintAcc.GetAddress(), fooDenom) + require.Equal(mintFooBalance.Amount, math.NewInt(0)) + mintBarBalance := suite.bankKeeper.GetBalance(ctx, mintAcc.GetAddress(), barDenom) + require.Equal(mintBarBalance.Amount, math.NewInt(0)) +} + +func (suite *KeeperTestSuite) TestSendCoins_Module_To_Module() { + ctx := suite.ctx + require := suite.Require() + balances := sdk.NewCoins(newFooCoin(100), newBarCoin(50)) + + require.NoError(suite.bankKeeper.MintCoins(ctx, mintAcc.GetAddress(), balances)) + + // Try send from burner module + err := suite.bankKeeper.SendCoins(ctx, burnerAcc.GetAddress(), mintAcc.GetAddress(), sdk.NewCoins(newFooCoin(100), newBarCoin(50))) + require.Error(err) + + // Send from mint module to burn module + err = suite.bankKeeper.SendCoins(ctx, mintAcc.GetAddress(), burnerAcc.GetAddress(), sdk.NewCoins(newFooCoin(100), newBarCoin(50))) + require.NoError(err) + + // Check balances + burnerFooBalance := suite.bankKeeper.GetBalance(ctx, burnerAcc.GetAddress(), fooDenom) + require.Equal(burnerFooBalance.Amount, math.NewInt(100)) + burnerBarBalance := suite.bankKeeper.GetBalance(ctx, burnerAcc.GetAddress(), barDenom) + require.Equal(burnerBarBalance.Amount, math.NewInt(50)) + mintFooBalance := suite.bankKeeper.GetBalance(ctx, mintAcc.GetAddress(), fooDenom) + require.Equal(mintFooBalance.Amount, math.NewInt(0)) + mintBarBalance := suite.bankKeeper.GetBalance(ctx, mintAcc.GetAddress(), barDenom) + require.Equal(mintBarBalance.Amount, math.NewInt(0)) +} diff --git a/x/bank/v2/testutil/expected_keepers_mocks.go b/x/bank/v2/testutil/expected_keepers_mocks.go new file mode 100644 index 000000000000..aeddace241f3 --- /dev/null +++ b/x/bank/v2/testutil/expected_keepers_mocks.go @@ -0,0 +1,218 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/bank/types/expected_keepers.go + +// Package testutil is a generated GoMock package. +package testutil + +import ( + context "context" + reflect "reflect" + + address "cosmossdk.io/core/address" + types "github.com/cosmos/cosmos-sdk/types" + types0 "github.com/cosmos/cosmos-sdk/x/auth/types" + gomock "github.com/golang/mock/gomock" +) + +// MockAccountKeeper is a mock of AccountKeeper interface. +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +// NewMockAccountKeeper creates a new mock instance. +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +// AddressCodec mocks base method. +func (m *MockAccountKeeper) AddressCodec() address.Codec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddressCodec") + ret0, _ := ret[0].(address.Codec) + return ret0 +} + +// AddressCodec indicates an expected call of AddressCodec. +func (mr *MockAccountKeeperMockRecorder) AddressCodec() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddressCodec", reflect.TypeOf((*MockAccountKeeper)(nil).AddressCodec)) +} + +// GetAccount mocks base method. +func (m *MockAccountKeeper) GetAccount(ctx context.Context, addr types.AccAddress) types.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccount", ctx, addr) + ret0, _ := ret[0].(types.AccountI) + return ret0 +} + +// GetAccount indicates an expected call of GetAccount. +func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) +} + +// GetModuleAccount mocks base method. +func (m *MockAccountKeeper) GetModuleAccount(ctx context.Context, moduleName string) types.ModuleAccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetModuleAccount", ctx, moduleName) + ret0, _ := ret[0].(types.ModuleAccountI) + return ret0 +} + +// GetModuleAccount indicates an expected call of GetModuleAccount. +func (mr *MockAccountKeeperMockRecorder) GetModuleAccount(ctx, moduleName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModuleAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetModuleAccount), ctx, moduleName) +} + +// GetModuleAccountAndPermissions mocks base method. +func (m *MockAccountKeeper) GetModuleAccountAndPermissions(ctx context.Context, moduleName string) (types.ModuleAccountI, []string) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetModuleAccountAndPermissions", ctx, moduleName) + ret0, _ := ret[0].(types.ModuleAccountI) + ret1, _ := ret[1].([]string) + return ret0, ret1 +} + +// GetModuleAccountAndPermissions indicates an expected call of GetModuleAccountAndPermissions. +func (mr *MockAccountKeeperMockRecorder) GetModuleAccountAndPermissions(ctx, moduleName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModuleAccountAndPermissions", reflect.TypeOf((*MockAccountKeeper)(nil).GetModuleAccountAndPermissions), ctx, moduleName) +} + +// GetModuleAddress mocks base method. +func (m *MockAccountKeeper) GetModuleAddress(moduleName string) types.AccAddress { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetModuleAddress", moduleName) + ret0, _ := ret[0].(types.AccAddress) + return ret0 +} + +// GetModuleAddress indicates an expected call of GetModuleAddress. +func (mr *MockAccountKeeperMockRecorder) GetModuleAddress(moduleName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModuleAddress", reflect.TypeOf((*MockAccountKeeper)(nil).GetModuleAddress), moduleName) +} + +// GetModuleAddressAndPermissions mocks base method. +func (m *MockAccountKeeper) GetModuleAddressAndPermissions(moduleName string) (types.AccAddress, []string) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetModuleAddressAndPermissions", moduleName) + ret0, _ := ret[0].(types.AccAddress) + ret1, _ := ret[1].([]string) + return ret0, ret1 +} + +// GetModuleAddressAndPermissions indicates an expected call of GetModuleAddressAndPermissions. +func (mr *MockAccountKeeperMockRecorder) GetModuleAddressAndPermissions(moduleName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModuleAddressAndPermissions", reflect.TypeOf((*MockAccountKeeper)(nil).GetModuleAddressAndPermissions), moduleName) +} + +// GetModulePermissions mocks base method. +func (m *MockAccountKeeper) GetModulePermissions() map[string]types0.PermissionsForAddress { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetModulePermissions") + ret0, _ := ret[0].(map[string]types0.PermissionsForAddress) + return ret0 +} + +// GetModulePermissions indicates an expected call of GetModulePermissions. +func (mr *MockAccountKeeperMockRecorder) GetModulePermissions() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModulePermissions", reflect.TypeOf((*MockAccountKeeper)(nil).GetModulePermissions)) +} + +// HasAccount mocks base method. +func (m *MockAccountKeeper) HasAccount(ctx context.Context, addr types.AccAddress) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasAccount", ctx, addr) + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasAccount indicates an expected call of HasAccount. +func (mr *MockAccountKeeperMockRecorder) HasAccount(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasAccount", reflect.TypeOf((*MockAccountKeeper)(nil).HasAccount), ctx, addr) +} + +// NewAccount mocks base method. +func (m *MockAccountKeeper) NewAccount(arg0 context.Context, arg1 types.AccountI) types.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewAccount", arg0, arg1) + ret0, _ := ret[0].(types.AccountI) + return ret0 +} + +// NewAccount indicates an expected call of NewAccount. +func (mr *MockAccountKeeperMockRecorder) NewAccount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAccount", reflect.TypeOf((*MockAccountKeeper)(nil).NewAccount), arg0, arg1) +} + +// NewAccountWithAddress mocks base method. +func (m *MockAccountKeeper) NewAccountWithAddress(ctx context.Context, addr types.AccAddress) types.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewAccountWithAddress", ctx, addr) + ret0, _ := ret[0].(types.AccountI) + return ret0 +} + +// NewAccountWithAddress indicates an expected call of NewAccountWithAddress. +func (mr *MockAccountKeeperMockRecorder) NewAccountWithAddress(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAccountWithAddress", reflect.TypeOf((*MockAccountKeeper)(nil).NewAccountWithAddress), ctx, addr) +} + +// SetAccount mocks base method. +func (m *MockAccountKeeper) SetAccount(ctx context.Context, acc types.AccountI) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetAccount", ctx, acc) +} + +// SetAccount indicates an expected call of SetAccount. +func (mr *MockAccountKeeperMockRecorder) SetAccount(ctx, acc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).SetAccount), ctx, acc) +} + +// SetModuleAccount mocks base method. +func (m *MockAccountKeeper) SetModuleAccount(ctx context.Context, macc types.ModuleAccountI) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetModuleAccount", ctx, macc) +} + +// SetModuleAccount indicates an expected call of SetModuleAccount. +func (mr *MockAccountKeeperMockRecorder) SetModuleAccount(ctx, macc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetModuleAccount", reflect.TypeOf((*MockAccountKeeper)(nil).SetModuleAccount), ctx, macc) +} + +// ValidatePermissions mocks base method. +func (m *MockAccountKeeper) ValidatePermissions(macc types.ModuleAccountI) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidatePermissions", macc) + ret0, _ := ret[0].(error) + return ret0 +} + +// ValidatePermissions indicates an expected call of ValidatePermissions. +func (mr *MockAccountKeeperMockRecorder) ValidatePermissions(macc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatePermissions", reflect.TypeOf((*MockAccountKeeper)(nil).ValidatePermissions), macc) +} diff --git a/x/bank/v2/testutil/helpers.go b/x/bank/v2/testutil/helpers.go new file mode 100644 index 000000000000..afb2d82bcea6 --- /dev/null +++ b/x/bank/v2/testutil/helpers.go @@ -0,0 +1,16 @@ +package testutil + +import ( + "context" + + bankkeeper "cosmossdk.io/x/bank/v2/keeper" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// FundAccount is a utility function that funds an account by minting and +// sending the coins to the address. This should be used for testing purposes +// only! +func FundAccount(ctx context.Context, bankKeeper bankkeeper.Keeper, addr []byte, amounts sdk.Coins) error { + return bankKeeper.MintCoins(ctx, addr, amounts) +} diff --git a/x/bank/v2/types/events.go b/x/bank/v2/types/events.go new file mode 100644 index 000000000000..ef63dac90a31 --- /dev/null +++ b/x/bank/v2/types/events.go @@ -0,0 +1,24 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// bank module event types +const ( + EventTypeTransfer = "transfer" + + AttributeKeyRecipient = "recipient" + AttributeKeySender = sdk.AttributeKeySender + + // supply and balance tracking events name and attributes + EventTypeCoinSpent = "coin_spent" + EventTypeCoinReceived = "coin_received" + EventTypeCoinMint = "coinbase" // NOTE(fdymylja): using mint clashes with mint module event + EventTypeCoinBurn = "burn" + + AttributeKeySpender = "spender" + AttributeKeyReceiver = "receiver" + AttributeKeyMinter = "minter" + AttributeKeyBurner = "burner" +) diff --git a/x/bank/v2/types/expected_keepers.go b/x/bank/v2/types/expected_keepers.go new file mode 100644 index 000000000000..ab1254f4c2be --- /dev/null +++ b/x/bank/v2/types/expected_keepers.go @@ -0,0 +1 @@ +package types diff --git a/x/bank/v2/types/keys.go b/x/bank/v2/types/keys.go index c4c6dfbc67af..79eff8ae1c3e 100644 --- a/x/bank/v2/types/keys.go +++ b/x/bank/v2/types/keys.go @@ -1,14 +1,34 @@ package types -import "cosmossdk.io/collections" +import ( + "cosmossdk.io/collections" +) const ( // ModuleName is the name of the module ModuleName = "bankv2" + // StoreKey defines the primary module store key + StoreKey = ModuleName + // GovModuleName duplicates the gov module's name to avoid a dependency with x/gov. GovModuleName = "gov" + + // MintModuleName duplicates the mint module's name to avoid a cyclic dependency with x/mint. + // It should be synced with the mint module's name if it is ever changed. + // See: https://github.com/cosmos/cosmos-sdk/blob/0e34478eb7420b69869ed50f129fc274a97a9b06/x/mint/types/keys.go#L13 + MintModuleName = "mint" ) -// ParamsKey is the prefix for x/bank/v2 parameters -var ParamsKey = collections.NewPrefix(2) +var ( + // ParamsKey is the prefix for x/bank/v2 parameters + ParamsKey = collections.NewPrefix(2) + + // BalancesPrefix is the prefix for the account balances store. We use a byte + // (instead of `[]byte("balances")` to save some disk space). + BalancesPrefix = collections.NewPrefix(3) + + DenomAddressPrefix = collections.NewPrefix(4) + + SupplyKey = collections.NewPrefix(5) +)