Calculated from recorded token losses using historical USD prices at the incident time.
0x53303ea516cb64cd4120c1b344d1be2d4cca03dbfdb7924ce2421c235a5f28c80x1356062e82a940a98ff7172ba1093e206b775015BSC0x51f61cef27e2fadb767d1a7f7277c9fd9ab3fbe9BSCOn BSC block 86089084, transaction 0x53303ea516cb64cd4120c1b344d1be2d4cca03dbfdb7924ce2421c235a5f28c8 exploited Sareon’s public registration and buy flow to mint unbacked referral rewards and convert them into real treasury USDT. An unprivileged attacker bootstrapped one referrer account, deployed 29 helper contracts, and had each helper register under the attacker and buy only 1e18 units of USDT (1 USDT). Because Sareon treated that deposit as sufficient for pool qualification and never consumed the one-time reward latch, helper accounts 10 through 29 each credited another 600e18 USDT to the attacker’s withdrawable reward balance.
The root cause is a pair of deterministic accounting bugs in Sareon’s reward qualification logic. checkranks compares an 18-decimal USDT deposit total against the raw literal 1000, so a 1 USDT deposit incorrectly qualifies as if it were 1000 USDT. The same function also checks users[currentReferrer].cpool==0 before awarding a 600 USDT pool reward, but never sets cpool afterward. claimUSDTRewards then transfers real USDT from Sareon’s treasury based on the inflated accounting balance. The exploit drained 9569999999999999999999 raw USDT units and produced 14830799479387857233 wei of net BNB profit for the adversary EOA.
Sareon’s live user-facing contract is proxy address 0x1356062e82a940a98ff7172ba1093e206b775015. Public explorer metadata confirms that this address is a verified proxy pointing to verified implementation . The collected source for the implementation contains the reward logic used by the proxy during the incident.
0x51f61cef27e2fadb767d1a7f7277c9fd9ab3fbe9The relevant asset is BSC USDT at 0x55d398326f99059ff775485246999027b3197955, which uses 18 decimals. Any threshold intended to represent whole-token amounts must therefore be scaled by 1e18. Sareon stores referral rewards in users[user].UIncomes.availableAmtUSDT, and claimUSDTRewards pays those balances from the contract’s actual USDT holdings. Before the exploit transaction, Sareon held 9777068000000000000000 raw USDT units, which made the accounting bug immediately monetizable.
The attacker also depended on a public bootstrap referrer. The trace shows idToAddress(1) returning a valid existing user, allowing a fresh attacker identity to register without privileged access. All relevant entry points in the exploit path are public and permissionless: register, BuyToken, getUserIncome, and claimUSDTRewards.
This is an ATTACK case caused by broken reward accounting, not by privileged access or private attacker knowledge. Sareon’s BuyToken function adds the incoming USDT amount directly to users[msg.sender].totalDeposit, using the token’s native 18-decimal units. checkranks later tests that value with totalDeposit >= 1000, which means any deposit of at least 1000 wei of USDT satisfies a condition that was clearly meant to require 1000 USDT. A 1 USDT buy therefore sets poolStatus = 1 for the buyer and increments the referrer’s activeDirect.
Once the referrer reaches ten such “qualified” directs, Sareon credits 600 * 1e18 USDT to the referrer’s availableAmtUSDT. The code attempts to guard this payout with users[currentReferrer].cpool == 0, but never flips cpool after the reward is granted. Every additional helper that satisfies the same broken qualification path therefore mints another 600 USDT into withdrawable accounting. claimUSDTRewards converts that synthetic balance into real USDT transfers with only a strict-less-than check on the requested amount and no reserve-backing control beyond the ERC20 transfer itself.
The code-level breakpoint is in Sareon’s reward qualification and claim paths:
function BuyToken(uint256 buyAmount) external nonReentrant {
require(buyAmount >= 1 * 1e18, "Minimum buy limit 1 USDT");
USDT.transferFrom(_msgSender(), address(this), buyAmount);
users[_msgSender()].totalDeposit += buyAmount;
...
checkranks(_msgSender());
}
function checkranks(address _user) internal {
uint256 totalDeposit = users[_user].totalDeposit + users[_user].totalBooster;
if (users[_user].poolStatus == 0 && totalDeposit >= 1000) {
users[_user].poolStatus = 1;
address currentReferrer = users[_user].referrer;
users[currentReferrer].activeDirect += 1;
if (
users[currentReferrer].poolStatus == 1 &&
users[currentReferrer].activeDirect >= 10 &&
users[currentReferrer].cpool == 0
) {
uint256 rewardAmt = 600 * 1e18;
users[currentReferrer].UIncomes.availableAmtUSDT += rewardAmt;
emit userIncome(currentReferrer, _msgSender(), rewardAmt, 1, 1, "REWARD INCOME");
}
}
}
function claimUSDTRewards(uint256 withAmt) external nonReentrant {
uint256 available = users[msg.sender].UIncomes.availableAmtUSDT;
require(withAmt < available, "Insufficient balance");
uint256 netUserAmt = withAmt * 80 / 100;
require(USDT.transfer(msg.sender, netUserAmt), "USDT transfer failed");
users[_msgSender()].UIncomes.availableAmtUSDT -= withAmt;
}
This logic creates two separate failures.
First, the qualification threshold is unscaled. Because USDT has 18 decimals, 1e18 represents 1 USDT. Comparing the raw accumulated deposit to 1000 means a 1 USDT buy exceeds the gate by fifteen orders of magnitude. The attacker first registered itself under the public root referrer and bought 1 USDT, which immediately set the attacker’s own poolStatus to 1.
Second, the one-time reward latch is never consumed. After the attacker became pool-qualified, each helper contract registered under the attacker and bought 1 USDT. Every helper also satisfied the same broken totalDeposit >= 1000 check, so each helper increased users[attacker].activeDirect. When helper 10 arrived, Sareon credited the attacker with the first 600e18 reward. Because cpool remained zero, helpers 11 through 29 each repeated the same credit path.
The on-chain trace shows the full deterministic sequence:
PancakeRouter::swapExactETHForTokensSupportingFeeOnTransferTokens{value: 300000000000000000}(...)
Sareon::idToAddress(1)
Sareon::register(0x3F2E04A3fA917837E4eBc30768D7984F6ce42Bcd)
Sareon::BuyToken(1000000000000000000)
...
emit userIncome(receiver: 0xbE876B08bEF10Ff9bc7483Cab1f6AdA122740b4c, incomeAmt: 600000000000000000000, incomeType: "REWARD INCOME")
... repeated 20 times ...
Sareon::getUserIncome(0xbE876B08bEF10Ff9bc7483Cab1f6AdA122740b4c)
-> 12000000000000000000000
Sareon::claimUSDTRewards(11999999999999999999999)
PancakeRouter::swapExactTokensForETHSupportingFeeOnTransferTokens(9762467373934909566099, ...)
The trace also shows 29 helper CREATE operations before the reward claim, matching the attacker’s helper-farming phase. The repeated REWARD INCOME emissions occur exactly 20 times, which matches the attacker receiving one reward for each helper from number 10 through number 29 inclusive. getUserIncome returns 12000000000000000000000, confirming that the attacker accumulated exactly 12,000 USDT of synthetic reward balance before the claim call.
The balance diff proves that this accounting bug became a real loss. Sareon’s USDT balance fell from 9777068000000000000000 to 207068000000000000001, a net decrease of 9569999999999999999999 raw units. The attacker EOA’s native balance increased by 14830799479387857233 wei after gas, which matches the report’s profit predicate.
The exploit was executed in one adversary-crafted transaction by EOA 0xfa4ee59836eb16609a643bf41c961065b88fa641, which created exploit contract 0xbe876b08bef10ff9bc7483cab1f6ada122740b4c.
Stage 1 was bootstrap and initial qualification. The attacker swapped 0.3 BNB into 192.467373934909566100 USDT through PancakeSwap, queried idToAddress(1) to obtain a valid existing referrer, registered the exploit contract under that referrer, and bought 1 USDT of Sareon. Because of the unscaled threshold, that 1 USDT deposit made the attacker pool-qualified.
Stage 2 was helper-account farming. The exploit contract deployed 29 helper contracts and transferred 1 USDT to each one. Each helper approved Sareon, registered under the attacker, and bought 1 USDT. Those buys were sufficient to increment the attacker’s activeDirect counter. When the tenth helper completed its buy, Sareon credited the first 600 USDT reward; helpers 11 through 29 each triggered another 600 USDT reward because cpool was still zero. After the helper loop, getUserIncome returned 12000000000000000000000 as the attacker’s available USDT rewards.
Stage 3 was reward realization and unwind. The attacker called claimUSDTRewards(11999999999999999999999) to satisfy Sareon’s strict withAmt < available requirement while still claiming essentially the full inflated balance. Sareon transferred 9599999999999999999999 raw USDT units to the attacker contract after the protocol’s built-in 20 percent deduction. The attacker then swapped 9762467373934909566099 raw USDT units for 15132644525221389183 wei of BNB on PancakeSwap and forwarded the BNB to the originating EOA.
The measurable loss is Sareon’s USDT treasury drain. The contract lost 9569999999999999999999 raw units of USDT, leaving only 207068000000000000001 raw units in the treasury after the exploit transaction. The loss amount is denominated in a token with 18 decimals, so the realized treasury loss was 9569.999999999999999999 USDT.
The attacker also minted large amounts of SARE to attacker-controlled addresses during the forced helper purchases, but the direct protocol loss evidenced on-chain is the depletion of real USDT reserves. The attacker’s realized native-asset profit was 14830799479387857233 wei of BNB after gas according to the native balance diff for the originating EOA.
0x53303ea516cb64cd4120c1b344d1be2d4cca03dbfdb7924ce2421c235a5f28c8.REWARD INCOME events, getUserIncome, claimUSDTRewards, and the final PancakeSwap unwind.0x51f61cef27e2fadb767d1a7f7277c9fd9ab3fbe9.0x1356062e82a940a98ff7172ba1093e206b775015 is a publicly verified proxy pointing to the same implementation.