Reproduced Exploit
Lumin — Disabled lender's loan configuration can be used by a borrower
1. A LoanConfig has an enabled flag, true by default, that a lender can flip to false via updateLoanConfigEnabledStatus — her only lever to stop further loans from being drawn against her allocated capital. 2. LoanManager::createLoan fetches the LoanConfig from storage and checks
Chain
Other
Category
access-control
Date
Sep 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: 27234-h-01-disabled-lenders-loan-configuration-can-be-used-by-a-bo. Standalone Foundry PoC and full write-up: 27234-h-01-disabled-lenders-loan-configuration-can-be-used-by-a-bo_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/access-control/missing-check · vuln/logic/state-flag-ignored
Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only
forge-std— no fork, no RPC, noanvil_state. Full trace: output.txt. PoC: test/27234-h-01-disabled-lenders-loan-configuration-can-be-used-by-a-bo_exp.sol.
Key info#
| Impact | HIGH (per AuditVault severity tag) — a lender's only lever to stop new loans against her capital never actually works; a borrower can keep drawing against a config she explicitly disabled |
| Protocol | Lumin — lending protocol (loan configuration / loan-creation core) |
| Vulnerable code | LoanManager::createLoan — never reads LoanConfig.enabled |
| Bug class | Missing check: a state flag is set and can be flipped, but the function that should honor it never reads it |
| Finding | Pashov Audit Group, 2023-09 · [H-01] · #27234 |
| Report | 2023-09-01-Lumin.md |
| Source | AuditVault |
| Status | Audit finding — caught in review (not exploited on-chain). Reproduced here as a standalone local PoC. |
| Compiler | ^0.8.24 (PoC) |
This is an audit finding, not a historical on-chain incident. The finding's
own report quotes the vulnerable createLoan snippet directly (the Lumin client
repo is not linked from the report); this reduction keeps that omission — the
missing enabled check — verbatim and builds the minimal lender/borrower
scaffolding needed to turn it into a measurable, quantified loss of control over
the lender's capital.
TL;DR#
- A
LoanConfighas anenabledflag,trueby default, that a lender can flip tofalseviaupdateLoanConfigEnabledStatus— her only lever to stop further loans from being drawn against her allocated capital. LoanManager::createLoanfetches theLoanConfigfrom storage and checks only that the requested principal fits underavailablePrincipal. It never checksenabled.- A borrower can therefore draw a brand-new loan against a config the lender has explicitly disabled — the "disable" lever has zero effect.
- Result: the lender's capital gets committed to a loan she opted out of, and she cannot reclaim it on demand — a real loss of control over her own funds' liquidity, quantifiable as exactly the amount borrowed after she disabled the config.
The vulnerable code#
The missing check (verbatim, LoanManager::createLoan):
function createLoan(uint256 configId, uint256 principalAmount) external returns (uint256 loanId) {
LoanConfig storage config = loanConfigs[configId]; // @> config.enabled is fetched but never checked below
require(principalAmount <= config.availablePrincipal, "exceeds available principal");
...
The report's own recommendation: "Check if a loan config is enabled in
LoanManager::createLoan and revert the transaction if it is not." No such check
exists anywhere in the function.
Root cause#
enabled is a first-class, mutable piece of state — a lender can create it,
read it, and flip it — but no code path that consumes LoanConfig ever
consults it. The createLoan entry point validates only the numeric
availablePrincipal cap, leaving enabled a dead flag: setting it has no
observable effect on protocol behavior.
Preconditions#
- A lender has created a
LoanConfigwith allocated capital. - The lender calls
updateLoanConfigEnabledStatus(configId, false). - A borrower calls
createLoanagainst that sameconfigIdwhile capital remains under the (still-decreasing)availablePrincipalcap.
Attack walkthrough#
From output.txt:
- Alice (lender) allocates 1000 units of capital to a new loan config.
- Alice disables the config immediately, expecting no further loans can touch her capital and that she can reclaim the full 1000 on demand.
- HARM: Bob (borrower) calls
createLoan(configId, 500)against the disabled config anyway — it succeeds, and Bob receives 500 tokens. - HARM: Alice tries to reclaim her allocation. She disabled the config before any borrow occurred, so she expects the full 1000 back — but she can only recover 500. The other 500 was committed to Bob's loan against her explicit choice to stop lending, and she has no way to get it back on demand.
Two control tests strengthen the finding: an availablePrincipal-exceeding
borrow still correctly reverts (the numeric cap works fine — only enabled is
ignored), and an enabled config is borrowed against in exactly the same way
as the disabled one in the main exploit — proving the flag has literally zero
effect on createLoan's behavior.
Diagrams#
Remediation#
Check config.enabled before drawing down availablePrincipal:
function createLoan(uint256 configId, uint256 principalAmount) external returns (uint256 loanId) {
LoanConfig storage config = loanConfigs[configId];
+ require(config.enabled, "config disabled");
require(principalAmount <= config.availablePrincipal, "exceeds available principal");
...
How to reproduce#
cd ~/RustroverProjects/audits/evm-hack-registry/27234-h-01-disabled-lenders-loan-configuration-can-be-used-by-a-bo_exp
forge test -vvv
# Fully local — no fork, no RPC, no anvil_state required.
# Expected: all three tests PASS:
# test_DisabledLoanConfigCanStillBeUsed (attack: Bob borrows 500 from a disabled config; Alice only reclaims 500 of 1000)
# test_Control_ExceedsAvailablePrincipal_Reverts (control: the numeric cap still works)
# test_Control_EnabledConfigBehavesIdentically (control: an enabled config behaves identically - enabled has zero effect)
PoC source: test/27234-h-01-disabled-lenders-loan-configuration-can-be-used-by-a-bo_exp.sol
— the verbatim vulnerable createLoan (with enabled fetched but unchecked)
plus a minimal lender/borrower scaffold and two control tests.
Note: Lumin's real
LoanManagersupports collateral posting, interest accrual, and full loan lifecycle bookkeeping (out of scope here); this reduction keeps a single loan config and the exact omission the finding blames — the bug class (a mutable safety flag that no code path ever reads) and the verbatim vulnerable line are faithful to the finding's own quoted snippet.
Reference: finding #27234 [H-01] by Pashov Audit Group in the 2023-09-01-Lumin.md report · curated by AuditVault
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 27234-h-01-disabled-lenders-loan-configuration-can-be-used-by-a-bo_exp (evm-hack-registry mirror).
- AuditVault finding: 27234-h-01-disabled-lenders-loan-configuration-can-be-used-by-a-bo.
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.