We do not have a reliable USD price for the recorded assets yet.
0xbeef09ee9d694d2b24f3f367568cc6ba1dad591ea9f969c36e5b181fd301be820x2c7112245fc4af701ebf90399264a7e89205dad4Ethereum0x83cb9449b7077947a13bf32025a8eaa3fb1d8a5eEthereumOn Ethereum mainnet block 19196686, transaction 0xbeef09ee9d694d2b24f3f367568cc6ba1dad591ea9f969c36e5b181fd301be82 exploited the FILX vesting proxy at 0x2c7112245fc4af701ebf90399264a7e89205dad4. The attacker used a helper contract to call the proxy's public init function again, which rewrote the privileged owner field and vesting configuration inside proxy storage. After taking ownership, the attacker invoked the owner-only withdraw function to move all 685000 FILX out of the proxy, then sold the stolen tokens through public Uniswap liquidity and ended the transaction with 67.610518755536797453 ETH net profit.
The root cause was a live, repeatable initializer on the verified LinearVesting implementation at 0xe58acb560fc33e283ab70c700a0f2eed0e333e0a. init(IERC20,uint256,uint256) is public and directly assigns token, periods, interval, and owner without either an initialization guard or an authorization check. On a proxy that already custody-held FILX, this converted a one-call reconfiguration bug into a direct asset-drain primitive.
The victim contract is a deployed at . Its verified implementation is the contract at . The proxy held FILX tokens on behalf of the protocol before the exploit transaction, and the implementation exposes privileged vesting-management and token-withdrawal functions through the proxy fallback.
TransparentUpgradeableProxy0x2c7112245fc4af701ebf90399264a7e89205dad4LinearVesting0xe58acb560fc33e283ab70c700a0f2eed0e333e0aThe relevant storage layout is simple and security-critical. token is stored in slot 0, owner is stored in slot 1, and both are rewritten by init. The attacker did not need any private key or allowlist entry because the proxy fallback was publicly callable and init had no one-time-use protection. Public AMM liquidity also existed for FILX against USDT and WETH, which made the stolen inventory immediately realizable as ETH within the same transaction.
function init(
IERC20 initToken,
uint256 initPeriods,
uint256 initInterval
) public {
token = initToken;
periods = initPeriods;
interval = initInterval;
owner = _msgSender();
}
The snippet above is from the verified LinearVesting implementation and shows the entire privilege-escalation surface: any caller can overwrite the stored owner on the proxy.
This is an unrestricted re-initialization vulnerability on an upgradeable proxy-backed vesting contract. The implementation treats init as a regular public function instead of a one-time initializer, so the live proxy remains permanently re-initializable after deployment. Because init writes owner directly from _msgSender(), any caller can seize administrative control. The same implementation also exposes withdraw(IERC20,uint256,address) behind onlyOwner, making ownership takeover immediately monetizable. The exploit therefore requires no complex state corruption: the attacker only needs the proxy to hold transferable tokens and public liquidation liquidity to exist. The first invariant break occurs when init rewrites proxy ownership on an already initialized instance.
The owner-gated drain path is explicit in the verified code:
function withdraw(
IERC20 otherToken,
uint256 amount,
address receiver
) public virtual onlyOwner {
uint256 currentBalance = otherToken.balanceOf(address(this));
require(receiver != address(0), "receiver must not empty");
require(currentBalance >= amount, "current balance insufficient");
otherToken.safeTransfer(receiver, amount);
}
Once owner is attacker-controlled, any ERC20 balance already held by the proxy becomes withdrawable.
The exploit transaction was sent by EOA 0xd215ffaf0f85fb6f93f11e49bd6175ad58af0dfd to attacker helper contract 0xd129d8c12f0e7aa51157d9e6cc3f7ece2dc84ecd. The trace shows the helper calling 0x2c7112245fc4af701ebf90399264a7e89205dad4::init(WETH, 1, 1e18), which the proxy forwarded by delegatecall into the verified LinearVesting implementation. That delegatecall is the key code-level breakpoint because it writes new values into the proxy's storage rather than isolated implementation storage.
Immediately after the delegatecall takeover, the same helper calls withdraw(FILX, 685000000000000000000000, helper) through the proxy. The trace emits a FILX transfer from the proxy to the helper for the full 685000 FILX balance, confirming that the exploit was not limited to configuration takeover; it reached direct fund extraction. The helper then routes the stolen FILX through 0xa7434b755852f2555d6f96b9e28bafe92f08df97 on Uniswap V3, receives 169577736489 USDT, forwards that USDT to the canonical WETH/USDT Uniswap V2 pair at 0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852, receives 67645684730046434621 WETH, unwraps to ETH, and forwards the ETH proceeds back to the originating EOA.
The seed trace captures the critical execution points directly:
0x2c7112245Fc4af701EBf90399264a7e89205Dad4::init(WETH9, 1, 1000000000000000000)
0xe58aCB560FC33e283ab70c700A0F2eed0E333e0a::init(...) [delegatecall]
0x2c7112245Fc4af701EBf90399264a7e89205Dad4::withdraw(FILX, 685000000000000000000000, 0xd129...4ecd)
emit Transfer(src: 0x2c7112245Fc4af701EBf90399264a7e89205Dad4, dst: 0xd129...4ecd, wad: 685000000000000000000000)
The balance diff independently corroborates the profit realization. The attacker EOA moved from 31.371010031784340065 ETH before the transaction to 98.981528787321137518 ETH after it, for a net delta of 67.610518755536797453 ETH. That delta is fully consistent with the drained FILX, the public AMM swaps, and the transaction fee paid in the same transaction.
The attacker flow is a single-transaction ACT sequence with no privileged dependencies:
init and becomes the stored owner through delegatecall.withdraw path to move all FILX out of the vesting proxy.Representative trace evidence for the liquidation sequence is below:
0xa7434b755852F2555D6F96B9E28bAfE92F08Df97::swap(..., true, 685000000000000000000000, 4295128740, 0x)
emit Transfer(src: pool, dst: helper, wad: 169577736489)
emit Transfer(src: helper, dst: pool, wad: 685000000000000000000000)
0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852::swap(67645684730046434621, 0, helper, 0x)
WETH9::withdraw(67645684730046434621)
This flow satisfies the ACT model because every step is permissionless and reproducible from canonical on-chain state, verified code, and public liquidity.
The direct victim loss was the FILX inventory already held by the vesting proxy at the time of exploitation. The full drained amount was 685000000000000000000000 base units of FILX, which equals 685000 FILX at 18 decimals. The proxy's FILX balance was reduced to zero in the exploit transaction.
The attacker converted that inventory into ETH inside the same transaction and realized a net gain of 67.610518755536797453 ETH after gas. The effect was not limited to accounting corruption or temporary ownership spoofing; it was a complete extraction of custodial assets from the protocol component that held them.
0xbeef09ee9d694d2b24f3f367568cc6ba1dad591ea9f969c36e5b181fd301be820x2c7112245fc4af701ebf90399264a7e89205dad40xe58acb560fc33e283ab70c700a0f2eed0e333e0a0x83cb9449b7077947a13bf32025a8eaa3fb1d8a5e0xa7434b755852f2555d6f96b9e28bafe92f08df970x0d4a11d5eeaac28ec3f61d100daf4d40471f1852https://etherscan.io/address/0xe58acb560fc33e283ab70c700a0f2eed0e333e0a#code