# Refund recipient overwrite via missing existence check

Payable calls and refund records that let a later caller replace the stored refund recipient before payout

_Area: Smart contract access control and control flow. Seen in 2 disclosed reports; latest 2025-05-29. Updated 2026-10-23._

## How it works
A contract records who should receive a refund, then pays that stored address later. The flaw is that the record can be written again — or the refund path derives the recipient from caller-supplied data — without checking that a record already exists or that the caller is the original party. Whoever writes last, or whoever supplies the refund field, gets the money. The broken assumption is that the refund recipient is fixed once set; in reality it is mutable or attacker-chosen.

## Where it shows up
- A single stored `disputer`/`challenger`/`claimant`/`refundAddress` slot written by a setter with no `== address(0)` guard [E4](https://bugbountylibrary.com/r/c4%3A2023-06-angle%3AH-02.md).
- Refund accounting keyed on a user-supplied `revertMessage` or `externalId` that is written without an existence check [E2](https://bugbountylibrary.com/r/sherlock%3A2025-05-dodo-cross-chain-dex%3AM-7.md).
- Payable calls forwarding `address(this).balance` as `msg.value` to a function that refunds excess to a caller-supplied address [E1](https://bugbountylibrary.com/r/c4%3A2023-07-tapioca%3AH-48.md).
- Cross-chain payload `from`/refund fields set to `msg.sender` (a source-chain contract) rather than the user's refund address [E3](https://bugbountylibrary.com/r/c4%3A2024-01-decent%3AH-03.md).

## How to review
For each refund path, identify the exact address that will be paid and prove it cannot change after the legitimate party sets it. The invariant: once a refund record exists for an id/actor, no other caller can overwrite it, and the recipient is the party who funded or is owed the refund. Check setters for zero/existence guards and caller authorization; check that refund fields come from trusted state, not calldata; check that `msg.value` forwarded equals the required fee, not the whole balance. Trace address fields end-to-end across chains and confirm the destination recipient is valid there [E3](https://bugbountylibrary.com/r/c4%3A2024-01-decent%3AH-03.md).

## Variants seen
- Frontrun overwrite of an active disputer slot, refunding the wrong party [E4](https://bugbountylibrary.com/r/c4%3A2023-06-angle%3AH-02.md).
- Overwrite of `refundsInfo` via crafted `revertMessage` with no existing-record check [E2](https://bugbountylibrary.com/r/sherlock%3A2025-05-dodo-cross-chain-dex%3AM-7.md).
- Whole-contract balance refunded to attacker-chosen `refundAddress` via `address(this).balance` [E1](https://bugbountylibrary.com/r/c4%3A2023-07-tapioca%3AH-48.md).
- Refund sent to a source-chain `msg.sender` that is wrong/nonexistent on the destination chain [E3](https://bugbountylibrary.com/r/c4%3A2024-01-decent%3AH-03.md).

## Impact and severity
Direct theft or loss of user and contract funds: full ETH balance drained [E1](https://bugbountylibrary.com/r/c4%3A2023-07-tapioca%3AH-48.md), refunds redirected to attacker [E2](https://bugbountylibrary.com/r/sherlock%3A2025-05-dodo-cross-chain-dex%3AM-7.md), honest disputer loses stake [E4](https://bugbountylibrary.com/r/c4%3A2023-06-angle%3AH-02.md), failed cross-chain execution loses funds to a dead address [E3](https://bugbountylibrary.com/r/c4%3A2024-01-decent%3AH-03.md). Severity is high where the overwrite is permissionless and the payout is immediate; worse when the amount is the entire balance or the loss is unrecoverable across chains.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2023-07-tapioca%3AH-48.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2025-05-dodo-cross-chain-dex%3AM-7.md
- E3: https://bugbountylibrary.com/r/c4%3A2024-01-decent%3AH-03.md
- E4: https://bugbountylibrary.com/r/c4%3A2023-06-angle%3AH-02.md
