Reproduced Exploit
Pear — a withdrawal closes the position and it loses 50 collateral
Liquid cash is 50 ASSET. The position holds 200 of collateral. The caller has 400 of 1,000 shares, worth 100 ASSET. That is more than the cash on hand, so requestWithdrawal closes the position at 75 percent. The close returns 150 and the position loses N = 50. The caller is paid 100 ASSET. The posi…
Chain
Other
Category
logic
Date
Nov 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: 65287-h-01-forced-premature-position-liquidation-through-withdrawa. Standalone Foundry PoC and full write-up: 65287-h-01-forced-premature-position-liquidation-through-withdrawa_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/liquidation-logic · vuln/logic/state-update
Reproduction: a self-contained Foundry PoC that compiles and runs offline with only
forge-std— no fork and no RPC. Trace: output.txt. PoC: test/65287-h-01-forced-premature-position-liquidation-through-withdrawa_exp.sol.
Key info#
| Impact | HIGH — the position of 200 is closed and loses 50 collateral so a 100 ASSET withdrawal can be paid |
| Protocol | Pear vault |
| Vulnerable code | requestWithdrawal closes the position when the share value exceeds liquid cash |
| Bug class | Withdrawal forces a liquidation |
| Finding | Shieldify — Pear vault, November 2025 · id 65287, H-01 |
| Report | Pear vault review |
| Source | AuditVault finding |
| Status | Reproduced here as a local synthetic. |
| Compiler | solc 0.8.27, cancun, via IR |
TL;DR#
Liquid cash is 50 ASSET. The position holds 200 of collateral. The caller has 400 of 1,000 shares, worth 100 ASSET. That is more than the cash on hand, so requestWithdrawal closes the position at 75 percent. The close returns 150 and the position loses N = 50. The caller is paid 100 ASSET. The position is 0.
Control pays only the liquid 50 ASSET. The position stays 200. loss stays 0.
The vulnerable code#
if (assets > liquid) {
uint256 received = position * 75 / 100;
// @> VULN: the withdrawal closes the position and the haircut is lost collateral
loss = position - received;
position = 0;
liquid += received;
}
The fix pays min(assets, liquid) and does not write position or loss.
Root cause#
The queue checks that the caller has the shares. It does not check that the asset value of those shares fits in liquid cash. A withdrawal larger than cash forces the position closed, including at a loss.
Preconditions#
- Liquid cash is 50 ASSET. Position collateral is 200. Total shares are 1,000.
- The caller holds 400 shares and requests all of them.
Attack walkthrough#
- The share value is
400 * 250 / 1000 = 100ASSET. That is greater than 50 liquid. - The position is sold for 150.
lossis 50.positionbecomes 0. - The vault pays the caller 100 ASSET.
Control transfers 50 ASSET and stops. The position is still 200.
Diagrams#
Impact#
The position loses 50 collateral that a cash-only withdrawal would have left in place. Remaining depositors eat that haircut. The caller is paid 100 ASSET.
Remediation#
Cap a withdrawal at liquid cash. Do not close the position inside requestWithdrawal.
Sources#
Taxonomy#
severity/highsector/lendingplatform/shieldify
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 65287-h-01-forced-premature-position-liquidation-through-withdrawa_exp (evm-hack-registry mirror).
- AuditVault finding: 65287-h-01-forced-premature-position-liquidation-through-withdrawa.
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.