Reproduced Exploit
Folks Finance: swapped stable-borrow indexes turn a 200 debt into 50 and release 80 more
calcBorrowBalance expects (balance, newIndex, oldIndex). calcStableBorrowBalance passes the stored index first and the updated index second. A year at 100 percent should turn 100 of principal into 200. The swapped call returns 50. Collateral of 150 then allows another 80 to be borrowed, and liquida…
Loss
80 USD drawn from lender cash while the true debt is already 200 against 150 of collateral
Chain
Ethereum
Category
logic
Date
Aug 2024
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: 61079-wrong-borrow-balance-calculation-in-the-getloanliquidity-fun. Standalone Foundry PoC and full write-up: 61079-wrong-borrow-balance-calculation-in-the-getloanliquidity-fun_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/incorrect-order-of-operations · vuln/logic/liquidation-logic
Key info#
| Field | Value |
|---|---|
| Loss | 80 USD drawn from lender cash while the true debt is already 200 against 150 of collateral |
| Vulnerable contract | LoanManager.calcStableBorrowBalance |
| Attacker EOA | 0x6107900000000000000000000000000000000001 |
| Attack contract | Exploit |
| Attack tx | Exploit.run() |
| Chain / block / date | Ethereum model · block 0 · 2024-08 |
| Compiler | solc 0.8.27, cancun, via IR |
| Bug class | calcBorrowBalance is called with the old index first |
TL;DR#
calcBorrowBalance expects (balance, newIndex, oldIndex). calcStableBorrowBalance passes the stored index first and the updated index second. A year at 100 percent should turn 100 of principal into 200. The swapped call returns 50. Collateral of 150 then allows another 80 to be borrowed, and liquidation reverts as over-collateralized. The corrected order reports 200 and the extra borrow reverts.
Background#
Immunefi Boost Folks Finance report 34122, submitted 5 August 2024 by Nyksx. updateLoanBorrowInterests already calls calcBorrowBalance with the new index first. getLoanLiquidity does not, so stable loans look healthier as interest accrues.
The vulnerable code#
uint256 stableBorrowInterestIndex = MathUtils.calcBorrowInterestIndex(
loanInterestRate, loanInterestIndex, stableBorrowChangeDelta
);
// @> VULN: loanInterestIndex is the old index, so calcBorrowBalance divides the debt by the updated index
return balance.calcBorrowBalance(loanInterestIndex, stableBorrowInterestIndex);
calcBorrowInterestIndex returns 2e18 from a stored index of 1e18. Dividing by that index yields half the principal.
Root cause#
The updated index and the stored index are passed in the opposite order from updateLoanBorrowInterests.
Preconditions#
- A stable borrow with principal 100, index
1e18, and rate1e18. - One year of elapsed time stored on the loan.
- Collateral value 150 and 200 of lender cash in the pool.
Attack walkthrough#
debtOfbuilds the new index and callscalcBorrowBalancewith the indexes swapped. The result is 50.- The same numbers in the correct order are 200, which is already above collateral.
borrow(80)uses the view of 50, so50 + 80fits under 150. 80 USD is transferred.liquidatereads the swapped debt again and revertsOverCollateralizedLoan. Collateral stays.- The fixed manager reverts the borrow and leaves the 200 in the pool.
Diagrams#
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 61079-wrong-borrow-balance-calculation-in-the-getloanliquidity-fun_exp (evm-hack-registry mirror).
- AuditVault finding: 61079-wrong-borrow-balance-calculation-in-the-getloanliquidity-fun.
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.