From 8653b197efe8246cf9bc8576bb572a88ec9c0789 Mon Sep 17 00:00:00 2001 From: codchen Date: Mon, 22 Jul 2024 11:37:49 +0800 Subject: [PATCH] Add recipient checker logics to bank send (#526) ## Describe your changes and provide context Allow checkers to be registered with bank send keeper so that certain recipients can be blocked. For example, to block a temp address (created by EVM module before address association) from receiving after its main address is associated, one would register a checker as follows: ``` app.BankKeeper.RegisterRecipientChecker(func(ctx sdk.Context, recipient sdk.AccAddress) bool { castEvmAddress := common.BytesToAddress(recipient) _, isAssociated := app.EvmKeeper.GetSeiAddress(ctx, castEvmAddress) return !isAssociated }) ``` ## Testing performed to validate your change unit test --- types/errors/errors.go | 3 +++ x/bank/keeper/keeper_test.go | 18 +++++++++++++++++ x/bank/keeper/send.go | 38 +++++++++++++++++++++++++++++------- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/types/errors/errors.go b/types/errors/errors.go index 6c547343d..a4f56890d 100644 --- a/types/errors/errors.go +++ b/types/errors/errors.go @@ -159,6 +159,9 @@ var ( // ErrEVMVMError defines an error for an evm vm error (eg. revert) ErrEVMVMError = Register(RootCodespace, 45, "evm reverted") + // ErrInvalidRecipient defines an error for sending to disallowed recipients in bank + ErrInvalidRecipient = Register(RootCodespace, 46, "invalid bank recipient") + // ErrPanic is only set when we recover from a panic, so we know to // redact potentially sensitive system info ErrPanic = Register(UndefinedCodespace, 111222, "panic") diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index 3dac1035d..67dbbca78 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -1361,6 +1361,24 @@ func (suite *IntegrationTestSuite) TestSetDenomMetaData() { suite.Require().Equal(metadata[1].GetDenomUnits()[1].GetAliases(), actualMetadata.GetDenomUnits()[1].GetAliases()) } +func (suite *IntegrationTestSuite) TestCanSendTo() { + app, ctx := suite.app, suite.ctx + badAddr := sdk.AccAddress([]byte("addr1_______________")) + goodAddr := sdk.AccAddress([]byte("addr2_______________")) + sourceAddr := sdk.AccAddress([]byte("addr3_______________")) + app.AccountKeeper.SetAccount(ctx, app.AccountKeeper.NewAccountWithAddress(ctx, badAddr)) + app.AccountKeeper.SetAccount(ctx, app.AccountKeeper.NewAccountWithAddress(ctx, goodAddr)) + app.AccountKeeper.SetAccount(ctx, app.AccountKeeper.NewAccountWithAddress(ctx, sourceAddr)) + suite.Require().NoError(simapp.FundAccount(app.BankKeeper, ctx, sourceAddr, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(100))))) + checker := func(_ sdk.Context, addr sdk.AccAddress) bool { return !addr.Equals(badAddr) } + app.BankKeeper.RegisterRecipientChecker(checker) + amt := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10))) + suite.Require().Nil(app.BankKeeper.SendCoins(ctx, sourceAddr, goodAddr, amt)) + suite.Require().NotNil(app.BankKeeper.SendCoins(ctx, sourceAddr, badAddr, amt)) + suite.Require().Nil(app.BankKeeper.SendCoinsAndWei(ctx, sourceAddr, goodAddr, sdk.OneInt(), sdk.ZeroInt())) + suite.Require().NotNil(app.BankKeeper.SendCoinsAndWei(ctx, sourceAddr, badAddr, sdk.OneInt(), sdk.ZeroInt())) +} + func (suite *IntegrationTestSuite) TestIterateAllDenomMetaData() { app, ctx := suite.app, suite.ctx diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 0b8512292..bfc7ea9d5 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -31,8 +31,11 @@ type SendKeeper interface { IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error BlockedAddr(addr sdk.AccAddress) bool + RegisterRecipientChecker(RecipientChecker) } +type RecipientChecker = func(ctx sdk.Context, recipient sdk.AccAddress) bool + var _ SendKeeper = (*BaseSendKeeper)(nil) var OneUseiInWei sdk.Int = sdk.NewInt(1_000_000_000_000) @@ -47,7 +50,8 @@ type BaseSendKeeper struct { paramSpace paramtypes.Subspace // list of addresses that are restricted from receiving transactions - blockedAddrs map[string]bool + blockedAddrs map[string]bool + recipientCheckers *[]RecipientChecker } func NewBaseSendKeeper( @@ -55,12 +59,13 @@ func NewBaseSendKeeper( ) BaseSendKeeper { return BaseSendKeeper{ - BaseViewKeeper: NewBaseViewKeeper(cdc, storeKey, ak), - cdc: cdc, - ak: ak, - storeKey: storeKey, - paramSpace: paramSpace, - blockedAddrs: blockedAddrs, + BaseViewKeeper: NewBaseViewKeeper(cdc, storeKey, ak), + cdc: cdc, + ak: ak, + storeKey: storeKey, + paramSpace: paramSpace, + blockedAddrs: blockedAddrs, + recipientCheckers: &[]RecipientChecker{}, } } @@ -232,6 +237,9 @@ func (k BaseSendKeeper) SubUnlockedCoins(ctx sdk.Context, addr sdk.AccAddress, a // AddCoins increase the addr balance by the given amt. Fails if the provided amt is invalid. // It emits a coin received event. func (k BaseSendKeeper) AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, checkNeg bool) error { + if !k.CanSendTo(ctx, addr) { + return sdkerrors.ErrInvalidRecipient + } if !amt.IsValid() { return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) } @@ -360,6 +368,9 @@ func (k BaseSendKeeper) SubWei(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Int } func (k BaseSendKeeper) AddWei(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Int) (err error) { + if !k.CanSendTo(ctx, addr) { + return sdkerrors.ErrInvalidRecipient + } if amt.Equal(sdk.ZeroInt()) { return nil } @@ -405,6 +416,19 @@ func (k BaseSendKeeper) SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to return nil } +func (k BaseSendKeeper) RegisterRecipientChecker(rc RecipientChecker) { + *k.recipientCheckers = append(*k.recipientCheckers, rc) +} + +func (k BaseSendKeeper) CanSendTo(ctx sdk.Context, recipient sdk.AccAddress) bool { + for _, rc := range *k.recipientCheckers { + if !rc(ctx, recipient) { + return false + } + } + return true +} + func SplitUseiWeiAmount(amt sdk.Int) (sdk.Int, sdk.Int) { return amt.Quo(OneUseiInWei), amt.Mod(OneUseiInWei) }