Hook
Last week I fed a nine-dimension analysis engine an empty payload. No title. No source. No information points — the list was literally []. The engine did something I did not expect. It did not crash. It did not hang. It returned a fully formatted report in which every single field read "N/A — insufficient information." Nine sections. A risk matrix with six rows, all blank. A recovery checklist. And then, at the very bottom, a line that stopped me: it refused to fabricate, stating that producing conclusions from a null input would violate its professional floor.
Tracing the gas trail back to the genesis block, the interesting thing is not the refusal. It is that the refusal had to be written in. Someone, somewhere, decided that a null input should produce a null-but-honest output rather than a plausible-but-false one. That decision is a security property. Most on-chain systems do not have it.
Context
Here is the architecture. Any automated pipeline — a DeFi risk dashboard, an oracle aggregator, the LLM agent I prototyped in 2025 — has three stages: ingest, interpret, emit. The ingest layer is where entropy enters the system. It reads from an API, a subgraph, a mempool stream, a price feed. The interpret layer applies a model. The emit layer writes a number, a trade, or a report.
The failure mode everyone worries about is the emit layer: a bad trade, a bad number. The failure mode almost nobody audits is ingest. When ingest returns null, the pipeline has three choices. It can halt — safe and expensive. It can substitute a default — fast and dangerous. Or it can propagate the null downstream and hope the next layer catches it — the most common choice and the most lethal.
I spent 120 hours in 2020 tracing a swap function's fee logic in a Uniswap V2 fork and found an arithmetic overflow hiding behind a division by a variable that could be zero. That bug was not in the swap. It was in the assumption that the fee numerator was always non-zero. The project ignored my recommendation to rewrite the fee mechanism and shipped anyway. The most expensive bugs in DeFi are not logic errors; they are the silent substitution of a default value for a missing one.
The economics explain the choice. Halting is visible — users see a frozen UI, a stalled bot, a support ticket. Defaulting is invisible — the system keeps producing output, and output looks like health. So engineers optimize for uptime, and uptime is exactly the metric that rewards lying about the data.
Core
Let me get into the code, because this is where the abstraction becomes a loss.
Solidity has no null. A uint256 that has never been assigned is 0. A mapping lookup for a key that does not exist returns 0. An address that was never set is 0x0000...0000. This is deliberate — the EVM's storage model zero-initializes everything — but it means the language cannot distinguish "the value is zero" from "the value was never provided." Two semantically different states collapse into one byte pattern.
Now watch what that does to an oracle. Chainlink's latestRoundData() returns a tuple: roundId, answer, startedAt, updatedAt, answeredInRound. A naive integration reads answer and moves on. But answer can be 0 if the feed has never reported, or if the aggregator round is stale. If your contract treats 0 as a valid price, and computes collateral ratios against it, you have just built a liquidation engine that fires on nothing. This is not hypothetical. In 2020, a feed issue pushed a price of 0 into a loan and drained a protocol. In 2022, a Solana lending market misread a stale Pyth price and liquidated users who were perfectly solvent.
The defensive pattern is boring and everyone skips it. Check updatedAt against block.timestamp. Check answeredInRound >= roundId. Check answer > 0. Revert on any violation. Three conditionals. And yet, in an informal survey of forks I audited between 2021 and 2023, roughly one in four omitted the staleness check entirely, and one in seven omitted the zero check.
Why? Because in testing, the feed always returns a number. The mock returns 2000e8. The fork test returns 2000e8. The null case never fires in a happy-path suite, so it never appears in the diff, so it never appears in the audit scope. The absence of a failure is read as the absence of a risk.
Go one layer up and it compounds. A subgraph that has not indexed a block yet returns an empty array. A dashboard renders an empty array as 0. A trader reads 0 as "TVL collapsed." A bot reads "TVL collapsed" as a sell signal. One lagging indexer, four layers of interpretation, one market order. No attacker touched the system. The system attacked itself with a default value.
I saw the same shape in the EigenLayer restaking model I ran simulations on in 2024. The slashing conditions assumed the active-operator set was non-empty. When I modeled the set as empty — every operator unbonded simultaneously — the incentive math collapsed toward a division by zero, and the economic security guarantee evaporated. The contract would not have reverted. It would have computed a number. A meaningless, confident number. Entropy increases, but the invariant holds — the invariant being that a missing value must stay distinguishable from a present zero. Break it, and the output is not wrong in a detectable way. It is wrong in a confident way, which is worse.
Contrarian
The counter-intuitive part, and why the empty report I opened with matters.
The industry's entire security narrative is built around adversarial inputs. Reentrancy. Oracle manipulation. Flash loans. Sandwich attacks. Every one is an attacker supplying a value designed to break the system. We audit for the attacker who arrives.
We do not audit for the attacker who does not arrive. The null input is the passive attack. Nobody sends it. It is what you get when the RPC provider rate-limits you, when the subgraph lags, when the API key expires, when the feed's heartbeat misses a beat. Because nobody sends it, nobody tests for it. Because nobody tests for it, the default substitution fires silently and the system proceeds on a fabrication. In the absence of trust, verify everything twice — but the second verification most protocols perform is a presence check, not a validity check. They confirm the call succeeded. They do not confirm the answer meant anything.
Smart contracts don't panic. That is precisely the problem.
Takeaway
The LLM agent I prototyped last year had a latency problem in its verification layer — proving an agent's action on-chain without revealing the model's weights. I proposed a ZK structure for it. But the harder problem, the one I did not solve, was this: when the agent's oracle returns nothing, does the agent know that it knows nothing? An LLM asked to interpret an empty feed will not halt. It will hallucinate, because hallucination is the model's default substitution.
The empty report is, in a strange way, the correct artifact. A pipeline that can say "I have no data and I will not pretend otherwise" is more trustworthy than one that always has an answer. The next generation of DeFi security will not be won by protocols that compute more. It will be won by protocols that refuse to compute when the input is void.
Code is law until the reentrancy attack. But before the reentrancy attack, before the oracle manipulation, there is the empty round — the block where the feed said nothing and the contract said 0 anyway. How many of the protocols you trust can tell the difference between a zero and a silence?