During the past week (2026/10/12 - 2026/10/18), the following 3 notable security incidents are featured, with a total estimated loss of approximately $3.87M.
| Date | Incident | Type | Estimated Loss |
|---|---|---|---|
| 2026/10/12 | Autheo Network | Private Key Compromise | ~$1.76M |
| 2026/10/14 | UNCX Network | Accounting Error | ~$807K |
| 2026/10/18 | Qubic Wallet | Weak Seed Generation | ~$1.30M |
The loss figures are estimates based on realized value.
- UNCX Network: This incident is analyzed in detail because its flawed aggregate share calculation provides the week's clearest contract-accounting lesson.
Weekly Highlight: UNCX Network
On October 14, 2026, attackers exploited UNCX Network's Smart Token Vesting V2 contracts across Ethereum, BNB Chain, Base, and Robinhood Chain. The incident affected 10 assets in 11 confirmed drain transactions and resulted in approximately $807,609 in realized or routed value [1]. The root cause was a flawed aggregate share calculation that mixed vesting tranches created at different token-to-share ratios. This caused position-level records to diverge from the global share supply, allowing attacker-controlled positions to withdraw more tokens than the attackers had supplied.
Background
UNCX Network is a multi-chain DeFi infrastructure provider whose Smart Token Vesting V2 product lets projects lock ERC-20 tokens behind time-based vesting schedules. Each schedule is represented by a vesting ID and records the token, creator, beneficiary, total amount, amount already released, and one or more unlock tranches. A soft, top-up-enabled position can receive additional tranches after creation, while its creator can cancel the unvested portion.
Positions for the same token share one balance in the VestingManager, and the protocol uses shares to determine how much of that balance each position owns. Its accounting therefore has two related layers:
Global pool, per token
create / topUp: receive tokens and mint shares
release: convert releasable shares into tokens
_totalTokenShares[token]: active shares across all positions
Position record, per vesting ID
_depositedShares[id]: share basis assigned to the position
totalAmount: token amount represented by its tranches
released: shares already paid to the beneficiary
For a release, the manager first calculates how much of the schedule is vested. It converts that fraction of totalAmount into vested shares using _depositedShares[vestingId], subtracts released, and calls the result releasableShares. In the formula below, tokenBalance is the manager's current ERC-20 balance for the schedule's token, shared across every position for that token, while _totalTokenShares[token] is the corresponding global supply of active shares. The token payout depends on their ratio:
amount = releasableShares * tokenBalance / _totalTokenShares[token]
Depositing through the vesting functions increases both token balance and share supply. A direct ERC-20 transfer to the manager changes only the token balance, so it changes the number of tokens represented by each share. This share model can support pooled positions only if every lifecycle operation keeps the position records and global supply reconciled.
Vulnerability Analysis
The affected Ethereum VestingManager at 0xA98F...99F5 contains a flawed aggregate share calculation that becomes exploitable across release(), topUpVesting(), and cancel(). Equivalent affected deployments exist on the other three networks.
To make the state changes easier to compare, the analysis below introduces D, R, and V as shorthand. These symbols do not appear in the contract code; the table maps each one to the corresponding contract variable or expression. The mapped values fall into two quantity types:
| Symbol | Contract variable / expression | Quantity type | Meaning |
|---|---|---|---|
D |
_depositedShares[vestingId] |
Share quantity | Cumulative share basis assigned to all deposits recorded for the position |
R |
schedule.released |
Share quantity | Cumulative shares already paid out to the beneficiary |
| - | schedule.totalAmount |
Token amount | Total token amount recorded across the position's current tranches |
| - | vestedAmount |
Token amount | Portion of totalAmount that has vested under those tranches at the current time |
V |
vestedShare |
Share quantity | Cumulative vested shares computed by applying one blended vested-token ratio to D |
In release(), the contract calculates releasableShares, increments schedule.released, and subtracts the same quantity from _totalTokenShares[schedule.token]. It does not reduce _depositedShares[vestingId]. After a release, the position therefore contributes only D - R live shares to the global supply, even though its stored basis remains D.
function release(uint256 vestingId) external nonReentrant {
...
uint256 releasableShares = _getReleasableShares(vestingId);
uint256 balance = IERC20(schedule.token).balanceOf(address(this));
uint256 amount =
(releasableShares * balance) / _totalTokenShares[schedule.token];
schedule.released += releasableShares;
_totalTokenShares[schedule.token] -= releasableShares;
IERC20(schedule.token).safeTransfer(msg.sender, amount);
}
cancel() applies one blended vesting ratio to the position's aggregate share basis. It first calculates vestedAmount through getVestedAmount(vestingId), then converts the token-denominated vested fraction into shares as V = (vestedAmount * D) / totalAmount. Finally, the function treats D - V as the position's removable unvested shares. V is a cumulative value, not the vested shares remaining after previous payouts.
function cancel(uint256 vestingId) external nonReentrant {
...
uint256 vestedAmount = getVestedAmount(vestingId);
uint256 remainingAmount = schedule.totalAmount - vestedAmount;
uint256 vestedShare =
vestedAmount * _depositedShares[vestingId] / schedule.totalAmount;
uint256 sharesToRemove =
_depositedShares[vestingId] - vestedShare;
_totalTokenShares[schedule.token] -= sharesToRemove;
_depositedShares[vestingId] = vestedShare;
schedule.totalAmount = vestedAmount;
schedule.cancelled = true;
IERC20(schedule.token).safeTransfer(schedule.creator, remainingAmount);
}
The flaw is the blended calculation itself. Each tranche has its own token amount, vesting progress, and number of shares minted at the pool ratio in effect when that tranche was added. Its vested shares should therefore be calculated separately and then summed. Instead, the contract aggregates the token amounts and share basis first, then applies one vesting ratio to the combined position because it stores only one D and one totalAmount. The two approaches are equivalent only when the tranches have the same share-per-token ratio or the same vesting progress. A direct token transfer changes the pool ratio before a top-up, so the new tranche receives far fewer shares per token than the original tranche. Applying one blended vesting ratio across both tranches can then produce a V that no longer represents their combined vested shares.
Because R has already been paid out from the position's cumulative vested shares, a consistent schedule must satisfy R <= V. Violating this inequality is the direct symptom of the blended calculation. The code does not maintain a separate live-share balance for the position; it subtracts D - V directly from the global _totalTokenShares. Suppose other positions contribute 1,000 shares, while this position has D = 100 and R = 80. Before cancellation, the global supply contains 1,000 + (D - R) = 1,020 shares. If the blended calculation produces V = 60, cancel() subtracts D - V = 40 and leaves only 980 global shares, 20 fewer than the 1,000 shares contributed by the other positions. If the global supply contained only this position's 20 live shares, subtracting 40 would underflow and revert; the excess deduction succeeds only because the shared pool contains shares contributed by other positions. The function then stores _depositedShares[vestingId] = 60 while schedule.released remains 80, leaving the position record in the impossible state D < R.
The code neither enforces R <= V nor caps sharesToRemove at the position's live shares. Cancellation can therefore subtract historical shares that are no longer part of the global supply. At the same time, it transfers totalAmount - vestedAmount directly in token units, without converting the refund through the pool's share ratio. Cancellation itself does not produce an excess payout; it only reduces the global share supply below the live shares recorded across positions, creating the condition for a later over-redemption.
Attack Analysis
The following analysis uses one representative Ethereum transaction, 0x352065...c23823. The attack contract executed the same core sequence against five tokens within the transaction, using flash liquidity for each token.
-
Step 1: The attacker created a small immediately vested position,
#5058, and two much larger immediately vested, soft, top-up-enabled positions,#5059and#5060. The small position would be retained for the final withdrawal, while the large positions supplied the historical share records needed to corrupt global accounting. -
Step 2: The attacker released the two large positions. At the initial near-1:1 pool ratio, each release returned approximately its net deposit. The corresponding shares left
_totalTokenShares, but the original values remained in the_depositedSharesrecords for positions#5059and#5060and were recorded as released only in each schedule's separatereleasedfield. -
Step 3: The attacker transferred tokens directly to the
VestingManager. This restored token balance without restoring the released shares, so the amount of tokens represented by each remaining share rose sharply. -
Step 4: The attacker topped up the already released large positions. Because the top-ups were priced at the newly elevated token-per-share ratio, substantial token deposits minted only a small number of new shares. The positions now combined a large stale historical basis with a small live share contribution.
-
Step 5: The attacker cancelled the two topped-up positions. Each cancellation applied one blended vesting ratio to the aggregate deposited-share history. The recomputed
vestedSharefell below the shares already released, sosharesToRemoveexceeded the position's live contribution. The global supply was reduced by roughly5.0e25phantom shares per position, even though each position had added only a small amount of live shares during the top-up. -
Step 6: With the denominator severely understated, the attacker released the small retained position. In the worked token example, a net deposit of approximately
5.73e22raw units withdrew approximately1.07e26raw units, about 1,873 times the deposit. The attacker repeated create-and-release cycles against the distorted ratio and repaid the flash liquidity.

The official incident report identified the same pattern across 11 confirmed drain transactions. Some variants completed atomically, while others performed the final release in a linked transaction. UNCX calculated the approximately $807,609 figure from realized or routed proceeds after excluding attacker principal, temporary flash volume, and returned assets [1].
Conclusion
The root cause was flawed aggregate share accounting. The protocol combined tranches created at different token-to-share ratios, then applied one vesting ratio to their aggregate share basis. This miscalculated cumulative vested shares, allowed a position to remove more shares than it still contributed, and caused position-level records to diverge from the global share supply. The attackers exploited the understated global supply to withdraw tokens from the shared balance that remained necessary to satisfy other vesting positions.
An accounting model that permits later deposits at changing share ratios should preserve each tranche's token amount, share basis, and vesting progress instead of blending them under one ratio. It should also enforce that cumulative payouts never exceed cumulative vested shares and that a position cannot remove more than its live contribution.
References
[1] UNCX Network Smart Token Vesting V2 Incident Report
Best Security Auditor for Web3
Validate design, code, and business logic before launch



