diff --git a/sei-cosmos/x/bank/keeper/send.go b/sei-cosmos/x/bank/keeper/send.go index b630dee27e..a017ec0fa2 100644 --- a/sei-cosmos/x/bank/keeper/send.go +++ b/sei-cosmos/x/bank/keeper/send.go @@ -44,6 +44,7 @@ type SendKeeper interface { BlockedAddr(addr sdk.AccAddress) bool RegisterRecipientChecker(RecipientChecker) + CanSendTo(ctx sdk.Context, recipient sdk.AccAddress) bool } type RecipientChecker = func(ctx sdk.Context, recipient sdk.AccAddress) bool diff --git a/sei-cosmos/x/distribution/keeper/keeper.go b/sei-cosmos/x/distribution/keeper/keeper.go index db84b910f5..00771e8c35 100644 --- a/sei-cosmos/x/distribution/keeper/keeper.go +++ b/sei-cosmos/x/distribution/keeper/keeper.go @@ -63,6 +63,10 @@ func (k Keeper) SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, w return types.ErrSetWithdrawAddrDisabled } + if !k.bankKeeper.CanSendTo(ctx, withdrawAddr) { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRecipient, "%s is not allowed to receive external funds", withdrawAddr) + } + ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeSetWithdrawAddress, @@ -74,6 +78,10 @@ func (k Keeper) SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, w return nil } +func (k Keeper) canReceiveWithdrawAddr(ctx sdk.Context, withdrawAddr sdk.AccAddress) bool { + return !k.blockedAddrs[withdrawAddr.String()] && k.bankKeeper.CanSendTo(ctx, withdrawAddr) +} + // withdraw rewards from a delegation func (k Keeper) WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) { val := k.stakingKeeper.Validator(ctx, valAddr) diff --git a/sei-cosmos/x/distribution/keeper/keeper_test.go b/sei-cosmos/x/distribution/keeper/keeper_test.go index be3a01fd43..ad0dc3a002 100644 --- a/sei-cosmos/x/distribution/keeper/keeper_test.go +++ b/sei-cosmos/x/distribution/keeper/keeper_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "testing" + "github.com/ethereum/go-ethereum/common" tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -32,9 +33,55 @@ func TestSetWithdrawAddr(t *testing.T) { err = app.DistrKeeper.SetWithdrawAddr(ctx, addr[0], addr[1]) require.Nil(t, err) + associatedAddr := seiapp.AddTestAddrs(app, ctx, 1, sdk.NewInt(1000000000))[0] + evmAddr := common.HexToAddress("0x1111111111111111111111111111111111111111") + castAddr := sdk.AccAddress(evmAddr[:]) + app.EvmKeeper.SetAddressMapping(ctx, associatedAddr, evmAddr) + require.Error(t, app.DistrKeeper.SetWithdrawAddr(ctx, addr[0], castAddr)) + require.Error(t, app.DistrKeeper.SetWithdrawAddr(ctx, addr[0], distrAcc.GetAddress())) } +func TestAfterValidatorRemovedFallsBackForInvalidWithdrawAddress(t *testing.T) { + app := seiapp.Setup(t, false, false, false) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + params := app.DistrKeeper.GetParams(ctx) + params.WithdrawAddrEnabled = true + app.DistrKeeper.SetParams(ctx, params) + + addr := seiapp.AddTestAddrs(app, ctx, 2, sdk.NewInt(1000000000)) + valAddr := seiapp.ConvertAddrsToValAddrs(addr[:1])[0] + valAccAddr := sdk.AccAddress(valAddr) + associatedAddr := addr[1] + evmAddr := common.HexToAddress("0x2222222222222222222222222222222222222222") + castAddr := sdk.AccAddress(evmAddr[:]) + + require.True(t, app.BankKeeper.CanSendTo(ctx, castAddr)) + require.NoError(t, app.DistrKeeper.SetWithdrawAddr(ctx, valAccAddr, castAddr)) + require.Equal(t, castAddr.String(), app.DistrKeeper.GetDelegatorWithdrawAddr(ctx, valAccAddr).String()) + + app.EvmKeeper.SetAddressMapping(ctx, associatedAddr, evmAddr) + require.False(t, app.BankKeeper.CanSendTo(ctx, castAddr)) + require.Equal(t, valAccAddr.String(), app.DistrKeeper.GetDelegatorWithdrawAddr(ctx, valAccAddr).String()) + + commission := sdk.DecCoins{sdk.NewDecCoin("usei", sdk.NewInt(10))} + coins := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10))) + distrAcc := app.DistrKeeper.GetDistributionAccount(ctx) + require.NoError(t, apptesting.FundModuleAccount(app.BankKeeper, ctx, distrAcc.GetName(), coins)) + app.AccountKeeper.SetModuleAccount(ctx, distrAcc) + + app.DistrKeeper.SetValidatorOutstandingRewards(ctx, valAddr, types.ValidatorOutstandingRewards{Rewards: commission}) + app.DistrKeeper.SetValidatorAccumulatedCommission(ctx, valAddr, types.ValidatorAccumulatedCommission{Commission: commission}) + + balanceBefore := app.BankKeeper.GetBalance(ctx, valAccAddr, "usei") + require.NotPanics(t, func() { + app.DistrKeeper.Hooks().AfterValidatorRemoved(ctx, sdk.ConsAddress{}, valAddr) + }) + balanceAfter := app.BankKeeper.GetBalance(ctx, valAccAddr, "usei") + require.Equal(t, balanceBefore.Amount.Add(sdk.NewInt(10)), balanceAfter.Amount) +} + func TestWithdrawValidatorCommission(t *testing.T) { app := seiapp.Setup(t, false, false, false) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) diff --git a/sei-cosmos/x/distribution/keeper/store.go b/sei-cosmos/x/distribution/keeper/store.go index 5f11374a8d..93e5cfa60e 100644 --- a/sei-cosmos/x/distribution/keeper/store.go +++ b/sei-cosmos/x/distribution/keeper/store.go @@ -14,7 +14,11 @@ func (k Keeper) GetDelegatorWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress if b == nil { return delAddr } - return sdk.AccAddress(b) + withdrawAddr := sdk.AccAddress(b) + if !k.canReceiveWithdrawAddr(ctx, withdrawAddr) { + return delAddr + } + return withdrawAddr } // set the delegator withdraw address diff --git a/sei-cosmos/x/distribution/types/expected_keepers.go b/sei-cosmos/x/distribution/types/expected_keepers.go index 72767aecec..90232fa816 100644 --- a/sei-cosmos/x/distribution/types/expected_keepers.go +++ b/sei-cosmos/x/distribution/types/expected_keepers.go @@ -27,6 +27,7 @@ type BankKeeper interface { SendCoinsFromModuleToModule(ctx sdk.Context, senderModule string, recipientModule string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + CanSendTo(ctx sdk.Context, recipient sdk.AccAddress) bool } // StakingKeeper expected staking keeper (noalias)