Reproduced Exploit
A shorter SapienVault top-up pulls the whole lock forward
stake on an existing position sets the unlock to the amount-weighted average of the old unlock and the new one. A user stakes 1000 tokens for 365 days, then stakes 10000 tokens for 30 days. The stored unlock moves earlier by 26312728 seconds, and all 11000 tokens are bound by that new time.
Loss
11000 tokens unlock 26312728 seconds earlier than the 365-day stake
Chain
Ethereum
Category
governance
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: 62026-lockup-period-for-unstaking-can-be-decreased-by-staking-agai. Standalone Foundry PoC and full write-up: 62026-lockup-period-for-unstaking-can-be-decreased-by-staking-agai_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/governance/timelock-bypass
Key info#
| Field | Value |
|---|---|
| Loss | 11000 tokens unlock 26312728 seconds earlier than the 365-day stake |
| Vulnerable contract | SapienVault.stake |
| Attacker EOA | 0x1111111111111111111111111111111111111111 |
| Attack contract | Exploit |
| Attack tx | Exploit.run() |
| Chain / block / date | Ethereum model · one block · 2025-11 |
| Compiler | solc 0.8.27, cancun, via IR |
| Bug class | Weighted-average unlock lets a larger short lock replace a longer one |
TL;DR#
stake on an existing position sets the unlock to the amount-weighted average of the old unlock and the new one. A user stakes 1000 tokens for 365 days, then stakes 10000 tokens for 30 days. The stored unlock moves earlier by 26312728 seconds, and all 11000 tokens are bound by that new time.
The number does not depend on block.timestamp, so the whole sequence fits in one block. run does not warp. The control rejects a new unlock that is earlier than the current one. The second stake reverts, the unlock stays on the original 365-day timestamp, and only the first 1000 tokens are locked.
The vulnerable code#
uint256 newUnlock = block.timestamp + lockup;
// @> VULN: weighted average can move an existing unlock earlier
p.unlock = (p.amount * p.unlock + amount * newUnlock) / (p.amount + amount);
p.amount += amount;
Root cause#
The average treats a shorter lock as a reason to reduce time already committed, instead of as a deposit that must last at least as long.
Preconditions#
- The user already has a position. Here it is 1000 tokens at
block.timestamp + 365 days. - The second stake is larger and shorter: 10000 tokens at 30 days.
- Both amounts are
n * 1e18. Integer division of the weighted timestamps yields 26312728 seconds.
Attack walkthrough#
- Approve and stake 1000 for 365 days. Record the unlock.
- Stake 10000 for 30 days.
- The new unlock is earlier.
earlyByis 26312728.lockedis 11000 tokens. - Control: the 30-day stake reverts. The unlock is unchanged. Locked amount stays 1000.
Diagrams#
Impact#
11000 tokens become free 26312728 seconds before the original 365-day commitment. That is the full position, not just the 10000 token top-up. The control keeps the original 1000 tokens locked until the first unlock.
Remediation#
Reject a stake whose new unlock is earlier than the position's current unlock. Extra stake must use a lockup at least as long as the one already stored.
How to reproduce#
cd evm-hack-registry/62026-lockup-period-for-unstaking-can-be-decreased-by-staking-agai_exp
forge test -vv
Sources#
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 62026-lockup-period-for-unstaking-can-be-decreased-by-staking-agai_exp (evm-hack-registry mirror).
- AuditVault finding: 62026-lockup-period-for-unstaking-can-be-decreased-by-staking-agai.
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.