-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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): Allow injectable restrictions on bank transfers #14224
Changes from 55 commits
83de950
1648159
66b2b93
845480d
df54462
ba605c3
66e8474
bc988d9
3f6ea3a
a5b1afe
64bb3c1
9bd62bf
987e9c5
e388801
1ef3412
60fbf14
915216e
66ec36f
1f0efb8
c9cc904
28f37ed
a0befb3
327f218
f4d0a12
6202a5d
8aba61a
a994e79
916608b
ed42c18
4260317
2a3f9fa
c9b5bab
c7583b4
f54f8fa
c9b7b71
c6e1dca
053a518
0000a6e
2f61c50
231c925
dfb0f30
a452e6a
1c009f5
a0f7191
c43f119
114838d
89680e8
a116b9a
0f36433
acbc96c
fdd3354
7140caa
d4eca5d
8fb5fd4
7ab7b5a
555523c
adb1e73
58b6f99
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 |
---|---|---|
|
@@ -236,8 +236,12 @@ accounts. The send keeper does not alter the total supply (mint or burn coins). | |
type SendKeeper interface { | ||
ViewKeeper | ||
|
||
InputOutputCoins(ctx context.Context, inputs types.Input, outputs []types.Output) error | ||
SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error | ||
AppendSendRestriction(restriction SendRestrictionFn) | ||
PrependSendRestriction(restriction SendRestrictionFn) | ||
ClearSendRestriction() | ||
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. When is this needed? If you've added restrictions, do you ever want to remove them again? 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.
|
||
|
||
InputOutputCoins(ctx context.Context, input types.Input, outputs []types.Output) error | ||
SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error | ||
|
||
GetParams(ctx context.Context) types.Params | ||
SetParams(ctx context.Context, params types.Params) error | ||
|
@@ -256,6 +260,86 @@ type SendKeeper interface { | |
} | ||
``` | ||
|
||
#### Send Restrictions | ||
|
||
The `SendKeeper` applies a `SendRestrictionFn` before each transfer of funds. | ||
|
||
```golang | ||
// A SendRestrictionFn can restrict sends and/or provide a new receiver address. | ||
type SendRestrictionFn func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (newToAddr sdk.AccAddress, err error) | ||
``` | ||
|
||
After the `SendKeeper` (or `BaseKeeper`) has been created, send restrictions can be added to it using the `AppendSendRestriction` or `PrependSendRestriction` functions. | ||
Both functions compose the provided restriction with any previously provided restrictions. | ||
`AppendSendRestriction` adds the provided restriction to be run after any previously provided send restrictions. | ||
`PrependSendRestriction` adds the restriction to be run before any previously provided send restrictions. | ||
The composition will short-circuit when an error is encountered. I.e. if the first one returns an error, the second is not run. | ||
|
||
During `SendCoins`, the send restriction is applied after coins are removed from the from address, but before adding them to the to address. | ||
During `InputOutputCoins`, the send restriction is applied after the input coins are removed and once for each output before the funds are added. | ||
|
||
A send restriction function should make use of a custom value in the context to allow bypassing that specific restriction. | ||
|
||
For example, in your module's keeper package, you'd define the send restriction function: | ||
|
||
```golang | ||
var _ banktypes.SendRestrictionFn = Keeper{}.SendRestrictionFn | ||
|
||
func (k Keeper) SendRestrictionFn(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { | ||
// Bypass if the context says to. | ||
if mymodule.HasBypass(ctx) { | ||
return toAddr, nil | ||
} | ||
|
||
// Your custom send restriction logic goes here. | ||
return nil, errors.New("not implemented") | ||
} | ||
``` | ||
|
||
The bank keeper should be provided to your keeper's constructor so the send restriction can be added to it: | ||
|
||
```golang | ||
func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, bankKeeper mymodule.BankKeeper) Keeper { | ||
rv := Keeper{/*...*/} | ||
bankKeeper.AppendSendRestriction(rv.SendRestrictionFn) | ||
return rv | ||
} | ||
``` | ||
|
||
Then, in the `mymodule` package, define the context helpers: | ||
|
||
```golang | ||
const bypassKey = "bypass-mymodule-restriction" | ||
|
||
// WithBypass returns a new context that will cause the mymodule bank send restriction to be skipped. | ||
func WithBypass(ctx context.Context) context.Context { | ||
return sdk.UnwrapSDKContext(ctx).WithValue(bypassKey, true) | ||
} | ||
|
||
// WithoutBypass returns a new context that will cause the mymodule bank send restriction to not be skipped. | ||
func WithoutBypass(ctx context.Context) context.Context { | ||
return sdk.UnwrapSDKContext(ctx).WithValue(bypassKey, false) | ||
} | ||
|
||
// HasBypass checks the context to see if the mymodule bank send restriction should be skipped. | ||
func HasBypass(ctx context.Context) bool { | ||
bypassValue := ctx.Value(bypassKey) | ||
if bypassValue == nil { | ||
return false | ||
} | ||
bypass, isBool := bypassValue.(bool) | ||
return isBool && bypass | ||
} | ||
``` | ||
|
||
Now, anywhere where you want to use `SendCoins` or `InputOutputCoins`, but you don't want your send restriction applied: | ||
|
||
```golang | ||
func (k Keeper) DoThing(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error { | ||
return k.bankKeeper.SendCoins(mymodule.WithBypass(ctx), fromAddr, toAddr, amt) | ||
} | ||
``` | ||
|
||
### ViewKeeper | ||
|
||
The view keeper provides read-only access to account balances. The view keeper does not have balance alteration functionality. All balance lookups are `O(1)`. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package keeper | ||
|
||
import "github.com/cosmos/cosmos-sdk/x/bank/types" | ||
|
||
// This file exists in the keeper package to expose some private things | ||
// for the purpose of testing in the keeper_test package. | ||
|
||
func (k BaseSendKeeper) SetSendRestriction(restriction types.SendRestrictionFn) { | ||
k.sendRestriction.fn = restriction | ||
} | ||
|
||
func (k BaseSendKeeper) GetSendRestrictionFn() types.SendRestrictionFn { | ||
return k.sendRestriction.fn | ||
} |
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.
This is late, and may have been discussed already, but I wonder what the need is for having 2 new methods, compared to just, say,
AppendSendRestriction
? More importantly, how can a module decide whether itsSendRestrictionFn
should be prepended vs appended? It seems like a global decision that can't be made locally.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.
The ordering of send restrictions might matter. I expect most things will use
Append
, but there could easily be a situation where a module needs theirs to go before the ones already injected.I hate when I get into a situation where I just can't do what I know I need to do because the only layer I have access too didn't provide enough control. So, while
Append
will usually be good enough,Prepend
andClear
can easily save the day.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.
But how does the module know whether it's safe to insert its restriction function before all others? That is, I see a problem where modules can be fighting each other for the correct position of their restrictions.
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.
The module can't know. The blockchain author can though, and needs to have the tools available to fix ordering problems if the arise.