Reproduced Exploit
Frankencoin — Successful challenge + collateral top-up skips cooldown
1. On a successful challenge, internalWithdrawCollateral only extends cooldown when remaining collateral is below minimumCollateral. 2. The owner MEV-front-runs end() by transferring matching collateral into the position. 3. After withdraw, balance is still ≥ minimum → cooldown is not extended. 4.…
Chain
Other
Category
untagged
Date
Apr 2023
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: 20018-h-03-when-the-challenge-is-successful-the-user-can-send-toke. Standalone Foundry PoC and full write-up: 20018-h-03-when-the-challenge-is-successful-the-user-can-send-toke_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/wrong-condition · vuln/direct-drain · vuln/frontrun-exposure
Reproduction: self-contained Foundry PoC with only
forge-std— no fork, no RPC. Full trace: output.txt. PoC: test/20018-h-03-when-the-challenge-is-successful-the-user-can-send-toke.sol.
Key info#
| Impact | HIGH — after a successful challenge, the position owner front-runs with a collateral top-up so cooldown is not extended to expiration, then mints at the inflated liquidation price the challenge was meant to lock out |
| Protocol | Frankencoin — collateralized ZCHF stablecoin with auction-based challenges |
| Vulnerable code | Position.internalWithdrawCollateral — only sets cooldown = expiration when post-withdraw balance is below minimumCollateral |
| Bug class | Conditional cooldown gate skipped via front-run deposit |
| Finding | Code4rena — Frankencoin, 2023-04 · #20018 · reporter cccz |
| Report | code4rena.com/reports/2023-04-frankencoin |
| Source | AuditVault |
| Status | Audit finding — confirmed by Frankencoin ("Excellent finding! Will implement 1 day cooldown on successful challenges.") |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
- On a successful challenge,
internalWithdrawCollateralonly extendscooldownwhen remaining collateral is belowminimumCollateral. - The owner MEV-front-runs
end()by transferring matching collateral into the position. - After withdraw, balance is still ≥ minimum → cooldown is not extended.
- Owner immediately mints at the inflated price the challenge was supposed to lock out.
The vulnerable code#
function internalWithdrawCollateral(address target, uint256 amount) internal returns (uint256) {
IERC20(collateral).transfer(target, amount);
uint256 balance = collateralBalance();
// @> VULN: cooldown only extended when balance < minimumCollateral
if (balance < minimumCollateral) {
cooldown = expiration;
}
return balance;
}
Fix: always extend cooldown on a successful challenge (e.g. 1 day), not only when balance drops below minimum.
Root cause#
The cooldown extension is gated on the post-withdraw balance rather than on the fact that a challenge succeeded. Any external ERC20 transfer into the position before end() can keep the balance above the threshold and silently disable the intended minting freeze.
Attack walkthrough#
- Position holds exactly
minimumCollateralat an inflatedprice. - Challenge covers full collateral; period already elapsed.
- Owner donates
minimumCollateralinto the position (front-run). end()withdraws one unit; one unit remains →if (balance < minimum)is false.- Owner mints
priceZCHF against the remaining collateral.
Diagrams#
Impact#
A successful challenge no longer freezes minting. The owner can mint large ZCHF against residual minimum collateral at the pre-challenge inflated price — direct under-collateralized debt creation.
Taxonomy#
- genome: wrong-condition, direct-drain, frontrun-exposure
- sector: lending
- severity: high
- platform: code4rena
Sources#
- AuditVault finding #20018
- Code4rena report 2023-04-frankencoin
- Reduced from code-423n4/2023-04-frankencoin@1022cb1 (
Position.internalWithdrawCollateral)
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 20018-h-03-when-the-challenge-is-successful-the-user-can-send-toke_exp (evm-hack-registry mirror).
- AuditVault finding: 20018-h-03-when-the-challenge-is-successful-the-user-can-send-toke.
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.