Reproduced Exploit

CWBC — Arkouda rewards use days held and ignore stakeCBWC

Feb 2022•Other•logic•2 min read

Chain

Other

Category

logic

Date

Feb 2022

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: 56420-staking-is-not-used-to-calculate-arkouda-tokens-to-be-reward. Standalone Foundry PoC and full write-up: 56420-staking-is-not-used-to-calculate-arkouda-tokens-to-be-reward_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/logic/reward-calculation · vuln/logic/missing-check · vuln/logic/wrong-condition

Reproduction: Local Foundry reproduction. An unstaked NFT is paid the same 100 ARK as a staked one. No fork. Trace: output.txt.

Root cause#

stakeCBWC sets staked[id]. claimReward reads that flag into ignoredStakeFlag and then does not use it. The mint is daysHeld * BASE_REWARD for any owner. Rewards have already started (rewardGenerationStartTime = 1), so this is not the pre-start claim.

An NFT acquired at timestamp 1, claimed in the block at 1 + 10 days, mints 100 ARK whether or not it was staked. The unstaked mint is 100 ARK that a staking-gated claim would not pay.

SOLIDITY
ignoredStakeFlag = staked[tokenId] ? 1 : 0;
// @> VULN:
lastReward = daysHeld * BASE_REWARD;

run() does not warp. The block timestamp is fixed for the transaction.

Attack path#

flowchart TD A["Token 1 is minted and never staked"] --> B["claimReward reads staked as false"] B --> C["Formula uses only days held"] C --> D["100 ARK is minted to the unstaked owner"] D --> E["Token 2 is staked and paid the same 100 ARK"]

PoC#

Unstaked mint and staked mint are both 100 ARK. stakeCalls is 1, and it happens after token 1 was paid. The unstaked 100 ARK is transferred to the holder.

The fixed claim reverts with "not staked" for an unstaked id and still pays a staked id.

BASH
cd 56420-staking-is-not-used-to-calculate-arkouda-tokens-to-be-reward_exp
forge test -vv

Remediation#

Require staked[tokenId] before computing the reward, and size the reward from the stake rather than from days held alone.

References#


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.