Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
167 commits
Select commit Hold shift + click to select a range
21739ee
added rules inspired form URD
bhargavbh May 7, 2026
030c175
tuned
bhargavbh May 7, 2026
8e2e5ee
merged main
bhargavbh May 19, 2026
4898d72
take correctness passes; added some diagnostic tests
bhargavbh May 19, 2026
215698b
removed the diagnostic rules
bhargavbh May 19, 2026
b62eae7
cleaned up comments
bhargavbh May 19, 2026
b9d943e
add completeness rule
bhargavbh May 19, 2026
9b92e50
abstract properties instantiated to midnight
bhargavbh May 19, 2026
0cd4f3e
cvl fmt
bhargavbh May 19, 2026
07d95e3
fmt
bhargavbh May 19, 2026
bcc5198
renamed spec; minor changes in OfferTree definition
bhargavbh May 20, 2026
38c1875
tuned
bhargavbh May 20, 2026
8208a49
fmt
bhargavbh May 20, 2026
80d2ad2
forge fmt
bhargavbh May 20, 2026
7a3e019
Merge remote-tracking branch 'origin/main' into certora/offer-tree
bhargavbh May 20, 2026
03ba8cd
resolved errors from merge
bhargavbh May 20, 2026
dd415d0
updated callback summary signatures
bhargavbh May 20, 2026
24140f4
new rule in Ratification.spec: isRatified always calls isLeaf
bhargavbh May 20, 2026
cfb723f
rhashNode summarised similar to hashOffer
bhargavbh May 20, 2026
8187c24
revamped soundness: Offer included in the node
bhargavbh May 21, 2026
2d1eb87
tuned comment
bhargavbh May 21, 2026
d77d0b3
add checker
bhargavbh May 25, 2026
939a570
add checker
bhargavbh May 25, 2026
d9f174e
summarise isRatify
bhargavbh May 25, 2026
fc758a3
trimmed down to core properties
bhargavbh May 26, 2026
253e37e
removed completeness
bhargavbh May 29, 2026
89c39d6
comment style adapted to URD
bhargavbh Jun 1, 2026
a43f97d
retained only testVerifyCertificate in Checker; split offer into stat…
bhargavbh Jun 1, 2026
65e69d1
removed ratifier
bhargavbh Jun 1, 2026
7091ed9
added justification rules for the hashLeaf trick
bhargavbh Jun 1, 2026
9d0635b
removed assumptions from soundness rule
bhargavbh Jun 2, 2026
4a3f121
tuned
bhargavbh Jun 2, 2026
5dec124
tuned; retained market-level hashLeaf
bhargavbh Jun 2, 2026
956a11f
cleanup ratification spec
bhargavbh Jun 2, 2026
ea94696
varibale renaming in create_certificate.py; tuned checker
bhargavbh Jun 2, 2026
7d251ea
Merge origin/main into certora/offer-tree
bhargavbh Jun 2, 2026
fa88df5
added high-level verification approach in README
bhargavbh Jun 2, 2026
a1b4b8e
fmt
bhargavbh Jun 2, 2026
be2a563
acknowledge URD spec in README
bhargavbh Jun 2, 2026
6aaa420
asserts replaced with raising exceptions
bhargavbh Jun 2, 2026
bec4e8f
tuned
bhargavbh Jun 3, 2026
c5fb0c9
Merge branch 'main' into certora/offer-tree
MathisGD Jul 8, 2026
bf8c505
Merge branch 'main' into certora/offer-tree
bhargavbh Aug 7, 2026
6a5f897
updated to match latest midnight
bhargavbh Aug 9, 2026
758bfba
Merge branch 'main' into certora/offer-tree
MathisGD Aug 10, 2026
b8c5a24
Improved multiplication overflow check
jhoenicke Jul 21, 2026
86fd2d1
Remove investigation of all reverting mulDiv
jhoenicke Jul 21, 2026
a613bc8
Rewording of comments
jhoenicke Jul 21, 2026
8da8e32
More comment refinement
jhoenicke Jul 21, 2026
80eec16
blue fallback rolling first iteration
MathisGD Jul 24, 2026
2fe1897
nit
MathisGD Jul 24, 2026
58cf26e
lint
MathisGD Jul 24, 2026
f112dec
refactor: roll event
peyha Jul 29, 2026
99aefcc
refactor: undo
peyha Jul 29, 2026
7942e0d
doc
peyha Aug 5, 2026
9ba4fdb
feat: auction
peyha Aug 5, 2026
fc42bf1
feat: auction end
peyha Aug 7, 2026
059e09f
test: remove wrong test
peyha Aug 7, 2026
dff0e87
refactor: error name
peyha Aug 7, 2026
9b4a0cb
fix: require end time
peyha Aug 7, 2026
fed6519
refactor: inline incentive()
peyha Aug 7, 2026
01a91b3
test: lltv test
peyha Aug 10, 2026
eb46443
fix: remove increasing incentive requirement
peyha Aug 10, 2026
bf81ea9
doc
peyha Aug 10, 2026
098e1e3
fix: incentive at start max value check
peyha Aug 10, 2026
1a1f195
doc
peyha Aug 10, 2026
b5aa320
doc
peyha Aug 10, 2026
ffb0dca
Update src/periphery/blue-fallback-rolling/BlueFallbackRolling.sol
peyha Aug 10, 2026
a729282
Add files via upload
MathisGD Aug 10, 2026
367e97b
newest file
MathisGD Aug 10, 2026
9c8831f
Add files via upload
MathisGD Aug 10, 2026
cea79ab
Rename 2026-05-19-midnight-core-stermi-audit.pdf to 2026-05-19-stermi…
MathisGD Aug 10, 2026
73755a4
refactor: interface folder
peyha Aug 11, 2026
79a30b8
fix: decreasing incentive
peyha Aug 12, 2026
f89c785
test: coverage
peyha Aug 12, 2026
1e52be2
test: midnight coverage
peyha Aug 11, 2026
4b9d2e7
test: periphery coverage
peyha Aug 11, 2026
bac71f7
chore: fmt
peyha Aug 11, 2026
c02787f
feat: min rollable debt
peyha Aug 12, 2026
df175fe
fix: partial roll when below minrollableassets
peyha Aug 14, 2026
9c1031a
feat: revert to assets=debt condition
peyha Aug 14, 2026
642713a
doc: readme
peyha Aug 14, 2026
7670ea8
Remove lltv check
claude Aug 12, 2026
88344c5
Reword lltv warning comment
claude Aug 13, 2026
82ff177
Move lltv warning to contract natspec
claude Aug 13, 2026
38fa6f8
chore: lint
peyha Aug 14, 2026
77188c9
doc: readme
peyha Aug 14, 2026
b8508cd
[rolling fallback] append LTV note to rounding comment
claude Aug 16, 2026
aa0d660
test: cover authorized fallback config setters
prd-carapulse[bot] Aug 17, 2026
d4e32d3
feat: allow authorized fallback config setters
prd-carapulse[bot] Aug 17, 2026
8297909
test: require fallback config authorization first
prd-carapulse[bot] Aug 17, 2026
6a129c4
fix: check fallback config authorization first
prd-carapulse[bot] Aug 17, 2026
35143e5
feat: equality case for end start comp
peyha Aug 17, 2026
f65b91e
Revert "feat: equality case for end start comp"
peyha Aug 17, 2026
7d727f8
doc: periphery readme
peyha Aug 17, 2026
631c286
Update src/periphery/blue-fallback-rolling/BlueFallbackRolling.sol
peyha Aug 17, 2026
c614e85
refactor: inline interface
peyha Aug 17, 2026
db3f600
refactor: rolled assets too low
peyha Aug 17, 2026
b5d2d62
Add Certora spec: realizable bad debt cannot increase without price u…
claude Jul 24, 2026
9a50fa9
Apply suggestion from @MathisGD
MathisGD Jul 24, 2026
e003af3
Fix RealizableBadDebt rules: health bridge for take/withdrawCollatera…
claude Jul 24, 2026
9446625
Tighten mulDiv ghosts in liquidateRealizesBadDebt to close seize-path…
claude Jul 24, 2026
d0051d2
Prove mulDivUp >= mulDivDown in MulDiv.spec
claude Jul 24, 2026
bfe5d11
Merge take/withdrawCollateral into parametric rule; assert liquidate …
claude Jul 24, 2026
eed485c
Help prover on liquidateRealizesBadDebt: import LIF seize bound + spl…
claude Jul 24, 2026
7f77776
Isolate hard liquidate realizable-bad-debt rule with getter-form seiz…
claude Jul 24, 2026
024a893
Split RealizableBadDebtLiquidate by liquidate input branch; lighten s…
claude Jul 24, 2026
bcdccd1
Bound realizable bad debt after liquidation by 2 (mulDivUp rounding d…
claude Jul 25, 2026
aad45e1
Bound realizable bad debt after liquidation by 3; enable case-splitti…
claude Jul 25, 2026
c3f8d3a
Pin collateral invariant for full bitmap loop in RealizableBadDebtLiq…
claude Jul 25, 2026
a1f624f
Use deterministic mulDiv ghost in RealizableBadDebtLiquidate to kill …
claude Jul 25, 2026
8e0a58f
Prove double-mulDivUp subadditivity lemma; apply in RealizableBadDebt…
claude Jul 26, 2026
94b10cc
Materialize seize-bound term in RealizableBadDebtLiquidate repaid bra…
claude Jul 26, 2026
28406dd
Restrict RealizableBadDebtLiquidate repaid branch to single collatera…
claude Jul 26, 2026
9c246c1
Try -depth 0 (match passing peers) + restore 2 collaterals on Realiza…
claude Jul 26, 2026
1e21778
Try cvc5-heavy portfolio + collateralIndex/single-collateral pins on …
claude Jul 27, 2026
aca534c
Drop forall quantifiers from RealizableBadDebtLiquidate repaid branch…
claude Jul 27, 2026
b105ad9
Spec improvements
jochencertora Jul 29, 2026
58f86a2
Fix formatting
jochencertora Jul 29, 2026
085246a
Apply suggestions from code review
jhoenicke Jul 30, 2026
bb98ee9
Some more review issues:
jochencertora Jul 30, 2026
5c05e7f
Fix summary
jochencertora Jul 30, 2026
c2d82fd
remove comment
jhoenicke Jul 30, 2026
b3c76af
nit
MathisGD Jul 30, 2026
5754a84
nit
MathisGD Jul 30, 2026
1940aeb
Remove some require
jochencertora Jul 30, 2026
7360f87
Remove the rules no longer needed
jochencertora Jul 31, 2026
dc3a82a
Apply suggestions from code review
MathisGD Jul 31, 2026
6eba1a5
Apply suggestions from code review
MathisGD Jul 31, 2026
88ec1d4
Inline axioms into rules and reference their MulDiv lemmas
claude Aug 1, 2026
4a86303
Prove liquidate does not increase another position's realizable bad debt
claude Aug 1, 2026
c60091c
Fix inlined axiomAddUpUp antecedent to match original
claude Aug 1, 2026
f10a7bd
Update certora/confs/RealizableBadDebt.conf
MathisGD Aug 3, 2026
8300f7c
Update certora/confs/RealizableBadDebtLiquidate.conf
MathisGD Aug 3, 2026
aeac0d0
Use general forall form for inlined axiom requires
claude Aug 3, 2026
18f8179
Pin AddUpUp/InverseUpDown/UpZero axioms to call args (forall form tim…
claude Aug 3, 2026
8ea0155
Test: make ERC20NoReturn.approve genuinely return nothing
claude Aug 17, 2026
7997fc5
Apply suggestion from @MathisGD
MathisGD Aug 17, 2026
314b913
[tests] BlueFallbackRolling: safe approve on loan token
claude Aug 18, 2026
86551f6
Port NONDET summaries for tickToPrice and mulDiv to ReentrancyView spec
claude Aug 10, 2026
aef0c2a
Separate MulDiv axiom spec file
jochencertora Aug 5, 2026
42ad512
fix assertion
jochencertora Aug 6, 2026
29b71a3
Make ghost persistent (they never change)
jochencertora Aug 13, 2026
e113fc0
[blue fallback rolling] require non-decreasing incentive
claude Aug 19, 2026
5277f60
[blue fallback rolling] remove redundant incentive check
claude Aug 19, 2026
56f27bf
[blue fallback rolling] add configId to Roll event
claude Aug 20, 2026
7f9a1e1
Apply suggestion from @peyha
peyha Aug 21, 2026
34b1ce0
ci: pin foundry-toolchain to v1.9.1
prd-carapulse[bot] Aug 25, 2026
cbdbe1d
Merge branch 'main' into certora/offer-tree
bhargavbh Aug 27, 2026
008b787
reduced scope to soundness and reference implementation generating roo
bhargavbh Aug 31, 2026
a52e7ac
trimmed comments
bhargavbh Aug 31, 2026
a258ed6
removed leafHashDisjointFromNodeHash and nodeHashInjective
bhargavbh Aug 31, 2026
f6cbe2f
fmt
bhargavbh Aug 31, 2026
c0e2d92
tuned
bhargavbh Aug 31, 2026
a539e66
removed hashLeafReproducesHashOffer
bhargavbh Aug 31, 2026
b5870a9
factored out generate root; restored empty leaf condition
bhargavbh Sep 1, 2026
5ce2a55
Merge branch 'main' into certora/offer-tree
bhargavbh Sep 18, 2026
588da4a
remove smt_timeout from configs
bhargavbh Sep 18, 2026
98c9332
model simplified: node ID is the hash
bhargavbh Sep 18, 2026
0ec785d
make offerTree creation idempotent
bhargavbh Sep 18, 2026
a325670
GenerateRoot now does not inherit from OfferTree
bhargavbh Sep 18, 2026
cd9458a
duplicate lead test added
bhargavbh Sep 18, 2026
bee5afa
Disable caching for OfferTree verification
bhargavbh Sep 18, 2026
8104167
all fields zero in isEmpty
bhargavbh Sep 21, 2026
4b669c0
use newLeaf and newInternalNode in generateRoot; don't inherit Offert…
bhargavbh Sep 21, 2026
36c3a5b
remove testGenerateRootOnlyPopulatesConfiguredTree
bhargavbh Sep 21, 2026
78fbdc9
removed GenerateRoot links from wellformed config
bhargavbh Sep 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions certora/confs/OfferTreeMembership.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"files": [
"certora/helpers/OfferTree.sol"
],
"verify": "OfferTree:certora/specs/OfferTreeMembership.spec",
"solc": "solc-0.8.34",
"solc_via_ir": true,
"solc_optimize": "200",
"solc_evm_version": "osaka",
"optimistic_loop": true,
"loop_iter": 4,
"optimistic_hashing": true,
"hashing_length_bound": 1024,
Comment thread
bhargavbh marked this conversation as resolved.
"prover_args": [
"-splitParallel true",
"-s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5}]"
],
"msg": "Offer Tree Membership"
}
20 changes: 20 additions & 0 deletions certora/confs/OfferTreeWellFormed.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"files": [
"certora/helpers/OfferTree.sol"
],
"verify": "OfferTree:certora/specs/OfferTreeWellFormed.spec",
"solc": "solc-0.8.34",
"solc_via_ir": true,
"solc_optimize": "200",
"solc_evm_version": "osaka",
"optimistic_loop": true,
"loop_iter": 4,
"optimistic_hashing": true,
"hashing_length_bound": 1024,
"disable_auto_cache_key_gen": true,
Comment thread
bhargavbh marked this conversation as resolved.
"prover_args": [
"-splitParallel true",
"-s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5}]"
],
"msg": "OfferTreeWellFormed"
}
34 changes: 34 additions & 0 deletions certora/helpers/GenerateRoot.sol
Comment thread
bhargavbh marked this conversation as resolved.
Comment thread
bhargavbh marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;

import {Offer} from "../../src/interfaces/IMidnight.sol";
import {OfferTree} from "./OfferTree.sol";

contract GenerateRoot {
OfferTree public immutable offerTree;

constructor(OfferTree _offerTree) {
offerTree = _offerTree;
}

// Populate the supplied model and return the root for a non-empty, power-of-two list of offers.
function generateRoot(Offer[] memory leaves) public returns (bytes32) {
require(leaves.length > 0 && (leaves.length & (leaves.length - 1)) == 0, "invalid leaves length");

bytes32[] memory level = new bytes32[](leaves.length);
for (uint256 i = 0; i < leaves.length; i++) {
level[i] = offerTree.newLeaf(leaves[i]);
}

uint256 levelLength = level.length;
while (levelLength > 1) {
levelLength /= 2;
for (uint256 i = 0; i < levelLength; i++) {
level[i] = offerTree.newInternalNode(level[2 * i], level[2 * i + 1]);
}
}

return level[0];
Comment thread
bhargavbh marked this conversation as resolved.
}
}
161 changes: 161 additions & 0 deletions certora/helpers/OfferTree.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;

import {Offer} from "../../src/interfaces/IMidnight.sol";
import {HashLib, OFFER_TYPEHASH} from "../../src/ratifiers/libraries/HashLib.sol";

// Fixed-size pre-image of HashLib.hashOffer. Dynamic fields are stored as hashes so CVL can re-hash a leaf without iterating over dynamic data.
struct Leaf {
bytes32 marketHash; // = HashLib.hashMarket(offer.market)
bool buy;
address maker;
uint256 start;
uint256 expiry;
uint256 tick;
bytes32 group;
address callback;
bytes32 callbackDataHash; // = keccak256(offer.callbackData)
address receiverIfMakerIsSeller;
address ratifier;
bool reduceOnly;
uint256 maxUnits;
uint256 maxAssets;
uint256 continuousFeeCap;
}

contract OfferTree {
struct Node {
bytes32 left;
bytes32 right;
Leaf leaf;
// Offer hash for leaves and hash of the children for internal nodes.
bytes32 hash;
}

// Every populated node is keyed by its hash.
mapping(bytes32 => Node) internal tree;

// Create a leaf or return its existing hash ID.
function newLeaf(Offer memory offer) public returns (bytes32 id) {
id = HashLib.hashOffer(offer);
require(id != 0, "id is the zero bytes");
Node storage n = tree[id];
if (!isEmpty(n)) return id;
Leaf storage l = n.leaf;
l.marketHash = HashLib.hashMarket(offer.market);
l.buy = offer.buy;
l.maker = offer.maker;
l.start = offer.start;
l.expiry = offer.expiry;
l.tick = offer.tick;
l.group = offer.group;
l.callback = offer.callback;
l.callbackDataHash = keccak256(offer.callbackData);
l.receiverIfMakerIsSeller = offer.receiverIfMakerIsSeller;
l.ratifier = offer.ratifier;
l.reduceOnly = offer.reduceOnly;
l.maxUnits = offer.maxUnits;
l.maxAssets = offer.maxAssets;
l.continuousFeeCap = offer.continuousFeeCap;
n.hash = id;
}

// Create an internal node or return its existing hash ID.
function newInternalNode(bytes32 left, bytes32 right) public returns (bytes32 id) {
bytes32 leftHash = tree[left].hash;
bytes32 rightHash = tree[right].hash;
require(leftHash != 0, "left empty");
require(rightHash != 0, "right empty");
id = HashLib.hashNode(leftHash, rightHash);
require(id != 0, "zero hash");
Node storage n = tree[id];
if (!isEmpty(n)) return id;
n.left = left;
n.right = right;
n.hash = id;
}

function isEmpty(Node storage n) internal view returns (bool) {
return n.hash == 0;
}

function isEmpty(bytes32 id) public view returns (bool) {
return isEmpty(tree[id]);
}

function isLeafNode(bytes32 id) public view returns (bool) {
return tree[id].left == 0 && tree[id].right == 0 && tree[id].hash != 0;
}

function hashOffer(Offer memory offer) public pure returns (bytes32) {
return HashLib.hashOffer(offer);
}

function isLeaf(bytes32 root, bytes32 leafHash, uint256 leafIndex, bytes32[] memory proof)
public
pure
returns (bool)
{
return HashLib.isLeaf(root, leafHash, leafIndex, proof);
}

// Reconstruct HashLib.hashOffer from the stored pre-image.
function hashLeaf(Leaf storage l) internal view returns (bytes32) {
return keccak256(
abi.encode(
OFFER_TYPEHASH,
l.marketHash,
l.buy,
l.maker,
l.start,
l.expiry,
l.tick,
l.group,
l.callback,
l.callbackDataHash,
l.receiverIfMakerIsSeller,
l.ratifier,
l.reduceOnly,
l.maxUnits,
l.maxAssets,
l.continuousFeeCap
)
Comment thread
bhargavbh marked this conversation as resolved.
);
}

// Empty nodes are fully zeroed. Populated nodes have their hash as identifier and hash their leaf data or their two non-empty children.
function isWellFormed(bytes32 id) public view returns (bool) {
Node storage n = tree[id];
if (isEmpty(n)) {
Leaf storage l = n.leaf;
return n.left == 0 && n.right == 0 && l.marketHash == 0 && !l.buy && l.maker == address(0) && l.start == 0
&& l.expiry == 0 && l.tick == 0 && l.group == 0 && l.callback == address(0) && l.callbackDataHash == 0
&& l.receiverIfMakerIsSeller == address(0) && l.ratifier == address(0) && !l.reduceOnly
&& l.maxUnits == 0 && l.maxAssets == 0 && l.continuousFeeCap == 0;
}
if (n.left == 0 && n.right == 0) {
bytes32 expected = hashLeaf(n.leaf);
return n.hash == expected && id == expected;
}
if (n.left != 0 && n.right != 0) {
bytes32 leftHash = tree[n.left].hash;
bytes32 rightHash = tree[n.right].hash;
return leftHash != 0 && rightHash != 0 && n.hash == id && n.hash == HashLib.hashNode(leftHash, rightHash);
}
return false;
}

// Check the path selected by leafIndex.
function wellFormedPath(bytes32 id, uint256 leafIndex, uint256 depth) public view returns (bool) {
for (uint256 i = depth;;) {
require(isWellFormed(id));

if (i == 0) break;

--i;
id = ((leafIndex >> i) & 1 == 0) ? tree[id].left : tree[id].right;
}
return true;
}
}
19 changes: 19 additions & 0 deletions certora/specs/OfferTreeMembership.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-2.0-or-later

methods {
function isEmpty(bytes32) external returns (bool) envfree;
function hashOffer(OfferTree.Offer) external returns (bytes32) envfree;
function isLeaf(bytes32, bytes32, uint256, bytes32[]) external returns (bool) envfree;
function isLeafNode(bytes32) external returns (bool) envfree;
function wellFormedPath(bytes32, uint256, uint256) external returns (bool) envfree;
}

// Soundness: if a Merkle proof verifies along a well-formed path, hashOffer(offer) must be a leaf node.
rule membershipSoundness(OfferTree.Offer offer, bytes32 root, uint256 leafIndex, bytes32[] proof) {
require !isEmpty(root), "root is a populated node";
require wellFormedPath(root, leafIndex, proof.length), "the path from the root to the leaf is well-formed";
Comment thread
bhargavbh marked this conversation as resolved.
bytes32 leafId = hashOffer(offer);
require isLeaf(root, leafId, leafIndex, proof), "Merkle proof verifies the offer";

assert isLeafNode(leafId);
}
21 changes: 21 additions & 0 deletions certora/specs/OfferTreeWellFormed.spec
Comment thread
QGarchery marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0-or-later

// Leaves store a fixed-size pre-image so isWellFormed can re-hash them without dynamic loops.

methods {
function isEmpty(bytes32) external returns (bool) envfree;
function isWellFormed(bytes32) external returns (bool) envfree;
}

// The zero node remains empty.
strong invariant zeroIsEmpty()
isEmpty(to_bytes32(0));

// Every node stays well-formed within the configured Solidity loop bounds.
strong invariant wellFormed(bytes32 id)
isWellFormed(id)
{
preserved {
requireInvariant zeroIsEmpty();
}
}
Loading
Loading