During the past week (2026/10/19 - 2026/10/25), this report tracked two security incidents with approximately $5.57M in estimated losses. The detailed analysis focuses on X100 Freezer's governance takeover on Ethereum.
| Date | Incident | Type | Estimated Loss* |
|---|---|---|---|
| 2026/10/21 | NEM Token | Flawed Business Logic | ~$820K |
| 2026/10/24 | X100 Freezer | Flawed Governance Design | ~$4.75M |
*Losses are approximate. X100 Freezer's figure covers the representative transaction analyzed below, matching Phalcon's initial estimate; the total does not include separately reported follow-on extractions. NEM's date is the on-chain transaction date; the public alert was posted on October 22 [1].
- X100 Freezer: The incident shows the combined risk of immediately effective governance eligibility and the absence of an execution timelock: the attacker used flash-loaned funds to purchase and freeze tokens, immediately acquired a majority of Senate seats, and passed and executed a malicious treasury proposal within the same transaction.
Best Security Auditor for Web3
Validate design, code, and business logic before launch
Weekly Highlight: X100 Freezer
On October 24, 2026, X100 Freezer, an on-chain staking and liquidity management system on Ethereum, was exploited for approximately $4.75M in the transaction examined here. A flawed governance design allowed newly acquired frozen balances to confer Senate seats immediately, with no timelock separating proposal approval from execution. The attacker secured two of three seats, directed a treasury swap into an attacker-controlled pool, and sold part of the recovered tokens to repay the flash loan and realize a profit [2].
Background
X100 users freeze X tokens in tiered staking positions to earn rewards. The Freezer contract at 0x79f3...2a46 also manages the project's liquidity on Uniswap V3/V4 and PancakeSwap V3. It inherits the Senate governance contract through DexTreasury; the Senate decides treasury operations through proposals and votes.
The public freeze() entry point transfers the user's tokens into the contract, then calls the private _freeze() function to create a staking position and update its accounting. _tierTotal[owner][tier] records each account's frozen amount in each tier. _freeze() updates these balances and notifies the governance system through _onStakeChanged(owner):
function _freeze(address owner, uint8 tier, uint256 amount) private returns (uint256 id) {
_settle(owner);
Account storage account = _accounts[owner];
if (account.firstFreezeAt == 0) {
account.firstFreezeAt = block.timestamp;
}
id = _create(owner, tier, amount, block.timestamp, block.timestamp);
_tierTotal[owner][tier] += amount;
totalFrozen += amount;
totalFrozenIn[tier] += amount;
_onStakeChanged(owner);
emit Frozen(id, owner, tier, amount);
}
To join the Senate candidate list, an account calls applyForSenate(). This invokes _register(), which records the registration, collects the candidacy fee, and calls the same stake-update hook:
function _register(address account) internal {
require(!_registered[account], AlreadyRegistered(account));
_registered[account] = true;
_collectCandidacyFee(account);
emit Registered(account);
_onStakeChanged(account);
}
The hook skips unregistered accounts. For registered accounts, it maintains a list ranked by frozen balances, inserting or moving accounts through _link(). isSenator() identifies the first senateSize accounts in that list as senators. The system supports 3 to 21 seats, with each seat carrying one vote. Frozen tokens are subject to a withdrawal term, calculated by _unfreezeAt() from the position's frozenAt timestamp and staking tier.
A senator submits an allowed treasury operation through propose(), which records the proposal and automatically casts the proposer's supporting vote. Other senators cast their votes through vote(), and _tally() counts supporting votes among the current seats. The proposal records its creation time and a deadline determined by votingPeriod, which defaults to three days.
Treasury functions such as v3Swap() are allowed proposal targets in DexTreasury._isProposable(). Their onlySenate modifier requires a call from the contract itself. The execution path makes this self-call using the approved proposal data. Afterward, Freezer._afterExecute() checks that the remaining X balance covers totalFrozen, preserving the aggregate frozen principal.
Vulnerability Analysis
The Freezer's flawed governance design lacked a delay before newly acquired stake could confer Senate authority and a timelock before an approved proposal could execute.
First, the stake used for ranking comes directly from current frozen balances. _stakeOf() calls frozenOf(), which sums _tierTotal without checking stake age or reading a historical balance:
function _stakeOf(address account) internal view override returns (uint256) {
return frozenOf(account);
}
function frozenOf(address owner) public view returns (uint256 total) {
total = 0;
for (uint8 tier = 0; tier < TIERS; ++tier) {
total += _tierTotal[owner][tier];
}
}
The deposit and registration paths shown above update eligibility within the same call sequence. Although _freeze() records timestamps for the position, the ranking and senator checks do not use them to enforce a minimum holding period. A sufficiently funded new candidate can therefore obtain a seat and exercise its proposal and voting rights within the same transaction. The token withdrawal term does not delay these governance rights.
Second, the three-day voting period only determines when a proposal expires. _state() returns Open as soon as the proposal exists and remains open until execution, cancellation, or expiry. The majority check is independent of elapsed time:
function _state(Proposal storage proposal) private view returns (ProposalState) {
if (proposal.executed) {
return ProposalState.Executed;
}
if (proposal.cancelled) {
return ProposalState.Cancelled;
}
if (block.timestamp > proposal.deadline) {
return ProposalState.Expired;
}
return ProposalState.Open;
}
function _isPassed(uint256 support) private view returns (bool) {
return support * 2 > senateSize;
}
execute() accepts an open proposal as soon as its supporting votes exceed half of senateSize. It checks neither a minimum interval since createdAt nor a delay after approval before calling the treasury operation:
function execute(uint256 id) external {
Proposal storage proposal = _requireOpen(id);
uint256 size = senateSize;
(uint256 support, address[] memory seats) = _tally(id);
require(_isPassed(support), NotPassed(id, support, size));
proposal.executed = true;
(bool ok, bytes memory result) = address(this).call(proposal.data);
if (!ok) {
assembly ("memory-safe") {
revert(add(result, 32), mload(result))
}
}
_afterExecute(id);
emit ProposalExecuted(id, _msgSender(), support, size, seats);
}
The self-call satisfies onlySenate, so the treasury access check adds no execution delay. The final frozen-principal check does not constrain when the proposal executes or enforce the treasury swap's minimum return. These conditions allow a newly established majority to authorize and execute a treasury operation within one transaction.
Attack Analysis
The following analysis is based on the transaction 0x38c608...4d81d0.
-
Step 1: The attacker used flash-loaned funds to purchase
Xtokens and distributed them to two controlled contracts. The borrowed funds financed the token purchase that would establish the two staking positions. -
Step 2: Each controlled contract froze its
Xin the Freezer and calledapplyForSenate(). The trace shows both contracts receiving aRankedevent after registering. Their frozen balances secured two Senate seats within the transaction. At the time of the attack, the Senate had three seats, so controlling two provided the majority needed to pass a treasury proposal.

- Step 3: The attacker created a PancakeSwap V3
X/USDTpool and supplied its liquidity. One controlled contract proposed a treasury swap, automatically casting a supporting vote; the other cast the second supporting vote. With the majority threshold met, the Freezer executed proposal 7. Itsv3Swap()call specified 400,000Xas input andamountOutMinimum = 0. The treasury received only about 35.68USDT. The trace recordsvotesFor = 2andsenateSize = 3, showing that the captured majority authorized the operation.

- Step 4: The attacker withdrew liquidity from their newly created PancakeSwap V3 pool to recover the treasury's
X. They sold part of those tokens through the existing Uniswap V3X/USDTpool and repaid the flash loan. The source of repayment was the subsequent sale, rather than an immediate withdrawal of the frozen staking positions.
Conclusion
The incident resulted from flawed governance design: current frozen balances immediately conferred authority, and approved proposals could execute without a timelock. The attacker combined these conditions to acquire a majority and use it before the flash loan had to be repaid. The treasury then sold 400,000 X for about 35.68 USDT through the attacker-controlled PancakeSwap V3 pool. The attacker withdrew liquidity from that pool to recover the treasury's tokens, sold part of them through the separate, existing Uniswap V3 X/USDT pool, and used the proceeds to repay the flash loan and retain a profit.
Governance should require a minimum staking age or determine seat eligibility and voting rights from a historical balance snapshot taken in a block before the current transaction. This prevents positions funded within the transaction from immediately authorizing treasury movements. A timelock between approval and execution should prevent the complete takeover-and-extraction sequence from settling within one transaction and provide time to review or cancel a malicious proposal. These controls address separate stages of the governance path and should be assessed together during a smart contract audit.
References
[1] BlockSec Phalcon: NEM Token incident analysis
[2] BlockSec Phalcon: X100 Freezer governance incident analysis
About BlockSec
BlockSec is a full-stack blockchain security and crypto compliance provider. We build products and services that help customers to perform code audit (including smart contracts, blockchain and wallets), intercept attacks in real time, analyze incidents, trace illicit funds, and meet AML/CFT obligations, across the full lifecycle of protocols and platforms.
BlockSec has published multiple blockchain security papers in prestigious conferences, reported several zero-day attacks of DeFi applications, blocked multiple hacks to rescue more than 20 million dollars, and secured billions of cryptocurrencies.
-
Official website: https://blocksec.com/
-
Official Twitter account: https://twitter.com/BlockSecTeam



