diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 3ee0d6ba..5842442f 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -132,16 +132,22 @@ func (k MoveSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, types.NewCoinSpentEvent(fromAddr, input.Coins), ) - // emit coin received events and do address caching - addrMap := make(map[string][]byte) - for _, output := range outputs { + // emit coin received events and resolve recipients + outAddrs := make([]sdk.AccAddress, len(outputs)) + for i, output := range outputs { addr, err := k.ak.AddressCodec().StringToBytes(output.Address) if err != nil { return err } - // cache bytes address - addrMap[output.Address] = addr + // apply the registered send restriction, consistent with SendCoins + addr, err = k.sendRestriction.apply(ctx, fromAddr, addr, output.Coins) + if err != nil { + return err + } + + // cache the post-restriction recipient + outAddrs[i] = addr // emit coin received event sdkCtx.EventManager().EmitEvent( @@ -152,7 +158,7 @@ func (k MoveSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeTransfer, - sdk.NewAttribute(types.AttributeKeyRecipient, output.Address), + sdk.NewAttribute(types.AttributeKeyRecipient, sdk.AccAddress(addr).String()), sdk.NewAttribute(sdk.AttributeKeyAmount, output.Coins.String()), ), ) @@ -165,9 +171,9 @@ func (k MoveSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, recipients := make([]sdk.AccAddress, 0, len(outputs)) amounts := make([]math.Int, 0, len(outputs)) - for _, output := range outputs { + for i, output := range outputs { // Create account if recipient does not exist. - outAddress := addrMap[output.Address] + outAddress := outAddrs[i] accExists := k.ak.HasAccount(ctx, outAddress) if !accExists { defer telemetry.IncrCounter(1, "new", "account") diff --git a/x/bank/keeper/send_test.go b/x/bank/keeper/send_test.go new file mode 100644 index 00000000..fd1ca0df --- /dev/null +++ b/x/bank/keeper/send_test.go @@ -0,0 +1,45 @@ +package keeper_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + bankkeeper "github.com/initia-labs/initia/x/bank/keeper" +) + +func TestInputOutputCoinsAppliesSendRestriction(t *testing.T) { + ctx, input := createDefaultTestInput(t) + + bk, ok := input.BankKeeper.(bankkeeper.BaseKeeper) + require.True(t, ok) + + denom := testDenoms[0] + coins := sdk.NewCoins(sdk.NewInt64Coin(denom, 1_000)) + + from := input.Faucet.NewFundedAccount(ctx, sdk.NewInt64Coin(denom, 1_000_000)) + to := input.Faucet.NewFundedAccount(ctx, sdk.NewInt64Coin(denom, 1)) + + // register a restriction that records every invocation + var calls int + bk.AppendSendRestriction(func(_ context.Context, _, toAddr sdk.AccAddress, _ sdk.Coins) (sdk.AccAddress, error) { + calls++ + return toAddr, nil + }) + + // SendCoins (MsgSend) must apply the restriction + calls = 0 + require.NoError(t, bk.SendCoins(ctx, from, to, coins)) + require.Equal(t, 1, calls) + + // InputOutputCoins (MsgMultiSend) must apply the restriction once per output + calls = 0 + in := banktypes.Input{Address: from.String(), Coins: coins} + outputs := []banktypes.Output{{Address: to.String(), Coins: coins}} + require.NoError(t, bk.InputOutputCoins(ctx, in, outputs)) + require.Equal(t, 1, calls) +}