Full Report
In Uniswap, the skim function is used for a recovery mechanism in the case of the reserves storage (uint112) failing. If the difference between the current balance of the pair (balanceOf on the ERC20 token) and the reserves are different, the sender of the request gets the difference. Although this can save the contract, this is a very dangerous function to have in. In the case of this particular coin, the balanceOf function is controlled the true balance of the user, _largeTotal, and _totalSupply. If any of these change, the balanceOf call will also change. The function transfer will update the totalSupply whenever a transfer occurs. The problem is that there is no validation on whether the sender and recipient are the same! This means that the _totalSupply can be inflated indefinitely. Since the price of the balanceOf function relies upon the totalSupply, it will increase the result! This means that the cost of the reserves and the amount that the tokens has is different. Using the skim function allows a user to withdraw funds now. In particular, an attacker needs to call skim using the contract address over and over again. Since the totalSupply is updated over and over again and this changes the price, the funds of the Uniswap pool can be stolen using the skim. Overall, a very interesting attack exploiting how Uniswap works in conjunction with a bad vulnerability within the ERC20 token.
Analysis Summary
# Vulnerability: Inflation-Based Skim Exploitation in Deflationary/Reflect ERC20 Tokens
## CVE Details
- **CVE ID:** N/A (General Smart Contract Design Flaw / Logic Error)
- **CVSS Score:** 9.1 (Critical) - *Estimated based on total loss of pool liquidity*
- **CWE:** CWE-682: Incorrect Calculation; CWE-20: Improper Input Validation
## Affected Systems
- **Products:** Uniswap V2-compatible Decentralized Exchanges (DEX) and custom ERC20 tokens.
- **Versions:** Uniswap V2 Core (`UniswapV2Pair.sol`).
- **Configurations:** Tokens that implement a "reflection" or "rebase" mechanism where `balanceOf` is dynamically calculated using a volatile `_totalSupply` or `_largeTotal`, and fail to validate that the sender and recipient are distinct addresses in the `transfer` function.
## Vulnerability Description
The vulnerability arises from a logic flaw in a specific ERC20 token's `transfer` function when interacting with the Uniswap `skim()` mechanism.
1. **Token Logic Flaw:** The token's `transfer` function updates the `_totalSupply` (or a global scaling factor) without checking if `msg.sender == recipient`. If a user transfers tokens to themselves, the `_totalSupply` is manipulated (inflated) while the user's actual balance remains constant.
2. **Price Manipulation:** The token's `balanceOf` function is calculated dynamically based on the `_totalSupply`. By artificially inflating the supply through self-transfers, an attacker causes the `balanceOf(pairAddress)` to report a much higher value than the pair's recorded `reserves`.
3. **Skim Exploitation:** Uniswap’s `skim(address to)` function is designed to recover excess tokens if the contract’s token balance exceeds its recorded `reserves`. By manipulating the token's internal state to report an inflated balance, the attacker triggers `skim()` to send the "excess" (the difference between the fake balance and real reserves) to their own address, effectively draining the pool.
## Exploitation
- **Status:** Exploited in the wild (Commonly seen in "safemoon-clone" or reflect-token rugs/exploits).
- **Complexity:** Low
- **Attack Vector:** Network (Smart Contract Interaction)
## Impact
- **Confidentiality:** None
- **Integrity:** High (Manipulation of pool balances and token supply)
- **Availability:** High (Total drain of liquidity from the Uniswap Pair)
## Remediation
### Patches
- **Token Side:** Update the `_transfer` function to include a requirement that the sender and recipient cannot be the same address: `require(sender != recipient, "Self-transfer not allowed");`.
- **Token Side:** Ensure that `_totalSupply` or reflection multipliers are not updated in a way that allows for circular inflation during self-transfers.
### Workarounds
- **DEX Side:** Uniswap V2 pairs are immutable; therefore, the primary workaround is for liquidity providers to withdraw liquidity if a token is found to have this vulnerability.
- **Protocol Side:** Implementation of "Sync" instead of "Skim" in some scenarios, though `sync()` does not solve the underlying token accounting error.
## Detection
- **Indicators of compromise:** Multiple `transfer` events where `from` and `to` addresses are identical (the Pair contract address or the attacker address), followed by frequent calls to the `skim()` function.
- **Detection methods and tools:**
- Static analysis of ERC20 contracts using Slither (check for `balanceOf` dependencies on global state variables).
- Monitoring tools like Forta for large discrepancies between `token.balanceOf(pair)` and `pair.getReserves()`.
## References
- Uniswap V2 Core Documentation: hxxps://docs[.]uniswap[.]org/contracts/v2/reference/smart-contracts/pair#skim
- SWC Registry (SWC-123): hxxps://swcregistry[.]io/docs/SWC-123 (Requirement Violation)
- Medium Analysis of Skim Exploits: hxxps://medium[.]com/ (Original article deleted/unavailable)