diff --git a/protocol/x/delaymsg/keeper/dispatch.go b/protocol/x/delaymsg/keeper/dispatch.go index 967ebc753c..473417b4ea 100644 --- a/protocol/x/delaymsg/keeper/dispatch.go +++ b/protocol/x/delaymsg/keeper/dispatch.go @@ -17,6 +17,13 @@ func DispatchMessagesForBlock(k types.DelayMsgKeeper, ctx sdk.Context) { return } + // Maintain a list of events emitted by all delayed messages executed in this block. + // As message handlers create new event managers, such emitted events need to be + // explicitly propagated to the current context. + // Note: events in EndBlocker can be found in `end_block_events` in response from + // `/block_results` endpoint. + var events sdk.Events + // Execute all delayed messages scheduled for this block and delete them from the store. for _, id := range blockMessageIds.Ids { delayedMsg, found := k.GetMessage(ctx, id) @@ -33,13 +40,21 @@ func DispatchMessagesForBlock(k types.DelayMsgKeeper, ctx sdk.Context) { if err = abci.RunCached(ctx, func(ctx sdk.Context) error { handler := k.Router().Handler(msg) - _, err := handler(ctx, msg) - return err + res, err := handler(ctx, msg) + if err != nil { + return err + } + // Append events emitted in message handler to `events`. + events = append(events, res.GetEvents()...) + return nil }); err != nil { k.Logger(ctx).Error("failed to execute delayed message with id %v: %v", id, err) } } + // Propagate events emitted in message handlers to current context. + ctx.EventManager().EmitEvents(events) + for _, id := range blockMessageIds.Ids { if err := k.DeleteMessage(ctx, id); err != nil { k.Logger(ctx).Error("failed to delete delayed message: %w", err) diff --git a/protocol/x/delaymsg/keeper/dispatch_test.go b/protocol/x/delaymsg/keeper/dispatch_test.go index e7396d42c2..418ca4b107 100644 --- a/protocol/x/delaymsg/keeper/dispatch_test.go +++ b/protocol/x/delaymsg/keeper/dispatch_test.go @@ -1,10 +1,12 @@ package keeper_test import ( - sdkmath "cosmossdk.io/math" "fmt" + "reflect" "testing" + sdkmath "cosmossdk.io/math" + "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" cometbfttypes "github.com/cometbft/cometbft/types" @@ -504,3 +506,57 @@ func TestSendDelayedCompleteBridgeMessage_Failure(t *testing.T) { _, found = tApp.App.DelayMsgKeeper.GetBlockMessageIds(ctx, 2) require.False(t, found) } + +// This test case verifies that events emitted from message executions are correctly +// propagated to the base context. +func TestDispatchMessagesForBlock_EventsArePropagated(t *testing.T) { + ctx, k, _, _, bankKeeper, _ := keepertest.DelayMsgKeepers(t) + // Mint coins to the bridge module account so that it has enough balance for completing bridges. + err := bankKeeper.MintCoins(ctx, bridgetypes.ModuleName, sdk.NewCoins(BridgeGenesisAccountBalance)) + require.NoError(t, err) + + // Delay a complete bridge message, which calls bank transfer that emits a transfer event. + bridgeEvent := bridgetypes.BridgeEvent{ + Id: 1, + Coin: sdk.NewCoin("dv4tnt", sdkmath.NewInt(1_000)), + Address: constants.AliceAccAddress.String(), + EthBlockHeight: 0, + } + _, err = k.DelayMessageByBlocks( + ctx, + &bridgetypes.MsgCompleteBridge{ + Authority: authtypes.NewModuleAddress(types.ModuleName).String(), + Event: bridgeEvent, + }, + 0, + ) + require.NoError(t, err) + + // Sanity check: messages appear for block 0. + blockMessageIds, found := k.GetBlockMessageIds(ctx, 0) + require.True(t, found) + require.Equal(t, []uint32{0}, blockMessageIds.Ids) + + // Dispatch messages for block 0. + keeper.DispatchMessagesForBlock(k, ctx) + + _, found = k.GetBlockMessageIds(ctx, 0) + require.False(t, found) + + emittedEvents := ctx.EventManager().Events() + expectedTransferEvent := sdk.NewEvent( + "transfer", + sdk.NewAttribute("recipient", bridgeEvent.Address), + sdk.NewAttribute("sender", BridgeAccountAddress.String()), + sdk.NewAttribute("amount", bridgeEvent.Coin.String()), + ) + + // Verify that emitted events contains the expected transfer event exactly once. + foundExpectedTransferEvent := 0 + for _, emittedEvent := range emittedEvents { + if reflect.DeepEqual(expectedTransferEvent, emittedEvent) { + foundExpectedTransferEvent++ + } + } + require.Equal(t, 1, foundExpectedTransferEvent) +}