[bank]: add balance tracking events (#8656)

* change(bank): add utxo events and simplify logic

* add(bank): balance and supply tracking test

* chore(bank): fix balance tracking test comment

* fix(grpc): service test

* fix(bank): sub unlocked coins to use less gas

* fix(auth): cli test gas

* fix(rest): grpc gas test

* fix(staking/cli): increase delegation required gas

* add: burn events, fix tests

* fix(auth/tx): grpc test

* add(bank): coin events in delegate

* fix(bank): add amt check in delegate coins back

* change(bank): add coin spent and coin recv events in burn and mint

* change(bank): revert sub coin function

* change(auth): revert cli test

* change(auth): revert service test

* chore(auth): fix events comment in service_test.go

* chore: update CHANGELOG.md

* remove(bank): balanceError func

* chore(bank): address lint warnings

* chore(bank): update events spec

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Frojdi Dymylja 2021-02-25 19:15:02 +01:00 committed by GitHub
parent 19e79e00d6
commit ef9968debd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 375 additions and 49 deletions

View File

@ -58,6 +58,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/ibc) [\#8405](https://github.com/cosmos/cosmos-sdk/pull/8405) Refactor IBC client update governance proposals to use a substitute client to update a frozen or expired client.
* (x/evidence) [\#8502](https://github.com/cosmos/cosmos-sdk/pull/8502) `HandleEquivocationEvidence` persists the evidence to state.
* (x/gov) [\#7733](https://github.com/cosmos/cosmos-sdk/pull/7733) ADR 037 Implementation: Governance Split Votes
* (x/bank) [\#8656](https://github.com/cosmos/cosmos-sdk/pull/8656) balance and supply are now correctly tracked via `coin_spent`, `coin_received`, `coinbase` and `burn` events.
### Improvements

View File

@ -1102,7 +1102,7 @@ func (s *IntegrationTestSuite) TestSignWithMultiSigners_AminoJSON() {
banktypes.NewMsgSend(val1.Address, addr1, sdk.NewCoins(val1Coin)),
)
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))))
txBuilder.SetGasLimit(testdata.NewTestGasLimit())
txBuilder.SetGasLimit(testdata.NewTestGasLimit()) // min required is 101892
require.Equal([]sdk.AccAddress{val0.Address, val1.Address}, txBuilder.GetTx().GetSigners())
// Write the unsigned tx into a file.
@ -1126,7 +1126,12 @@ func (s *IntegrationTestSuite) TestSignWithMultiSigners_AminoJSON() {
signedTxFile := testutil.WriteToNewTempFile(s.T(), signedTx.String())
// Now let's try to send this tx.
res, err := authtest.TxBroadcastExec(val0.ClientCtx, signedTxFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock))
res, err := authtest.TxBroadcastExec(
val0.ClientCtx,
signedTxFile.Name(),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
)
require.NoError(err)
var txRes sdk.TxResponse
require.NoError(val0.ClientCtx.JSONMarshaler.UnmarshalJSON(res.Bytes(), &txRes))

View File

@ -5,13 +5,14 @@ import (
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/testutil/network"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
query "github.com/cosmos/cosmos-sdk/types/query"
@ -106,7 +107,7 @@ func (s IntegrationTestSuite) TestSimulateTx_GRPC() {
} else {
s.Require().NoError(err)
// Check the result and gas used are correct.
s.Require().Equal(len(res.GetResult().GetEvents()), 4) // 1 transfer, 3 messages.
s.Require().Equal(len(res.GetResult().GetEvents()), 6) // 1 coin recv 1 coin spent, 1 transfer, 3 messages.
s.Require().True(res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
}
})
@ -143,7 +144,7 @@ func (s IntegrationTestSuite) TestSimulateTx_GRPCGateway() {
err = val.ClientCtx.JSONMarshaler.UnmarshalJSON(res, &result)
s.Require().NoError(err)
// Check the result and gas used are correct.
s.Require().Equal(len(result.GetResult().GetEvents()), 4) // 1 transfer, 3 messages.
s.Require().Equal(len(result.GetResult().GetEvents()), 6) // 1 coin recv, 1 coin spent,1 transfer, 3 messages.
s.Require().True(result.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
}
})
@ -412,7 +413,7 @@ func (s IntegrationTestSuite) TestBroadcastTx_GRPCGateway() {
var result tx.BroadcastTxResponse
err = val.ClientCtx.JSONMarshaler.UnmarshalJSON(res, &result)
s.Require().NoError(err)
s.Require().Equal(uint32(0), result.TxResponse.Code)
s.Require().Equal(uint32(0), result.TxResponse.Code, "rawlog", result.TxResponse.RawLog)
}
})
}

View File

@ -25,8 +25,6 @@ func (suite *IntegrationTestSuite) TestExportGenesis() {
Require().
NoError(app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, accAddr, expectedBalances[i].Coins))
}
// add mint module balance as nil
expectedBalances = append(expectedBalances, types.Balance{Address: "cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q", Coins: nil})
app.BankKeeper.SetParams(ctx, types.DefaultParams())
exportGenesis := app.BankKeeper.ExportGenesis(ctx)
@ -34,6 +32,8 @@ func (suite *IntegrationTestSuite) TestExportGenesis() {
suite.Require().Len(exportGenesis.Params.SendEnabled, 0)
suite.Require().Equal(types.DefaultParams().DefaultSendEnabled, exportGenesis.Params.DefaultSendEnabled)
suite.Require().Equal(totalSupply.Total, exportGenesis.Supply)
// add mint module balance as nil
expectedBalances = append(expectedBalances, types.Balance{Address: "cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q", Coins: nil})
suite.Require().Equal(expectedBalances, exportGenesis.Balances)
suite.Require().Equal(expectedMetadata, exportGenesis.DenomMetadata)
}

View File

@ -1,8 +1,6 @@
package keeper
import (
"time"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -107,9 +105,13 @@ func (k BaseKeeper) DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr
}
}
if err := k.trackDelegation(ctx, delegatorAddr, ctx.BlockHeader().Time, balances, amt); err != nil {
if err := k.trackDelegation(ctx, delegatorAddr, balances, amt); err != nil {
return sdkerrors.Wrap(err, "failed to track delegation")
}
// emit coin spent event
ctx.EventManager().EmitEvent(
types.NewCoinSpentEvent(delegatorAddr, amt),
)
err := k.addCoins(ctx, moduleAccAddr, amt)
if err != nil {
@ -134,7 +136,7 @@ func (k BaseKeeper) UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAdd
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
}
err := k.subtractCoins(ctx, moduleAccAddr, amt)
err := k.subUnlockedCoins(ctx, moduleAccAddr, amt)
if err != nil {
return err
}
@ -345,6 +347,11 @@ func (k BaseKeeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins)
logger := k.Logger(ctx)
logger.Info("minted coins from module account", "amount", amt.String(), "from", moduleName)
// emit mint event
ctx.EventManager().EmitEvent(
types.NewCoinMintEvent(acc.GetAddress(), amt),
)
return nil
}
@ -360,7 +367,7 @@ func (k BaseKeeper) BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins)
panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to burn tokens", moduleName))
}
err := k.subtractCoins(ctx, acc.GetAddress(), amt)
err := k.subUnlockedCoins(ctx, acc.GetAddress(), amt)
if err != nil {
return err
}
@ -373,10 +380,15 @@ func (k BaseKeeper) BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins)
logger := k.Logger(ctx)
logger.Info("burned tokens from module account", "amount", amt.String(), "from", moduleName)
// emit burn event
ctx.EventManager().EmitEvent(
types.NewCoinBurnEvent(acc.GetAddress(), amt),
)
return nil
}
func (k BaseKeeper) trackDelegation(ctx sdk.Context, addr sdk.AccAddress, blockTime time.Time, balance, amt sdk.Coins) error {
func (k BaseKeeper) trackDelegation(ctx sdk.Context, addr sdk.AccAddress, balance, amt sdk.Coins) error {
acc := k.ak.GetAccount(ctx, addr)
if acc == nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
@ -385,7 +397,7 @@ func (k BaseKeeper) trackDelegation(ctx sdk.Context, addr sdk.AccAddress, blockT
vacc, ok := acc.(vestexported.VestingAccount)
if ok {
// TODO: return error on account.TrackDelegation
vacc.TrackDelegation(blockTime, balance, amt)
vacc.TrackDelegation(ctx.BlockHeader().Time, balance, amt)
}
return nil

View File

@ -153,17 +153,17 @@ func (suite *IntegrationTestSuite) TestSupply_SendCoins() {
suite.Require().NoError(
keeper.SendCoinsFromModuleToModule(ctx, holderAcc.GetName(), authtypes.Burner, initCoins),
)
suite.Require().Equal(sdk.Coins(nil), getCoinsByName(ctx, keeper, authKeeper, holderAcc.GetName()))
suite.Require().Equal(sdk.NewCoins().String(), getCoinsByName(ctx, keeper, authKeeper, holderAcc.GetName()).String())
suite.Require().Equal(initCoins, getCoinsByName(ctx, keeper, authKeeper, authtypes.Burner))
suite.Require().NoError(
keeper.SendCoinsFromModuleToAccount(ctx, authtypes.Burner, baseAcc.GetAddress(), initCoins),
)
suite.Require().Equal(sdk.Coins(nil), getCoinsByName(ctx, keeper, authKeeper, authtypes.Burner))
suite.Require().Equal(sdk.NewCoins().String(), getCoinsByName(ctx, keeper, authKeeper, authtypes.Burner).String())
suite.Require().Equal(initCoins, keeper.GetAllBalances(ctx, baseAcc.GetAddress()))
suite.Require().NoError(keeper.SendCoinsFromAccountToModule(ctx, baseAcc.GetAddress(), authtypes.Burner, initCoins))
suite.Require().Equal(sdk.Coins(nil), keeper.GetAllBalances(ctx, baseAcc.GetAddress()))
suite.Require().Equal(sdk.NewCoins().String(), keeper.GetAllBalances(ctx, baseAcc.GetAddress()).String())
suite.Require().Equal(initCoins, getCoinsByName(ctx, keeper, authKeeper, authtypes.Burner))
}
@ -266,7 +266,7 @@ func (suite *IntegrationTestSuite) TestSupply_BurnCoins() {
err = keeper.BurnCoins(ctx, authtypes.Burner, initCoins)
suite.Require().NoError(err)
suite.Require().Equal(sdk.Coins(nil), getCoinsByName(ctx, keeper, authKeeper, authtypes.Burner))
suite.Require().Equal(sdk.NewCoins().String(), getCoinsByName(ctx, keeper, authKeeper, authtypes.Burner).String())
suite.Require().Equal(supplyAfterInflation.GetTotal().Sub(initCoins), keeper.GetSupply(ctx).GetTotal())
// test same functionality on module account with multiple permissions
@ -280,7 +280,7 @@ func (suite *IntegrationTestSuite) TestSupply_BurnCoins() {
err = keeper.BurnCoins(ctx, multiPermAcc.GetName(), initCoins)
suite.Require().NoError(err)
suite.Require().Equal(sdk.Coins(nil), getCoinsByName(ctx, keeper, authKeeper, multiPermAcc.GetName()))
suite.Require().Equal(sdk.NewCoins().String(), getCoinsByName(ctx, keeper, authKeeper, multiPermAcc.GetName()).String())
suite.Require().Equal(supplyAfterInflation.GetTotal().Sub(initCoins), keeper.GetSupply(ctx).GetTotal())
}
@ -537,6 +537,7 @@ func (suite *IntegrationTestSuite) TestMsgSendEvents() {
event1.Attributes,
abci.EventAttribute{Key: []byte(sdk.AttributeKeyAmount), Value: []byte(newCoins.String())},
)
event2 := sdk.Event{
Type: sdk.EventTypeMessage,
Attributes: []abci.EventAttribute{},
@ -556,9 +557,9 @@ func (suite *IntegrationTestSuite) TestMsgSendEvents() {
// events are shifted due to the funding account events
events = ctx.EventManager().ABCIEvents()
suite.Require().Equal(6, len(events))
suite.Require().Equal(abci.Event(event1), events[4])
suite.Require().Equal(abci.Event(event2), events[5])
suite.Require().Equal(12, len(events))
suite.Require().Equal(abci.Event(event1), events[8])
suite.Require().Equal(abci.Event(event2), events[9])
}
func (suite *IntegrationTestSuite) TestMsgMultiSendEvents() {
@ -597,7 +598,7 @@ func (suite *IntegrationTestSuite) TestMsgMultiSendEvents() {
suite.Require().Error(app.BankKeeper.InputOutputCoins(ctx, inputs, outputs))
events = ctx.EventManager().ABCIEvents()
suite.Require().Equal(3, len(events)) // 3 events because minting event is there
suite.Require().Equal(8, len(events)) // 7 events because account funding causes extra minting + coin_spent + coin_recv events
event1 := sdk.Event{
Type: sdk.EventTypeMessage,
@ -607,7 +608,7 @@ func (suite *IntegrationTestSuite) TestMsgMultiSendEvents() {
event1.Attributes,
abci.EventAttribute{Key: []byte(types.AttributeKeySender), Value: []byte(addr.String())},
)
suite.Require().Equal(abci.Event(event1), events[2]) // it's the third event since we have the minting event before
suite.Require().Equal(abci.Event(event1), events[7])
// Set addr's coins and addr2's coins
suite.Require().NoError(simapp.FundAccount(app, ctx, addr, sdk.NewCoins(sdk.NewInt64Coin(fooDenom, 50))))
@ -619,7 +620,7 @@ func (suite *IntegrationTestSuite) TestMsgMultiSendEvents() {
suite.Require().NoError(app.BankKeeper.InputOutputCoins(ctx, inputs, outputs))
events = ctx.EventManager().ABCIEvents()
suite.Require().Equal(11, len(events))
suite.Require().Equal(28, len(events)) // 25 due to account funding + coin_spent + coin_recv events
event2 := sdk.Event{
Type: sdk.EventTypeMessage,
@ -652,12 +653,11 @@ func (suite *IntegrationTestSuite) TestMsgMultiSendEvents() {
event4.Attributes,
abci.EventAttribute{Key: []byte(sdk.AttributeKeyAmount), Value: []byte(newCoins2.String())},
)
// events are shifted due to the funding account events
suite.Require().Equal(abci.Event(event1), events[7])
suite.Require().Equal(abci.Event(event2), events[8])
suite.Require().Equal(abci.Event(event3), events[9])
suite.Require().Equal(abci.Event(event4), events[10])
suite.Require().Equal(abci.Event(event1), events[21])
suite.Require().Equal(abci.Event(event2), events[23])
suite.Require().Equal(abci.Event(event3), events[25])
suite.Require().Equal(abci.Event(event4), events[27])
}
func (suite *IntegrationTestSuite) TestSpendableCoins() {
@ -1002,6 +1002,103 @@ func (suite *IntegrationTestSuite) TestIterateAllDenomMetaData() {
}
}
func (suite *IntegrationTestSuite) TestBalanceTrackingEvents() {
// replace account keeper and bank keeper otherwise the account keeper won't be aware of the
// existence of the new module account because GetModuleAccount checks for the existence via
// permissions map and not via state... weird
maccPerms := simapp.GetMaccPerms()
maccPerms[multiPerm] = []string{authtypes.Burner, authtypes.Minter, authtypes.Staking}
suite.app.AccountKeeper = authkeeper.NewAccountKeeper(
suite.app.AppCodec(), suite.app.GetKey(authtypes.StoreKey), suite.app.GetSubspace(authtypes.ModuleName),
authtypes.ProtoBaseAccount, maccPerms,
)
suite.app.BankKeeper = keeper.NewBaseKeeper(suite.app.AppCodec(), suite.app.GetKey(types.StoreKey),
suite.app.AccountKeeper, suite.app.GetSubspace(types.ModuleName), nil)
// set account with multiple permissions
suite.app.AccountKeeper.SetModuleAccount(suite.ctx, multiPermAcc)
// mint coins
suite.Require().NoError(
suite.app.BankKeeper.MintCoins(
suite.ctx,
multiPermAcc.Name,
sdk.NewCoins(sdk.NewCoin("utxo", sdk.NewInt(100000)))),
)
// send coins to address
addr1 := sdk.AccAddress("addr1_______________")
suite.Require().NoError(
suite.app.BankKeeper.SendCoinsFromModuleToAccount(
suite.ctx,
multiPermAcc.Name,
addr1,
sdk.NewCoins(sdk.NewCoin("utxo", sdk.NewInt(50000))),
),
)
// burn coins from module account
suite.Require().NoError(
suite.app.BankKeeper.BurnCoins(
suite.ctx,
multiPermAcc.Name,
sdk.NewCoins(sdk.NewInt64Coin("utxo", 1000)),
),
)
// process balances and supply from events
supply := sdk.NewCoins()
balances := make(map[string]sdk.Coins)
for _, e := range suite.ctx.EventManager().ABCIEvents() {
switch e.Type {
case types.EventTypeCoinBurn:
burnedCoins, err := sdk.ParseCoinsNormalized((string)(e.Attributes[1].Value))
suite.Require().NoError(err)
supply = supply.Sub(burnedCoins)
case types.EventTypeCoinMint:
mintedCoins, err := sdk.ParseCoinsNormalized((string)(e.Attributes[1].Value))
suite.Require().NoError(err)
supply = supply.Add(mintedCoins...)
case types.EventTypeCoinSpent:
coinsSpent, err := sdk.ParseCoinsNormalized((string)(e.Attributes[1].Value))
suite.Require().NoError(err)
spender, err := sdk.AccAddressFromBech32((string)(e.Attributes[0].Value))
suite.Require().NoError(err)
balances[spender.String()] = balances[spender.String()].Sub(coinsSpent)
case types.EventTypeCoinReceived:
coinsRecv, err := sdk.ParseCoinsNormalized((string)(e.Attributes[1].Value))
suite.Require().NoError(err)
receiver, err := sdk.AccAddressFromBech32((string)(e.Attributes[0].Value))
suite.Require().NoError(err)
balances[receiver.String()] = balances[receiver.String()].Add(coinsRecv...)
}
}
// check balance and supply tracking
savedSupply := suite.app.BankKeeper.GetSupply(suite.ctx)
utxoSupply := savedSupply.GetTotal().AmountOf("utxo")
suite.Require().Equal(utxoSupply, supply.AmountOf("utxo"))
// iterate accounts and check balances
suite.app.BankKeeper.IterateAllBalances(suite.ctx, func(address sdk.AccAddress, coin sdk.Coin) (stop bool) {
// if it's not utxo coin then skip
if coin.Denom != "utxo" {
return false
}
balance, exists := balances[address.String()]
suite.Require().True(exists)
expectedUtxo := sdk.NewCoin("utxo", balance.AmountOf(coin.Denom))
suite.Require().Equal(expectedUtxo.String(), coin.String())
return false
})
}
func (suite *IntegrationTestSuite) getTestMetadata() []types.Metadata {
return []types.Metadata{{
Name: "Cosmos Hub Atom",

View File

@ -83,7 +83,7 @@ func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input,
return err
}
err = k.subtractCoins(ctx, inAddress, in.Coins)
err = k.subUnlockedCoins(ctx, inAddress, in.Coins)
if err != nil {
return err
}
@ -144,7 +144,7 @@ func (k BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAd
),
})
err := k.subtractCoins(ctx, fromAddr, amt)
err := k.subUnlockedCoins(ctx, fromAddr, amt)
if err != nil {
return err
}
@ -167,9 +167,10 @@ func (k BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAd
return nil
}
// subtractCoins removes amt coins the account by the given address. An error is
// subUnlockedCoins removes the unlocked amt coins of the given account. An error is
// returned if the resulting balance is negative or the initial amount is invalid.
func (k BaseSendKeeper) subtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error {
// A coin_spent event is emitted after.
func (k BaseSendKeeper) subUnlockedCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error {
if !amt.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
}
@ -194,12 +195,15 @@ func (k BaseSendKeeper) subtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt
}
}
// emit coin spent event
ctx.EventManager().EmitEvent(
types.NewCoinSpentEvent(addr, amt),
)
return nil
}
// addCoins adds amt to the account balance given by the provided address. An
// error is returned if the initial amount is invalid or if any resulting new
// balance is negative.
// addCoins increase the addr balance by the given amt. Fails if the provided amt is invalid.
// It emits a coin received event.
func (k BaseSendKeeper) addCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error {
if !amt.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
@ -215,11 +219,16 @@ func (k BaseSendKeeper) addCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.C
}
}
// emit coin received event
ctx.EventManager().EmitEvent(
types.NewCoinReceivedEvent(addr, amt),
)
return nil
}
// ClearBalances removes all balances for a given account by address.
func (k BaseSendKeeper) ClearBalances(ctx sdk.Context, addr sdk.AccAddress) {
// clearBalances removes all balances for a given account by address.
func (k BaseSendKeeper) clearBalances(ctx sdk.Context, addr sdk.AccAddress) {
keys := [][]byte{}
k.IterateAccountBalances(ctx, addr, func(balance sdk.Coin) bool {
keys = append(keys, []byte(balance.Denom))
@ -237,7 +246,7 @@ func (k BaseSendKeeper) ClearBalances(ctx sdk.Context, addr sdk.AccAddress) {
// clear out all balances prior to setting the new coins as to set existing balances
// to zero if they don't exist in amt. An error is returned upon failure.
func (k BaseSendKeeper) setBalances(ctx sdk.Context, addr sdk.AccAddress, balances sdk.Coins) error {
k.ClearBalances(ctx, addr)
k.clearBalances(ctx, addr)
for _, balance := range balances {
err := k.setBalance(ctx, addr, balance)

View File

@ -171,15 +171,23 @@ func (k BaseViewKeeper) LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Co
// by address. If the account has no spendable coins, an empty Coins slice is
// returned.
func (k BaseViewKeeper) SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
balances := k.GetAllBalances(ctx, addr)
spendable, _ := k.spendableCoins(ctx, addr)
return spendable
}
// spendableCoins returns the coins the given address can spend alongside the total amount of coins it holds.
// It exists for gas efficiency, in order to avoid to have to get balance multiple times.
func (k BaseViewKeeper) spendableCoins(ctx sdk.Context, addr sdk.AccAddress) (spendable, total sdk.Coins) {
total = k.GetAllBalances(ctx, addr)
locked := k.LockedCoins(ctx, addr)
spendable, hasNeg := balances.SafeSub(locked)
spendable, hasNeg := total.SafeSub(locked)
if hasNeg {
return sdk.NewCoins()
spendable = sdk.NewCoins()
return
}
return spendable
return
}
// ValidateBalance validates all balances for a given account address returning

View File

@ -27,3 +27,123 @@ The bank module emits the following events:
| message | module | bank |
| message | action | multisend |
| message | sender | {senderAddress} |
## Keeper events
In addition to handlers events, the bank keeper will produce events when the following methods are called (or any method which ends up calling them)
### MintCoins
```json
{
"type": "coinbase",
"attributes": [
{
"key": "minter",
"value": "{{sdk.AccAddress of the module minting coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being minted}}",
"index": true
}
]
}
```
```json
{
"type": "coin_received",
"attributes": [
{
"key": "receiver",
"value": "{{sdk.AccAddress of the module minting coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being received}}",
"index": true
}
]
}
```
### BurnCoins
```json
{
"type": "burn",
"attributes": [
{
"key": "burner",
"value": "{{sdk.AccAddress of the module burning coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being burned}}",
"index": true
}
]
}
```
```json
{
"type": "coin_spent",
"attributes": [
{
"key": "spender",
"value": "{{sdk.AccAddress of the module burning coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being burned}}",
"index": true
}
]
}
```
### addCoins
```json
{
"type": "coin_received",
"attributes": [
{
"key": "receiver",
"value": "{{sdk.AccAddress of the address beneficiary of the coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being received}}",
"index": true
}
]
}
```
### subUnlockedCoins/DelegateCoins
```json
{
"type": "coin_spent",
"attributes": [
{
"key": "spender",
"value": "{{sdk.AccAddress of the address which is spending coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being spent}}",
"index": true
}
]
}
```

View File

@ -1,5 +1,9 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// bank module event types
const (
EventTypeTransfer = "transfer"
@ -8,4 +12,55 @@ const (
AttributeKeySender = "sender"
AttributeValueCategory = ModuleName
// 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"
)
// NewCoinSpentEvent constructs a new coin spent sdk.Event
// nolint: interfacer
func NewCoinSpentEvent(spender sdk.AccAddress, amount sdk.Coins) sdk.Event {
return sdk.NewEvent(
EventTypeCoinSpent,
sdk.NewAttribute(AttributeKeySpender, spender.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()),
)
}
// NewCoinReceivedEvent constructs a new coin received sdk.Event
// nolint: interfacer
func NewCoinReceivedEvent(receiver sdk.AccAddress, amount sdk.Coins) sdk.Event {
return sdk.NewEvent(
EventTypeCoinReceived,
sdk.NewAttribute(AttributeKeyReceiver, receiver.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()),
)
}
// NewCoinMintEvent construct a new coin minted sdk.Event
// nolint: interfacer
func NewCoinMintEvent(minter sdk.AccAddress, amount sdk.Coins) sdk.Event {
return sdk.NewEvent(
EventTypeCoinMint,
sdk.NewAttribute(AttributeKeyMinter, minter.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()),
)
}
// NewCoinBurnEvent constructs a new coin burned sdk.Event
// nolint: interfacer
func NewCoinBurnEvent(burner sdk.AccAddress, amount sdk.Coins) sdk.Event {
return sdk.NewEvent(
EventTypeCoinBurn,
sdk.NewAttribute(AttributeKeyBurner, burner.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()),
)
}

View File

@ -58,11 +58,17 @@ func (s *IntegrationTestSuite) SetupSuite() {
val2 := s.network.Validators[1]
// redelegate
_, err = stakingtestutil.MsgRedelegateExec(val.ClientCtx, val.Address, val.ValAddress, val2.ValAddress, unbond)
_, err = stakingtestutil.MsgRedelegateExec(
val.ClientCtx,
val.Address,
val.ValAddress,
val2.ValAddress,
unbond,
fmt.Sprintf("--%s=%d", flags.FlagGas, 202954), // 202954 is the required
)
s.Require().NoError(err)
_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
// unbonding
_, err = stakingtestutil.MsgUnbondExec(val.ClientCtx, val.Address, val.ValAddress, unbond)
s.Require().NoError(err)

View File

@ -6,6 +6,8 @@ import (
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/suite"
@ -46,7 +48,15 @@ func (s *IntegrationTestSuite) SetupSuite() {
val2 := s.network.Validators[1]
// redelegate
_, err = stakingtestutil.MsgRedelegateExec(val.ClientCtx, val.Address, val.ValAddress, val2.ValAddress, unbond)
_, err = stakingtestutil.MsgRedelegateExec(
val.ClientCtx,
val.Address,
val.ValAddress,
val2.ValAddress,
unbond,
fmt.Sprintf("--%s=%d", flags.FlagGas, 254000),
) // expected gas is 202987
s.Require().NoError(err)
_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
@ -330,7 +340,7 @@ func (s *IntegrationTestSuite) TestQueryDelegationGRPC() {
s.Run(tc.name, func() {
resp, err := rest.GetRequest(tc.url)
s.Require().NoError(err)
s.T().Logf("%s", resp)
err = val.ClientCtx.JSONMarshaler.UnmarshalJSON(resp, tc.respType)
if tc.error {

View File

@ -27,6 +27,7 @@ func MsgRedelegateExec(clientCtx client.Context, from, src, dst, amount fmt.Stri
amount.String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, from.String()),
}
args = append(args, extraArgs...)
args = append(args, commonArgs...)
return clitestutil.ExecTestCLICmd(clientCtx, stakingcli.NewRedelegateCmd(), args)
@ -43,5 +44,6 @@ func MsgUnbondExec(clientCtx client.Context, from fmt.Stringer, valAddress,
}
args = append(args, commonArgs...)
args = append(args, extraArgs...)
return clitestutil.ExecTestCLICmd(clientCtx, stakingcli.NewUnbondCmd(), args)
}