-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(bank/v2): Introduce global send restriction #21925
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -28,18 +28,21 @@ type Keeper struct { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
params collections.Item[types.Params] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
balances *collections.IndexedMap[collections.Pair[[]byte, string], math.Int, BalancesIndexes] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
supply collections.Map[string, math.Int] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
sendRestriction *sendRestriction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
func NewKeeper(authority []byte, addressCodec address.Codec, env appmodulev2.Environment, cdc codec.BinaryCodec) *Keeper { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
sb := collections.NewSchemaBuilder(env.KVStoreService) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
k := &Keeper{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Environment: 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), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Environment: 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), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
sendRestriction: newSendRestriction(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
schema, err := sb.Build() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -94,7 +97,10 @@ func (k Keeper) SendCoins(ctx context.Context, from, to []byte, amt sdk.Coins) e | |||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
var err error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// TODO: Send restriction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
to, err = k.sendRestriction.apply(ctx, from, to, amt) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
err = k.subUnlockedCoins(ctx, from, amt) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -252,3 +258,18 @@ func newBalancesIndexes(sb *collections.SchemaBuilder) BalancesIndexes { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
type BalancesIndexes struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Denom *indexes.ReversePair[[]byte, string, math.Int] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// AppendSendRestriction adds the provided SendRestrictionFn to run after previously provided restrictions. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (k Keeper) AppendSendRestriction(restriction types.SendRestrictionFn) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can be move this in another file, and call the API Global? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it should relay in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fine, I meant renaming to |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
k.sendRestriction.append(restriction) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// PrependSendRestriction adds the provided SendRestrictionFn to run before previously provided restrictions. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (k Keeper) PrependSendRestriction(restriction types.SendRestrictionFn) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
k.sendRestriction.prepend(restriction) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// ClearSendRestriction removes the send restriction (if there is one). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (k Keeper) ClearSendRestriction() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
k.sendRestriction.clear() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use pointer receivers for methods that modify The methods Apply this diff to change the receiver to a pointer: -func (k Keeper) AppendSendRestriction(restriction types.SendRestrictionFn) {
+func (k *Keeper) AppendSendRestriction(restriction types.SendRestrictionFn) {
} -func (k Keeper) PrependSendRestriction(restriction types.SendRestrictionFn) {
+func (k *Keeper) PrependSendRestriction(restriction types.SendRestrictionFn) {
} -func (k Keeper) ClearSendRestriction() {
+func (k *Keeper) ClearSendRestriction() {
} 📝 Committable suggestion
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
package keeper_test | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
|
@@ -184,3 +186,53 @@ func (suite *KeeperTestSuite) TestSendCoins_Module_To_Module() { | |
mintBarBalance := suite.bankKeeper.GetBalance(ctx, mintAcc.GetAddress(), barDenom) | ||
require.Equal(mintBarBalance.Amount, math.NewInt(0)) | ||
} | ||
|
||
func (suite *KeeperTestSuite) TestSendCoins_WithRestriction() { | ||
ctx := suite.ctx | ||
require := suite.Require() | ||
balances := sdk.NewCoins(newFooCoin(100), newBarCoin(50)) | ||
sendAmt := sdk.NewCoins(newFooCoin(10), newBarCoin(10)) | ||
|
||
require.NoError(banktestutil.FundAccount(ctx, suite.bankKeeper, accAddrs[0], balances)) | ||
|
||
// Add first restriction | ||
addrRestrictFunc := func(ctx context.Context, from, to []byte, amount sdk.Coins) ([]byte, error) { | ||
if bytes.Equal(from, to) { | ||
return nil, fmt.Errorf("Can not send to same address") | ||
} | ||
return to, nil | ||
} | ||
Comment on lines
+199
to
+204
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error message should start with lowercase and use "cannot" Per the Uber Go Style Guide, error messages should start with a lowercase letter, and "cannot" should be a single word. Please update the error message accordingly. Apply this diff to correct the error message: - return nil, fmt.Errorf("Can not send to same address")
+ return nil, fmt.Errorf("cannot send to same address") Also, update the test assertion to match the new error message: - require.Contains(err.Error(), "Can not send to same address")
+ require.Contains(err.Error(), "cannot send to same address")
|
||
suite.bankKeeper.AppendSendRestriction(addrRestrictFunc) | ||
|
||
err := suite.bankKeeper.SendCoins(ctx, accAddrs[0], accAddrs[0], sendAmt) | ||
require.Error(err) | ||
require.Contains(err.Error(), "Can not send to same address") | ||
|
||
// Add second restriction | ||
amtRestrictFunc := func(ctx context.Context, from, to []byte, amount sdk.Coins) ([]byte, error) { | ||
if len(amount) > 1 { | ||
return nil, fmt.Errorf("Allow only one denom per one send") | ||
} | ||
return to, nil | ||
} | ||
Comment on lines
+213
to
+217
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error message should start with lowercase and improve wording According to the Uber Go Style Guide, error messages should start with a lowercase letter. Additionally, consider rephrasing the message for clarity. Apply this diff to improve the error message: - return nil, fmt.Errorf("Allow only one denom per one send")
+ return nil, fmt.Errorf("allow only one denom per send") Also, update the test assertion to match the new error message: - require.Contains(err.Error(), "Allow only one denom per one send")
+ require.Contains(err.Error(), "allow only one denom per send")
|
||
suite.bankKeeper.AppendSendRestriction(amtRestrictFunc) | ||
|
||
// Pass the 1st but failt at the 2nd | ||
err = suite.bankKeeper.SendCoins(ctx, accAddrs[0], accAddrs[1], sendAmt) | ||
require.Error(err) | ||
require.Contains(err.Error(), "Allow only one denom per one send") | ||
|
||
// Pass both 2 restrictions | ||
err = suite.bankKeeper.SendCoins(ctx, accAddrs[0], accAddrs[1], sdk.NewCoins(newFooCoin(10))) | ||
require.NoError(err) | ||
|
||
// 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(50)) | ||
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.ZeroInt()) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,47 @@ | ||||||||||||||||||||||||||||||
package keeper | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||||
"context" | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
"cosmossdk.io/x/bank/v2/types" | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
sdk "github.com/cosmos/cosmos-sdk/types" | ||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// sendRestriction is a struct that houses a SendRestrictionFn. | ||||||||||||||||||||||||||||||
// It exists so that the SendRestrictionFn can be updated in the SendKeeper without needing to have a pointer receiver. | ||||||||||||||||||||||||||||||
type sendRestriction struct { | ||||||||||||||||||||||||||||||
fn types.SendRestrictionFn | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// newSendRestriction creates a new sendRestriction with nil send restriction. | ||||||||||||||||||||||||||||||
func newSendRestriction() *sendRestriction { | ||||||||||||||||||||||||||||||
return &sendRestriction{ | ||||||||||||||||||||||||||||||
fn: nil, | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// append adds the provided restriction to this, to be run after the existing function. | ||||||||||||||||||||||||||||||
func (r *sendRestriction) append(restriction types.SendRestrictionFn) { | ||||||||||||||||||||||||||||||
r.fn = r.fn.Then(restriction) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// prepend adds the provided restriction to this, to be run before the existing function. | ||||||||||||||||||||||||||||||
func (r *sendRestriction) prepend(restriction types.SendRestrictionFn) { | ||||||||||||||||||||||||||||||
r.fn = restriction.Then(r.fn) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// clear removes the send restriction (sets it to nil). | ||||||||||||||||||||||||||||||
func (r *sendRestriction) clear() { | ||||||||||||||||||||||||||||||
r.fn = nil | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
var _ types.SendRestrictionFn = (*sendRestriction)(nil).apply | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// apply applies the send restriction if there is one. If not, it's a no-op. | ||||||||||||||||||||||||||||||
func (r *sendRestriction) apply(ctx context.Context, fromAddr, toAddr []byte, amt sdk.Coins) ([]byte, error) { | ||||||||||||||||||||||||||||||
if r == nil || r.fn == nil { | ||||||||||||||||||||||||||||||
return toAddr, nil | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return r.fn(ctx, fromAddr, toAddr, amt) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
Comment on lines
+41
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion LGTM: Correct implementation of apply method with proper error handling. The Consider a minor optimization to reduce nesting: func (r *sendRestriction) apply(ctx context.Context, fromAddr, toAddr []byte, amt sdk.Coins) ([]byte, error) {
- if r == nil || r.fn == nil {
+ if r == nil || r.fn == nil {
return toAddr, nil
}
- return r.fn(ctx, fromAddr, toAddr, amt)
+ return r.fn(ctx, fromAddr, toAddr, amt)
} This change maintains the same logic but reduces nesting, potentially improving readability. 📝 Committable suggestion
Suggested change
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unnecessary use of
slices.Collect
The function
slices.Collect
is not part of the standardslices
package and may cause a compilation error. Sincemaps.Keys
already returns a slice of keys, you can simplify the code by removingslices.Collect
.Apply this fix to eliminate the undefined function:
📝 Committable suggestion