Midas Growth-Aware Feed Safety Guarantees
If a submitted round satisfies the safe path's guardrails, setRoundDataSafe succeeds and writes that round exactly as submitted.
If it does not satisfy those guardrails, the safe path rejects it before it can update the feed.
Names are simplified on purpose: answer, start, apr, and now are the call inputs.oldFeed is the feed state before the call.safeInputsOk(...) means the submission satisfies the safe path's guardrails.safeAccepted(...) means setRoundDataSafe succeeds.writesSubmittedRound(...) means the newly written round stores the submitted answer, timestamp, block time, and APR exactly.
Midas's CustomAggregatorV3CompatibleFeedGrowth is an onchain price feed that stores a raw answer, a start timestamp, and a growth APR for each round. In other words, the contract does not treat the stored answer as the final price forever. Starting from the round's startedAt, it updates that answer over time using the configured APR. This lets the feed track assets whose price is expected to increase between submissions, instead of staying flat until the next manual update.
That makes the safe submission path economically important. When a new round is submitted, the contract compares the new growth-adjusted price at the current block time against the previous growth-adjusted price at the current block time. It does not just compare the two stored round values as if no time had passed.
In the shape of this contract, most of the risk is operator risk: an authorized updater submits the next round by hand. Formal verification helps here by showing exactly what the safe wrapper setRoundDataSafe does on both sides of the guard. In particular, the benchmark now exposes human-readable theorems saying that guardrail-safe submissions are accepted and written exactly as submitted, while submissions outside those guardrails are rejected.
Why this matters
This is a manually updated price feed. That means the main operational risk is not an adversary exploiting arithmetic from nowhere, but an authorized operator submitting a round that should have been rejected by the feed's own configured rules.
If the safe path accepted a submission outside those rules, every downstream integration reading the feed would inherit a price the contract itself was supposed to screen out. The point of this case study is to make those guardrails explicit and machine-check that they hold on the guarded entrypoint.
What this covers
This case study is about setRoundDataSafe, the guarded submission path. It also models the underlying write behavior of setRoundData, because a successful safe call ends by writing a new round through that path.
- Positive-path guarantee: if a submitted round satisfies the safe path's own guardrails, then
setRoundDataSafesucceeds and the next round stores exactly the submitted answer,startedAt, current block timestamp, and growth APR. - Rejection guarantee: if the submitted round does not satisfy those guardrails, then
setRoundDataSaferejects before the feed can be updated.
The comparison is growth-aware on both sides. The previous round is first turned into a live price at the current block time, and the incoming submission is also projected forward to the current block time before deviation is measured.
In the new spec layer, those guardrails are bundled into a readable helper safeInputsOk. It packages the deviation check, time ordering, one-hour gap, onlyUp behavior, and the answer/APR range checks that the safe path relies on.
Out of scope: direct calls that bypass setRoundDataSafe and invoke setRoundData directly, access control plumbing, events, unrelated read-only interface methods, downstream protocol integrations outside this contract.
How this was modeled
The benchmark isolates the contract slice that determines whether a safe submission is accepted and what gets written if it is. That includes the current configuration fields, the storage for the latest round, and the next-round write path that records the new answer, timestamps, APR, and round id.
The updated spec layer also introduces human-readable helpers such as safeAccepted, safeRejected, safeInputsOk, and writesSubmittedRound. Those names let the top-level theorem talk about the user-facing behavior of the safe setter instead of only exposing lower-level arithmetic lemmas.
The model also captures the growth arithmetic used to reconstruct the previous live price and the candidate live price of the new submission. This matters because the safety check is about the economically meaningful price at the current moment, not just the raw number stored in storage.
Deviation is modeled with the contract's special zero-price branch, where a candidate live price of zero is treated as a full 100% move. The negative-deviation branch used by onlyUp is included as well, along with the underlying band checks from setRoundData.
Verify it yourself
git clone https://github.com/lfglabs-dev/verity-benchmark cd verity-benchmark git checkout 07564a24d6a85eefc0479af3a547381bb7a40a68 lake build Benchmark.Cases.Midas.CustomFeedGrowthSafe.Proofs
If the build succeeds, the proofs are correct. Source repository
Proof status
This case is now organized around two human-readable headline specs in Lean 4: one theorem for accepted submissions and one theorem for rejected submissions. The corresponding proof file in the Verity benchmark repository is sorry-free, so the proved claims below are accepted by Lean's kernel rather than by informal review.
| Guarantee | Status |
|---|---|
| safeInputsOk -> accepts and writes submitted round | proven |
| not safeInputsOk -> rejected | proven |
| Zero price rejected below 100% cap | proven |
The rejection theorem is proved by contrapositive: any successful safe call is shown to satisfy safeInputsOk, so a call that does not satisfy safeInputsOk must reject. The helper packages the lower-level deviation, timing, monotonicity, onlyUp, and band checks into a single readable condition.
Assumptions
The proofs use zero axioms. The accept/reject theorems are phrased through the helper safeInputsOk. The two assumptions below matter specifically for the separate zero-price rejection corollary.
hHistorythe feed already has a previous roundsetRoundDataSafe compares against lastAnswer() only when history exists
The zero-price rejection corollary is about states where the safe path actually compares the new submission against an existing live price. If there is no prior round,
setRoundDataSafeskips the deviation check entirely.hStrictDeviationmaxAnswerDeviation < 100 * 1e8Fixed-point threshold in the contract's percentage units
The contract allows a maximum deviation up to and including 100%. The zero-price rejection corollary therefore needs the configured deviation cap to be strictly below 100%. At exactly 100%, that specific rejection claim no longer follows from the deviation check alone.
Learn more
Upstream Solidity contract, the production source modeled in this case.
Verity benchmark repository, which contains the formal specification, machine-checked proofs, and the broader benchmark suite this case belongs to.
What is a formal proof? A short explanation for non-specialists.
More research
Alchemix V3 Earmark Conservation
Formally verified earmark conservation invariant for Alchemix V3's lazy-accrual debt accounting system.

Reserve DTF Auction Price Band Invariant
Formally verified per-pair price band invariant for the auction pricing path in Reserve DTF Protocol.
Cork Protocol Pool Solvency
Formally verified solvency invariant for the unwindExerciseOther function in Cork Phoenix.