Skip to content

Commit 1d21f55

Browse files
committed
Merge branch 'main' into issue-20630
2 parents 88da1ba + 0102077 commit 1d21f55

File tree

7 files changed

+301
-15
lines changed

7 files changed

+301
-15
lines changed

x/bank/proto/cosmos/bank/module/v2/module.proto

+6
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ message Module {
1414

1515
// authority defines the custom module authority. If not set, defaults to the governance module.
1616
string authority = 1;
17+
18+
// restrictions_order specifies the order of send restrictions and should be
19+
// a list of module names which provide a send restriction instance. If no
20+
// order is provided, then restrictions will be applied in alphabetical order
21+
// of module names.
22+
repeated string restrictions_order = 2;
1723
}

x/bank/v2/depinject.go

+42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package bankv2
22

33
import (
4+
"fmt"
5+
"maps"
6+
"slices"
7+
"sort"
8+
49
"cosmossdk.io/core/address"
510
"cosmossdk.io/core/appmodule"
611
"cosmossdk.io/depinject"
@@ -22,6 +27,7 @@ func init() {
2227
appconfig.RegisterModule(
2328
&moduletypes.Module{},
2429
appconfig.Provide(ProvideModule),
30+
appconfig.Invoke(InvokeSetSendRestrictions),
2531
)
2632
}
2733

@@ -61,3 +67,39 @@ func ProvideModule(in ModuleInputs) ModuleOutputs {
6167
Module: m,
6268
}
6369
}
70+
71+
func InvokeSetSendRestrictions(
72+
config *moduletypes.Module,
73+
keeper keeper.Keeper,
74+
restrictions map[string]types.SendRestrictionFn,
75+
) error {
76+
if config == nil {
77+
return nil
78+
}
79+
80+
modules := slices.Collect(maps.Keys(restrictions))
81+
order := config.RestrictionsOrder
82+
if len(order) == 0 {
83+
order = modules
84+
sort.Strings(order)
85+
}
86+
87+
if len(order) != len(modules) {
88+
return fmt.Errorf("len(restrictions order: %v) != len(restriction modules: %v)", order, modules)
89+
}
90+
91+
if len(modules) == 0 {
92+
return nil
93+
}
94+
95+
for _, module := range order {
96+
restriction, ok := restrictions[module]
97+
if !ok {
98+
return fmt.Errorf("can't find send restriction for module %s", module)
99+
}
100+
101+
keeper.AppendGlobalSendRestriction(restriction)
102+
}
103+
104+
return nil
105+
}

x/bank/v2/keeper/keeper.go

+13-7
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,21 @@ type Keeper struct {
2828
params collections.Item[types.Params]
2929
balances *collections.IndexedMap[collections.Pair[[]byte, string], math.Int, BalancesIndexes]
3030
supply collections.Map[string, math.Int]
31+
32+
sendRestriction *sendRestriction
3133
}
3234

3335
func NewKeeper(authority []byte, addressCodec address.Codec, env appmodulev2.Environment, cdc codec.BinaryCodec) *Keeper {
3436
sb := collections.NewSchemaBuilder(env.KVStoreService)
3537

3638
k := &Keeper{
37-
Environment: env,
38-
authority: authority,
39-
addressCodec: addressCodec, // TODO(@julienrbrt): Should we add address codec to the environment?
40-
params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)),
41-
balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.PairKeyCodec(collections.BytesKey, collections.StringKey), sdk.IntValue, newBalancesIndexes(sb)),
42-
supply: collections.NewMap(sb, types.SupplyKey, "supply", collections.StringKey, sdk.IntValue),
39+
Environment: env,
40+
authority: authority,
41+
addressCodec: addressCodec, // TODO(@julienrbrt): Should we add address codec to the environment?
42+
params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)),
43+
balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.PairKeyCodec(collections.BytesKey, collections.StringKey), sdk.IntValue, newBalancesIndexes(sb)),
44+
supply: collections.NewMap(sb, types.SupplyKey, "supply", collections.StringKey, sdk.IntValue),
45+
sendRestriction: newSendRestriction(),
4346
}
4447

4548
schema, err := sb.Build()
@@ -94,7 +97,10 @@ func (k Keeper) SendCoins(ctx context.Context, from, to []byte, amt sdk.Coins) e
9497
}
9598

9699
var err error
97-
// TODO: Send restriction
100+
to, err = k.sendRestriction.apply(ctx, from, to, amt)
101+
if err != nil {
102+
return err
103+
}
98104

99105
err = k.subUnlockedCoins(ctx, from, amt)
100106
if err != nil {

x/bank/v2/keeper/keeper_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package keeper_test
22

33
import (
4+
"bytes"
45
"context"
6+
"fmt"
57
"testing"
68
"time"
79

@@ -184,3 +186,53 @@ func (suite *KeeperTestSuite) TestSendCoins_Module_To_Module() {
184186
mintBarBalance := suite.bankKeeper.GetBalance(ctx, mintAcc.GetAddress(), barDenom)
185187
require.Equal(mintBarBalance.Amount, math.NewInt(0))
186188
}
189+
190+
func (suite *KeeperTestSuite) TestSendCoins_WithRestriction() {
191+
ctx := suite.ctx
192+
require := suite.Require()
193+
balances := sdk.NewCoins(newFooCoin(100), newBarCoin(50))
194+
sendAmt := sdk.NewCoins(newFooCoin(10), newBarCoin(10))
195+
196+
require.NoError(banktestutil.FundAccount(ctx, suite.bankKeeper, accAddrs[0], balances))
197+
198+
// Add first restriction
199+
addrRestrictFunc := func(ctx context.Context, from, to []byte, amount sdk.Coins) ([]byte, error) {
200+
if bytes.Equal(from, to) {
201+
return nil, fmt.Errorf("Can not send to same address")
202+
}
203+
return to, nil
204+
}
205+
suite.bankKeeper.AppendGlobalSendRestriction(addrRestrictFunc)
206+
207+
err := suite.bankKeeper.SendCoins(ctx, accAddrs[0], accAddrs[0], sendAmt)
208+
require.Error(err)
209+
require.Contains(err.Error(), "Can not send to same address")
210+
211+
// Add second restriction
212+
amtRestrictFunc := func(ctx context.Context, from, to []byte, amount sdk.Coins) ([]byte, error) {
213+
if len(amount) > 1 {
214+
return nil, fmt.Errorf("Allow only one denom per one send")
215+
}
216+
return to, nil
217+
}
218+
suite.bankKeeper.AppendGlobalSendRestriction(amtRestrictFunc)
219+
220+
// Pass the 1st but failt at the 2nd
221+
err = suite.bankKeeper.SendCoins(ctx, accAddrs[0], accAddrs[1], sendAmt)
222+
require.Error(err)
223+
require.Contains(err.Error(), "Allow only one denom per one send")
224+
225+
// Pass both 2 restrictions
226+
err = suite.bankKeeper.SendCoins(ctx, accAddrs[0], accAddrs[1], sdk.NewCoins(newFooCoin(10)))
227+
require.NoError(err)
228+
229+
// Check balances
230+
acc0FooBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[0], fooDenom)
231+
require.Equal(acc0FooBalance.Amount, math.NewInt(90))
232+
acc0BarBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[0], barDenom)
233+
require.Equal(acc0BarBalance.Amount, math.NewInt(50))
234+
acc1FooBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[1], fooDenom)
235+
require.Equal(acc1FooBalance.Amount, math.NewInt(10))
236+
acc1BarBalance := suite.bankKeeper.GetBalance(ctx, accAddrs[1], barDenom)
237+
require.Equal(acc1BarBalance.Amount, math.ZeroInt())
238+
}

x/bank/v2/keeper/restriction.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package keeper
2+
3+
import (
4+
"context"
5+
6+
"cosmossdk.io/x/bank/v2/types"
7+
8+
sdk "github.com/cosmos/cosmos-sdk/types"
9+
)
10+
11+
// sendRestriction is a struct that houses a SendRestrictionFn.
12+
// It exists so that the SendRestrictionFn can be updated in the SendKeeper without needing to have a pointer receiver.
13+
type sendRestriction struct {
14+
fn types.SendRestrictionFn
15+
}
16+
17+
// newSendRestriction creates a new sendRestriction with nil send restriction.
18+
func newSendRestriction() *sendRestriction {
19+
return &sendRestriction{
20+
fn: nil,
21+
}
22+
}
23+
24+
// append adds the provided restriction to this, to be run after the existing function.
25+
func (r *sendRestriction) append(restriction types.SendRestrictionFn) {
26+
r.fn = r.fn.Then(restriction)
27+
}
28+
29+
// prepend adds the provided restriction to this, to be run before the existing function.
30+
func (r *sendRestriction) prepend(restriction types.SendRestrictionFn) {
31+
r.fn = restriction.Then(r.fn)
32+
}
33+
34+
// clear removes the send restriction (sets it to nil).
35+
func (r *sendRestriction) clear() {
36+
r.fn = nil
37+
}
38+
39+
var _ types.SendRestrictionFn = (*sendRestriction)(nil).apply
40+
41+
// apply applies the send restriction if there is one. If not, it's a no-op.
42+
func (r *sendRestriction) apply(ctx context.Context, fromAddr, toAddr []byte, amt sdk.Coins) ([]byte, error) {
43+
if r == nil || r.fn == nil {
44+
return toAddr, nil
45+
}
46+
return r.fn(ctx, fromAddr, toAddr, amt)
47+
}
48+
49+
// AppendSendRestriction adds the provided SendRestrictionFn to run after previously provided restrictions.
50+
func (k Keeper) AppendGlobalSendRestriction(restriction types.SendRestrictionFn) {
51+
k.sendRestriction.append(restriction)
52+
}
53+
54+
// PrependSendRestriction adds the provided SendRestrictionFn to run before previously provided restrictions.
55+
func (k Keeper) PrependGlobalSendRestriction(restriction types.SendRestrictionFn) {
56+
k.sendRestriction.prepend(restriction)
57+
}
58+
59+
// ClearSendRestriction removes the send restriction (if there is one).
60+
func (k Keeper) ClearGlobalSendRestriction() {
61+
k.sendRestriction.clear()
62+
}

x/bank/v2/types/module/module.pb.go

+69-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)