Reproduced Exploit

Persistence: empty funds list locks 100 of Alice's LST

The amount check runs only when funds contains the denom. An empty list skips it, then the lock balance still increases. joinPool pays that credit from the tokens Alice deposited.

Jan 2024•Other•logic•2 min read

Chain

Other

Category

logic

Date

Jan 2024

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

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.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 53505-prst-5-user-can-lock-and-stake-arbitrary-amount-of-tokens-wi. Standalone Foundry PoC and full write-up: 53505-prst-5-user-can-lock-and-stake-arbitrary-amount-of-tokens-wi_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/logic/missing-check

Key info#

ProtocolPersistence
FindingHexens PRST-5, AuditVault 53505
Vulnerable codeLocker.lockLstAsset
HarmBob locks 100 with an empty funds list and joinPool pays him Alice's 100 LST. The fixed locker reverts and her 100 stays.
Chainlocal synthetic

TL;DR#

The amount check runs only when funds contains the denom. An empty list skips it, then the lock balance still increases. joinPool pays that credit from the tokens Alice deposited.

The vulnerable code#

SOLIDITY
        // @> VULN: lock credit increases even when funds contained no matching coin
        locked_amount = locked_amount + asset.amount;

Root cause#

Nothing requires that the funds loop actually matched the denom before the credit is stored.

Preconditions#

Alice has locked 100 ux and the contract holds those tokens. Bob can call lock with an empty coin list.

Attack walkthrough#

  1. Alice locks 100 and pays 100 LST. See output.txt.
  2. The credit line runs for her paying call.
  3. Bob locks 100 with an empty funds array. No tokens move.
  4. Bob's lock record is 100.
  5. joinPool transfers 100 to Bob.
  6. The locker balance is 0 and Alice's credit is still 100. The fixed path reverts the unpaid lock.

Diagrams#

flowchart TD A["Alice pays 100 into the locker"] --> B["Bob locks 100 with no coins"] B["Bob locks 100 with no coins"] --> C["Credit increases anyway"] C["Credit increases anyway"] --> D["joinPool pays Bob"] D["joinPool pays Bob"] --> E["Alice's 100 is gone"]

Impact#

Alice loses 100 LST. Bob paid nothing. The fixed locker reverts unless a matching coin was pulled.

Remediation#

Require that a coin of the locked denom was received for the full amount before increasing the lock balance.

How to reproduce#

BASH
cd 53505-prst-5-user-can-lock-and-stake-arbitrary-amount-of-tokens-wi_exp
forge test -vv

Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

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.