Reproduced Exploit
Devve — airdrop withdrawal counts minutes as days
tokensToWithdraw is supposed to be airdropTokensPerDay times the number of days since the last withdrawal. The code divides the elapsed seconds by 60. Two days is 172800 seconds, 2880 minutes, and 2 days. The caller receives 2880 tokens. Dividing by one day pays 2 and leaves 2878 in the contract.
Chain
Other
Category
logic
Date
Jun 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: 37617-calculation-of-tokenstowithdraw-is-wrong-zokyo-none-devve-ma. Standalone Foundry PoC and full write-up: 37617-calculation-of-tokenstowithdraw-is-wrong-zokyo-none-devve-ma_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/reward-calculation · vuln/arithmetic/precision-loss
Reproduction: a self-contained Foundry PoC that compiles and runs offline with only
forge-std— no fork and no RPC. Trace: output.txt. PoC: test/37617-calculation-of-tokenstowithdraw-is-wrong-zokyo-none-devve-ma_exp.sol.
Key info#
| Impact | HIGH — two days of vesting pays 2880 tokens instead of 2. The extra is 2878 |
| Protocol | Devve withdrawAirdropTokens / getCurrentAirdropBalance |
| Vulnerable code | airdropTokensPerDay * (elapsed / 60) |
| Bug class | Minutes used where the formula says days |
| Finding | Zokyo — Devve · id 37617 |
| Report | Zokyo Devve, 30 Jun 2024 |
| Source | AuditVault finding |
| Status | The report says the issue was resolved. Reproduced as a local synthetic. |
| Compiler | solc 0.8.27, cancun, via IR |
TL;DR#
tokensToWithdraw is supposed to be airdropTokensPerDay times the number of days since the last withdrawal. The code divides the elapsed seconds by 60. Two days is 172800 seconds, 2880 minutes, and 2 days. The caller receives 2880 tokens. Dividing by one day pays 2 and leaves 2878 in the contract.
run() does not warp. The block timestamp is already two days after lastWithdraw, which was stored when the contract was constructed. The same clock is used for the preview and the withdrawal.
The vulnerable code#
uint256 elapsed = block.timestamp - lastWithdraw[msg.sender];
// @> VULN: tokensToWithdraw multiplies airdropTokensPerDay by minutes, not days
tokensToWithdraw = airdropTokensPerDay * (elapsed / 60);
getCurrentAirdropBalance uses the same division.
Root cause#
The report says both functions multiply airdropTokenPerDay by minutes passed, not by days passed.
Preconditions#
airdropTokensPerDayis 1 token.lastWithdrawis 172800 seconds before the block timestamp.- The contract holds at least 2880 tokens.
Attack walkthrough#
getCurrentAirdropBalancereturns 2880 tokens.withdrawAirdropTokenscomputes the same number and transfers it.- The contract balance is 0. The fair amount is 2.
Diagrams#
Impact#
The caller takes 2878 tokens that the daily rate did not vest. Every other claimant is paid from a balance that is already short.
Remediation#
Divide elapsed time by one day before multiplying by airdropTokensPerDay, in both the withdrawal and the view.
How to reproduce#
cd evm-hack-registry/37617-calculation-of-tokenstowithdraw-is-wrong-zokyo-none-devve-ma_exp
forge test -vv
The test warps once, before either contract is constructed. run() itself does not warp.
Sources#
- AuditVault finding 37617
- Zokyo, Devve, 30 June 2024
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 37617-calculation-of-tokenstowithdraw-is-wrong-zokyo-none-devve-ma_exp (evm-hack-registry mirror).
- AuditVault finding: 37617-calculation-of-tokenstowithdraw-is-wrong-zokyo-none-devve-ma.
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.