Verify. At 03:47 Singapore time on 11 March 2026, the arbitrage agent I built executed 1,412 transactions against a price feed that had already stopped updating. The contract did not revert. The oracle did not go down in any way an uptime monitor would flag. The RPC endpoint answered every request with an HTTP 200. The pipeline returned an empty payload, the safety check read that empty payload as a boolean, the boolean defaulted to true, and the machine did what machines do with a true. It kept going. Ninety-one minutes later I froze the contract from my phone on the platform at Raffles Place, down 15% on the allocated book. The post-mortem took eleven hours. The bug took ninety seconds to explain.
Six weeks later I ran a data pipeline of my own, one built to reduce a document into nine analytical dimensions. Its input stage came back empty. Not partial. Not malformed. Empty. And the pipeline, to its credit, refused to run. It printed nine blocked modules and stopped. No fabricated conclusions. No placeholder confidence. No plausible-looking output that a downstream reader would have mistaken for a finding.
That refusal is the most important technical behavior I have seen in this industry this year. Not because the pipeline was clever. Because it was the only component in the chain that understood the difference between an answer and the absence of an answer. An empty answer and a safe answer are different objects, and almost nothing in crypto can tell them apart. Everything downstream of that distinction, from a multi-billion-dollar lending market to a fifteen-thousand-dollar-a-day trading bot to a wealth manager's compliance wrapper, inherits the failure silently and calls it normal operation.
We are in a bear market. Survival is the only yield that compounds reliably right now. The loss mechanism I am describing is not exotic. It is not a zero-day. It is not on any auditor's checklist, which is exactly why it keeps working. It is the most common way capital disappears in this industry, and it is the least discussed.
Context: Twelve Years of Silent Success
The EVM gives a message call exactly two outcomes. It succeeds, or it reverts and the state rolls back. That binary is elegant and it is the reason Ethereum survived the last decade. It is also incomplete. There is a third outcome that the design never named: the call succeeds, returns, and the data it returns is meaningless. Bytes arrive. Decoding succeeds. Arithmetic proceeds on numbers that mean nothing. Nobody designed for that state because it does not look like a failure.
I want to be precise about the lineage here, because the pattern is older than most of the people trading it.
In 2016, The DAO drained because a recursive call returned successfully. The reentrancy was not the bug; the bug was that the contract treated a returned success as a settled fact. In 2017, hundreds of multi-signature wallets froze when a library was left uninitialized and the fallback function answered every invocation with success and no data. Two hundred and eighty thousand ETH, roughly $150 million at the time, locked because a contract said yes to a question nobody had asked it. I was auditing ERC-20 contracts that year, twelve hours a day, for ICOs that mostly no longer exist. I found an integer overflow in a token called GlobalCoin before launch, which saved an estimated $2 million and earned me a referral and half a bitcoin that I sold immediately because I did not trust the volatility. The lesson I took from that year was not about overflow. It was that the dangerous contract is rarely the one that fails loudly.
In September 2021, Compound distributed roughly $80 million of COMP to users who had not earned it. A rate calculation returned a value that was arithmetically valid and economically absurd. No revert. No alert. Users woke up to free tokens and the protocol woke up to a governance crisis. In July 2023, Curve pools were drained because a compiler emitted code that a function did not expect. The calls succeeded. In May 2022, roughly $40 billion of value in the Terra ecosystem was destroyed by a mechanism in which every single state was defined as continue.
None of these are the same bug. All of them are the same shape.

Chainlink's own documentation tells integrators to validate five things before trusting a round: that the round ID is not zero, that the answer is not zero, that the round was answered, that the update timestamp is inside a heartbeat window, and that the answer is positive. That documentation has been public for years. My own production agent, written by a person who had read that documentation twice, implemented two of the five.
The reason is not laziness. The reason is that the two missing checks only matter in a state that the developer has never personally observed. You do not add defensive code for a state you believe is impossible, and an empty oracle response feels impossible right up until the moment it is the only thing in your logs.
There is a structural reason this is getting worse rather than better, and it has three parts.
The first is where Bitcoin's price now comes from. After the spot ETF approval in January 2024, the marginal discovery of BTC's price migrated away from a permissionless mempool and toward a 4 p.m. Eastern auction on a regulated venue, arbitraged against a futures basis by desks that close their books on a schedule. The input to Bitcoin's price is now a feed with a publication calendar. Feeds with calendars have maintenance windows, and maintenance windows are where empty answers live.
The second is Layer 2 fragmentation. There are dozens of rollups in production, sharing a user base that has not grown proportionally, and each one adds a sequencer feed, a bridge message queue, and a cross-domain latency budget to every position you hold. You have not scaled the user base. You have sliced a fixed amount of liquidity into more fragments and multiplied the number of interfaces where a null response can be misread.
The third is that the exchange layer has hardened around a handful of licensed venues. The $4.3 billion Binance settlement in November 2023 did not weaken the incumbent; it converted a regulatory liability into the deepest moat in the industry, because the entry ticket is now a set of licenses that no new venue can buy. When the number of venues that matter is small, the number of price inputs that matter is small, and every downstream protocol that consumes those inputs inherits the same single point of semantic failure.
In a system where every dependency is a call, the most dangerous output is not a revert. It is nothing at all, formatted as a number.
Core: Where the Zeros Come From
I want to walk the failure up the stack, because the guard you write depends entirely on which layer you think you are defending.
Layer One: The Call Itself
The most common pattern in Solidity integrations looks like this:
(bool ok, bytes memory data) = address(feed).staticCall(
abi.encodeWithSignature('latestRoundData()')
);
if (ok) {
(uint80 roundId, int256 answer, , , ) = abi.decode(data, (uint80, int256, uint256, uint256, uint80));
price = uint256(answer);
}
Read the variable name. ok. It is a lie by naming convention. ok does not mean the oracle answered. It does not mean the answer is fresh. It does not mean the answer is nonzero. It means one thing only: the call did not revert.
And there is a case where a call to an address that is not a contract returns true with empty return data. The EVM does not distinguish between a contract that executed and returned nothing and an address that has no code at all. If you are calling an oracle address that was never deployed on this chain, or that self-destructed, or that you typo'd in a config file, you get ok == true and data.length == 0.
Now decode empty bytes into a tuple of unsigned integers. Depending on how the data was produced, you either revert at decode time, which is the good outcome, or you receive zeroed memory, which is the bad one. A proxy written in assembly that returns without writing to the return buffer hands you a tuple of zeros. answer is 0. roundId is 0. updatedAt is 0.
Zero is a perfectly valid int256. Zero is a perfectly valid price. Zero means every asset in your lending market is free, every collateral position is infinitely undercollateralized, and every liquidation bot on the network has the same idea at the same block. Code doesn't care what you meant. It only knows that you passed it a number and it is arithmetic.
The fix at this layer is one line, and it is the line almost nobody writes:
if (data.length == 0) revert NoData();
Or, in assembly, if iszero(returndatasize()) { revert(0, 0) }. In a try/catch block, remember that the catch clause fires on revert, not on an empty success. Try/catch protects you from the loud failure and steps politely aside for the quiet one.
Layer Two: The Decoder
Here is the canonical validation, straight from the vendor's documentation, that I should have written in 2026 and did not:
(uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) = feed.latestRoundData();
require(answer > 0, 'bad answer');
require(updatedAt > 0, 'round not started');
require(answeredInRound >= roundId, 'stale round');
require(block.timestamp - updatedAt <= HEARTBEAT, 'stale price');
require(roundId != 0, 'no round');
Five checks. My agent had two: answer > 0 and block.timestamp - updatedAt <= HEARTBEAT. The two it had were the two that fire when the feed is completely dark. The three it lacked were the three that fire when the feed is half-lit: a round that opened and never closed, an answer carried forward from a previous round, a fresh timestamp attached to a stale value.
A stale-but-freshly-stamped price is the cruelest input in this industry because it defeats the check you are proudest of. You sleep well because you have a heartbeat check. The heartbeat check passes. The price is from four hours ago. The market has moved eleven percent. Your agent trades the gap between reality and a number that your own validation just certified as correct.
I have written before that Trust is a variable; verify the proof, then sleep. I want to amend my own slogan. Verification of the proof is not enough. You must also verify that a proof was produced. Those are two different assertions and only one of them is in the code you wrote.
Layer Three: The Consumer
Even when the data is structurally valid, the consumer can lack a state for unknown.
In 2020 I ran a Python rebalancer against Uniswap and Compound, moving $50,000 of my own capital through the DeFi Summer with custom scripts because doing it by hand was slower than the opportunities. One afternoon the subgraph endpoint returned an empty response body instead of a rate-limited error. My client code did this:
price = response.get('data', {}).get('token', {}).get('derivedETH', 0)
if price < threshold:
exit_position()
Empty response. Empty dict. Default of zero. Zero is below threshold. Exit everything.
The exit fired into a congested mempool and I paid roughly $3,000 in gas unwinding a position I never should have touched, on a signal generated by the absence of a signal. The protocol worked. The API worked. My code worked, in the narrow sense that every line executed exactly as written. There was simply no branch in my program that could represent the concept I do not know.

That is the whole disease in one line of Python. Most systems in this industry have a state for profit, a state for loss, and no state for unresolved. The third state gets mapped onto whichever of the first two has a lower branch cost, and in most implementations, that mapping is toward action, because inaction feels like a missed opportunity and action feels like a decision.
Case File 001: The Agent, March 2026
Here is the full forensics, because I owe the industry the same treatment I have given Terra and because the shape generalizes.
The agent ran arbitrage across three L2 networks, roughly 50,000 transactions a day at peak, a 98% success rate measured by receipt status, and about $15,000 a day in net profit for the first quarter. Success rate by receipt status, I should note, is the wrong metric, and it is the metric that hid the problem, because on 11 March the receipt status was green on all 1,412 losing transactions.
I would not have modelled the mechanism on paper, so let me state it plainly: the exploit did not touch my contract. It touched the pool.
My agent's edge depended on a thin on-chain pool on the least liquid of the three networks. The pool was a two-asset constant-product AMM with about $1.1 million of depth at the time. To price the second leg, my strategy read a time-weighted average price from the pool's own oracle accumulator. That accumulator is computed from the pool's reserves, updated on every swap, and readable by anyone with an RPC connection.
At 03:47 SGT, someone with roughly $700,000 and a flash loan did this: executed twenty-eight swaps of decreasing size across a ninety-second window, each one nudging the accumulator's last observation, then closed out. The reserves never looked absurd on a single block. The manipulation was designed to be invisible to a spot price. It was designed to be visible to a TWAP with a short window. The attacker knew my agent's window because my agent's window was in a public contract that the attacker could read for free.
The manipulated TWAP said the correct arbitrage was to buy. The true market said otherwise. My agent bought 1,412 times.
The safety check that should have caught it was an oracle-health module that queried a fallback price source and compared deviation. That module had this shape:
(bool ok, bytes memory data) = fallback.staticCall(abi.encodeWithSignature('getPrice()'));
if (ok) {
uint256 secondary = abi.decode(data, (uint256));
if (deviation(primary, secondary) > MAX_DEVIATION) revert Divergence();
}
The fallback endpoint was an RPC provider that had an undocumented maintenance window that night. During that window, the provider returned an empty JSON body with a 200 status. The staticCall returned ok == true and data.length == 0. Look at the code again. When ok is false, the if block is skipped. The divergence check is skipped. The function returns. The caller sees no revert. The agent proceeds.
I wrote a guard in which the failure path is identical to the pass path. That is not a subtle bug. That is a design error with a receipt.
The drawdown hit 15%. I noticed because a Telegram alert on realized PnL tripped, not because any health check fired. I opened the contract on my phone, called the freeze function, and the agent stopped mid-quarter. One manual transaction, executed by a human who was reading a chart on a phone on a train platform, was the only thing in the entire stack that could represent the state unknown.
The lesson I keep is not that AI agents are dangerous. The agent did 96 hours of work per week without complaint and produced a 98% hit rate for a quarter. The lesson is that an autonomous system requires a state for undefined, and a human must be the one who can declare it, because the machine has no ground truth to compare against and will interpolate between the numbers it has.
Case File 002: Terra, May 2022
I exited UST 48 hours before the peg broke, preserving about $80,000. I did not exit because I predicted the collapse. I exited because I read the mint-and-burn mechanism and could not find the halt condition.
The design was this: burn one UST, receive one dollar of LUNA at the oracle price. Burn one dollar of LUNA, receive one UST. Arbitrageurs were supposed to police the peg by exploiting any deviation. For the peg to hold, someone had to be willing to absorb supply. For the mechanism to ever stop, someone had to define a state in which it would not.
There was no such state. Every parameter in the system was defined for continuation. The oracle reported a price. The burn executed. The mint executed. Anchor paid 19.5% on deposits drawn from a reserve that was finite and whose depletion rate was public. The mechanism had no if branch in which the answer was unknown, and therefore no if branch in which the answer was stop.
When the price of LUNA fell 99.9% over three days, the mechanism did not break. It ran exactly as designed, minting into the void. Roughly $40 billion of value left the ecosystem through a pipe that was functioning correctly the entire time.
I published a technical breakdown of the mechanism on GitHub in the days after. It got 10,000 views in a week, which tells you how many people were looking for the failure mode while it was happening and how few had written the halt condition in advance.
The transferable rule from Terra is not about algorithmic stablecoins. It is this: if you cannot describe the conditions under which a system must stop, you do not understand the system. That rule applies to a stablecoin, a lending market, a trading agent, and a data pipeline that is asked to analyze an empty document.
Case File 003: The Rollup Sequencer
Layer 2s added a second null state that Layer 1 never had: an ordering service that can simply stop, or worse, keep producing blocks while an external feed is dark.
If you hold collateral on a rollup and the sequencer halts, your oracle on Layer 1 keeps reporting a price and the rollup keeps reporting a state, and for an interval that can run to hours, the two are not reconciled. Lending protocols on Optimism and Arbitrum have shipped an uptime feed for exactly this reason: they read whether the sequencer has been live for a defined grace period before they accept a liquidation or a price update. If the sequencer went down sixty seconds ago, they refuse. If it has been live for thirty minutes, they proceed. That grace period is the state unknown, implemented, tested, and shipped in production by teams that took the failure mode seriously.
Most of the ecosystem has not done this, because most of the ecosystem is one rollup deep and has never seen the sequencer blink. That is survivorship masquerading as robustness.
And note the arithmetic of the fragmentation itself. Dozens of rollups, each with its own sequencer, its own bridge latency, its own oracle deployment, its own governance calendar. The user base that must supply liquidity across all of them has not expanded at the same rate. Adding venues without adding participants does not distribute risk. It multiplies the number of surfaces where a null response can be consumed as a valid one, while slicing the same liquidity thinner under each of them.
Every new chain is a new place to lose money to the same bug.
Case File 004: The Compliance Wrapper
In 2024 I built a compliant DeFi yield strategy for a Singapore wealth manager, integrating Aave V3 behind a legal wrapper with KYC and AML obligations, targeting high-net-worth clients. We ran about $2 million under management at roughly 12% annualized, and the technical integration was the easy part. The API bridges were straightforward engineering. The hard part was that compliance pipelines have the identical null-semantics problem, and in that setting the consequence is not a drawdown. It is a regulatory finding.
Consider what a screening vendor returns. It returns a list of matches. An empty list means no match. An empty list also means the vendor's endpoint timed out and their gateway returned 200 with nothing in the body. A human compliance officer reading a printed report can tell those apart by instinct. An automated onboarding pipeline that checks if len(matches) == 0: approve cannot, and it will onboard a sanctioned entity with a green checkmark and a timestamp.
I found this class of issue three times in one integration, in three different vendor interfaces. None of the vendors considered it a bug. Their documentation said the response body would contain an array of results. It did not say what an empty body meant, because to them, an empty body was not a scenario.
This is where the regulation-technology intersection actually bites. The licensed venue can absorb a compliance failure with lawyers and capital. A new entrant cannot. That asymmetry is the moat, and it is not a technology moat; it is an ambiguity-resilience moat. The firms that survive the next cycle will be the ones whose pipelines treat empty as a first-class state, and they will be the ones that can afford the lawyers to write the policy that defines it.
Case File 005: Proof of Reserves
A Merkle-tree proof of reserves is a snapshot. It answers exactly one question, on one block, about one set of addresses: did these leaves hash to this root. It does not answer whether the root is complete, whether the operator has an undisclosed liability, whether the address list omitted a related entity, or whether the snapshot block was chosen for a date when the ratios flattered the exchange.
In my framing, a proof of reserves is an input, not a conclusion. It is a number handed to a consumer. And the consumer, in the case of retail deposits, has no branch for the state in which the exchange simply did not answer the question that was asked.
An exchange that posts a snapshot quarterly and a live attestation never is running a pipeline where the empty answer is the normal answer. And the market has decided, correctly or not, that this is acceptable, because the alternative venues are fewer than they were, and the licensed ones have moats deep enough that a user's only leverage is the deposit itself.
The Contrarian Read: Retail Watches the Exploit, Smart Money Watches the Interface
Here is the divergence I see in the order flow of attention during this bear market, and I will state it as sharply as I can.
Retail watches for exploits. Smart money watches for interfaces.
An exploit is a story. It has a perpetrator, a number, a tweet, and a recovery thread. It produces the news cycle this industry runs on. When Curve was drained, when Compound over-distributed, when a bridge froze, the market responded within minutes. Retail exits the token. Retail checks the chart. Retail buys the dip in whatever bled, on the theory that the wick was sentiment rather than structure.
Meanwhile the failure that compounds quarters at a time is an interface mismatch, and no one writes a thread about it. A lending market whose oracle adapter returns zero on a missing round. A liquidation bot whose health check is a try with no empty-data guard. A yield strategy whose rebalance trigger fires on a null response. An AI agent whose deviation guard has identical success and failure paths. A screening vendor whose empty body reads as cleared.
None of these look like an attack. All of them cost capital. And because they arrive silently, they compound, because the position that gets liquidated on bad data does not announce that the data was bad. It just gets liquidated, and the trader blames the market, and the market blames volatility, and the code is never fixed.
I want to name the specific blind spot in the security industry here, because I have bought audits and I know what they check.
An audit is a review of intent against implementation. It asks whether the code does what the specification says. It very rarely asks whether the specification contains a state for the absence of a value, because specifications written by humans share the human intuition that an answer will arrive. Audits are insurance, not a guarantee is the line people quote, but the deeper problem is that an audit will happily certify a system whose failure mode is a zero, as long as the zero is reachable through the specified logic path.
There is a second blind spot, which is monitoring. Almost every protocol I have reviewed with a monitoring stack monitors for reverts, for gas, for TVL, for latency. Almost none of them monitor for the event that did not happen. A feed that stops updating does not emit a revert. It emits nothing, and nothing is not an alert.
The only monitoring that catches this class of failure is a differential check against a second source, with a defined behavior for the case where the second source is also silent. That means three states, not two. Agree, disagree, and unknown. Most stacks implement agree and disagree, then route unknown into agree because the code path is shorter.
Smart money does the opposite, and it is less sophisticated than it sounds. Smart money writes the halt condition first. Before a strategy is deployed, the question is asked in this order: what makes this stop, who can declare the stop, and what happens to the position when the stop fires. Profit is derived after that, because a strategy with no stop is not a strategy. It is a position with an unbounded loss function and a pending timestamp.

The uncomfortable corollary is that most of the AI-driven trading infrastructure being deployed in 2026 is structurally unable to be smart money, because a model cannot perceive a missing input. Give a language model an empty document and it will tell you about the document. Give a price model a zero and it will tell you the asset is free. The model is not wrong. The interface is wrong. There is no token in the vocabulary that means we do not know.
Takeaway: What I Check Now
The forward-looking judgment is this. By the end of 2027, I expect null-state handling to be a named category in smart contract review, the way reentrancy became a named category after 2016. The protocols that adopt it early will be indistinguishable from the incumbents for two years, and then will be the only ones left standing, because the incumbents will keep losing capital to zeros and calling it volatility.
Until that standardization arrives, here is what I actually run. Not as advice for your portfolio. As a maintenance log for mine.
First, every external call in a production contract of mine has an explicit length check before decode, and a named revert for the empty case, because an unnamed zero is indistinguishable from a price. Second, every oracle read validates all five of the vendor's documented conditions, and I write them as five lines rather than one, because a single require with three && operators gets shortened by the next person who touches the file. Third, every monitoring dashboard has a panel for last successful update per feed, not last error per feed, because errors are loud and absences are silent.
Fourth, and this is the one I had to learn on a phone at Raffles Place: every autonomous process I run has a manual freeze that a human can call from a mobile device in under thirty seconds, and the freeze function is audited more carefully than the strategy. Code doesn't negotiate. It does exactly what the last person who edited it believed was true.
The empty payload is not a hypothetical. It is a routine event in the life of every system that touches the internet, and this industry builds its entire capital structure on top of systems that touch the internet. The question is not whether your feed will return nothing. The question is what your contract does on the block where the number is not a number, and whether the answer is written down anywhere, or whether you will find out the way I did.
Trust is a variable; verify the proof, then sleep. But verify that a proof exists first. The gap between an answer and no answer is where the money goes, and it goes quietly, on a green receipt.