Reproduced Exploit

Beraji KO: claimTokenFee zeros the fee and then transfers 0, so 1000 stays

claimTokenFee does lock.tokenFeeBalance = 0 and then safeTransfer(msg.sender, lock.tokenFeeBalance). The transfer amount is the value just written, which is 0. The call succeeds. The owner is paid 0. The 1000 that was accrued stays in the staking contract, and because the balance is now 0 the if (l…

Mar 2025•Other•logic•3 min read

Chain

Other

Category

logic

Date

Mar 2025

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: 49516-h-04-owner-cannot-claim-token-fees-shieldify-none-beraji-ko. Standalone Foundry PoC and full write-up: 49516-h-04-owner-cannot-claim-token-fees-shieldify-none-beraji-ko_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/logic/fee-calculation · vuln/logic/incorrect-order-of-operations

Key info#

ProtocolBeraji KO
FindingShieldify H-04, AuditVault 49516
Vulnerable codeStakingKo.claimTokenFee
HarmA 1000 token fee is cleared and then transferred. The owner receives 0. The 1000 stays in the contract, and the fee balance is 0 so a later claim still pays 0. The control caches 1000 and pays the owner 1000
Chainlocal synthetic

TL;DR#

claimTokenFee does lock.tokenFeeBalance = 0 and then safeTransfer(msg.sender, lock.tokenFeeBalance). The transfer amount is the value just written, which is 0. The call succeeds. The owner is paid 0. The 1000 that was accrued stays in the staking contract, and because the balance is now 0 the if (lock.tokenFeeBalance != 0) guard skips that lock forever. The control copies the balance into fee first and transfers fee, so the owner receives 1000.

The vulnerable code#

SOLIDITY
if (lock.tokenFeeBalance != 0) {
    lock.tokenFeeClaimed += lock.tokenFeeBalance;
    // @> VULN: the fee is cleared before it is used as the transfer amount
    lock.tokenFeeBalance = 0;
    pool.stakingToken.safeTransfer(msg.sender, lock.tokenFeeBalance);
}

The fix is uint256 fee = lock.tokenFeeBalance before the clear, and safeTransfer(msg.sender, fee).

Root cause#

The transfer reads the same storage slot the previous statement just set to 0. tokenFeeClaimed records the pre-clear amount, so the books say the fee was claimed, while the token movement uses the post-clear amount.

Preconditions#

One lock has tokenFeeBalance of 1000 and the contract holds those 1000 tokens. The caller is the owner. run() does not warp.

Attack walkthrough#

  1. Accrue 1000 into the lock. The contract balance is 1000.
  2. The owner calls claimTokenFee.
  3. The balance is added into tokenFeeClaimed, then set to 0.
  4. safeTransfer sends 0. The owner's balance does not change. The contract still holds 1000.
  5. The control accrues 1000, caches it, and transfers the cache. The owner receives 1000 and the control contract is empty.

Diagrams#

flowchart TD A["Lock fee balance is 1000"] --> B["claimTokenFee adds 1000 to tokenFeeClaimed"] B --> C["VULN: tokenFeeBalance is set to 0"] C --> D["safeTransfer sends that 0 to the owner"] D --> E["Owner is paid 0 and 1000 stays in the contract"] E --> F["Fee balance is 0 so a later claim still pays 0"] F --> G["Control caches 1000 and pays the owner 1000"]

Impact#

The owner cannot collect the 1000 staking fee. The tokens remain in the contract with no remaining balance to claim. The control pays the owner 1000.

How to reproduce#

BASH
cd 49516-h-04-owner-cannot-claim-token-fees-shieldify-none-beraji-ko_exp
forge test -vv

run() does not warp. The vulnerable contract still holds 1000. The control owner receives 1000.

Sources#


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.