Skip to content

Commit 8c989fd

Browse files
fedekunzealexanderbez
authored andcommitted
Merge PR #4797: blacklist module accounts from receiving txs
1 parent 83b1a9f commit 8c989fd

File tree

20 files changed

+313
-143
lines changed

20 files changed

+313
-143
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#4795 restrict module accounts from receiving transactions.
2+
Allowing this would cause an invariant on the module account coins.

docs/spec/supply/01_concepts.md

+10-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
## Supply
44

5-
The `supply` module:
6-
- passively tracks the total supply of coins within a chain,
7-
- provides a pattern for modules to hold/interact with `Coins`, and
8-
- introduces the invariant check to verify a chain's total supply.
5+
The `supply` module:
6+
7+
- passively tracks the total supply of coins within a chain,
8+
- provides a pattern for modules to hold/interact with `Coins`, and
9+
- introduces the invariant check to verify a chain's total supply.
910

1011
### Total Supply
1112

@@ -32,11 +33,14 @@ The `ModuleAccount` interface is defined as follows:
3233
type ModuleAccount interface {
3334
auth.Account // same methods as the Account interface
3435
GetName() string // name of the module; used to obtain the address
35-
GetPermissions() []string // permissions of module account
36-
HasPermission(string) bool
36+
GetPermissions() []string // permissions of module account
37+
HasPermission(string) bool
3738
}
3839
```
3940

41+
> **WARNING!**
42+
Any module or message handler that allows either direct or indirect sending of funds must explicitly guarantee those funds cannot be sent to module accounts (unless allowed).
43+
4044
The supply `Keeper` also introduces new wrapper functions for the auth `Keeper`
4145
and the bank `Keeper` that are related to `ModuleAccount`s in order to be able
4246
to:

simapp/app.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,13 @@ func NewSimApp(
141141

142142
// add keepers
143143
app.accountKeeper = auth.NewAccountKeeper(app.cdc, keys[auth.StoreKey], authSubspace, auth.ProtoBaseAccount)
144-
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, bankSubspace, bank.DefaultCodespace)
144+
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, bankSubspace, bank.DefaultCodespace, app.ModuleAccountAddrs())
145145
app.supplyKeeper = supply.NewKeeper(app.cdc, keys[supply.StoreKey], app.accountKeeper, app.bankKeeper, supply.DefaultCodespace, maccPerms)
146146
stakingKeeper := staking.NewKeeper(app.cdc, keys[staking.StoreKey], tkeys[staking.TStoreKey],
147147
app.supplyKeeper, stakingSubspace, staking.DefaultCodespace)
148148
app.mintKeeper = mint.NewKeeper(app.cdc, keys[mint.StoreKey], mintSubspace, &stakingKeeper, app.supplyKeeper, auth.FeeCollectorName)
149149
app.distrKeeper = distr.NewKeeper(app.cdc, keys[distr.StoreKey], distrSubspace, &stakingKeeper,
150-
app.supplyKeeper, distr.DefaultCodespace, auth.FeeCollectorName)
150+
app.supplyKeeper, distr.DefaultCodespace, auth.FeeCollectorName, app.ModuleAccountAddrs())
151151
app.slashingKeeper = slashing.NewKeeper(app.cdc, keys[slashing.StoreKey], &stakingKeeper,
152152
slashingSubspace, slashing.DefaultCodespace)
153153
app.crisisKeeper = crisis.NewKeeper(crisisSubspace, invCheckPeriod, app.supplyKeeper, auth.FeeCollectorName)

x/bank/app_test.go

+57-23
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ import (
55

66
sdk "github.com/cosmos/cosmos-sdk/types"
77
"github.com/cosmos/cosmos-sdk/x/auth"
8-
"github.com/cosmos/cosmos-sdk/x/bank"
9-
"github.com/cosmos/cosmos-sdk/x/bank/internal/keeper"
108
"github.com/cosmos/cosmos-sdk/x/bank/internal/types"
119
"github.com/cosmos/cosmos-sdk/x/mock"
12-
"github.com/cosmos/cosmos-sdk/x/supply"
1310

1411
"github.com/stretchr/testify/require"
1512

@@ -50,6 +47,7 @@ var (
5047
freeFee = auth.NewStdFee(100000, sdk.Coins{sdk.NewInt64Coin("foocoin", 0)})
5148

5249
sendMsg1 = types.NewMsgSend(addr1, addr2, coins)
50+
sendMsg2 = types.NewMsgSend(addr1, moduleAccAddr, coins)
5351

5452
multiSendMsg1 = types.MsgMultiSend{
5553
Inputs: []types.Input{types.NewInput(addr1, coins)},
@@ -88,26 +86,15 @@ var (
8886
types.NewOutput(addr2, manyCoins),
8987
},
9088
}
91-
)
92-
93-
// initialize the mock application for this module
94-
func getMockApp(t *testing.T) *mock.App {
95-
mapp, err := getBenchmarkMockApp()
96-
supply.RegisterCodec(mapp.Cdc)
97-
require.NoError(t, err)
98-
return mapp
99-
}
100-
101-
// overwrite the mock init chainer
102-
func getInitChainer(mapp *mock.App, keeper keeper.BaseKeeper) sdk.InitChainer {
103-
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
104-
mapp.InitChainer(ctx, req)
105-
bankGenesis := bank.DefaultGenesisState()
106-
bank.InitGenesis(ctx, keeper, bankGenesis)
107-
108-
return abci.ResponseInitChain{}
89+
multiSendMsg6 = types.MsgMultiSend{
90+
Inputs: []types.Input{
91+
types.NewInput(addr1, coins),
92+
},
93+
Outputs: []types.Output{
94+
types.NewOutput(moduleAccAddr, coins),
95+
},
10996
}
110-
}
97+
)
11198

11299
func TestSendNotEnoughBalance(t *testing.T) {
113100
mapp := getMockApp(t)
@@ -140,14 +127,53 @@ func TestSendNotEnoughBalance(t *testing.T) {
140127
require.True(t, res2.GetSequence() == origSeq+1)
141128
}
142129

130+
func TestSendToModuleAcc(t *testing.T) {
131+
mapp := getMockApp(t)
132+
acc := &auth.BaseAccount{
133+
Address: addr1,
134+
Coins: coins,
135+
}
136+
137+
macc := &auth.BaseAccount{
138+
Address: moduleAccAddr,
139+
}
140+
141+
mock.SetGenesis(mapp, []auth.Account{acc, macc})
142+
143+
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
144+
145+
res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1)
146+
require.NotNil(t, res1)
147+
require.Equal(t, acc, res1.(*auth.BaseAccount))
148+
149+
origAccNum := res1.GetAccountNumber()
150+
origSeq := res1.GetSequence()
151+
152+
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
153+
mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, header, []sdk.Msg{sendMsg2}, []uint64{origAccNum}, []uint64{origSeq}, false, false, priv1)
154+
155+
mock.CheckBalance(t, mapp, addr1, coins)
156+
mock.CheckBalance(t, mapp, moduleAccAddr, sdk.Coins(nil))
157+
158+
res2 := mapp.AccountKeeper.GetAccount(mapp.NewContext(true, abci.Header{}), addr1)
159+
require.NotNil(t, res2)
160+
161+
require.True(t, res2.GetAccountNumber() == origAccNum)
162+
require.True(t, res2.GetSequence() == origSeq+1)
163+
}
164+
143165
func TestMsgMultiSendWithAccounts(t *testing.T) {
144166
mapp := getMockApp(t)
145167
acc := &auth.BaseAccount{
146168
Address: addr1,
147169
Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 67)},
148170
}
149171

150-
mock.SetGenesis(mapp, []auth.Account{acc})
172+
macc := &auth.BaseAccount{
173+
Address: moduleAccAddr,
174+
}
175+
176+
mock.SetGenesis(mapp, []auth.Account{acc, macc})
151177

152178
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
153179

@@ -176,6 +202,14 @@ func TestMsgMultiSendWithAccounts(t *testing.T) {
176202
expPass: false,
177203
privKeys: []crypto.PrivKey{priv1},
178204
},
205+
{
206+
msgs: []sdk.Msg{multiSendMsg6},
207+
accNums: []uint64{0},
208+
accSeqs: []uint64{0},
209+
expSimPass: false,
210+
expPass: false,
211+
privKeys: []crypto.PrivKey{priv1},
212+
},
179213
}
180214

181215
for _, tc := range testCases {

x/bank/bench_test.go

+28-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bank_test
33
import (
44
"testing"
55

6+
"github.com/stretchr/testify/require"
67
abci "github.com/tendermint/tendermint/abci/types"
78

89
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -11,18 +12,44 @@ import (
1112
"github.com/cosmos/cosmos-sdk/x/bank/internal/keeper"
1213
"github.com/cosmos/cosmos-sdk/x/bank/internal/types"
1314
"github.com/cosmos/cosmos-sdk/x/mock"
15+
"github.com/cosmos/cosmos-sdk/x/supply"
1416
)
1517

18+
var moduleAccAddr = sdk.AccAddress([]byte("moduleAcc"))
19+
20+
// initialize the mock application for this module
21+
func getMockApp(t *testing.T) *mock.App {
22+
mapp, err := getBenchmarkMockApp()
23+
supply.RegisterCodec(mapp.Cdc)
24+
require.NoError(t, err)
25+
return mapp
26+
}
27+
28+
// overwrite the mock init chainer
29+
func getInitChainer(mapp *mock.App, keeper keeper.BaseKeeper) sdk.InitChainer {
30+
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
31+
mapp.InitChainer(ctx, req)
32+
bankGenesis := bank.DefaultGenesisState()
33+
bank.InitGenesis(ctx, keeper, bankGenesis)
34+
35+
return abci.ResponseInitChain{}
36+
}
37+
}
38+
1639
// getBenchmarkMockApp initializes a mock application for this module, for purposes of benchmarking
1740
// Any long term API support commitments do not apply to this function.
1841
func getBenchmarkMockApp() (*mock.App, error) {
1942
mapp := mock.NewApp()
20-
2143
types.RegisterCodec(mapp.Cdc)
44+
45+
blacklistedAddrs := make(map[string]bool)
46+
blacklistedAddrs[moduleAccAddr.String()] = true
47+
2248
bankKeeper := keeper.NewBaseKeeper(
2349
mapp.AccountKeeper,
2450
mapp.ParamsKeeper.Subspace(types.DefaultParamspace),
2551
types.DefaultCodespace,
52+
blacklistedAddrs,
2653
)
2754
mapp.Router().AddRoute(types.RouterKey, bank.NewHandler(bankKeeper))
2855
mapp.SetInitChainer(getInitChainer(mapp, bankKeeper))

x/bank/handler.go

+10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ func handleMsgSend(ctx sdk.Context, k keeper.Keeper, msg types.MsgSend) sdk.Resu
3333
return types.ErrSendDisabled(k.Codespace()).Result()
3434
}
3535

36+
if k.BlacklistedAddr(msg.ToAddress) {
37+
return sdk.ErrUnauthorized(fmt.Sprintf("%s is not allowed to receive transactions", msg.ToAddress)).Result()
38+
}
39+
3640
err := k.SendCoins(ctx, msg.FromAddress, msg.ToAddress, msg.Amount)
3741
if err != nil {
3842
return err.Result()
@@ -55,6 +59,12 @@ func handleMsgMultiSend(ctx sdk.Context, k keeper.Keeper, msg types.MsgMultiSend
5559
return types.ErrSendDisabled(k.Codespace()).Result()
5660
}
5761

62+
for _, out := range msg.Outputs {
63+
if k.BlacklistedAddr(out.Address) {
64+
return sdk.ErrUnauthorized(fmt.Sprintf("%s is not allowed to receive transactions", out.Address)).Result()
65+
}
66+
}
67+
5868
err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs)
5969
if err != nil {
6070
return err.Result()

x/bank/internal/keeper/common_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package keeper
2+
3+
// DONTCOVER
4+
5+
import (
6+
abci "github.com/tendermint/tendermint/abci/types"
7+
dbm "github.com/tendermint/tendermint/libs/db"
8+
"github.com/tendermint/tendermint/libs/log"
9+
10+
"github.com/cosmos/cosmos-sdk/codec"
11+
"github.com/cosmos/cosmos-sdk/store"
12+
sdk "github.com/cosmos/cosmos-sdk/types"
13+
"github.com/cosmos/cosmos-sdk/x/auth"
14+
"github.com/cosmos/cosmos-sdk/x/bank/internal/types"
15+
"github.com/cosmos/cosmos-sdk/x/params"
16+
)
17+
18+
type testInput struct {
19+
cdc *codec.Codec
20+
ctx sdk.Context
21+
k Keeper
22+
ak auth.AccountKeeper
23+
pk params.Keeper
24+
}
25+
26+
func setupTestInput() testInput {
27+
db := dbm.NewMemDB()
28+
29+
cdc := codec.New()
30+
auth.RegisterCodec(cdc)
31+
codec.RegisterCrypto(cdc)
32+
33+
authCapKey := sdk.NewKVStoreKey("authCapKey")
34+
keyParams := sdk.NewKVStoreKey("params")
35+
tkeyParams := sdk.NewTransientStoreKey("transient_params")
36+
37+
ms := store.NewCommitMultiStore(db)
38+
ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db)
39+
ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db)
40+
ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db)
41+
ms.LoadLatestVersion()
42+
43+
blacklistedAddrs := make(map[string]bool)
44+
blacklistedAddrs[sdk.AccAddress([]byte("moduleAcc")).String()] = true
45+
46+
pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
47+
48+
ak := auth.NewAccountKeeper(
49+
cdc, authCapKey, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount,
50+
)
51+
ctx := sdk.NewContext(ms, abci.Header{ChainID: "test-chain-id"}, false, log.NewNopLogger())
52+
53+
ak.SetParams(ctx, auth.DefaultParams())
54+
55+
bankKeeper := NewBaseKeeper(ak, pk.Subspace(types.DefaultParamspace), types.DefaultCodespace, blacklistedAddrs)
56+
bankKeeper.SetSendEnabled(ctx, true)
57+
58+
return testInput{cdc: cdc, ctx: ctx, k: bankKeeper, ak: ak, pk: pk}
59+
}

x/bank/internal/keeper/invariants.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/cosmos/cosmos-sdk/x/bank/internal/types"
88
)
99

10-
// register bank invariants
10+
// RegisterInvariants registers the bank module invariants
1111
func RegisterInvariants(ir sdk.InvariantRegistry, ak types.AccountKeeper) {
1212
ir.RegisterRoute(types.ModuleName, "nonnegative-outstanding",
1313
NonnegativeBalanceInvariant(ak))

x/bank/internal/keeper/keeper.go

+18-6
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ type BaseKeeper struct {
3434
// NewBaseKeeper returns a new BaseKeeper
3535
func NewBaseKeeper(ak types.AccountKeeper,
3636
paramSpace params.Subspace,
37-
codespace sdk.CodespaceType) BaseKeeper {
37+
codespace sdk.CodespaceType, blacklistedAddrs map[string]bool) BaseKeeper {
3838

3939
ps := paramSpace.WithKeyTable(types.ParamKeyTable())
4040
return BaseKeeper{
41-
BaseSendKeeper: NewBaseSendKeeper(ak, ps, codespace),
41+
BaseSendKeeper: NewBaseSendKeeper(ak, ps, codespace, blacklistedAddrs),
4242
ak: ak,
4343
paramSpace: ps,
4444
}
@@ -145,6 +145,8 @@ type SendKeeper interface {
145145

146146
GetSendEnabled(ctx sdk.Context) bool
147147
SetSendEnabled(ctx sdk.Context, enabled bool)
148+
149+
BlacklistedAddr(addr sdk.AccAddress) bool
148150
}
149151

150152
var _ SendKeeper = (*BaseSendKeeper)(nil)
@@ -156,16 +158,20 @@ type BaseSendKeeper struct {
156158

157159
ak types.AccountKeeper
158160
paramSpace params.Subspace
161+
162+
// list of addresses that are restricted from receiving transactions
163+
blacklistedAddrs map[string]bool
159164
}
160165

161166
// NewBaseSendKeeper returns a new BaseSendKeeper.
162167
func NewBaseSendKeeper(ak types.AccountKeeper,
163-
paramSpace params.Subspace, codespace sdk.CodespaceType) BaseSendKeeper {
168+
paramSpace params.Subspace, codespace sdk.CodespaceType, blacklistedAddrs map[string]bool) BaseSendKeeper {
164169

165170
return BaseSendKeeper{
166-
BaseViewKeeper: NewBaseViewKeeper(ak, codespace),
167-
ak: ak,
168-
paramSpace: paramSpace,
171+
BaseViewKeeper: NewBaseViewKeeper(ak, codespace),
172+
ak: ak,
173+
paramSpace: paramSpace,
174+
blacklistedAddrs: blacklistedAddrs,
169175
}
170176
}
171177

@@ -321,6 +327,12 @@ func (keeper BaseSendKeeper) SetSendEnabled(ctx sdk.Context, enabled bool) {
321327
keeper.paramSpace.Set(ctx, types.ParamStoreKeySendEnabled, &enabled)
322328
}
323329

330+
// BlacklistedAddr checks if a given address is blacklisted (i.e restricted from
331+
// receiving funds)
332+
func (keeper BaseSendKeeper) BlacklistedAddr(addr sdk.AccAddress) bool {
333+
return keeper.blacklistedAddrs[addr.String()]
334+
}
335+
324336
var _ ViewKeeper = (*BaseViewKeeper)(nil)
325337

326338
// ViewKeeper defines a module interface that facilitates read only access to

0 commit comments

Comments
 (0)