# paymaster will refund spentOnPubdata to user

zkSync bootloader passes gasLeft+reservedGas as maxRefundedGas to paymaster postOp without subtracting spentOnPubdata, letting users siphon gas refunds.

- Payout: Contest reward
- Severity: high
- Program: zkSync
- Credited: bin2chen
- Disclosed: 2024-05-29
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2024-03-zksync-findings/issues/78
- Library page: https://bugbountylibrary.com/r/c4%3A2024-03-zksync%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm technique:source-review vuln:business-logic vuln:fee-bypass

**Target:** bootloader.yul refundCurrentL2Transaction / paymaster postOp
**Root cause:** refundCurrentL2Transaction computes _maxRefundedGas as safeAdd(gasLeft, reservedGas) for the paymaster postOp call without subtracting spentOnPubdata, so the paymaster refunds gas already consumed by pubdata.
**Steps:**
1. Read bootloader.yul refundCurrentL2Transaction: paymaster branch calls ZKSYNC_NEAR_CALL_callPostOp with `safeAdd(gasLeft, reservedGas, "jkl")` as maxRefundedGas.
2. Note spentOnPubdata is only computed after the postOp call and subtracted from totalRefund, not from the value passed to postOp.
3. Submit an L2 tx with a paymaster; postOp receives maxRefundedGas inflated by spentOnPubdata.
4. Paymaster refunds the inflated amount to the user, returning gas already spent on pubdata.
5. Repeat to slowly siphon gas funds from the paymaster/system.
**How found:** Code review of the bootloader refund path after a change moved pubdata gas collection to the final step; noticed maxRefundedGas passed to postOp omitted the spentOnPubdata subtraction applied later.
**Exploitation:** Attacker uses a paymaster on L2 transactions so postOp's maxRefundedGas includes pubdata gas; the paymaster over-refunds, and repeating drains gas value from the system.
**Escalation:** Repeated transactions accumulate the surplus refund, slowly siphoning funds.
**Insight:** When a refund/charge computation is split across two points (pre-postOp and post-postOp), check that every deduction applied to the final total is also reflected in values handed to hooks like postOp, else hooks over-refund.
