# Native gas tokens can become stuck in ASDRouter contract

ASDRouter's _sendASD never refunds excess msg.value on successful asD redemption, permanently trapping native CANTO in the contract.

- Payout: Audit contest
- Severity: high
- Program: Canto
- Found by: 0xTheC0der
- Disclosed: 2024-05-03
- Write-up quality: 6/10
- Original: https://code4rena.com/reports/2024-03-canto#h-01
- Library page: https://bugbountylibrary.com/r/c4%3A2024-03-canto%3AH-01
- Tags: area:web3 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:** ASDRouter lzCompose / _sendASD native value handling
**Root cause:** _sendASD only forwards _payload._feeForSend (or zero for same-chain) of msg.value and never refunds the remainder, violating the invariant that the router's native balance is always zero.
**Steps:**
1. Call `ASDRouter.lzCompose(...)` with a valid payload and `msg.value = 1 ether` for a same-chain asD redemption.
2. Observe `ASDSent` emitted and ASD transferred to receiver.
3. Check `ethers.provider.getBalance(ASDRouter.target)` — equals 1 ether, not 0.
4. Check refundAddress balance — unchanged; excess CANTO is stuck with no recovery path.
**How found:** Code review of _sendASD refund branches: noticed error paths refund msg.value but success paths only spend _feeForSend, then checked the README invariant that router native balance should always be zero and wrote a test asserting balance==0.
**Exploitation:** User (or integrator) sends msg.value greater than the required fee; on success the surplus stays in the router with no withdrawal function, permanently locking funds.
**Insight:** When auditing cross-chain composers, diff every msg.value branch: error paths often refund fully while success paths silently drop the surplus — check the protocol's stated balance invariants against each terminal branch.
