During the reporting period (2026/08/24 - 2026/08/30), we observed 5 security incidents with a total estimated loss of approximately $22.7M.
| Date | Incident | Type | Estimated Loss |
|---|---|---|---|
| 2026/08/20* | The Cosmos EVM Exploit Series (MANTRA, TAC, KiiChain, +3) | Arithmetic Underflow/Overflow | ~$5.7M* |
| 2026/08/27 | Moonwell | Price Manipulation | ~$9.1M |
| 2026/08/28 | Ajna | Improper Business Logic | ~$775K |
| 2026/08/28 | Rain Card Contract Exploit Series (Avici, Tria, and others) | Signature Verification Bypass | ~$1.1M† |
| 2026/08/30 | Tectonic | Price Manipulation | ~$6M‡ |
*The Cosmos EVM series began on 08/20 (MANTRA), before this week's reporting period and not covered in last week's report; it is included here for completeness. The ~$5.7M is what attackers realized across the six affected chains (~$2.87M via DEXes and ~$2.85M via centralized exchanges, since frozen), at August-19 prices, per the official Cosmos post-mortem. Nominal drains were larger where a token amount is known (TAC ~3B TAC, ~$7.5M; MANTRA 720.9M tokens, ~$3.6M; KiiChain 148.3M KII), but most tokens went unsold, frozen, or recoverable on-chain.
†The ~$1.1M is the estimated aggregate across the Rain-supported programs exposed by the shared contract; Avici and Tria are the two largest, disclosing ~$500,859 (1,685 users) and ~$431,945 (636 users) respectively.
‡The ~$6M is the realized loss, bridged to Ethereum before the rollback. Estimates of the total drained range from ~$74M (tracked in the attacker's wallets) to ~$119.5M (gross market outflow); the bulk stayed on Cronos and was erased when validators rolled the chain back to its pre-exploit state. Neither Tectonic nor Cronos has confirmed a final loss figure.
Best Security Auditor for Web3
Validate design, code, and business logic before launch
Weekly Highlight: The Cosmos EVM Exploit Series (Traced on TAC Chain)
This series was selected for what it reveals about shared-infrastructure disclosure: because the bug was misjudged as not a serious security threat, its fix shipped as a silent public patch rather than through coordinated private distribution, and a third-party fork then described the exploit path in the open, exposing every unpatched chain still running the module.
Between 2026/08/20 and 08/25, attackers ran a single exploit chain combining two vulnerabilities in the shared cosmos/evm module across six Cosmos EVM chains. Both bugs sat in the code that reconciles EVM state with the Cosmos x/bank ledger: a balance underflow and a matching overflow, chained inside one supply-neutral transaction.
Per the official Cosmos post-mortem, the bug was reported through the bounty program in April and initially misjudged as not threatening production funds, so it went through the silent public-patch process and shipped in v0.6.2 and v0.7.2 on 08/19. On 08/20 a third-party fork publicly described the exploit path, and the first drains began hours later, hitting MANTRA first (08/20), then TAC and KiiChain (08/22) [1][2]. Across all six chains, the attackers realized roughly $5.7M at August-19 prices (about $2.87M sold on DEXes and ~$2.85M on centralized exchanges, since frozen) [1].
This report analyzes TAC Chain in detail as the worked example; it was the hardest-hit chain in the series, with a staking-pool loss of ~$7.5M nominal [3].
Background
TAC Chain runs both the Cosmos SDK and the EVM. A tac1... address and a 0x... address share the same underlying 20 bytes, so a single address can first be created as a Cosmos vesting account and later have an EVM contract deployed to it. These are not two separate accounts: the same address simultaneously carries vesting state and contract code. The two run side by side, and TAC exposes native Cosmos actions such as staking to EVM callers through precompiled contracts at fixed addresses, so an EVM contract can invoke them like any other call.
A vesting account's Bank total balance includes locked tokens. The locked portion cannot be transferred before it vests, while the spendable balance is what remains after subtracting the locked amount from the total. When the EVM loads an account, it initializes the StateDB balance from the spendable balance, and contract transfers during execution operate on that balance.


At the end of a transaction, the balance changes in the StateDB are settled back into x/bank, the authoritative ledger, and only what settles there is real, transferable TAC. TAC runs the v0.7.x line, which writes an EVM balance into x/bank directly, but only if the value survives a uint256-to-int256 conversion, so a balance near 2^256 cannot be settled at all.
Locked tokens cannot be transferred, but they can still be delegated for staking. Delegation checks the Bank total balance, which includes locked tokens, and then keeps the vesting restriction through DelegatedVesting. For a fully locked account with total=1, delegating that unit reduces the Bank total balance to 0 and updates DelegatedVesting, while the spendable balance correctly remains 0 both before and after.
Vulnerability Analysis
The buggy component is the balance synchronization in the shared cosmos/evm handler that runs on every stateful staking precompile call, exposed through the staking precompile at 0x0000...0800 and implemented in [4]. It reconciles EVM balances with the Cosmos ledger through unchecked uint256 arithmetic, and this exposes two complementary defects: a balance underflow on the staking write-back path, and a matching overflow on the ordinary addition path. Neither is dangerous alone; the risk comes from their combination on a shared arithmetic path.
The first defect is an underflow on the balance write-back. When a stateful staking precompile call executes, the native Cosmos action runs between a BeforeBalanceChange and an AfterBalanceChange hook, and AfterBalanceChange is responsible for reflecting the native balance change back into the EVM StateDB.


Delegation validates against the Bank total balance, so a fully locked account can pass the delegation check while its spendable balance stays 0. The native delegation deducts the delegated amount from the Bank total balance and emits a coin_spent event. The defect is in how AfterBalanceChange consumes that event: instead of reloading the account's current spendable balance and assigning it, it replays the coin_spent amount as stateDB.SubBalance(spender, amount), subtracting from the existing EVM balance.

Because SubBalance performs the subtraction with unchecked uint256 arithmetic, any account whose EVM balance is smaller than the event amount underflows. For an account with EVM balance 0 and a coin_spent amount of 1, 0 - 1 wraps to MAX_UINT256.

The second defect is a matching overflow on the addition path. Every balance transfer runs SubBalance on the sender and AddBalance on the recipient through the same StateDB, so both directions share one arithmetic path.

Both resolve to the same stateObject primitives, where AddBalance and SubBalance compute new(uint256.Int).Add(s.Balance(), amount) and .Sub(s.Balance(), amount) with no range check. Just as the subtraction underflows past 0, a large enough addition overflows past MAX_UINT256 and wraps back down.

The two defects are complementary. On its own, the underflow produces a MAX_UINT256 balance that is inert, because a value near 2^256 cannot survive the uint256-to-int256 conversion that settlement into x/bank requires. The unchecked addition is the counterpart: it is the only path that can bring such an oversized balance back below that settlement ceiling.
Attack Analysis
The following analysis is based on the transaction 0xae4e9b...da46fc.
-
Step 1: In transaction 0x4da591...df1af7, the attacker deployed a
CREATE2factory so that the attack contract address0x5711...c978could be computed before the contract was deployed. -
Step 2: In transaction 95F43742...6A885BA, the attacker used
MsgCreateVestingAccountto transfer and lock one base unit (1utac) at that future contract address, giving it a Bank total balance that could pass the delegation check while keeping its spendable balance at0.

- Step 3: In transaction 0x2400f8...c57c81, the attacker used
CREATE2to deploy the attack contract to the same0x5711...c978address, making it both a Cosmos vesting account and an EVM contract so an external account could pay gas while the contract address remained the delegator withspendable=0.

-
Step 4: The attack contract delegated the locked base unit through the staking precompile. The staking flow accepted the delegation against the Bank total balance, and the balance write-back then wrapped the contract's EVM balance to
MAX_UINT256. -
Step 5: The wrapped
MAX_UINT256balance could not be settled back to the Cosmos ledger as-is, so the attack contract first brought it down to a settleable value. It sent almost the entire balance tobonded_tokens_pool, the chain's largest account and the one holding all staked TAC, choosing the amount so that the pool's balance overflowed on addition and wrapped to0. Because that amount was deducted from the attack contract's ownMAX_UINT256, the contract was left holding exactly the pool's former balance, with no new supply created. Zeroing the pool takes its entire balance, the most this overflow could yield, which is why the attacker targeted the chain's largest account in the first place. -
Step 6: The attack contract then transferred that balance, 2,985,651,403.40 TAC, to the attacker's address.

Our transaction-level analysis matches the official Cosmos post-mortem, which describes two chained vulnerabilities: the underflow above produces the anomalous balance, and the overflow in Attack Analysis Step 5 converts it into the pool's real funds [1]. An earlier KiiChain post-mortem, published before that official report, held that at least three upstream defects were involved and that only the underflow has been patched [2]. Independent press coverage summarized the same disagreement over how many upstream defects remain [5].
Conclusion
The root cause of the Cosmos EVM Exploit Series was an inconsistency in how the EVM and Cosmos layers accounted for the same balance, combined with arithmetic that was never bounds-checked. When two runtimes share one ledger, they must agree on balance semantics down to each individual account, and every balance change must be checked for overflow and underflow. Because the flaw lived in a shared module rather than any single chain's code, one defect exposed every chain running it, which is what turned a single bug into a multi-chain event. Beyond the code, the incident is a lesson in severity assessment. The vulnerability was initially judged not to threaten production funds, so its fix shipped as a silent public patch; by the time that judgment was corrected, the patch was already public, and a third party's disclosure of the exploit path turned the misjudgment into six exploited chains. A shared-infrastructure bug that can move real funds needs private, coordinated distribution from the start, not a silent public patch anyone can decode.
More Incidents This Week
Moonwell
On 2026/08/27, Moonwell on Base was exploited for about $9.1M by combining collateral-accounting inflation with oracle price manipulation of MAMO, a low-liquidity asset listed in its Core Market. Beyond pushing up the MAMO price, the attacker transferred MAMO directly into the mMAMO market contract without minting shares, which raised each share's backing and inflated the collateral value on top of the price move. Against the doubly inflated collateral the attacker took roughly $11.03M in gross borrows across cbBTC, WETH, USDC, and wstETH, leaving about $9.13M in residual obligations after liquidations [6][7].
Vulnerability Analysis
Moonwell's Core Market runs on Compound v2 code, where one market share (mMAMO) is worth (cash + totalBorrows - totalReserves) / totalSupply. Two weaknesses combine here. First, the MAMO supply cap only checks the formal mint path, so a direct transfer of MAMO into the mMAMO contract adds to the market's cash without minting shares; this raises the computed exchange rate (exchangeRateStored()) and lifts the collateral value of every existing share while bypassing the cap entirely. Second, MAMO was listed as collateral with a 50% collateral factor despite thin liquidity, so its oracle price can be moved with modest capital. Because collateral value is computed as shares times exchange rate times oracle price, both the exchange rate and the price are manipulable surfaces, and inflating them together multiplies borrowing power against far more liquid assets.
Attack Analysis
The following analysis is based on the transaction 0x09687d...395593e. The operation was seeded with about $1.947M (799 ETH converted to USDC and bridged to Base); gross MAMO purchasing volume reached about $7.50M once borrowed assets were recycled.
-
Step 1: The attacker supplied
MAMOformally to mintmMAMO, then transferred53,393,290 MAMOdirectly into themMAMOcontract in two transactions that emitted noMintevent, raising the market's computed exchange rate about3.68xand revaluing themMAMOshares the attacker had just minted along with every other holder's. -
Step 2: The attacker bought
MAMOacross DEX pools while liquidity was thin, driving theMAMO/USDfeed from about$0.0106up to about$0.43.

- Step 3: With both the exchange rate and the price inflated, the attacker completed 18 borrows across
cbBTC,WETH,USDC, andwstETH(about $11.03M gross), then converted and consolidated the proceeds, bridging about8.729M USDCto Ethereum through CCTP and converting it to about8.728M DAI.
Conclusion
The root cause was that Moonwell valued a low-liquidity collateral asset on two independently manipulable surfaces at once: its oracle price, which thin liquidity let the attacker move with modest capital, and its receipt-token exchange rate, which a direct transfer of the underlying inflated because the supply cap guarded only the formal mint path. Since collateral value multiplies the two, inflating both together multiplied borrowing power against far more liquid assets. A lending market should treat both the price and the share-accounting of collateral as manipulable: exclude unsolicited transfers from the exchange-rate calculation, and pair conservative collateral factors with liquidity-aware pricing and strict supply and borrow caps for thin assets.
Ajna
On 2026/08/28, Ajna was exploited for about $775K across seven Ethereum pools through a business-logic flaw in its liquidation path. Ajna is a lending protocol that uses no external oracle; it prices positions through the LUP (Lowest Utilized Price) derived from bucket liquidity. The attacker first manipulated the LUP to create a severely insolvent position, then had the protocol liquidate a controlled position at a Dutch-auction price the protocol still held far above market, so that bucket deposit claims were consumed at their face value in quote token to repay debt while the attacker received real collateral in return [8].
Background
Ajna is a permissionless protocol where anyone can create a pool of paired tokens for supplying and borrowing, similar to a Uniswap pool. Each pool is divided into buckets, similar to price ticks in Uniswap V3, where each tick represents a fixed amount of quote token per unit of collateral. Lenders choose a bucket according to their preferred loan-to-value ratio and provide liquidity there, receiving LP (a claim on that bucket's deposits) in return. Because there is no external oracle, the LUP is derived directly from the bucket deposit distribution and the total debt.
A position becomes liquidatable once its threshold price (its debt divided by its collateral) rises above the LUP. Anyone can then kick it into liquidation by posting a bond, which opens a Dutch auction. The auction price is not tied to the spot market: it starts at 32 times a reference price and halves every hour, and it is held flat for the first hour (a cure period) before it begins to decay.
Auctioned collateral can be taken in two ways. A caller can take() collateral by paying quote token at the current auction price, or call bucketTake(), which uses a chosen bucket's quote deposits to buy the auctioned collateral and repay the borrower. In a bucketTake(), the collateral is credited into that bucket, so its lenders are the ones acquiring it; for an arbitrage bucket take, the caller is additionally paid LP rewards (a claim on that bucket) on the spread between the bucket price and the auction price as the incentive to trigger the liquidation. The design assumes a taker only steps in once the auction price has fallen to or below what the collateral is worth: with no oracle, this descending auction is how Ajna discovers a fair price, and a bucket's lenders profit by acquiring collateral below their own bucket price. The auction itself ends only when the borrower's debt is cleared or the loan becomes collateralized again; a separate settlement path resolves an auction after a 72-hour grace period, or earlier once no collateral remains, absorbing any residual bad debt and returning leftover collateral to the borrower.
Three implementation details of this machinery determine the figures a take produces. First, the auction price is not market-derived; it decays as 32 * max(kickMomp, neutralPrice) * 2^(-max(elapsedHours - 1, 0)), so it stays flat through the first-hour cure period and only then begins to fall, remaining near 32 times its reference price shortly after that period.

Second, a kick is admitted only when the borrower is already undercollateralized at its pre-penalty debt: _kick() runs the _isCollateralized() check and reverts with BorrowerOk() before it adds the three-month-interest penalty, so that penalty raises the recorded post-kick debt without having helped the position qualify.

Third, the first take on an auction adds a 7% penalty to the borrower's debt before the repayment and collateral amounts for the take are calculated, so a single bucketTake() need not clear the debt on its own.

Vulnerability Analysis
The buggy contract is the Ajna pool (0xad24...178e); the liquidation logic described here is taken from that verified source. Inside bucketTake(), the protocol uses a bucket's deposit claims at their face value in quote token to repay an auction borrower's debt, and, on an arbitrage bucket take, pays the taker LP rewards based on the spread between the bucket price and the auction price.
It never checks the actual recoverable value of those deposit claims or whether the auction price is economically reasonable; it simply assumes the claims can be redeemed later at face value. The two prices it relies on are computed independently, and neither is validated against the other: the LUP follows the bucket deposit distribution and the pool's total debt, while the auction price decays purely on elapsed time from a reference price fixed at kick. A claim's on-chain face value can therefore diverge from what it can actually recover, and bucketTake() still settles debt against that face value.
Internally, bucketTake() routes through _takeBucket() into _rewardBucketTake(), where, on an arbitrage take, the taker's LP reward is computed as the collateral taken multiplied by the spread between the bucket price and the auction price.

Attack Analysis
The following is based on on-chain analysis of the cbETH pool, one of the seven affected, using transactions 0x8a8793...016e64 and 0x12dfde...14e4f5.
- Step 1: In the setup transaction, the attacker added about 49.343
WETHof quote liquidity to bucket index2000, a price far above the market. Backed by that inflated bucket, the attacker opened an almost unsecured position, pledging about 0.001cbETHto borrow about 49.319WETH. This position holds almost no collateral, so it is not the prize; it serves two setup purposes. First, as an insolvent debt it drags theLUPdown once the market returns to normal. Second, because nearly all of the 49.343WETHdeposited into bucket2000has now been borrowed back out, leaving only a dust amount, bucket2000is left with a deposit claim whose face value, about 49.343WETH, far exceeds what it can actually recover; that impaired claim is the ammunition the attacker later spends at face value in Step 4.

-
Step 2: In the same setup transaction, the attacker used a second controlled address,
0x02d329...6f5f, to open a borrower position at the normal price level, pledging about 48.128cbETHof collateral and borrowing about 49.319WETHagainst it. This returned the market price to a normal range, leaving the first position severely insolvent while the second position sat just around its health threshold. This second position, which holds the real collateral, is the one the attacker actually intends to drain; the insolvent first position is only the lever that moved theLUP. -
Step 3: The attacker then kicked the second position (the one opened by
0x02d329...6f5f) into auction rather than the insolvent first one. A kick is only accepted if the position is already undercollateralized at its pre-penalty debt against the currentLUP, and the insolvent first position had by now dragged theLUPlow enough that the second position's accrued debt met that bar. Only after the eligibility check does the protocol add a three-month-interest kick penalty, which is why the recorded post-kick debt comes to about 49.362WETH. The kick opened the position's Dutch auction.

- Step 4: The attacker waited until just after the one-hour cure period, when the auction price had only begun to decay and was still close to 32 times the reference price. Calling
bucketTake()on the second position at bucket2000, the same bucket now holding the impaired claim, liquidated it at that still-high, far-above-market price. Because this was the first take on the auction, the protocol added a 7% penalty to the borrower's debt before calculating the take's repayment and collateral amounts. The collateral is priced at the auction price, so consuming about 49.34WETHof bucket2000's deposit repaid only part of that debt and removed only about 1.51cbETHof collateral, leaving most of the collateral untouched while the caller collected a large amount ofLPrewards on the spread.

-
Step 5: The attacker redeemed those
LPrewards throughremoveCollateral(), taking part of the profit out as collateral. -
Step 6: To close out, the attacker took a Balancer flash loan and called Ajna's
take()to pay the residual debt thebucketTake()had left, this time with real quote token, until the borrower's debt reached zero and the auction exited. A finalrepayDebt()call then withdrew the now-unencumbered collateral, about 46.51cbETH, withquoteRepaid=0: no quote token was returned for it. The gain came at the pool's expense. The shortfall did not disappear, it moved: with the second position closed, the first position's still-unpaid, near-unbacked loan remained in the pool as bad debt borne by the other lenders.

Conclusion
The root cause is that Ajna's liquidation path settles an auction borrower's debt against a bucket's deposit claims at face value, without checking their real recoverable value or whether the auction price is economically reasonable. That missing check is what let a manufactured impaired claim be spent at face value to drain real collateral, leaving the shortfall in the pool as bad debt. The liquidation path should value deposit claims by their real recoverable amount rather than face value, confirm the auction price is economically reasonable before completing a take, and guard against consuming claims that a pool's existing bad debt has already impaired. The affected pool contracts are immutable and expose no administrative pause, so once the drain began there was no way to halt it; users could only exit on their own.
The Rain Card Contract Exploit Series (Traced on Avici)
On 2026/08/28 (UTC), an outdated version of Rain's shared Solana card-collateral program was exploited through an Ed25519 signature-verification bypass. By making the program accept a fabricated admin approval, the attacker took control of user collateral accounts and drained the token balances held in them. Because the flaw sat in program code shared across Rain-powered card programs, a single bug exposed all of them at once: the exploit drained an estimated ~$1.1M in total, with Avici and Tria the two largest, disclosing about $500,859 (1,685 users) and $431,945 (636 users) respectively [9].
The analysis below uses Avici as the worked example.
Background
On Solana, signature checks are performed by a native Ed25519 precompile as part of transaction processing: if a referenced signature is invalid, the whole transaction fails. A business program does not receive that result directly; it inspects the other instructions in the same transaction (through the Instructions sysvar) and relies on the validation having passed. When a user tops up a Rain-powered card (as with Avici), the balance is held in a per-user collateral account managed by the shared Rain program, and that account's token authority is derived from the program, so the account's admin can move the assets in that account through the program's transfer flow.
Changing a collateral account's admin requires two signatures. One must come from the protocol-designated admin; the other signer has no special identity requirement. This design relies on off-chain authorization: once the protocol admin has signed the admin-change message, the program treats it as approved.
An Ed25519 verification instruction begins with a one-byte count of the signatures to check and a padding byte, followed by one Ed25519SignatureOffsets structure per signature. Each structure specifies not only the byte offsets of the signature, public key, and message, but also the instruction index where each of those should be read [10]. These indexes are an intentional feature: they let one verification read its inputs from the data of any indexed instruction in the transaction, for instance to verify a signature over another instruction's data without copying it. The native verifier simply reads whatever the offsets and instruction indexes point to.

Vulnerability Analysis
The flaw is in the collateral program (3zVB...yBzDuc): it trusts a public key without confirming that key is the one the verifier actually checked. To record the admin-approving signer, it reads a public key from a fixed position inside the Ed25519 verify instruction it inspects, then takes the mere presence of a passed verification as proof that the holder of this key signed the admin-change message.
What it never confirms is where that verification actually looked. Because those instruction-index fields are caller-controlled and the runtime hands every instruction's data to the native verifier, the verify instruction can hold one public key in its own body while its instruction indexes steer the verifier onto a different instruction, to verify a signature under a different public key over a separately sourced message.


So two reads of "the admin key" exist with nothing tying them together: the program trusts the key sitting in the instruction it inspects, while the verifier only ever checked whatever the indexes pointed to. The admin's key can be present in the transaction as data even though no signature valid under that key was ever verified. That decoupling is the vulnerability, and the missing check is the binding that would force the public key and message the verifier actually verified to be the same key and message the program trusts.
Attack Analysis
The following analysis is based on the transaction ZmpBgn...mqWL.
- Step 1: The attacker built a transaction with two Ed25519 verification instructions followed by
SubmitSignatures. Instruction0carried the attacker's own public key (cafa…53db) and a real signature over the admin-change message, giving the transaction one genuinely valid Ed25519 verification.

-
Step 2: Instruction
1placed the protocol admin's public key (a2fc…959a) in the public-key slot but filled the signature slot with fake0x09bytes, so this instruction would fail if its own data were actually verified. -
Step 3: The attacker set instruction
1's Ed25519 header to01003000000010000000700020000000. Only the three instruction-index fields do the redirection, and decoded they are all zero, so the signature, public key, and message are all read from instruction0(the byte offsets still point within that instruction's data):num_signatures = 1, padding = 0 signature_offset = 48, signature_instruction_index = 0 public_key_offset = 16, public_key_instruction_index = 0 message_data_offset = 112, message_data_size = 32, message_instruction_index = 0The second verification therefore re-read instruction
0and re-verified the attacker's own valid signature instead of the invalid0x09payload in instruction1. -
Step 4: The attacker called
SubmitSignatures. The program saw two successful Ed25519 verifications, but when recording the second signer it read the protocol admin public key embedded in instruction1, so it accepted that admin key as an approved second signer. -
Step 5: With that false approval recorded, the attacker used the admin-change flow to set the victim collateral account's admin to the attacker, converting the verification bypass into direct control of that account.

- Step 6: Having validated the caller as the collateral admin, the program moved assets out of the user's card-collateral token account through its transfer flow, invoking the transfer with its own program-derived address (PDA) as that token account's authority.

Conclusion
The root cause of the Rain Card Contract Exploit Series was that the collateral program confirmed a native Ed25519 verification had run but never confirmed that the public key and message it trusted were the ones the verifier had actually checked. A program that leans on native signature verification must bind the authorization data it consumes to that verification: either require a self-contained Ed25519 layout that reads its inputs from the current instruction, or resolve every referenced instruction and compare the verified public key and message, byte for byte, against the data it acts on. That the same flaw ran unpatched across many Rain-powered card programs is what turned one bug into a multi-program event.
Tectonic
On 2026/08/30, Tectonic, a Compound-style lending protocol on Cronos, was exploited because its low-liquidity governance token TONIC was allowed as collateral with a 20% collateral factor. The attacker inflated TONIC-denominated collateral on two surfaces at once: a direct-transfer manipulation of the tTONIC exchange rate, paired with DEX buying that pushed the oracle price up. Against the doubly inflated collateral, the attacker borrowed across multiple lending markets. About $6.29M (2,592 ETH) was bridged to Ethereum and stands as the realized loss; the bulk of the drain stayed on Cronos and was erased when validators rolled the chain back to a pre-exploit state. Neither Tectonic nor Cronos has confirmed a final loss figure [11].
Vulnerability Analysis
The root cause was listing TONIC, a low-liquidity governance token, as collateral with a 20% collateral factor. Despite its governance role, TONIC had extremely thin on-chain liquidity, so its valuation could be moved sharply with limited capital. As in the Moonwell exploit, two surfaces were manipulable at once: the TONIC/USD oracle price, and the tTONIC receipt-token exchange rate, which a plain transfer of TONIC into the market lifts without canceling the matching debt [11]. Any non-zero collateral factor then converted that inflated valuation into borrowing power against far more liquid assets.
Attack Analysis
The reconstruction of the attack below is based on on-chain intelligence and a detailed archive-node reconstruction [11][12].
-
Step 1: The attacker supplied about 5M
USDCto Tectonic as collateral. While theTONIC/USDoracle still valued the token near$1.06e-8, one controlled account borrowed roughly 376.54TTONICand moved it to a second account. -
Step 2: The second account supplied about 41.87T
TONICnormally, receivingtTONIC(the market's receipt token) in return. -
Step 3: The attacker transferred most of the remaining borrowed
TONICdirectly into thetTONICmarket contract without repaying the originalTONICdebt, raising thetTONICexchange rate and lifting the collateral value of thetTONICheld by the second account. -
Step 4: Using the inflated
tTONICcollateral, the attacker borrowed 200,000USDCand about 6.96MCRO, then bought roughly 16.23T moreTONICacross theTONIC/USDC,TONIC/WCRO, andTONIC/VVSpools and routed it back into the market. This lifted the exchange rate further and drove the DEX spot price up; Tectonic's offchainTONIC/USDfeed then accepted a series of rapidly rising quotes (from about$1.06e-8at 12:19 UTC to about$2.08e-6by 12:49 UTC). This is the external surface.

-
Step 5: Further borrows of about 3.31M
USDCand 21.21MCRObought another 7.68TTONIC, again routed into the market, reinforcing both the inflated exchange rate and the manipulated price immediately before extraction. -
Step 6: In the final transaction, the attacker drew against the doubly inflated collateral across multiple lending markets, extracting roughly 55.24M
USDC, 45.65MUSDT, 98WBTC, 1,895WETH, 16.75MCRO, and other assets. About $6.29M was bridged to Ethereum (~2,592 ETH) before validators halted the chain. Block production resumed at 2026/08/30 23:49 UTC (announced 08/31) after validators rolled the state back to a pre-exploit block, erasing the on-Cronos balance; only the bridged ~$6.29M stands as the realized loss.
Conclusion
Like the Moonwell exploit earlier this week, this was a price-manipulation attack on a Compound-style market that accepted a low-liquidity token as collateral, inflating the collateral's value on two surfaces at once: the oracle price and the receipt-token exchange rate. Lending protocols should avoid listing low-liquidity assets as collateral, apply strict supply and borrow caps where they must be supported, and use time-weighted or liquidity-aware pricing so a thin spot market cannot move the oracle. The exchange-rate surface needs its own guard: a market's collateral accounting should exclude unsolicited transfers of the underlying, so a direct transfer cannot inflate the receipt-token exchange rate while the matching debt stays outstanding. On-chain monitoring of abnormal price and borrowing activity can further shorten the response window before value is bridged out.
References
- [1] Cosmos EVM GHSA-7g4w-cg88-2cq2 Post-Mortem
- [2] KiiChain incident disclosure
- [3] TAC Chain incident disclosure
- [4] cosmos/evm balance handler commit
- [5] crypto.news: Cosmos EVM vulnerability drains MANTRA, TAC and KiiChain
- [6] Blockaid alert on the Moonwell exploit
- [7] Moonwell: Post-Mortem, MAMO Market Incident on Base
- [8] Defimon alert on the Ajna exploit
- [9] crypto.news: Rain contract exploit drains $1.1M from card users
- [10] Solana Docs: Ed25519 program
- [11] The Defiant: Cronos rolls chain back after Tectonic exploit
- [12] MASTR: Tectonic archive-node reconstruction
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



