Last week a nine-field analysis template came back to me with every field empty. The parser did not throw. The schema validator did not object. The scoring routine downstream read nine zeros, summed them against additive weights, and returned a verdict of zero risk.
That is the entire bug, compressed into one paragraph.
In a typed system, absence has a value. The value is zero. And zero is a legal input to almost every function ever written. Nothing in the type system distinguishes "the price is zero" from "we never got a price." Nothing in the schema distinguishes "this dimension was assessed and found clean" from "this dimension was never assessed at all." Both encode to the same bit pattern, and both pass the same bounds check, because the check was written as require(x <= MAX) and never as require(x != 0).
I have spent the last several years auditing exactly this seam — the boundary between a protocol's arithmetic, which is usually sound, and its input domain, which is usually unexamined. The conclusion I keep arriving at is uncomfortable for an industry that has poured enormous resources into formal verification: the arithmetic is rarely the thing that kills you. The assumption that the input was ever real is.
What follows is a technical anatomy of that assumption. Not a market call. A failure-mode map.
In a market that has gone nowhere for a quarter, this bug is invisible. Every feed sits within a percent or two of its thirty-day mean, so a substituted last-known-good value and a live value are numerically indistinguishable, and the guard that was never written costs nothing. Chop is not the absence of risk; it is the period in which the risk accrues interest without being billed. The fire only starts when the tape moves.
Context
Solidity has no null. This is not an oversight; it is a consequence of the EVM's storage model. Every slot is initialized to zero, every mapping lookup for an unset key returns zero, every struct field in a freshly allocated memory region is zero. The language gives you no way to express "this variable has not been assigned" because the machine underneath has no such state.
The consequence is that protocols must manufacture the concept of absence. They do it with sentinels: type(uint256).max, a paired boolean (exists, initialized, isValid), a separate updatedAt timestamp, or a dedicated event log that the contract itself cannot read. Every protocol that forgets to manufacture it inherits a class of bug that no amount of arithmetic review will catch, because the code is arithmetically correct — it is correct for all inputs in the domain the author had in mind, and the author's domain excluded the case that mattered.
The most consequential instance lives in price feeds. Chainlink's aggregator interface, latestRoundData(), returns five values: (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound). Five values, and the overwhelming majority of integrations in the wild read exactly one of them.
That single read is the industry's most-copied line of code and its least-examined one.
The second instance lives in lending markets. Aave and Compound do not discover an interest rate; they compute one from a governance-set function, and that function has a shape — a kink, a slope, a base — that was chosen by a risk committee and ratified by token holders. The rate is an administrative price. Once you internalize that, the relationship between the oracle and the rate model stops looking like "market data feeding a market mechanism" and starts looking like "one administrative input feeding another administrative input." That reframing matters, because it changes what a corrupted input actually corrupts.
The third instance lives in the share accounting of every vault. totalSupply == 0 is a null. totalAssets == 0 is a null. And the ERC-4626 standard, which is otherwise the most carefully specified interface in DeFi, leaves the zero state to the implementer.
Let me take these in order, because they share a single root and the root is worth stating plainly before the details: a protocol is only as correct as the domain of its inputs, and the domain is almost never written down.
Core
The canonical correct integration of a price feed looks like this: