From ff2e5a236282de7844bc318dde280a175244c077 Mon Sep 17 00:00:00 2001 From: Daniel Wedul Date: Fri, 18 Aug 2023 16:45:01 -0600 Subject: [PATCH] feat(bank): Allow injectable restrictions on bank transfers (#14224) --- CHANGELOG.md | 1 + x/bank/README.md | 88 ++- x/bank/keeper/export_test.go | 14 + x/bank/keeper/keeper.go | 23 +- x/bank/keeper/keeper_test.go | 527 ++++++++++++- x/bank/keeper/send.go | 94 ++- x/bank/types/restrictions.go | 99 +++ x/bank/types/restrictions_test.go | 919 +++++++++++++++++++++++ x/gov/testutil/expected_keepers_mocks.go | 46 +- 9 files changed, 1774 insertions(+), 37 deletions(-) create mode 100644 x/bank/keeper/export_test.go create mode 100644 x/bank/types/restrictions.go create mode 100644 x/bank/types/restrictions_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index e903b21ab327..109c510c3cd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (client/rpc) [#17274](https://github.com/cosmos/cosmos-sdk/pull/17274) Add `QueryEventForTxCmd` cmd to subscribe and wait event for transaction by hash. * (baseapp) [#16239](https://github.com/cosmos/cosmos-sdk/pull/16239) Add Gas Limits to allow node operators to resource bound queries. * (baseapp) [#17393](https://github.com/cosmos/cosmos-sdk/pull/17394) Check BlockID Flag on Votes in `ValidateVoteExtensions` +* (x/bank) [#14224](https://github.com/cosmos/cosmos-sdk/pull/14224) Allow injection of restrictions on transfers using `AppendSendRestriction` or `PrependSendRestriction`. ### Improvements diff --git a/x/bank/README.md b/x/bank/README.md index 340822aa38d6..990634b8b3b3 100644 --- a/x/bank/README.md +++ b/x/bank/README.md @@ -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() + + 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)`. diff --git a/x/bank/keeper/export_test.go b/x/bank/keeper/export_test.go new file mode 100644 index 000000000000..91960e9a8a82 --- /dev/null +++ b/x/bank/keeper/export_test.go @@ -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 +} diff --git a/x/bank/keeper/keeper.go b/x/bank/keeper/keeper.go index da2937372c6a..bfa45d23f64e 100644 --- a/x/bank/keeper/keeper.go +++ b/x/bank/keeper/keeper.go @@ -23,7 +23,7 @@ var _ Keeper = (*BaseKeeper)(nil) // between accounts. type Keeper interface { SendKeeper - WithMintCoinsRestriction(MintingRestrictionFn) BaseKeeper + WithMintCoinsRestriction(types.MintingRestrictionFn) BaseKeeper InitGenesis(context.Context, *types.GenesisState) ExportGenesis(context.Context) *types.GenesisState @@ -59,12 +59,10 @@ type BaseKeeper struct { ak types.AccountKeeper cdc codec.BinaryCodec storeService store.KVStoreService - mintCoinsRestrictionFn MintingRestrictionFn + mintCoinsRestrictionFn types.MintingRestrictionFn logger log.Logger } -type MintingRestrictionFn func(ctx context.Context, coins sdk.Coins) error - // GetPaginatedTotalSupply queries for the supply, ignoring 0 coins, with a given pagination func (k BaseKeeper) GetPaginatedTotalSupply(ctx context.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error) { coins, pageResp, err := query.CollectionPaginate(ctx, k.Supply, pagination, func(key string, value math.Int) (sdk.Coin, error) { @@ -103,7 +101,7 @@ func NewBaseKeeper( ak: ak, cdc: cdc, storeService: storeService, - mintCoinsRestrictionFn: func(ctx context.Context, coins sdk.Coins) error { return nil }, + mintCoinsRestrictionFn: types.NoOpMintingRestrictionFn, logger: logger, } } @@ -113,19 +111,8 @@ func NewBaseKeeper( // Previous restriction functions can be nested as such: // // bankKeeper.WithMintCoinsRestriction(restriction1).WithMintCoinsRestriction(restriction2) -func (k BaseKeeper) WithMintCoinsRestriction(check MintingRestrictionFn) BaseKeeper { - oldRestrictionFn := k.mintCoinsRestrictionFn - k.mintCoinsRestrictionFn = func(ctx context.Context, coins sdk.Coins) error { - err := check(ctx, coins) - if err != nil { - return err - } - err = oldRestrictionFn(ctx, coins) - if err != nil { - return err - } - return nil - } +func (k BaseKeeper) WithMintCoinsRestriction(check types.MintingRestrictionFn) BaseKeeper { + k.mintCoinsRestrictionFn = check.Then(k.mintCoinsRestrictionFn) return k } diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index 49688b906df8..363686b70fb3 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto/sha256" "encoding/hex" + "errors" "fmt" "strings" "testing" @@ -249,6 +250,64 @@ func (suite *KeeperTestSuite) mockUnDelegateCoins(ctx context.Context, acc, mAcc suite.authKeeper.EXPECT().GetAccount(ctx, mAcc.GetAddress()).Return(mAcc) } +func (suite *KeeperTestSuite) TestAppendSendRestriction() { + var calls []int + testRestriction := func(index int) banktypes.SendRestrictionFn { + return func(_ context.Context, _, _ sdk.AccAddress, _ sdk.Coins) (sdk.AccAddress, error) { + calls = append(calls, index) + return nil, nil + } + } + + bk := suite.bankKeeper + + // Initial append of the test restriction. + bk.SetSendRestriction(nil) + bk.AppendSendRestriction(testRestriction(1)) + _, _ = bk.GetSendRestrictionFn()(suite.ctx, nil, nil, nil) + suite.Require().Equal([]int{1}, calls, "restriction calls after first append") + + // Append the test restriction again. + calls = nil + bk.AppendSendRestriction(testRestriction(2)) + _, _ = bk.GetSendRestrictionFn()(suite.ctx, nil, nil, nil) + suite.Require().Equal([]int{1, 2}, calls, "restriction calls after second append") + + // make sure the original bank keeper has the restrictions too. + calls = nil + _, _ = suite.bankKeeper.GetSendRestrictionFn()(suite.ctx, nil, nil, nil) + suite.Require().Equal([]int{1, 2}, calls, "restriction calls from original bank keeper") +} + +func (suite *KeeperTestSuite) TestPrependSendRestriction() { + var calls []int + testRestriction := func(index int) banktypes.SendRestrictionFn { + return func(_ context.Context, _, _ sdk.AccAddress, _ sdk.Coins) (sdk.AccAddress, error) { + calls = append(calls, index) + return nil, nil + } + } + + bk := suite.bankKeeper + + // Initial append of the test restriction. + bk.SetSendRestriction(nil) + bk.PrependSendRestriction(testRestriction(1)) + _, _ = bk.GetSendRestrictionFn()(suite.ctx, nil, nil, nil) + suite.Require().Equal([]int{1}, calls, "restriction calls after first append") + + // Append the test restriction again. + calls = nil + bk.PrependSendRestriction(testRestriction(2)) + _, _ = bk.GetSendRestrictionFn()(suite.ctx, nil, nil, nil) + suite.Require().Equal([]int{2, 1}, calls, "restriction calls after second append") + + // make sure the original bank keeper has the restrictions too. + calls = nil + _, _ = suite.bankKeeper.GetSendRestrictionFn()(suite.ctx, nil, nil, nil) + suite.Require().Equal([]int{2, 1}, calls, "restriction calls from original bank keeper") +} + func (suite *KeeperTestSuite) TestGetAuthority() { storeService := runtime.NewKVStoreService(storetypes.NewKVStoreKey(banktypes.StoreKey)) NewKeeperWithAuthority := func(authority string) keeper.BaseKeeper { @@ -647,6 +706,294 @@ func (suite *KeeperTestSuite) TestInputOutputCoins() { require.Equal(expected, acc3Balances) } +func (suite *KeeperTestSuite) TestInputOutputCoinsWithRestrictions() { + type restrictionArgs struct { + ctx context.Context + fromAddr sdk.AccAddress + toAddr sdk.AccAddress + amt sdk.Coins + } + var actualRestrictionArgs []*restrictionArgs + restrictionError := func(messages ...string) banktypes.SendRestrictionFn { + i := -1 + return func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { + actualRestrictionArgs = append(actualRestrictionArgs, &restrictionArgs{ + ctx: ctx, + fromAddr: fromAddr, + toAddr: toAddr, + amt: amt, + }) + i++ + if i < len(messages) { + if len(messages[i]) > 0 { + return nil, errors.New(messages[i]) + } + } + return toAddr, nil + } + } + restrictionPassthrough := func() banktypes.SendRestrictionFn { + return func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { + actualRestrictionArgs = append(actualRestrictionArgs, &restrictionArgs{ + ctx: ctx, + fromAddr: fromAddr, + toAddr: toAddr, + amt: amt, + }) + return toAddr, nil + } + } + restrictionNewTo := func(newToAddrs ...sdk.AccAddress) banktypes.SendRestrictionFn { + i := -1 + return func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { + actualRestrictionArgs = append(actualRestrictionArgs, &restrictionArgs{ + ctx: ctx, + fromAddr: fromAddr, + toAddr: toAddr, + amt: amt, + }) + i++ + if i < len(newToAddrs) { + if len(newToAddrs[i]) > 0 { + return newToAddrs[i], nil + } + } + return toAddr, nil + } + } + type expBals struct { + from sdk.Coins + to1 sdk.Coins + to2 sdk.Coins + } + + setupCtx := suite.ctx + balances := sdk.NewCoins(newFooCoin(1000), newBarCoin(500)) + fromAddr := accAddrs[0] + fromAcc := authtypes.NewBaseAccountWithAddress(fromAddr) + inputAccs := []sdk.AccountI{fromAcc} + toAddr1 := accAddrs[1] + toAddr2 := accAddrs[2] + + suite.mockFundAccount(accAddrs[0]) + suite.Require().NoError(banktestutil.FundAccount(setupCtx, suite.bankKeeper, accAddrs[0], balances)) + + tests := []struct { + name string + fn banktypes.SendRestrictionFn + inputCoins sdk.Coins + outputs []banktypes.Output + outputAddrs []sdk.AccAddress + expArgs []*restrictionArgs + expErr string + expBals expBals + }{ + { + name: "nil restriction", + fn: nil, + inputCoins: sdk.NewCoins(newFooCoin(5)), + outputs: []banktypes.Output{{Address: toAddr1.String(), Coins: sdk.NewCoins(newFooCoin(5))}}, + outputAddrs: []sdk.AccAddress{toAddr1}, + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(995), newBarCoin(500)), + to1: sdk.NewCoins(newFooCoin(5)), + to2: sdk.Coins{}, + }, + }, + { + name: "passthrough restriction single output", + fn: restrictionPassthrough(), + inputCoins: sdk.NewCoins(newFooCoin(10)), + outputs: []banktypes.Output{{Address: toAddr1.String(), Coins: sdk.NewCoins(newFooCoin(10))}}, + outputAddrs: []sdk.AccAddress{toAddr1}, + expArgs: []*restrictionArgs{ + { + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr1, + amt: sdk.NewCoins(newFooCoin(10)), + }, + }, + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(985), newBarCoin(500)), + to1: sdk.NewCoins(newFooCoin(15)), + to2: sdk.Coins{}, + }, + }, + { + name: "new to restriction single output", + fn: restrictionNewTo(toAddr2), + inputCoins: sdk.NewCoins(newFooCoin(26)), + outputs: []banktypes.Output{{Address: toAddr1.String(), Coins: sdk.NewCoins(newFooCoin(26))}}, + outputAddrs: []sdk.AccAddress{toAddr2}, + expArgs: []*restrictionArgs{ + { + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr1, + amt: sdk.NewCoins(newFooCoin(26)), + }, + }, + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(959), newBarCoin(500)), + to1: sdk.NewCoins(newFooCoin(15)), + to2: sdk.NewCoins(newFooCoin(26)), + }, + }, + { + name: "error restriction single output", + fn: restrictionError("restriction test error"), + inputCoins: sdk.NewCoins(newBarCoin(88)), + outputs: []banktypes.Output{{Address: toAddr1.String(), Coins: sdk.NewCoins(newBarCoin(88))}}, + outputAddrs: []sdk.AccAddress{}, + expArgs: []*restrictionArgs{ + { + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr1, + amt: sdk.NewCoins(newBarCoin(88)), + }, + }, + expErr: "restriction test error", + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(959), newBarCoin(412)), + to1: sdk.NewCoins(newFooCoin(15)), + to2: sdk.NewCoins(newFooCoin(26)), + }, + }, + { + name: "passthrough restriction two outputs", + fn: restrictionPassthrough(), + inputCoins: sdk.NewCoins(newFooCoin(11), newBarCoin(12)), + outputs: []banktypes.Output{ + {Address: toAddr1.String(), Coins: sdk.NewCoins(newFooCoin(11))}, + {Address: toAddr2.String(), Coins: sdk.NewCoins(newBarCoin(12))}, + }, + outputAddrs: []sdk.AccAddress{toAddr1, toAddr2}, + expArgs: []*restrictionArgs{ + { + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr1, + amt: sdk.NewCoins(newFooCoin(11)), + }, + { + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr2, + amt: sdk.NewCoins(newBarCoin(12)), + }, + }, + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(948), newBarCoin(400)), + to1: sdk.NewCoins(newFooCoin(26)), + to2: sdk.NewCoins(newFooCoin(26), newBarCoin(12)), + }, + }, + { + name: "error restriction two outputs error on second", + fn: restrictionError("", "second restriction error"), + inputCoins: sdk.NewCoins(newFooCoin(44)), + outputs: []banktypes.Output{ + {Address: toAddr1.String(), Coins: sdk.NewCoins(newFooCoin(12))}, + {Address: toAddr2.String(), Coins: sdk.NewCoins(newFooCoin(32))}, + }, + outputAddrs: []sdk.AccAddress{toAddr1}, + expArgs: []*restrictionArgs{ + { + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr1, + amt: sdk.NewCoins(newFooCoin(12)), + }, + { + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr2, + amt: sdk.NewCoins(newFooCoin(32)), + }, + }, + expErr: "second restriction error", + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(904), newBarCoin(400)), + to1: sdk.NewCoins(newFooCoin(38)), + to2: sdk.NewCoins(newFooCoin(26), newBarCoin(12)), + }, + }, + { + name: "new to restriction two outputs", + fn: restrictionNewTo(toAddr2, toAddr1), + inputCoins: sdk.NewCoins(newBarCoin(35)), + outputs: []banktypes.Output{ + {Address: toAddr1.String(), Coins: sdk.NewCoins(newBarCoin(10))}, + {Address: toAddr2.String(), Coins: sdk.NewCoins(newBarCoin(25))}, + }, + outputAddrs: []sdk.AccAddress{toAddr1, toAddr2}, + expArgs: []*restrictionArgs{ + { + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr1, + amt: sdk.NewCoins(newBarCoin(10)), + }, + { + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr2, + amt: sdk.NewCoins(newBarCoin(25)), + }, + }, + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(904), newBarCoin(365)), + to1: sdk.NewCoins(newFooCoin(38), newBarCoin(25)), + to2: sdk.NewCoins(newFooCoin(26), newBarCoin(22)), + }, + }, + } + + for _, tc := range tests { + suite.Run(tc.name, func() { + existingSendRestrictionFn := suite.bankKeeper.GetSendRestrictionFn() + defer suite.bankKeeper.SetSendRestriction(existingSendRestrictionFn) + actualRestrictionArgs = nil + suite.bankKeeper.SetSendRestriction(tc.fn) + ctx := suite.ctx + suite.mockInputOutputCoins(inputAccs, tc.outputAddrs) + input := banktypes.Input{ + Address: fromAddr.String(), + Coins: tc.inputCoins, + } + + var err error + testFunc := func() { + err = suite.bankKeeper.InputOutputCoins(ctx, input, tc.outputs) + } + suite.Require().NotPanics(testFunc, "InputOutputCoins") + if len(tc.expErr) > 0 { + suite.Assert().EqualError(err, tc.expErr, "InputOutputCoins error") + } else { + suite.Assert().NoError(err, "InputOutputCoins error") + } + if len(tc.expArgs) > 0 { + for i, expArgs := range tc.expArgs { + suite.Assert().Equal(expArgs.ctx, actualRestrictionArgs[i].ctx, "[%d] ctx provided to restriction", i) + suite.Assert().Equal(expArgs.fromAddr, actualRestrictionArgs[i].fromAddr, "[%d] fromAddr provided to restriction", i) + suite.Assert().Equal(expArgs.toAddr, actualRestrictionArgs[i].toAddr, "[%d] toAddr provided to restriction", i) + suite.Assert().Equal(expArgs.amt.String(), actualRestrictionArgs[i].amt.String(), "[%d] amt provided to restriction", i) + } + } else { + suite.Assert().Nil(actualRestrictionArgs, "args provided to a restriction") + } + fromBal := suite.bankKeeper.GetAllBalances(ctx, fromAddr) + suite.Assert().Equal(tc.expBals.from.String(), fromBal.String(), "fromAddr balance") + to1Bal := suite.bankKeeper.GetAllBalances(ctx, toAddr1) + suite.Assert().Equal(tc.expBals.to1.String(), to1Bal.String(), "toAddr1 balance") + to2Bal := suite.bankKeeper.GetAllBalances(ctx, toAddr2) + suite.Assert().Equal(tc.expBals.to2.String(), to2Bal.String(), "toAddr2 balance") + }) + } +} + func (suite *KeeperTestSuite) TestSendCoins() { ctx := suite.ctx require := suite.Require() @@ -684,6 +1031,184 @@ func (suite *KeeperTestSuite) TestSendCoins() { require.Equal(newBarCoin(25), coins[0], "expected only bar coins in the account balance, got: %v", coins) } +func (suite *KeeperTestSuite) TestSendCoinsWithRestrictions() { + type restrictionArgs struct { + ctx context.Context + fromAddr sdk.AccAddress + toAddr sdk.AccAddress + amt sdk.Coins + } + var actualRestrictionArgs *restrictionArgs + restrictionError := func(message string) banktypes.SendRestrictionFn { + return func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { + actualRestrictionArgs = &restrictionArgs{ + ctx: ctx, + fromAddr: fromAddr, + toAddr: toAddr, + amt: amt, + } + return nil, errors.New(message) + } + } + restrictionPassthrough := func() banktypes.SendRestrictionFn { + return func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { + actualRestrictionArgs = &restrictionArgs{ + ctx: ctx, + fromAddr: fromAddr, + toAddr: toAddr, + amt: amt, + } + return toAddr, nil + } + } + restrictionNewTo := func(newToAddr sdk.AccAddress) banktypes.SendRestrictionFn { + return func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { + actualRestrictionArgs = &restrictionArgs{ + ctx: ctx, + fromAddr: fromAddr, + toAddr: toAddr, + amt: amt, + } + return newToAddr, nil + } + } + type expBals struct { + from sdk.Coins + to1 sdk.Coins + to2 sdk.Coins + } + + setupCtx := suite.ctx + balances := sdk.NewCoins(newFooCoin(1000), newBarCoin(500)) + fromAddr := accAddrs[0] + fromAcc := authtypes.NewBaseAccountWithAddress(fromAddr) + toAddr1 := accAddrs[1] + toAddr2 := accAddrs[2] + + suite.mockFundAccount(accAddrs[0]) + suite.Require().NoError(banktestutil.FundAccount(setupCtx, suite.bankKeeper, accAddrs[0], balances)) + + tests := []struct { + name string + fn banktypes.SendRestrictionFn + toAddr sdk.AccAddress + finalAddr sdk.AccAddress + amt sdk.Coins + expArgs *restrictionArgs + expErr string + expBals expBals + }{ + { + name: "nil restriction", + fn: nil, + toAddr: toAddr1, + finalAddr: toAddr1, + amt: sdk.NewCoins(newFooCoin(5)), + expArgs: nil, + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(995), newBarCoin(500)), + to1: sdk.NewCoins(newFooCoin(5)), + to2: sdk.Coins{}, + }, + }, + { + name: "passthrough restriction", + fn: restrictionPassthrough(), + toAddr: toAddr1, + finalAddr: toAddr1, + amt: sdk.NewCoins(newFooCoin(10)), + expArgs: &restrictionArgs{ + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr1, + amt: sdk.NewCoins(newFooCoin(10)), + }, + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(985), newBarCoin(500)), + to1: sdk.NewCoins(newFooCoin(15)), + to2: sdk.Coins{}, + }, + }, + { + name: "new to addr restriction", + fn: restrictionNewTo(toAddr2), + toAddr: toAddr1, + finalAddr: toAddr2, + amt: sdk.NewCoins(newBarCoin(27)), + expArgs: &restrictionArgs{ + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr1, + amt: sdk.NewCoins(newBarCoin(27)), + }, + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(985), newBarCoin(473)), + to1: sdk.NewCoins(newFooCoin(15)), + to2: sdk.NewCoins(newBarCoin(27)), + }, + }, + { + name: "restriction returns error", + fn: restrictionError("test restriction error"), + toAddr: toAddr1, + finalAddr: toAddr1, + amt: sdk.NewCoins(newFooCoin(100), newBarCoin(200)), + expArgs: &restrictionArgs{ + ctx: suite.ctx, + fromAddr: fromAddr, + toAddr: toAddr1, + amt: sdk.NewCoins(newFooCoin(100), newBarCoin(200)), + }, + expErr: "test restriction error", + expBals: expBals{ + from: sdk.NewCoins(newFooCoin(885), newBarCoin(273)), + to1: sdk.NewCoins(newFooCoin(15)), + to2: sdk.NewCoins(newBarCoin(27)), + }, + }, + } + + for _, tc := range tests { + suite.Run(tc.name, func() { + existingSendRestrictionFn := suite.bankKeeper.GetSendRestrictionFn() + defer suite.bankKeeper.SetSendRestriction(existingSendRestrictionFn) + actualRestrictionArgs = nil + suite.bankKeeper.SetSendRestriction(tc.fn) + ctx := suite.ctx + if len(tc.expErr) > 0 { + suite.authKeeper.EXPECT().GetAccount(ctx, fromAddr).Return(fromAcc) + } else { + suite.mockSendCoins(ctx, fromAcc, tc.finalAddr) + } + + var err error + testFunc := func() { + err = suite.bankKeeper.SendCoins(ctx, fromAddr, tc.toAddr, tc.amt) + } + suite.Require().NotPanics(testFunc, "SendCoins") + if len(tc.expErr) > 0 { + suite.Assert().EqualError(err, tc.expErr, "SendCoins error") + } else { + suite.Assert().NoError(err, "SendCoins error") + } + if tc.expArgs != nil { + suite.Assert().Equal(tc.expArgs.ctx, actualRestrictionArgs.ctx, "ctx provided to restriction") + suite.Assert().Equal(tc.expArgs.fromAddr, actualRestrictionArgs.fromAddr, "fromAddr provided to restriction") + suite.Assert().Equal(tc.expArgs.toAddr, actualRestrictionArgs.toAddr, "toAddr provided to restriction") + suite.Assert().Equal(tc.expArgs.amt.String(), actualRestrictionArgs.amt.String(), "amt provided to restriction") + } else { + suite.Assert().Nil(actualRestrictionArgs, "args provided to a restriction") + } + fromBal := suite.bankKeeper.GetAllBalances(ctx, fromAddr) + suite.Assert().Equal(tc.expBals.from.String(), fromBal.String(), "fromAddr balance") + to1Bal := suite.bankKeeper.GetAllBalances(ctx, toAddr1) + suite.Assert().Equal(tc.expBals.to1.String(), to1Bal.String(), "toAddr1 balance") + to2Bal := suite.bankKeeper.GetAllBalances(ctx, toAddr2) + suite.Assert().Equal(tc.expBals.to2.String(), to2Bal.String(), "toAddr2 balance") + }) + } +} + func (suite *KeeperTestSuite) TestSendCoins_Invalid_SendLockedCoins() { balances := sdk.NewCoins(newFooCoin(50)) @@ -1441,7 +1966,7 @@ func (suite *KeeperTestSuite) TestMintCoinRestrictions() { } for _, test := range tests { - keeper := suite.bankKeeper.WithMintCoinsRestriction(keeper.MintingRestrictionFn(test.restrictionFn)) + keeper := suite.bankKeeper.WithMintCoinsRestriction(banktypes.MintingRestrictionFn(test.restrictionFn)) for _, testCase := range test.testCases { if testCase.expectPass { suite.mockMintCoins(multiPermAcc) diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 327a9881e779..4eb0fdaa5a7a 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -22,7 +22,11 @@ import ( type SendKeeper interface { ViewKeeper - InputOutputCoins(ctx context.Context, inputs types.Input, outputs []types.Output) error + AppendSendRestriction(restriction types.SendRestrictionFn) + PrependSendRestriction(restriction types.SendRestrictionFn) + ClearSendRestriction() + + 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 @@ -63,6 +67,8 @@ type BaseSendKeeper struct { // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string + + sendRestriction *sendRestriction } func NewBaseSendKeeper( @@ -78,16 +84,32 @@ func NewBaseSendKeeper( } return BaseSendKeeper{ - BaseViewKeeper: NewBaseViewKeeper(cdc, storeService, ak, logger), - cdc: cdc, - ak: ak, - storeService: storeService, - blockedAddrs: blockedAddrs, - authority: authority, - logger: logger, + BaseViewKeeper: NewBaseViewKeeper(cdc, storeService, ak, logger), + cdc: cdc, + ak: ak, + storeService: storeService, + blockedAddrs: blockedAddrs, + authority: authority, + logger: logger, + sendRestriction: newSendRestriction(), } } +// AppendSendRestriction adds the provided SendRestrictionFn to run after previously provided restrictions. +func (k BaseSendKeeper) AppendSendRestriction(restriction types.SendRestrictionFn) { + k.sendRestriction.append(restriction) +} + +// PrependSendRestriction adds the provided SendRestrictionFn to run before previously provided restrictions. +func (k BaseSendKeeper) PrependSendRestriction(restriction types.SendRestrictionFn) { + k.sendRestriction.prepend(restriction) +} + +// ClearSendRestriction removes the send restriction (if there is one). +func (k BaseSendKeeper) ClearSendRestriction() { + k.sendRestriction.clear() +} + // GetAuthority returns the x/bank module's authority. func (k BaseSendKeeper) GetAuthority() string { return k.authority @@ -137,8 +159,14 @@ func (k BaseSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, sdkCtx := sdk.UnwrapSDKContext(ctx) + var outAddress sdk.AccAddress for _, out := range outputs { - outAddress, err := k.ak.AddressCodec().StringToBytes(out.Address) + outAddress, err = k.ak.AddressCodec().StringToBytes(out.Address) + if err != nil { + return err + } + + outAddress, err = k.sendRestriction.apply(ctx, inAddress, outAddress, out.Coins) if err != nil { return err } @@ -150,7 +178,7 @@ func (k BaseSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeTransfer, - sdk.NewAttribute(types.AttributeKeyRecipient, out.Address), + sdk.NewAttribute(types.AttributeKeyRecipient, outAddress.String()), sdk.NewAttribute(sdk.AttributeKeyAmount, out.Coins.String()), ), ) @@ -172,7 +200,13 @@ func (k BaseSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, // SendCoins transfers amt coins from a sending account to a receiving account. // An error is returned upon failure. func (k BaseSendKeeper) SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error { - err := k.subUnlockedCoins(ctx, fromAddr, amt) + var err error + err = k.subUnlockedCoins(ctx, fromAddr, amt) + if err != nil { + return err + } + + toAddr, err = k.sendRestriction.apply(ctx, fromAddr, toAddr, amt) if err != nil { return err } @@ -423,3 +457,41 @@ func (k BaseSendKeeper) getSendEnabledOrDefault(ctx context.Context, denom strin return defaultVal } + +// 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 sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { + if r == nil || r.fn == nil { + return toAddr, nil + } + return r.fn(ctx, fromAddr, toAddr, amt) +} diff --git a/x/bank/types/restrictions.go b/x/bank/types/restrictions.go new file mode 100644 index 000000000000..6d7d6fe6bc86 --- /dev/null +++ b/x/bank/types/restrictions.go @@ -0,0 +1,99 @@ +package types + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// A MintingRestrictionFn can restrict minting of coins. +type MintingRestrictionFn func(ctx context.Context, coins sdk.Coins) error + +var _ MintingRestrictionFn = NoOpMintingRestrictionFn + +// NoOpMintingRestrictionFn is a no-op MintingRestrictionFn. +func NoOpMintingRestrictionFn(_ context.Context, _ sdk.Coins) error { + return nil +} + +// Then creates a composite restriction that runs this one then the provided second one. +func (r MintingRestrictionFn) Then(second MintingRestrictionFn) MintingRestrictionFn { + return ComposeMintingRestrictions(r, second) +} + +// ComposeMintingRestrictions combines multiple MintingRestrictionFn into one. +// nil entries are ignored. +// If all entries are nil, nil is returned. +// If exactly one entry is not nil, it is returned. +// Otherwise, a new MintingRestrictionFn is returned that runs the non-nil restrictions in the order they are given. +// The composition runs each minting restriction until an error is encountered and returns that error. +func ComposeMintingRestrictions(restrictions ...MintingRestrictionFn) MintingRestrictionFn { + toRun := make([]MintingRestrictionFn, 0, len(restrictions)) + for _, r := range restrictions { + if r != nil { + toRun = append(toRun, r) + } + } + switch len(toRun) { + case 0: + return nil + case 1: + return toRun[0] + } + return func(ctx context.Context, coins sdk.Coins) error { + for _, r := range toRun { + err := r(ctx, coins) + if err != nil { + return err + } + } + return nil + } +} + +// 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) + +var _ SendRestrictionFn = NoOpSendRestrictionFn + +// NoOpSendRestrictionFn is a no-op SendRestrictionFn. +func NoOpSendRestrictionFn(_ context.Context, _, toAddr sdk.AccAddress, _ sdk.Coins) (sdk.AccAddress, error) { + return toAddr, nil +} + +// Then creates a composite restriction that runs this one then the provided second one. +func (r SendRestrictionFn) Then(second SendRestrictionFn) SendRestrictionFn { + return ComposeSendRestrictions(r, second) +} + +// ComposeSendRestrictions combines multiple SendRestrictionFn into one. +// nil entries are ignored. +// If all entries are nil, nil is returned. +// If exactly one entry is not nil, it is returned. +// Otherwise, a new SendRestrictionFn is returned that runs the non-nil restrictions in the order they are given. +// The composition runs each send restriction until an error is encountered and returns that error, +// otherwise it returns the toAddr of the last send restriction. +func ComposeSendRestrictions(restrictions ...SendRestrictionFn) SendRestrictionFn { + toRun := make([]SendRestrictionFn, 0, len(restrictions)) + for _, r := range restrictions { + if r != nil { + toRun = append(toRun, r) + } + } + switch len(toRun) { + case 0: + return nil + case 1: + return toRun[0] + } + return func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { + var err error + for _, r := range toRun { + toAddr, err = r(ctx, fromAddr, toAddr, amt) + if err != nil { + return toAddr, err + } + } + return toAddr, err + } +} diff --git a/x/bank/types/restrictions_test.go b/x/bank/types/restrictions_test.go new file mode 100644 index 000000000000..47cb047da9fc --- /dev/null +++ b/x/bank/types/restrictions_test.go @@ -0,0 +1,919 @@ +package types_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// MintingRestrictionArgs are the args provided to a MintingRestrictionFn function. +type MintingRestrictionArgs struct { + Name string + Coins sdk.Coins +} + +// MintingRestrictionTestHelper is a struct with stuff helpful for testing the MintingRestrictionFn stuff. +type MintingRestrictionTestHelper struct { + Calls []*MintingRestrictionArgs +} + +func NewMintingRestrictionTestHelper() *MintingRestrictionTestHelper { + return &MintingRestrictionTestHelper{Calls: make([]*MintingRestrictionArgs, 0, 2)} +} + +// RecordCall makes note that the provided args were used as a funcion call. +func (s *MintingRestrictionTestHelper) RecordCall(name string, coins sdk.Coins) { + s.Calls = append(s.Calls, s.NewArgs(name, coins)) +} + +// NewCalls is just a shorter way to create a []*MintingRestrictionArgs. +func (s *MintingRestrictionTestHelper) NewCalls(args ...*MintingRestrictionArgs) []*MintingRestrictionArgs { + return args +} + +// NewArgs creates a new MintingRestrictionArgs. +func (s *MintingRestrictionTestHelper) NewArgs(name string, coins sdk.Coins) *MintingRestrictionArgs { + return &MintingRestrictionArgs{ + Name: name, + Coins: coins, + } +} + +// NamedRestriction creates a new MintingRestrictionFn function that records the arguments it's called with and returns nil. +func (s *MintingRestrictionTestHelper) NamedRestriction(name string) types.MintingRestrictionFn { + return func(_ context.Context, coins sdk.Coins) error { + s.RecordCall(name, coins) + return nil + } +} + +// ErrorRestriction creates a new MintingRestrictionFn function that returns an error. +func (s *MintingRestrictionTestHelper) ErrorRestriction(message string) types.MintingRestrictionFn { + return func(_ context.Context, coins sdk.Coins) error { + s.RecordCall(message, coins) + return errors.New(message) + } +} + +// MintingRestrictionTestParams are parameters to test regarding calling a MintingRestrictionFn. +type MintingRestrictionTestParams struct { + // ExpNil is whether to expect the provided MintingRestrictionFn to be nil. + // If it is true, the rest of these test params are ignored. + ExpNil bool + // Coins is the MintingRestrictionFn coins input. + Coins sdk.Coins + // ExpErr is the expected return error string. + ExpErr string + // ExpCalls is the args of all the MintingRestrictionFn calls that end up being made. + ExpCalls []*MintingRestrictionArgs +} + +// TestActual tests the provided MintingRestrictionFn using the provided test parameters. +func (s *MintingRestrictionTestHelper) TestActual(t *testing.T, tp *MintingRestrictionTestParams, actual types.MintingRestrictionFn) { + t.Helper() + if tp.ExpNil { + require.Nil(t, actual, "resulting MintingRestrictionFn") + } else { + require.NotNil(t, actual, "resulting MintingRestrictionFn") + s.Calls = s.Calls[:0] + err := actual(sdk.Context{}, tp.Coins) + if len(tp.ExpErr) != 0 { + assert.EqualError(t, err, tp.ExpErr, "composite MintingRestrictionFn output error") + } else { + assert.NoError(t, err, "composite MintingRestrictionFn output error") + } + assert.Equal(t, tp.ExpCalls, s.Calls, "args given to funcs in composite MintingRestrictionFn") + } +} + +func TestMintingRestriction_Then(t *testing.T) { + coins := sdk.NewCoins(sdk.NewInt64Coin("acoin", 2), sdk.NewInt64Coin("bcoin", 4)) + + h := NewMintingRestrictionTestHelper() + + tests := []struct { + name string + base types.MintingRestrictionFn + second types.MintingRestrictionFn + exp *MintingRestrictionTestParams + }{ + { + name: "nil nil", + base: nil, + second: nil, + exp: &MintingRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "nil noop", + base: nil, + second: h.NamedRestriction("noop"), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("noop", coins)), + }, + }, + { + name: "noop nil", + base: h.NamedRestriction("noop"), + second: nil, + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("noop", coins)), + }, + }, + { + name: "noop noop", + base: h.NamedRestriction("noop1"), + second: h.NamedRestriction("noop2"), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("noop1", coins), h.NewArgs("noop2", coins)), + }, + }, + { + name: "noop error", + base: h.NamedRestriction("noop"), + second: h.ErrorRestriction("this is a test error"), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpErr: "this is a test error", + ExpCalls: h.NewCalls(h.NewArgs("noop", coins), h.NewArgs("this is a test error", coins)), + }, + }, + { + name: "error noop", + base: h.ErrorRestriction("another test error"), + second: h.NamedRestriction("noop"), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpErr: "another test error", + ExpCalls: h.NewCalls(h.NewArgs("another test error", coins)), + }, + }, + { + name: "error error", + base: h.ErrorRestriction("first test error"), + second: h.ErrorRestriction("second test error"), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpErr: "first test error", + ExpCalls: h.NewCalls(h.NewArgs("first test error", coins)), + }, + }, + { + name: "double chain", + base: types.ComposeMintingRestrictions(h.NamedRestriction("r1"), h.NamedRestriction("r2")), + second: types.ComposeMintingRestrictions(h.NamedRestriction("r3"), h.NamedRestriction("r4")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls( + h.NewArgs("r1", coins), + h.NewArgs("r2", coins), + h.NewArgs("r3", coins), + h.NewArgs("r4", coins), + ), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var actual types.MintingRestrictionFn + testFunc := func() { + actual = tc.base.Then(tc.second) + } + require.NotPanics(t, testFunc, "MintingRestrictionFn.Then") + h.TestActual(t, tc.exp, actual) + }) + } +} + +func TestComposeMintingRestrictions(t *testing.T) { + rz := func(rs ...types.MintingRestrictionFn) []types.MintingRestrictionFn { + return rs + } + coins := sdk.NewCoins(sdk.NewInt64Coin("ccoin", 8), sdk.NewInt64Coin("dcoin", 16)) + + h := NewMintingRestrictionTestHelper() + + tests := []struct { + name string + input []types.MintingRestrictionFn + exp *MintingRestrictionTestParams + }{ + { + name: "nil list", + input: nil, + exp: &MintingRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "empty list", + input: rz(), + exp: &MintingRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "only nil entry", + input: rz(nil), + exp: &MintingRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "five nil entries", + input: rz(nil, nil, nil, nil, nil), + exp: &MintingRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "only noop entry", + input: rz(h.NamedRestriction("noop")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("noop", coins)), + }, + }, + { + name: "only error entry", + input: rz(h.ErrorRestriction("test error")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpErr: "test error", + ExpCalls: h.NewCalls(h.NewArgs("test error", coins)), + }, + }, + { + name: "noop nil nil", + input: rz(h.NamedRestriction("noop"), nil, nil), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("noop", coins)), + }, + }, + { + name: "nil noop nil", + input: rz(nil, h.NamedRestriction("noop"), nil), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("noop", coins)), + }, + }, + { + name: "nil nil noop", + input: rz(nil, nil, h.NamedRestriction("noop")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("noop", coins)), + }, + }, + { + name: "noop noop nil", + input: rz(h.NamedRestriction("r1"), h.NamedRestriction("r2"), nil), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("r1", coins), h.NewArgs("r2", coins)), + }, + }, + { + name: "noop nil noop", + input: rz(h.NamedRestriction("r1"), nil, h.NamedRestriction("r2")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("r1", coins), h.NewArgs("r2", coins)), + }, + }, + { + name: "nil noop noop", + input: rz(nil, h.NamedRestriction("r1"), h.NamedRestriction("r2")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("r1", coins), h.NewArgs("r2", coins)), + }, + }, + { + name: "noop noop noop", + input: rz(h.NamedRestriction("r1"), h.NamedRestriction("r2"), h.NamedRestriction("r3")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpCalls: h.NewCalls(h.NewArgs("r1", coins), h.NewArgs("r2", coins), h.NewArgs("r3", coins)), + }, + }, + { + name: "err noop noop", + input: rz(h.ErrorRestriction("first error"), h.NamedRestriction("r2"), h.NamedRestriction("r3")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpErr: "first error", + ExpCalls: h.NewCalls(h.NewArgs("first error", coins)), + }, + }, + { + name: "noop err noop", + input: rz(h.NamedRestriction("r1"), h.ErrorRestriction("second error"), h.NamedRestriction("r3")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpErr: "second error", + ExpCalls: h.NewCalls(h.NewArgs("r1", coins), h.NewArgs("second error", coins)), + }, + }, + { + name: "noop noop err", + input: rz(h.NamedRestriction("r1"), h.NamedRestriction("r2"), h.ErrorRestriction("third error")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpErr: "third error", + ExpCalls: h.NewCalls(h.NewArgs("r1", coins), h.NewArgs("r2", coins), h.NewArgs("third error", coins)), + }, + }, + { + name: "noop err err", + input: rz(h.NamedRestriction("r1"), h.ErrorRestriction("second error"), h.ErrorRestriction("third error")), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpErr: "second error", + ExpCalls: h.NewCalls(h.NewArgs("r1", coins), h.NewArgs("second error", coins)), + }, + }, + { + name: "big bang", + input: rz( + h.NamedRestriction("r1"), nil, h.NamedRestriction("r2"), nil, + h.NamedRestriction("r3"), h.NamedRestriction("r4"), h.NamedRestriction("r5"), + nil, h.NamedRestriction("r6"), h.NamedRestriction("r7"), nil, + h.NamedRestriction("r8"), nil, nil, h.ErrorRestriction("oops, an error"), + h.NamedRestriction("r9"), nil, h.NamedRestriction("ra"), // Not called. + ), + exp: &MintingRestrictionTestParams{ + Coins: coins, + ExpErr: "oops, an error", + ExpCalls: h.NewCalls( + h.NewArgs("r1", coins), + h.NewArgs("r2", coins), + h.NewArgs("r3", coins), + h.NewArgs("r4", coins), + h.NewArgs("r5", coins), + h.NewArgs("r6", coins), + h.NewArgs("r7", coins), + h.NewArgs("r8", coins), + h.NewArgs("oops, an error", coins), + ), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var actual types.MintingRestrictionFn + testFunc := func() { + actual = types.ComposeMintingRestrictions(tc.input...) + } + require.NotPanics(t, testFunc, "ComposeMintingRestrictions") + h.TestActual(t, tc.exp, actual) + }) + } +} + +func TestNoOpMintingRestrictionFn(t *testing.T) { + var err error + testFunc := func() { + err = types.NoOpMintingRestrictionFn(sdk.Context{}, sdk.Coins{}) + } + require.NotPanics(t, testFunc, "NoOpMintingRestrictionFn") + assert.NoError(t, err, "NoOpSendRestrictionFn error") +} + +// SendRestrictionArgs are the args provided to a SendRestrictionFn function. +type SendRestrictionArgs struct { + Name string + FromAddr sdk.AccAddress + ToAddr sdk.AccAddress + Coins sdk.Coins +} + +// SendRestrictionTestHelper is a struct with stuff helpful for testing the SendRestrictionFn stuff. +type SendRestrictionTestHelper struct { + Calls []*SendRestrictionArgs +} + +func NewSendRestrictionTestHelper() *SendRestrictionTestHelper { + return &SendRestrictionTestHelper{Calls: make([]*SendRestrictionArgs, 0, 2)} +} + +// RecordCall makes note that the provided args were used as a funcion call. +func (s *SendRestrictionTestHelper) RecordCall(name string, fromAddr, toAddr sdk.AccAddress, coins sdk.Coins) { + s.Calls = append(s.Calls, s.NewArgs(name, fromAddr, toAddr, coins)) +} + +// NewCalls is just a shorter way to create a []*SendRestrictionArgs. +func (s *SendRestrictionTestHelper) NewCalls(args ...*SendRestrictionArgs) []*SendRestrictionArgs { + return args +} + +// NewArgs creates a new SendRestrictionArgs. +func (s *SendRestrictionTestHelper) NewArgs(name string, fromAddr, toAddr sdk.AccAddress, coins sdk.Coins) *SendRestrictionArgs { + return &SendRestrictionArgs{ + Name: name, + FromAddr: fromAddr, + ToAddr: toAddr, + Coins: coins, + } +} + +// NamedRestriction creates a new SendRestrictionFn function that records the arguments it's called with and returns the provided toAddr. +func (s *SendRestrictionTestHelper) NamedRestriction(name string) types.SendRestrictionFn { + return func(_ context.Context, fromAddr, toAddr sdk.AccAddress, coins sdk.Coins) (sdk.AccAddress, error) { + s.RecordCall(name, fromAddr, toAddr, coins) + return toAddr, nil + } +} + +// NewToRestriction creates a new SendRestrictionFn function that returns a different toAddr than provided. +func (s *SendRestrictionTestHelper) NewToRestriction(name string, addr sdk.AccAddress) types.SendRestrictionFn { + return func(_ context.Context, fromAddr, toAddr sdk.AccAddress, coins sdk.Coins) (sdk.AccAddress, error) { + s.RecordCall(name, fromAddr, toAddr, coins) + return addr, nil + } +} + +// ErrorRestriction creates a new SendRestrictionFn function that returns a nil toAddr and an error. +func (s *SendRestrictionTestHelper) ErrorRestriction(message string) types.SendRestrictionFn { + return func(_ context.Context, fromAddr, toAddr sdk.AccAddress, coins sdk.Coins) (sdk.AccAddress, error) { + s.RecordCall(message, fromAddr, toAddr, coins) + return nil, errors.New(message) + } +} + +// SendRestrictionTestParams are parameters to test regarding calling a SendRestrictionFn. +type SendRestrictionTestParams struct { + // ExpNil is whether to expect the provided SendRestrictionFn to be nil. + // If it is true, the rest of these test params are ignored. + ExpNil bool + // FromAddr is the SendRestrictionFn fromAddr input. + FromAddr sdk.AccAddress + // ToAddr is the SendRestrictionFn toAddr input. + ToAddr sdk.AccAddress + // Coins is the SendRestrictionFn coins input. + Coins sdk.Coins + // ExpAddr is the expected return address. + ExpAddr sdk.AccAddress + // ExpErr is the expected return error string. + ExpErr string + // ExpCalls is the args of all the SendRestrictionFn calls that end up being made. + ExpCalls []*SendRestrictionArgs +} + +// TestActual tests the provided SendRestrictionFn using the provided test parameters. +func (s *SendRestrictionTestHelper) TestActual(t *testing.T, tp *SendRestrictionTestParams, actual types.SendRestrictionFn) { + t.Helper() + if tp.ExpNil { + require.Nil(t, actual, "resulting SendRestrictionFn") + } else { + require.NotNil(t, actual, "resulting SendRestrictionFn") + s.Calls = s.Calls[:0] + addr, err := actual(sdk.Context{}, tp.FromAddr, tp.ToAddr, tp.Coins) + if len(tp.ExpErr) != 0 { + assert.EqualError(t, err, tp.ExpErr, "composite SendRestrictionFn output error") + } else { + assert.NoError(t, err, "composite SendRestrictionFn output error") + } + assert.Equal(t, tp.ExpAddr, addr, "composite SendRestrictionFn output address") + assert.Equal(t, tp.ExpCalls, s.Calls, "args given to funcs in composite SendRestrictionFn") + } +} + +func TestSendRestriction_Then(t *testing.T) { + fromAddr := sdk.AccAddress("fromaddr____________") + addr0 := sdk.AccAddress("0addr_______________") + addr1 := sdk.AccAddress("1addr_______________") + addr2 := sdk.AccAddress("2addr_______________") + addr3 := sdk.AccAddress("3addr_______________") + addr4 := sdk.AccAddress("4addr_______________") + coins := sdk.NewCoins(sdk.NewInt64Coin("ecoin", 32), sdk.NewInt64Coin("fcoin", 64)) + + h := NewSendRestrictionTestHelper() + + tests := []struct { + name string + base types.SendRestrictionFn + second types.SendRestrictionFn + exp *SendRestrictionTestParams + }{ + { + name: "nil nil", + base: nil, + second: nil, + exp: &SendRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "nil noop", + base: nil, + second: h.NamedRestriction("noop"), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr1, + Coins: coins, + ExpAddr: addr1, + ExpCalls: h.NewCalls(h.NewArgs("noop", fromAddr, addr1, coins)), + }, + }, + { + name: "noop nil", + base: h.NamedRestriction("noop"), + second: nil, + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr1, + Coins: coins, + ExpAddr: addr1, + ExpCalls: h.NewCalls(h.NewArgs("noop", fromAddr, addr1, coins)), + }, + }, + { + name: "noop noop", + base: h.NamedRestriction("noop1"), + second: h.NamedRestriction("noop2"), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr1, + Coins: coins, + ExpAddr: addr1, + ExpCalls: h.NewCalls( + h.NewArgs("noop1", fromAddr, addr1, coins), + h.NewArgs("noop2", fromAddr, addr1, coins), + ), + }, + }, + { + name: "setter setter", + base: h.NewToRestriction("r1", addr2), + second: h.NewToRestriction("r2", addr3), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr1, + Coins: coins, + ExpAddr: addr3, + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr1, coins), + h.NewArgs("r2", fromAddr, addr2, coins), + ), + }, + }, + { + name: "setter error", + base: h.NewToRestriction("r1", addr2), + second: h.ErrorRestriction("this is a test error"), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr1, + Coins: coins, + ExpAddr: nil, + ExpErr: "this is a test error", + ExpCalls: h.NewCalls(h.NewArgs( + "r1", fromAddr, addr1, coins), + h.NewArgs("this is a test error", fromAddr, addr2, coins), + ), + }, + }, + { + name: "error setter", + base: h.ErrorRestriction("another test error"), + second: h.NewToRestriction("r2", addr3), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr1, + Coins: coins, + ExpAddr: nil, + ExpErr: "another test error", + ExpCalls: h.NewCalls(h.NewArgs("another test error", fromAddr, addr1, coins)), + }, + }, + { + name: "error error", + base: h.ErrorRestriction("first test error"), + second: h.ErrorRestriction("second test error"), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr1, + Coins: coins, + ExpAddr: nil, + ExpErr: "first test error", + ExpCalls: h.NewCalls(h.NewArgs("first test error", fromAddr, addr1, coins)), + }, + }, + { + name: "double chain", + base: types.ComposeSendRestrictions(h.NewToRestriction("r1", addr1), h.NewToRestriction("r2", addr2)), + second: types.ComposeSendRestrictions(h.NewToRestriction("r3", addr3), h.NewToRestriction("r4", addr4)), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr0, + Coins: coins, + ExpAddr: addr4, + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr0, coins), + h.NewArgs("r2", fromAddr, addr1, coins), + h.NewArgs("r3", fromAddr, addr2, coins), + h.NewArgs("r4", fromAddr, addr3, coins), + ), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var actual types.SendRestrictionFn + testFunc := func() { + actual = tc.base.Then(tc.second) + } + require.NotPanics(t, testFunc, "SendRestrictionFn.Then") + h.TestActual(t, tc.exp, actual) + }) + } +} + +func TestComposeSendRestrictions(t *testing.T) { + rz := func(rs ...types.SendRestrictionFn) []types.SendRestrictionFn { + return rs + } + fromAddr := sdk.AccAddress("fromaddr____________") + addr0 := sdk.AccAddress("0addr_______________") + addr1 := sdk.AccAddress("1addr_______________") + addr2 := sdk.AccAddress("2addr_______________") + addr3 := sdk.AccAddress("3addr_______________") + addr4 := sdk.AccAddress("4addr_______________") + coins := sdk.NewCoins(sdk.NewInt64Coin("gcoin", 128), sdk.NewInt64Coin("hcoin", 256)) + + h := NewSendRestrictionTestHelper() + + tests := []struct { + name string + input []types.SendRestrictionFn + exp *SendRestrictionTestParams + }{ + { + name: "nil list", + input: nil, + exp: &SendRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "empty list", + input: rz(), + exp: &SendRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "only nil entry", + input: rz(nil), + exp: &SendRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "five nil entries", + input: rz(nil, nil, nil, nil, nil), + exp: &SendRestrictionTestParams{ + ExpNil: true, + }, + }, + { + name: "only noop entry", + input: rz(h.NamedRestriction("noop")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr0, + Coins: coins, + ExpAddr: addr0, + ExpCalls: h.NewCalls(h.NewArgs("noop", fromAddr, addr0, coins)), + }, + }, + { + name: "only error entry", + input: rz(h.ErrorRestriction("test error")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr0, + Coins: coins, + ExpAddr: nil, + ExpErr: "test error", + ExpCalls: h.NewCalls(h.NewArgs("test error", fromAddr, addr0, coins)), + }, + }, + { + name: "noop nil nil", + input: rz(h.NamedRestriction("noop"), nil, nil), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr0, + Coins: coins, + ExpAddr: addr0, + ExpCalls: h.NewCalls(h.NewArgs("noop", fromAddr, addr0, coins)), + }, + }, + { + name: "nil noop nil", + input: rz(nil, h.NamedRestriction("noop"), nil), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr1, + Coins: coins, + ExpAddr: addr1, + ExpCalls: h.NewCalls(h.NewArgs("noop", fromAddr, addr1, coins)), + }, + }, + { + name: "nil nil noop", + input: rz(nil, nil, h.NamedRestriction("noop")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr2, + Coins: coins, + ExpAddr: addr2, + ExpCalls: h.NewCalls(h.NewArgs("noop", fromAddr, addr2, coins)), + }, + }, + { + name: "noop noop nil", + input: rz(h.NamedRestriction("r1"), h.NamedRestriction("r2"), nil), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr0, + Coins: coins, + ExpAddr: addr0, + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr0, coins), + h.NewArgs("r2", fromAddr, addr0, coins), + ), + }, + }, + { + name: "noop nil noop", + input: rz(h.NamedRestriction("r1"), nil, h.NamedRestriction("r2")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr1, + Coins: coins, + ExpAddr: addr1, + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr1, coins), + h.NewArgs("r2", fromAddr, addr1, coins), + ), + }, + }, + { + name: "nil noop noop", + input: rz(nil, h.NamedRestriction("r1"), h.NamedRestriction("r2")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr2, + Coins: coins, + ExpAddr: addr2, + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr2, coins), + h.NewArgs("r2", fromAddr, addr2, coins), + ), + }, + }, + { + name: "noop noop noop", + input: rz(h.NamedRestriction("r1"), h.NamedRestriction("r2"), h.NamedRestriction("r3")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr3, + Coins: coins, + ExpAddr: addr3, + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr3, coins), + h.NewArgs("r2", fromAddr, addr3, coins), + h.NewArgs("r3", fromAddr, addr3, coins), + ), + }, + }, + { + name: "err noop noop", + input: rz(h.ErrorRestriction("first error"), h.NamedRestriction("r2"), h.NamedRestriction("r3")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr4, + Coins: coins, + ExpAddr: nil, + ExpErr: "first error", + ExpCalls: h.NewCalls(h.NewArgs("first error", fromAddr, addr4, coins)), + }, + }, + { + name: "noop err noop", + input: rz(h.NamedRestriction("r1"), h.ErrorRestriction("second error"), h.NamedRestriction("r3")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr4, + Coins: coins, + ExpAddr: nil, + ExpErr: "second error", + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr4, coins), + h.NewArgs("second error", fromAddr, addr4, coins), + ), + }, + }, + { + name: "noop noop err", + input: rz(h.NamedRestriction("r1"), h.NamedRestriction("r2"), h.ErrorRestriction("third error")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr4, + Coins: coins, + ExpAddr: nil, + ExpErr: "third error", + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr4, coins), + h.NewArgs("r2", fromAddr, addr4, coins), + h.NewArgs("third error", fromAddr, addr4, coins), + ), + }, + }, + { + name: "new-to err err", + input: rz(h.NewToRestriction("r1", addr0), h.ErrorRestriction("second error"), h.ErrorRestriction("third error")), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr4, + Coins: coins, + ExpAddr: nil, + ExpErr: "second error", + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr4, coins), + h.NewArgs("second error", fromAddr, addr0, coins), + ), + }, + }, + { + name: "big bang", + input: rz( + h.NamedRestriction("r1"), nil, h.NewToRestriction("r2", addr1), // Called with orig toAddr. + nil, h.NamedRestriction("r3"), h.NewToRestriction("r4", addr2), // Called with addr1 toAddr. + h.NewToRestriction("r5", addr3), // Called with addr2 toAddr. + nil, h.NamedRestriction("r6"), h.NewToRestriction("r7", addr4), // Called with addr3 toAddr. + nil, h.NamedRestriction("r8"), nil, nil, h.ErrorRestriction("oops, an error"), // Called with addr4 toAddr. + h.NewToRestriction("r9", addr0), nil, h.NamedRestriction("ra"), // Not called. + ), + exp: &SendRestrictionTestParams{ + FromAddr: fromAddr, + ToAddr: addr0, + Coins: coins, + ExpAddr: nil, + ExpErr: "oops, an error", + ExpCalls: h.NewCalls( + h.NewArgs("r1", fromAddr, addr0, coins), + h.NewArgs("r2", fromAddr, addr0, coins), + h.NewArgs("r3", fromAddr, addr1, coins), + h.NewArgs("r4", fromAddr, addr1, coins), + h.NewArgs("r5", fromAddr, addr2, coins), + h.NewArgs("r6", fromAddr, addr3, coins), + h.NewArgs("r7", fromAddr, addr3, coins), + h.NewArgs("r8", fromAddr, addr4, coins), + h.NewArgs("oops, an error", fromAddr, addr4, coins), + ), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var actual types.SendRestrictionFn + testFunc := func() { + actual = types.ComposeSendRestrictions(tc.input...) + } + require.NotPanics(t, testFunc, "ComposeSendRestrictions") + h.TestActual(t, tc.exp, actual) + }) + } +} + +func TestNoOpSendRestrictionFn(t *testing.T) { + expAddr := sdk.AccAddress("__expectedaddr__") + var addr sdk.AccAddress + var err error + testFunc := func() { + addr, err = types.NoOpSendRestrictionFn(sdk.Context{}, sdk.AccAddress("first_addr"), expAddr, sdk.Coins{}) + } + require.NotPanics(t, testFunc, "NoOpSendRestrictionFn") + assert.NoError(t, err, "NoOpSendRestrictionFn error") + assert.Equal(t, expAddr, addr, "NoOpSendRestrictionFn addr") +} diff --git a/x/gov/testutil/expected_keepers_mocks.go b/x/gov/testutil/expected_keepers_mocks.go index 0dc0ac0b6365..95200e2b8fb8 100644 --- a/x/gov/testutil/expected_keepers_mocks.go +++ b/x/gov/testutil/expected_keepers_mocks.go @@ -159,6 +159,18 @@ func (mr *MockBankKeeperMockRecorder) AllBalances(arg0, arg1 interface{}) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllBalances", reflect.TypeOf((*MockBankKeeper)(nil).AllBalances), arg0, arg1) } +// AppendSendRestriction mocks base method. +func (m *MockBankKeeper) AppendSendRestriction(restriction types0.SendRestrictionFn) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AppendSendRestriction", restriction) +} + +// AppendSendRestriction indicates an expected call of AppendSendRestriction. +func (mr *MockBankKeeperMockRecorder) AppendSendRestriction(restriction interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendSendRestriction", reflect.TypeOf((*MockBankKeeper)(nil).AppendSendRestriction), restriction) +} + // Balance mocks base method. func (m *MockBankKeeper) Balance(arg0 context.Context, arg1 *types0.QueryBalanceRequest) (*types0.QueryBalanceResponse, error) { m.ctrl.T.Helper() @@ -202,6 +214,18 @@ func (mr *MockBankKeeperMockRecorder) BurnCoins(ctx, moduleName, amt interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BurnCoins", reflect.TypeOf((*MockBankKeeper)(nil).BurnCoins), ctx, moduleName, amt) } +// ClearSendRestriction mocks base method. +func (m *MockBankKeeper) ClearSendRestriction() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ClearSendRestriction") +} + +// ClearSendRestriction indicates an expected call of ClearSendRestriction. +func (mr *MockBankKeeperMockRecorder) ClearSendRestriction() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearSendRestriction", reflect.TypeOf((*MockBankKeeper)(nil).ClearSendRestriction)) +} + // DelegateCoins mocks base method. func (m *MockBankKeeper) DelegateCoins(ctx context.Context, delegatorAddr, moduleAccAddr types.AccAddress, amt types.Coins) error { m.ctrl.T.Helper() @@ -548,17 +572,17 @@ func (mr *MockBankKeeperMockRecorder) InitGenesis(arg0, arg1 interface{}) *gomoc } // InputOutputCoins mocks base method. -func (m *MockBankKeeper) InputOutputCoins(ctx context.Context, inputs types0.Input, outputs []types0.Output) error { +func (m *MockBankKeeper) InputOutputCoins(ctx context.Context, input types0.Input, outputs []types0.Output) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InputOutputCoins", ctx, inputs, outputs) + ret := m.ctrl.Call(m, "InputOutputCoins", ctx, input, outputs) ret0, _ := ret[0].(error) return ret0 } // InputOutputCoins indicates an expected call of InputOutputCoins. -func (mr *MockBankKeeperMockRecorder) InputOutputCoins(ctx, inputs, outputs interface{}) *gomock.Call { +func (mr *MockBankKeeperMockRecorder) InputOutputCoins(ctx, input, outputs interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InputOutputCoins", reflect.TypeOf((*MockBankKeeper)(nil).InputOutputCoins), ctx, inputs, outputs) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InputOutputCoins", reflect.TypeOf((*MockBankKeeper)(nil).InputOutputCoins), ctx, input, outputs) } // IsSendEnabledCoin mocks base method. @@ -711,6 +735,18 @@ func (mr *MockBankKeeperMockRecorder) Params(arg0, arg1 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Params", reflect.TypeOf((*MockBankKeeper)(nil).Params), arg0, arg1) } +// PrependSendRestriction mocks base method. +func (m *MockBankKeeper) PrependSendRestriction(restriction types0.SendRestrictionFn) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "PrependSendRestriction", restriction) +} + +// PrependSendRestriction indicates an expected call of PrependSendRestriction. +func (mr *MockBankKeeperMockRecorder) PrependSendRestriction(restriction interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrependSendRestriction", reflect.TypeOf((*MockBankKeeper)(nil).PrependSendRestriction), restriction) +} + // SendCoins mocks base method. func (m *MockBankKeeper) SendCoins(ctx context.Context, fromAddr, toAddr types.AccAddress, amt types.Coins) error { m.ctrl.T.Helper() @@ -963,7 +999,7 @@ func (mr *MockBankKeeperMockRecorder) ValidateBalance(ctx, addr interface{}) *go } // WithMintCoinsRestriction mocks base method. -func (m *MockBankKeeper) WithMintCoinsRestriction(arg0 keeper.MintingRestrictionFn) keeper.BaseKeeper { +func (m *MockBankKeeper) WithMintCoinsRestriction(arg0 types0.MintingRestrictionFn) keeper.BaseKeeper { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "WithMintCoinsRestriction", arg0) ret0, _ := ret[0].(keeper.BaseKeeper)