Skip to content

test: add Heap invariant harness#6555

Open
knoal wants to merge 5 commits into
OpenZeppelin:masterfrom
knoal:test/heap-invariants-5120
Open

test: add Heap invariant harness#6555
knoal wants to merge 5 commits into
OpenZeppelin:masterfrom
knoal:test/heap-invariants-5120

Conversation

@knoal

@knoal knoal commented May 28, 2026

Copy link
Copy Markdown

Summary

  • add a handler-based Foundry invariant harness for Heap.Uint256Heap
  • cover both min-heap and max-heap flows across insert, pop, replace, and clear
  • assert heap ordering, model/heap length parity, and peek() consistency against a shadow model

Testing

  • forge fmt --check test/utils/structs/Heap.t.sol
  • PATH=/home/alex/.foundry/bin:$PATH forge test --match-path test/utils/structs/Heap.t.sol -vv

Closes #5120

@knoal knoal requested a review from a team as a code owner May 28, 2026 13:01
@changeset-bot

changeset-bot Bot commented May 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 436418d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR extends the Heap test suite with invariant-based testing infrastructure. A new HeapHandler contract maintains both an OpenZeppelin Heap.Uint256Heap and a parallel model array, supporting min-heap and max-heap modes. Two invariant test contracts—Uint256HeapInvariantTest and Uint256HeapInvariantGtTest—configure the handler for their respective modes and assert that the heap-order property holds, heap length matches the model, and the peek value matches the expected min/max value.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'test: add Heap invariant harness' accurately summarizes the main change: adding invariant testing for the Heap struct using a handler-based Foundry approach.
Description check ✅ Passed The description is directly related to the changeset, explaining the handler-based Foundry invariant harness, coverage of min/max-heap flows, assertions made, testing commands, and the linked issue.
Linked Issues check ✅ Passed The PR successfully addresses issue #5120 by implementing a handler-based invariant harness for Heap.Uint256Heap, solving the deployment/setup constraint mentioned in the issue.
Out of Scope Changes check ✅ Passed All changes are in-scope: two test contracts for invariant testing, one handler contract for state management, and a changeset entry documenting the test addition.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/utils/structs/Heap.t.sol`:
- Around line 103-106: The handler currently signals model/handler desync by
calling revert("unexpected pop") / revert("unexpected replace") in the Heap test
handlers (see uses of _isMaxHeap, _heap.pop(Comparators.gt), root(), and
_removeFirst), which can be ignored by Foundry when fail_on_revert is false; fix
this by either setting fail_on_revert = true under the active [invariant]
profile in foundry.toml so these reverts cause invariant runs to fail, or
replace the revert paths in the handlers with explicit invariant assertions
(e.g., require/assert style checks that fail the test harness) to ensure
mismatches are always reported.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 58816cf1-16d5-4a6d-a42e-962fe0a8e671

📥 Commits

Reviewing files that changed from the base of the PR and between 74edc4b and f772d09.

📒 Files selected for processing (1)
  • test/utils/structs/Heap.t.sol

Comment thread test/utils/structs/Heap.t.sol Outdated
Comment on lines +103 to +106
uint256 expected = root();
uint256 removed = _isMaxHeap ? _heap.pop(Comparators.gt) : _heap.pop();
if (removed != expected) revert("unexpected pop");
_removeFirst(expected);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate Foundry config =="
fd -a '^foundry\.toml$'

echo "== Invariant-related settings =="
if [ -f foundry.toml ]; then
  rg -n -C2 '^\s*\[invariant\]|^\s*\[profile\..*\.invariant\]|\bfail_on_revert\s*=' foundry.toml
else
  echo "foundry.toml not found at repo root"
fi

echo "== Any other fail_on_revert declarations in repo =="
rg -n '\bfail_on_revert\s*='

Repository: OpenZeppelin/openzeppelin-contracts

Length of output: 172


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== All foundry.toml files =="
fd -a '^foundry\.toml$'

echo
echo "== Global search for fail_on_revert =="
rg -n '\bfail_on_revert\s*=' -S .

echo
echo "== Show any invariant-related config blocks in top-level foundry.toml =="
if [ -f foundry.toml ]; then
  rg -n -C3 '\[(invariant|profile\.[^.]+\.invariant)[^\]]*\]' foundry.toml || true
  rg -n -C3 'fail_on_revert' foundry.toml || true
else
  echo "top-level foundry.toml not present"
fi

echo
echo "== List any foundry config extensions (e.g., *.toml) =="
fd -a '\.toml$' -t f

Repository: OpenZeppelin/openzeppelin-contracts

Length of output: 180


🌐 Web query:

Foundry invariant fail_on_revert default value when not set

💡 Result:

The default value for fail_on_revert in Foundry's invariant testing configuration is false [1][2][3]. When this setting is false (the default), the fuzzer will ignore reverts that occur during the execution of function calls in an invariant test sequence [4][2][5]. This allows the fuzzer to continue testing other sequences even if specific calls revert [2][5]. If you wish to ensure that any revert within your handler functions triggers a test failure, you must explicitly set fail_on_revert = true in your foundry.toml file under the [invariant] section [1][6][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== invariant-related sections in repo root foundry.toml =="
rg -n -C3 '^\s*\[invariant\]' foundry.toml || true
rg -n -C3 '^\s*\[profile\.[^.]+\.invariant\]' foundry.toml || true
echo
echo "== any mention of fail_on_revert (context) =="
rg -n -C2 'fail_on_revert' foundry.toml || true

Repository: OpenZeppelin/openzeppelin-contracts

Length of output: 185


Make handler mismatch revert(...) paths fail invariant runs (fail_on_revert)

  • test/utils/structs/Heap.t.sol (e.g., lines 103-106, and similarly 112-115) uses revert("unexpected pop") / revert("unexpected replace") to detect handler/model desync.
  • The repo’s root foundry.toml has no fail_on_revert setting, so Foundry’s default (fail_on_revert = false) can cause these reverts during invariant target calls to be ignored—masking real heap regressions.
  • Add fail_on_revert = true under the active [invariant] (or invariant profile) config, or assert the mismatch from invariant targets instead of relying on handler reverts.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/utils/structs/Heap.t.sol` around lines 103 - 106, The handler currently
signals model/handler desync by calling revert("unexpected pop") /
revert("unexpected replace") in the Heap test handlers (see uses of _isMaxHeap,
_heap.pop(Comparators.gt), root(), and _removeFirst), which can be ignored by
Foundry when fail_on_revert is false; fix this by either setting fail_on_revert
= true under the active [invariant] profile in foundry.toml so these reverts
cause invariant runs to fail, or replace the revert paths in the handlers with
explicit invariant assertions (e.g., require/assert style checks that fail the
test harness) to ensure mismatches are always reported.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/utils/structs/Heap.t.sol`:
- Around line 124-129: The root() function reads _model[0] and will revert on
empty arrays; add an explicit empty-array guard at the start of root() (e.g.,
require(_model.length > 0, "Heap: empty") or revert with a clear message) to
prevent out-of-bounds panics and make the precondition explicit when using
_model and _isMaxHeap.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f15bbcc9-48cd-435e-9bc0-376bbed7d3af

📥 Commits

Reviewing files that changed from the base of the PR and between 74edc4b and 174c531.

📒 Files selected for processing (2)
  • .changeset/twelve-pants-listen.md
  • test/utils/structs/Heap.t.sol

Comment thread test/utils/structs/Heap.t.sol
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Write invariant testing for Heap.sol

1 participant