# Lack of a return value handing in ArbitrumBranchBridgeAgent._performCall() could cause users' deposit to be locked in contract

ArbitrumBranchBridgeAgent._performCall ignores anyExecute's false return, so failed dApp calls don't revert and user bridged deposits get locked.

- Payout: Contest reward
- Severity: high
- Program: Maia DAO Ecosystem
- Credited: peakbolt
- Disclosed: 2023-09-18
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-05-maia-findings/issues/266
- Library page: https://bugbountylibrary.com/r/c4%3A2023-05-maia%3AH-27
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** ArbitrumBranchBridgeAgent._performCall / RootBridgeAgent.anyExecute
**Root cause:** Overridden _performCall calls RootBridgeAgent.anyExecute() directly without checking its success return; anyExecute's try/catch swallows the executor revert, so the outer callOutSignedAndBridge never reverts.
**Steps:**
1. Deploy a mock contract whose test() reverts.
2. Build Multicall2.Call[] targeting the mock, wrap with OutputParams and toChain, prepend funcId 0x02.
3. Mint/approve underlying token to localPort, build DepositInput.
4. Call arbitrumMulticallBridgeAgent.callOutSignedAndBridge{value:1 ether}(packedData, depositInput, 0.5 ether).
5. Observe deposit entry created and marked executed in executionHistory, but LocalPort/user/global balances not credited as expected.
6. retryDeposit() reverts with GasErrorOrRepeatedTx since tx already marked executed.
**How found:** Code reading: noticed the Arbitrum override bypasses AnyCall and lacks the return-value handling AnyCall provides, then cross-referenced RootBridgeAgent.anyExecute's try/catch that expects the caller to revert on success==false.
**Exploitation:** User calls callOutSignedAndBridge to interact with a dApp via multicall; the dApp call reverts inside the executor, anyExecute catches it and returns success=false, but _performCall ignores it, so the bridging is not reversed and the deposit stays locked in the contract while executionHistory marks it executed, blocking retryDeposit/retrieveDeposit.
**Insight:** When a contract overrides a cross-chain call path to a direct local call, check that it replicates the messaging layer's revert-on-failure semantics; try/catch in the callee that returns success=false shifts the revert obligation to the caller.
