# Events emitted by L2 bridge hooks are discarded

Initia L2 bridge hooks discard the handler's sdk.Result, dropping emitted events; a withdrawal initiated via hook burns L2 tokens with no event, permanently locking funds.

- Payout: Contest reward
- Severity: medium
- Program: Initia
- Credited: RadiantLabs
- Disclosed: 2025-04-10
- Write-up quality: 7/10
- Original: https://code4rena.com/reports/2025-01-initia-rollup-modules#m-03
- Library page: https://bugbountylibrary.com/r/c4%3A2025-01-initia-rollup-modules%3AM-03
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** Cosmos SDK opchild keeper FinalizeTokenDeposit bridge hook execution
**Root cause:** The deposit loop calls handler(cacheCtx, msg) and discards the returned sdk.Result, which holds events; the msg_service_router replaces the EventManager with a fresh one, so hook events never reach the parent context.
**Steps:**
1. Read opchild/keeper/deposit.go L54-66: handler(cacheCtx, msg) result discarded.
2. Note msg_service_router.go L171-172 swaps in a new EventManager, so events live only in the discarded Result.
3. Compare with MsgExecuteMessages handler which appends res.GetEvents() and emits them.
4. Craft a signed hook tx containing MsgInitiateTokenWithdrawal and pass it as hook data to MsgFinalizeTokenDeposit.
5. Assert EventTypeInitiateTokenWithdrawal is absent from the parent EventManager after deposit.
**How found:** Code review of the deposit hook loop, noticing the discarded sdk.Result, then cross-referencing the SDK msg_service_router's fresh EventManager and the contrasting MsgExecuteMessages event-copy pattern.
**Exploitation:** User bridges L1 gas tokens with a hook payload that initiates an L2 withdrawal of another token; the withdrawal handler burns L2 tokens and emits InitiateTokenWithdrawal, but the event is dropped, so the L1 bot never relays it and MsgFinalizeTokenWithdrawal can never succeed.
**Escalation:** Loss of the bridging event means burned L2 tokens are never ported to L1, permanently locking funds.
**Insight:** In Cosmos SDK, any direct handler() call bypasses baseapp's event plumbing; always check whether the returned Result's events are re-emitted to the parent context, especially where events drive off-chain relayers/bots.
