From bf64e0d4a5b5894fc5e7b0f944c9b5ff8f51111b Mon Sep 17 00:00:00 2001 From: traviolus <42636319+traviolus@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:22:22 +0700 Subject: [PATCH 1/2] fix(bank): apply send restriction in InputOutputCoins --- x/bank/keeper/send.go | 6 +++++ x/bank/keeper/send_test.go | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 x/bank/keeper/send_test.go diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 3ee0d6ba..3482a9e8 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -140,6 +140,12 @@ func (k MoveSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, return err } + // apply the registered send restriction, consistent with SendCoins + addr, err = k.sendRestriction.apply(ctx, fromAddr, addr, output.Coins) + if err != nil { + return err + } + // cache bytes address addrMap[output.Address] = addr 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) +} From e99667aa5343941f60d0d531bc2e45a7bc1cfc2b Mon Sep 17 00:00:00 2001 From: traviolus <42636319+traviolus@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:41:49 +0700 Subject: [PATCH 2/2] fix(bank): use post-restriction recipient consistently --- x/bank/keeper/send.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 3482a9e8..5842442f 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -132,9 +132,9 @@ 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 @@ -146,8 +146,8 @@ func (k MoveSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, return err } - // cache bytes address - addrMap[output.Address] = addr + // cache the post-restriction recipient + outAddrs[i] = addr // emit coin received event sdkCtx.EventManager().EmitEvent( @@ -158,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()), ), ) @@ -171,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")