InugamiStaking Expired-Pool Debt Initialization Bypass (BSC)
Exploit Transactions
0xa487da310b02dfa3e6da2d9b3c797b656957924f4b08e38ed256cfeed48dbbcaVictim Addresses
0x2001144a0485b0b3748a167848cdd73837345d73BSCLoss Breakdown
Similar Incidents
BSC PoolWithdraw Signature-Bypass Drains USDT Pool
37%WILL/USDT expired short overfunding exploit
34%BSC staking pool reentrancy drain
34%Ocean Pool NFT Reward Drain
32%0x3Af7 burn-rate manipulation drains WBNB from BSC pool
32%BSC WBNB allowance drain from unsafe spender approvals
31%Root Cause Analysis
InugamiStaking Expired-Pool Debt Initialization Bypass (BSC)
1. Incident Overview TL;DR
Transaction 0xa487da310b02dfa3e6da2d9b3c797b656957924f4b08e38ed256cfeed48dbbca on BSC block 84377206 is an ACT exploit against InugamiStaking (0x2001144a0485b0b3748a167848cdd73837345d73).
A fresh, unprivileged EOA (0x43c2f458e3aa73dcfd872144ded5c7faf56e33f8) deployed an executor contract (0x20a8047fd8b23db7446041c32c442a77eb46f989) and, in one transaction, acquired LP, staked LP, reactivated an expired reward pool with a 1 wei WBNB top-up, claimed historical rewards, then exited and transferred profit back to the EOA.
Root cause: expired reward pools bypass user debt initialization in _updateUserDebt, while streamReward() can reactivate those pools without resetting stale accRewardPerShare. This allows a newly staked user to claim rewards accrued before stake time.
2. Key Background
The staking system uses accumulator-based reward accounting:
accRewardPerSharetracks cumulative reward per stake unit.- Per-user debt (
_userDebt) must be initialized against current accumulator at entry time.
Security-critical expectation: after stake, user debt for each reward PID should be set to balance * accRewardPerShare / 1e36, so claims only include post-entry accrual.
In this incident, reward PID0 (WBNB) was expired but retained a very large stale accumulator and significant reserves. That state made debt initialization gating exploitable.
3. Vulnerability Analysis & Root Cause Summary
This is an ATTACK-class logic/accounting exploit, not a privileged-access incident. The vulnerable invariant is debt initialization consistency at user entry across all active accounting dimensions.
The verified InugamiStaking source confirms _updateUserDebt only updates debt when endRewardTimestamp >= block.timestamp. For expired pools, debt is left unchanged (zero for a new staker). streamReward() is permissionless and can reactivate an expired pool by adding any positive reward amount, while preserving stale accumulator context. After reactivation, claim uses current balance and stale high accumulator against zero debt, producing an oversized payout. Trace evidence confirms the exact sequence stake -> transfer(1 wei WBNB) -> streamReward -> claim -> unstake.
4. Detailed Root Cause Analysis
4.1 Code-Level Breakpoint
Victim code (verified source for 0x2001144a0485b0b3748a167848cdd73837345d73) shows both enabling conditions:
function streamReward() external {
_updatePools(false, address(0));
...
if (newRewards > 0) {
...
rewardInfo.reserves += newRewards;
rewardInfo.endRewardTimestamp = lastRewardTimestamp + WINDOW_LENGTH;
}
}
function _updateUserDebt(address user) internal {
for (uint256 i = 0; i < rewardTokensCount; ++i) {
if (
rewardInfos[i].rewardToken != address(0) &&
rewardInfos[i].endRewardTimestamp >= block.timestamp
) {
_userDebt[i][user] =
(balanceOf[user] * rewardInfos[i].accRewardPerShare) / 1e36;
}
}
}
If a reward PID is expired when stake executes, debt is not initialized for that PID.
4.2 Exploitable Pre-State (Immediately Before Exploit)
State snapshot at block 0x5077e75 (pre-state) shows PID0 expired but still carrying large stale accounting state and reserves:
reward0 token: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
accRewardPerShare: 911232950638852218698941779818023669046
reserves: 13904954155454625823
endRewardTimestamp: 1649459132 (expired)
WBNB bal: 13904954155454625823
4.3 On-Chain Exploit Sequence
Collector trace confirms the deterministic sequence and key values:
0x2001144a...::stake(15259494452769803)
WBNB::transfer(0x2001144a..., 1)
0x2001144a...::streamReward()
0x2001144a...::claim(0x20A8047f..., [0])
WBNB::transfer(0x20A8047f..., 13904954155454625145)
0x2001144a...::unstake(15259494452769803)
Post-state at block 0x5077e76 shows pool reactivated (endRewardTimestamp pushed forward) and WBNB reserve nearly drained:
reward0 reserves: 679
reward0 endRewardTimestamp: 1773120610
WBNB bal: 679
4.4 Invariant Violation Realization
Intended invariant: for each reward PID, a new staker should have debt initialized to current accumulator-weighted balance at stake time.
Observed breakpoint: because PID0 was expired at stake time, debt remained zero; after reactivation, claim was computed against stale high accumulator and paid out historical rewards, violating temporal reward attribution.
5. Adversary Flow Analysis
- Stage 1: Deploy executor with seed capital.
- Sender EOA:
0x43c2f458e3aa73dcfd872144ded5c7faf56e33f8 - Deployed contract:
0x20a8047fd8b23db7446041c32c442a77eb46f989 - Same transaction (
0xa487da31...) funds and executes the attack path atomically.
- Stage 2: Acquire LP stake asset.
- Executor performs Pancake router swaps/add-liquidity to obtain INUGAMI/WBNB LP (
0xe7989a82615b68c09b6fc0d1d24c95551a47e0cf).
- Stage 3: Trigger accounting exploit and cash out.
- Stake LP into
InugamiStaking. - Transfer
1wei WBNB to staking. - Call
streamReward()to reactivate expired PID0. - Call
claim(...,[0])to receive13904954155454625145wei WBNB. - Unstake LP, unwrap WBNB to BNB, forward BNB to the controlling EOA.
All calls are public/permissionless and feasible by any unprivileged actor.
6. Impact & Losses
Victim-side loss:
- WBNB drained from staking pool:
13.904954155454625144WBNB (13904954155454625144wei), leaving679wei.
Attacker-side realized gain:
- EOA native balance delta:
+13895178308441569254wei (+13.895178308441569254BNB) in the exploit transaction.
Affected victim contract:
InugamiStakingat0x2001144a0485b0b3748a167848cdd73837345d73.
7. References
- Exploit transaction:
0xa487da310b02dfa3e6da2d9b3c797b656957924f4b08e38ed256cfeed48dbbca(BSC, block84377206). - Victim contract:
0x2001144a0485b0b3748a167848cdd73837345d73. - Attacker EOA:
0x43c2f458e3aa73dcfd872144ded5c7faf56e33f8. - Attacker executor:
0x20a8047fd8b23db7446041c32c442a77eb46f989. - Verified source (Etherscan V2 contract source API for victim contract).
- Collected evidence used in validation:
artifacts/collector/seed/56/0xa487da310b02dfa3e6da2d9b3c797b656957924f4b08e38ed256cfeed48dbbca/trace.cast.logartifacts/collector/seed/56/0xa487da310b02dfa3e6da2d9b3c797b656957924f4b08e38ed256cfeed48dbbca/balance_diff.jsonartifacts/auditor/iter_0/staking_state_pre_post.txt