# A user can call callOutSigned without paying for gas by reentering anyExecute with Virtual Account

Reentering anyExecute via Virtual Account callOut deletes initialGas/userFeeInfo, so the original callOutSigned pays no execution gas.

- Payout: Contest reward
- Severity: medium
- Program: Maia DAO Ecosystem
- Credited: xuwinnie
- Disclosed: 2023-09-18
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-05-maia-findings/issues/331
- Library page: https://bugbountylibrary.com/r/c4%3A2023-05-maia%3AM-30
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:business-logic vuln:fee-bypass vuln:reentrancy

**Target:** Root Bridge Agent anyExecute / Arbitrum Branch Bridge Agent callOut
**Root cause:** anyExecute stores initialGas and userFeeInfo in storage; a reentrant anyExecute overwrites and then deletes them, so the outer call's _payExecutionGas sees initialGas == 0 and skips charging.
**Steps:**
1. Initiate callOutSigned from a non-local branch chain so _fromChain != localChainId in _payExecutionGas.
2. During root-chain execution, have the Virtual Account call callOut on the Arbitrum Branch Bridge Agent, reentering Root Bridge Agent anyExecute.
3. Inner anyExecute sets initialGas/userFeeInfo, runs, then _payExecutionGas deletes both and returns early (fromChain == localChainId).
4. Outer anyExecute finishes execution; _payExecutionGas sees initialGas == 0 and does not charge gas.
**How found:** Code reading of anyExecute/_payExecutionGas: noticed initialGas and userFeeInfo are storage variables deleted in _payExecutionGas, and that Virtual Account external calls can reenter anyExecute, making the outer check see initialGas == 0.
**Exploitation:** Attacker triggers a cross-chain callOutSigned whose execution reenters anyExecute via Virtual Account callOut; the inner call wipes initialGas and userFeeInfo, so the outer _payExecutionGas passes without replenishing gas or charging the user.
**Insight:** When gas-accounting state (initialGas, fee info) lives in storage and is deleted in a payment function, look for reentrancy paths that invoke the same entrypoint so the outer frame's accounting is zeroed before it is read.
