# Self-match and self-funded purchase flaws

Payable functions that self-fund instead of requiring msg.value, and matching that allows buyer == seller

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

## How it works
Two assumptions break. First, a payable function assumes the caller supplies the funds, but instead checks the caller's balance and pays from the contract itself (e.g. via `payable(this).call`), so the contract's own ETH is spent on the caller's behalf. Second, an order-matching engine assumes buyer and seller are distinct parties, but never enforces it, so one address can be both sides of a trade. In both cases the contract treats an internal transfer as if it were an external payment, and value that should come from outside is instead drawn from the contract or from the same party.

## Where it shows up
- Payable functions that read `address(msg.sender).balance` or call `payable(this).call{value:...}` instead of requiring `msg.value`.
- Vendor/referrer payout logic combined with self-funding, where repeated calls drain proxy ETH [E1](https://bugbountylibrary.com/r/dhl%3A20250603-pegaball.md).
- Fixed-reward payouts for a token whose cheapest mint path is below the reward [E2](https://bugbountylibrary.com/r/dhl%3A20250725-whereismydragontreasure.md).
- Order matching functions (`matchOneToOneOrders`, `matchOneToManyOrders`, `matchOrders`, `takeMultipleOneOrders`, `takeOrders`) with no `seller != buyer` check [E3](https://bugbountylibrary.com/r/c4%3A2022-06-infinity%3AM-04.md).

## How to review
For every payable entry point, confirm the invariant: the ETH spent equals `msg.value` supplied by the caller, and the contract's own balance is never used to fund a purchase. Trace any `payable(this)` or balance check and ask who pays. For matching engines, confirm the invariant: buyer and seller are distinct addresses on every path, including batch and many-order variants. For reward payouts, confirm the invariant: the fixed reward is not greater than the cheapest way to acquire the rewarded token across all mint and acquire paths [E2](https://bugbountylibrary.com/r/dhl%3A20250725-whereismydragontreasure.md).

## Variants seen
- Self-funded purchase via `payable(this).call` plus vendor payout, draining proxy ETH [E1](https://bugbountylibrary.com/r/dhl%3A20250603-pegaball.md).
- Fixed reward exceeding the effective mint cost through a recipe path, netting a profit per redemption [E2](https://bugbountylibrary.com/r/dhl%3A20250725-whereismydragontreasure.md).
- Missing `seller != buyer` constraint across all order matching functions, enabling self-matching [E3](https://bugbountylibrary.com/r/c4%3A2022-06-infinity%3AM-04.md).

## Impact and severity
E1 drained proxy ETH through repeated self-funded calls. E2 netted about $47k by redeeming cards minted below the fixed reward. E3 caused under-fill and fund loss or griefing. Severity rises when the flaw is repeatable, when the contract holds pooled ETH, and when multiple entry points share the same missing check.

## Why fixes fail
Fixing one matching function while leaving the batch variants unchecked leaves the same self-match open [E3](https://bugbountylibrary.com/r/c4%3A2022-06-infinity%3AM-04.md); the constraint must hold on every path.

## Evidence
- E1: https://bugbountylibrary.com/r/dhl%3A20250603-pegaball.md
- E2: https://bugbountylibrary.com/r/dhl%3A20250725-whereismydragontreasure.md
- E3: https://bugbountylibrary.com/r/c4%3A2022-06-infinity%3AM-04.md
