Skip to content

Commit 5b45deb

Browse files
authored
stargate: balance and metadata validation (#8421)
From: #8417
1 parent 7cb7a3a commit 5b45deb

9 files changed

+545
-74
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ Ref: https://keepachangelog.com/en/1.0.0/
3434

3535
# Changelog
3636

37+
## [Unreleased]
38+
39+
### Bug Fixes
40+
41+
* (x/bank) [\#8417](https://github.com/cosmos/cosmos-sdk/pull/8417) Validate balances and coin denom metadata on genesis
42+
3743
## [v0.40.1](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.40.1) - 2021-01-19
3844

3945
### Improvements

x/bank/module.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, _ client.TxEncodi
5858
return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)
5959
}
6060

61-
return types.ValidateGenesis(data)
61+
return data.Validate()
6262
}
6363

6464
// RegisterRESTRoutes registers the REST routes for the bank module.

x/bank/types/balance.go

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package types
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
fmt "fmt"
7+
"sort"
8+
9+
"github.com/cosmos/cosmos-sdk/codec"
10+
sdk "github.com/cosmos/cosmos-sdk/types"
11+
"github.com/cosmos/cosmos-sdk/x/bank/exported"
12+
)
13+
14+
var _ exported.GenesisBalance = (*Balance)(nil)
15+
16+
// GetAddress returns the account address of the Balance object.
17+
func (b Balance) GetAddress() sdk.AccAddress {
18+
addr, _ := sdk.AccAddressFromBech32(b.Address)
19+
return addr
20+
}
21+
22+
// GetCoins returns the account coins of the Balance object.
23+
func (b Balance) GetCoins() sdk.Coins {
24+
return b.Coins
25+
}
26+
27+
// Validate checks for address and coins correctness.
28+
func (b Balance) Validate() error {
29+
_, err := sdk.AccAddressFromBech32(b.Address)
30+
if err != nil {
31+
return err
32+
}
33+
34+
if len(b.Coins) == 0 {
35+
return fmt.Errorf("empty or nil coins for address %s", b.Address)
36+
}
37+
38+
seenDenoms := make(map[string]bool)
39+
40+
// NOTE: we perform a custom validation since the coins.Validate function
41+
// errors on zero balance coins
42+
for _, coin := range b.Coins {
43+
if seenDenoms[coin.Denom] {
44+
return fmt.Errorf("duplicate denomination %s", coin.Denom)
45+
}
46+
47+
if err := sdk.ValidateDenom(coin.Denom); err != nil {
48+
return err
49+
}
50+
51+
if coin.IsNegative() {
52+
return fmt.Errorf("coin %s amount is cannot be negative", coin.Denom)
53+
}
54+
55+
seenDenoms[coin.Denom] = true
56+
}
57+
58+
// sort the coins post validation
59+
b.Coins = b.Coins.Sort()
60+
61+
return nil
62+
}
63+
64+
// SanitizeGenesisBalances sorts addresses and coin sets.
65+
func SanitizeGenesisBalances(balances []Balance) []Balance {
66+
sort.Slice(balances, func(i, j int) bool {
67+
addr1, _ := sdk.AccAddressFromBech32(balances[i].Address)
68+
addr2, _ := sdk.AccAddressFromBech32(balances[j].Address)
69+
return bytes.Compare(addr1.Bytes(), addr2.Bytes()) < 0
70+
})
71+
72+
for _, balance := range balances {
73+
balance.Coins = balance.Coins.Sort()
74+
}
75+
76+
return balances
77+
}
78+
79+
// GenesisBalancesIterator implements genesis account iteration.
80+
type GenesisBalancesIterator struct{}
81+
82+
// IterateGenesisBalances iterates over all the genesis balances found in
83+
// appGenesis and invokes a callback on each genesis account. If any call
84+
// returns true, iteration stops.
85+
func (GenesisBalancesIterator) IterateGenesisBalances(
86+
cdc codec.JSONMarshaler, appState map[string]json.RawMessage, cb func(exported.GenesisBalance) (stop bool),
87+
) {
88+
for _, balance := range GetGenesisStateFromAppState(cdc, appState).Balances {
89+
if cb(balance) {
90+
break
91+
}
92+
}
93+
}

x/bank/types/balance_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package types
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
sdk "github.com/cosmos/cosmos-sdk/types"
9+
)
10+
11+
func TestBalanceValidate(t *testing.T) {
12+
13+
testCases := []struct {
14+
name string
15+
balance Balance
16+
expErr bool
17+
}{
18+
{
19+
"valid balance",
20+
Balance{
21+
Address: "cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t",
22+
Coins: sdk.Coins{sdk.NewInt64Coin("uatom", 1)},
23+
},
24+
false,
25+
},
26+
{"empty balance", Balance{}, true},
27+
{
28+
"nil balance coins",
29+
Balance{
30+
Address: "cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t",
31+
},
32+
true,
33+
},
34+
{
35+
"dup coins",
36+
Balance{
37+
Address: "cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t",
38+
Coins: sdk.Coins{
39+
sdk.NewInt64Coin("uatom", 1),
40+
sdk.NewInt64Coin("uatom", 1),
41+
},
42+
},
43+
true,
44+
},
45+
{
46+
"invalid coin denom",
47+
Balance{
48+
Address: "cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t",
49+
Coins: sdk.Coins{
50+
sdk.Coin{Denom: "", Amount: sdk.OneInt()},
51+
},
52+
},
53+
true,
54+
},
55+
{
56+
"negative coin",
57+
Balance{
58+
Address: "cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t",
59+
Coins: sdk.Coins{
60+
sdk.Coin{Denom: "uatom", Amount: sdk.NewInt(-1)},
61+
},
62+
},
63+
true,
64+
},
65+
}
66+
67+
for _, tc := range testCases {
68+
tc := tc
69+
t.Run(tc.name, func(t *testing.T) {
70+
71+
err := tc.balance.Validate()
72+
73+
if tc.expErr {
74+
require.Error(t, err)
75+
} else {
76+
require.NoError(t, err)
77+
}
78+
})
79+
}
80+
}

x/bank/types/genesis.go

+28-46
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,49 @@
11
package types
22

33
import (
4-
"bytes"
54
"encoding/json"
6-
"sort"
5+
"fmt"
76

87
"github.com/cosmos/cosmos-sdk/codec"
98
sdk "github.com/cosmos/cosmos-sdk/types"
10-
"github.com/cosmos/cosmos-sdk/x/bank/exported"
119
)
1210

13-
var _ exported.GenesisBalance = (*Balance)(nil)
11+
// Validate performs basic validation of supply genesis data returning an
12+
// error for any failed validation criteria.
13+
func (gs GenesisState) Validate() error {
14+
if err := gs.Params.Validate(); err != nil {
15+
return err
16+
}
1417

15-
// GetAddress returns the account address of the Balance object.
16-
func (b Balance) GetAddress() sdk.AccAddress {
17-
addr1, _ := sdk.AccAddressFromBech32(b.Address)
18-
return addr1
19-
}
18+
seenBalances := make(map[string]bool)
19+
seenMetadatas := make(map[string]bool)
2020

21-
// GetAddress returns the account coins of the Balance object.
22-
func (b Balance) GetCoins() sdk.Coins {
23-
return b.Coins
24-
}
21+
for _, balance := range gs.Balances {
22+
if seenBalances[balance.Address] {
23+
return fmt.Errorf("duplicate balance for address %s", balance.Address)
24+
}
2525

26-
// SanitizeGenesisAccounts sorts addresses and coin sets.
27-
func SanitizeGenesisBalances(balances []Balance) []Balance {
28-
sort.Slice(balances, func(i, j int) bool {
29-
addr1, _ := sdk.AccAddressFromBech32(balances[i].Address)
30-
addr2, _ := sdk.AccAddressFromBech32(balances[j].Address)
31-
return bytes.Compare(addr1.Bytes(), addr2.Bytes()) < 0
32-
})
26+
if err := balance.Validate(); err != nil {
27+
return err
28+
}
3329

34-
for _, balance := range balances {
35-
balance.Coins = balance.Coins.Sort()
30+
seenBalances[balance.Address] = true
3631
}
3732

38-
return balances
39-
}
33+
for _, metadata := range gs.DenomMetadata {
34+
if seenMetadatas[metadata.Base] {
35+
return fmt.Errorf("duplicate client metadata for denom %s", metadata.Base)
36+
}
4037

41-
// ValidateGenesis performs basic validation of supply genesis data returning an
42-
// error for any failed validation criteria.
43-
func ValidateGenesis(data GenesisState) error {
44-
if err := data.Params.Validate(); err != nil {
45-
return err
38+
if err := metadata.Validate(); err != nil {
39+
return err
40+
}
41+
42+
seenMetadatas[metadata.Base] = true
4643
}
4744

48-
return NewSupply(data.Supply).ValidateBasic()
45+
// NOTE: this errors if supply for any given coin is zero
46+
return NewSupply(gs.Supply).ValidateBasic()
4947
}
5048

5149
// NewGenesisState creates a new genesis state.
@@ -74,19 +72,3 @@ func GetGenesisStateFromAppState(cdc codec.JSONMarshaler, appState map[string]js
7472

7573
return &genesisState
7674
}
77-
78-
// GenesisAccountIterator implements genesis account iteration.
79-
type GenesisBalancesIterator struct{}
80-
81-
// IterateGenesisAccounts iterates over all the genesis accounts found in
82-
// appGenesis and invokes a callback on each genesis account. If any call
83-
// returns true, iteration stops.
84-
func (GenesisBalancesIterator) IterateGenesisBalances(
85-
cdc codec.JSONMarshaler, appState map[string]json.RawMessage, cb func(exported.GenesisBalance) (stop bool),
86-
) {
87-
for _, balance := range GetGenesisStateFromAppState(cdc, appState).Balances {
88-
if cb(balance) {
89-
break
90-
}
91-
}
92-
}

0 commit comments

Comments
 (0)