Grizzifi Sybil Milestone Drain
Exploit Transactions
0x40bccc37eae68cd6a53d5d896b1af3d37b170dba923fabc8384733a262cf85280x4302de51c8126e7934da9be1affbde73e5153fe1f9d0200a738a269fe07d22c70x36438165d701c883fd9a03631ee0cdeec35a138153720006ab59264db7e075c10x52567db016dc48bc27441665c25e1ad51efea72a34aef2bbb4e0bb4bdd1ada310xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7Victim Addresses
0x21ab8943380b752306abf4d49c203b011a89266bBSCLoss Breakdown
Similar Incidents
NFD Reward Sybil Exploit
36%UEarnPool Reward Drain
34%Sareon Reward Drain
33%OKC Flash-LP Reward Drain
32%BCT Referral Treasury Drain
31%T3913 Pair-Skim Referral Drain
31%Root Cause Analysis
Grizzifi Sybil Milestone Drain
1. Incident Overview TL;DR
An unprivileged BSC attacker built a synthetic referral tree inside Grizzifi and then withdrew milestone bonuses that were meant to reward genuine team growth. The attacker EOA 0xe2336b08a43f87a4ac8de7707ab7333ba4dbaf7c deployed helper contracts, funded fresh child contracts with 20 USDT, registered those contracts under attacker-controlled referrers, and finally aggregated the withdrawals back to the attacker. In transaction 0xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7, Grizzifi lost 48,900 USDT and the attacker EOA gained the same amount according to the collected balance diff.
The root cause is a milestone-accounting flaw, not privileged access. Grizzifi credits milestone progress from raw address-count growth. Because contract creation and referral registration are permissionless, the attacker could deterministically create sybil addresses that advanced teamsCount, unlocked milestoneReward, and later withdrew those balances through collectRefBonus().
2. Key Background
Grizzifi is an on-chain USDT investment/referral system at 0x21ab8943380b752306abf4d49c203b011a89266b. Users invest through harvestHoney(uint256,uint256,address), can nominate a previously registered referrer, and accrue referral-side rewards through both level commissions and milestone bonuses.
The relevant milestone settings are embedded directly in the verified contract. minInvestForMilestone is 10 * 1e18, minDirect is 2, teamMilestones tracks address-count thresholds, and rewardAmounts maps each threshold to a fixed USDT reward. Once credited, milestone rewards become immediately withdrawable through collectRefBonus().
The collected seed traces show the attacker using ordinary helper contracts rather than any privileged protocol component. The helper at 0x03ba640c955ebb07520a31ea1ef572c404a3f9ae seeded child contracts with 20 USDT each, while 0xed35746f389177ecd52a16987b2aac74aa0c1128 extended the referral tree and later withdrew bonuses. The attacker EOA owns and drives these contracts through standard externally owned transactions.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is a sybilable milestone design. Grizzifi assumes that each newly registered address under a referral tree represents genuine incremental team growth, but the contract never verifies that those addresses are economically independent or even human-controlled. Any attacker can cheaply deploy fresh contracts, fund them with the minimum deposit, and register them under attacker-controlled referrers.
The concrete breakpoint is inside harvestHoney(). For any deposit that meets the 10 USDT milestone threshold, the function calls _incrementUplineTeamCount(msg.sender) before finalizing the investment bookkeeping. _incrementUplineTeamCount() then walks the upline chain, marks the new address in inTeam, increments teamsCount, and when a threshold is met, credits users[upline].milestoneReward += rewardAmounts[index]. No uniqueness check, sybil resistance, or conservation rule prevents the same attacker cluster from manufacturing all of those addresses.
Finally, collectRefBonus() combines claimableReferralBonus and milestoneReward, zeros those balances, and transfers the total USDT amount to msg.sender. That makes the inflated milestone balances directly realizable as protocol-funded withdrawals.
4. Detailed Root Cause Analysis
The verified Grizzifi source shows the exploit path directly:
function harvestHoney(uint256 _planId, uint256 _amount, address _referrer) external {
...
if (!registered[msg.sender]) {
registered[msg.sender] = true;
...
if (_referrer != address(0) && _referrer != msg.sender && registered[_referrer]) {
users[msg.sender].referrer = _referrer;
users[_referrer].referralCount++;
users[_referrer].directReferrals.push(msg.sender);
_updateReferralCounts(_referrer);
}
}
if (_amount >= minInvestForMilestone) {
_incrementUplineTeamCount(msg.sender);
}
require(USDT.transferFrom(msg.sender, address(this), _amount), "USDT transfer failed");
...
}
That call enters the milestone counter:
function _incrementUplineTeamCount(address _user) internal {
address upline = users[_user].referrer;
for (uint8 i = 0; i < 30; i++) {
if (upline == address(0)) break;
if (users[upline].totalInvested >= minInvestForMilestone) {
if (!users[upline].inTeam[_user]) {
if (i == 0 && !users[_user].inDirect) {
users[_user].inDirect = true;
users[upline].directCount++;
}
users[upline].inTeam[_user] = true;
users[upline].teamsCount++;
uint256 index = users[upline].milestoneIndex;
if (index < teamMilestones.length && users[upline].teamsCount == teamMilestones[index]) {
if (users[upline].directCount >= minDirect) {
uint256 reward = rewardAmounts[index];
users[upline].milestoneReward += reward;
}
users[upline].milestoneIndex++;
}
} else {
break;
}
}
upline = users[upline].referrer;
}
}
This code enforces only three conditions for milestone growth: the new address must be fresh to that upline, the upline must have invested at least 10 USDT, and for milestone payout the upline must have at least two directs. None of those conditions distinguish real users from attacker-created contracts. As a result, the attacker can recursively manufacture an entire referral graph with clean addresses that all belong to the same adversary cluster.
The seed trace for 0x36438165d701c883fd9a03631ee0cdeec35a138153720006ab59264db7e075c1 shows the expansion phase repeatedly calling:
Grizzifi::harvestHoney(0, 10000000000000000000, <attacker-controlled referrer>)
The corresponding balance diff shows multiple child contracts losing 20 USDT each while Grizzifi gains 108000000000000000000 units of USDT in that transaction, consistent with attacker-funded seeding of sybil nodes and their runner contracts. The exploit does not depend on a specific attacker artifact beyond the ability to deploy and fund fresh contracts.
The payout path is equally direct. collectRefBonus() transfers the already-accrued milestone balance:
function collectRefBonus() external {
User storage user = users[msg.sender];
uint256 referralAmount = user.claimableReferralBonus;
uint256 milestoneAmount = user.milestoneReward;
uint256 totalToClaim = referralAmount + milestoneAmount;
require(totalToClaim > 0, "Grizzifi: No referral or milestone bonuses to claim");
require(USDT.balanceOf(address(this)) >= totalToClaim, "Grizzifi: Insufficient contract balance");
user.claimableReferralBonus = 0;
user.milestoneReward = 0;
user.totalReferralBonusWithdrawn += totalToClaim;
totalPayouts += totalToClaim;
require(USDT.transfer(msg.sender, totalToClaim), "Grizzifi: Bonus transfer failed");
}
The seed trace for 0xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7 contains repeated Grizzifi::collectRefBonus() calls from attacker-controlled children. The withdrawal balance diff confirms the value transfer: Grizzifi’s USDT balance drops from 58193354084490740740745 to 9293354084490740740745, while the attacker EOA balance rises from 4660000000000000000000 to 53560000000000000000000, a net delta of 48900000000000000000000.
5. Adversary Flow Analysis
The attacker cluster consists of EOA 0xe2336b08a43f87a4ac8de7707ab7333ba4dbaf7c plus helper contracts 0xed35746f389177ecd52a16987b2aac74aa0c1128 and 0x03ba640c955ebb07520a31ea1ef572c404a3f9ae. The EOA sends every exploit transaction and is the final profit recipient in the balance diff evidence.
The observed flow is:
0x40bccc37eae68cd6a53d5d896b1af3d37b170dba923fabc8384733a262cf8528The attacker deploys the helper used later for recursive tree construction.0x4302de51c8126e7934da9be1affbde73e5153fe1f9d0200a738a269fe07d22c7The attacker seeds child contracts with 20 USDT each so they can satisfy the minimum 10 USDT investment and still fund the nested runner call.0x36438165d701c883fd9a03631ee0cdeec35a138153720006ab59264db7e075c1The attacker recursively grows the sybil referral chain through repeatedharvestHoney(0, 10e18, referrer)calls.0x52567db016dc48bc27441665c25e1ad51efea72a34aef2bbb4e0bb4bdd1ada31The attacker continues the mass-creation phase using selector0xd4ad2673on the main helper to expand the fabricated network further.0xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7The attacker triggers withdrawals from the attacker-controlled child contracts, aggregates the payouts through the helper, and realizes the USDT drain.
The exploit is ACT because every step uses permissionless on-chain actions available to any unprivileged searcher: deploy contracts, transfer tokens, call harvestHoney(), and call collectRefBonus(). No private key compromise, allowlist bypass, or privileged admin path appears anywhere in the evidence.
6. Impact & Losses
The direct measured loss is 48,900 USDT from Grizzifi in the final withdrawal transaction. In raw token units, the recorded loss is:
{
"token_symbol": "USDT",
"amount": "48900000000000000000000",
"decimal": 18
}
The broader impact is that milestone payouts intended to reward organic network growth can be minted synthetically by any attacker willing to fund enough minimum deposits. That means the protocol’s milestone treasury is structurally drainable whenever the contract holds sufficient USDT to satisfy the withdrawals.
7. References
- Verified Grizzifi contract source:
https://bscscan.com/address/0x21ab8943380b752306abf4d49c203b011a89266b#code - Expansion trace:
0x36438165d701c883fd9a03631ee0cdeec35a138153720006ab59264db7e075c1 - Withdrawal trace:
0xdb5296b19693c3c5032abe5c385a4f0cd14e863f3d44f018c1ed318fa20058f7 - Withdrawal balance diff showing the 48,900 USDT transfer from victim to attacker
- Attacker transaction history around blocks
57477000to57483000