Reproduced Exploit
Liquorice: 67 inflation rounds make a 293 DAI deposit mint 1 share, and the attacker keeps 72.713 DAI
borrow() increases totalDeposits without minting shares, so 1 share backs 2 wei. supply then mints amount totalSupply / totalDeposits. Each round deposits 2 totalDeposits - 1 and withdraws the previous total, which is the sequence in the MixBytes report. After 67 rounds the rate is 1475739525896764…
Loss
72.713 DAI taken from a 293 DAI deposit, about 25 percent. The victim receives 1 wei of shares
Chain
Ethereum
Category
arithmetic
Date
Feb 2025
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 49433-inflation-attack-mixbytes-none-liquorice-markdown. Standalone Foundry PoC and full write-up: 49433-inflation-attack-mixbytes-none-liquorice-markdown_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/arithmetic/rounding · vuln/logic/missing-check
Key info#
| Field | Value |
|---|---|
| Loss | 72.713 DAI taken from a 293 DAI deposit, about 25 percent. The victim receives 1 wei of shares |
| Vulnerable contract | LendingPool.supply |
| Attacker EOA | 0x1111111111111111111111111111111111111111 |
| Attack contract | Exploit |
| Attack tx | Exploit.run() |
| Chain / block / date | Ethereum model · block 0 · 2025-02 |
| Compiler | solc 0.8.27, cancun, via IR |
| Bug class | Share mint is amount * totalSupply / totalDeposits with no virtual offset |
TL;DR#
borrow() increases totalDeposits without minting shares, so 1 share backs 2 wei. supply then mints amount * totalSupply / totalDeposits. Each round deposits 2 * totalDeposits - 1 and withdraws the previous total, which is the sequence in the MixBytes report. After 67 rounds the rate is 147573952589676412929, about 147.57 DAI per share. The victim's 293 DAI deposit rounds down to 1 share. The attacker withdraws half the pool and profits 72713023705161793535 wei, 72.713 DAI. A pool with the report's 1000 wei virtual shares gives the victim about 290 shares and the attacker no profit.
Background#
MixBytes' Liquorice audit, timeline 22 October 2024 through 21 February 2025. Finding 3, "Inflation attack", shows borrow() nudging totalDeposits while totalSupply stays put, then an exponential deposit/withdraw loop. Step 67 in the report reaches an exchange rate of about 147 ether per share. The victim deposits 293 ether and the attacker profits about 25 percent. The recommended mitigation is virtual shares, with 1000 wei named in the report.
The vulnerable code#
} else if (virtualShares == 0) {
// @> VULN: shares round down against totalDeposits / totalSupply with no virtual offset
shares = amount * totalSupply / totalDeposits;
}
borrow is the seed:
function borrow(uint256 amount) external {
require(dai.transferFrom(msg.sender, address(this), amount), "pull");
totalDeposits += amount;
}
Root cause#
The share price is totalDeposits / totalSupply with a floor on the way in. One extra wei of deposits against one share starts a doubling recurrence. A later depositor whose amount is below two rates receives one share for almost two shares of assets, and the attacker redeems half.
Preconditions#
- The attacker can
supplyandborrowagainst DAI. This run starts from an empty pool. - The victim holds 293 DAI and deposits after the 67 rounds.
- No virtual shares are configured. The control uses 1000.
Attack walkthrough#
supply(1)mints 1 share.borrow(1)makestotalDeposits2 and leavestotalSupplyat 1.- Sixty-seven times,
supply(2 * totalDeposits - 1)thenwithdraw(old totalDeposits). The attacker ends holding 1 share backed by147573952589676412929wei. - The victim deposits 293 DAI.
293e18 / ratefloors to 1 share. withdrawAllpays the attacker's share: half ofrate + 293 DAI, which is220286976294838206464wei.- Profit versus the attacker's starting 300 DAI is
72713023705161793535wei. The victim's DAI balance is 0. - The control runs the same 67 rounds with 1000 virtual shares. The victim receives more than 290 shares and the attacker's ending DAI is below the start.
Diagrams#
Impact#
The victim pays 293 DAI and receives 1 wei of shares. The attacker, who left about 147.57 DAI in the pool, withdraws 220.286 DAI. The difference, 72.713 DAI, is about 24.8 percent of the victim's deposit, matching the report's "approximately 25%". The virtual-share control does not profit.
Remediation#
Mint and burn shares against totalSupply + VIRTUAL and totalDeposits + VIRTUAL. The report's suggested virtual amount is 1000 wei. This run's control uses that offset.
How to reproduce#
cd 49433-inflation-attack-mixbytes-none-liquorice-markdown_exp
forge test -vv
test_exploit_inflated_rate_takes_72_ether_from_victim asserts the rate, a 1-share victim mint, and profit 72713023705161793535. test_control_virtual_shares_give_victim_about_290 asserts the victim receives more than 290 shares and the attacker ends below the starting balance. Captured output is output.txt.
Sources#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/49433-inflation-attack-mixbytes-none-liquorice-markdown.md
- Report: https://github.com/mixbytes/audits_public/blob/master/Liquorice/README.md#3-inflation-attack
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 49433-inflation-attack-mixbytes-none-liquorice-markdown_exp (evm-hack-registry mirror).
- AuditVault finding: 49433-inflation-attack-mixbytes-none-liquorice-markdown.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.