Verdicts that follow the code.

A verdict is only true for the exact code and checks it ran on. When a codebase changes, SCAR re-runs the checks on the new revision, shows which requirements changed, refuses to carry the old result forward, and blocks approval if a check disappears.

PreviewShown on four public changes to OpenZeppelin Contracts and Safe Smart Account from June and September 2026. The requirements and the faulty variants were written by SCAR after reading the public source; independent review is pending, and this verifier is not yet open source. No affiliation with either project is implied.
On every change

Four rules, enforced by the gate.

A revision verdict is bound to its source the way an incident verdict is bound to a frozen block: candidate source, requirement pack, evaluator and execution evidence, each hashed.

01 · Pin

Bind the verdict to one revision.

The source revision, each requirement’s definition and its checks are hashed into the job. A result belongs to that job and nothing else.

02 · Diff

Show what changed, including meaning.

Code, comments, dependencies and configuration are compared, and each requirement is marked carried, changed or added. Comment-only changes stay visible.

03 · Re-run

Never inherit a pass.

The parent’s passing result is refused for the new revision: “Result belongs to different requested inputs”. The exact current job must execute.

04 · Gate

Block approval when a check disappears.

Removing a selected check fails the gate even when every remaining check passes. Passing execution is not review approval.

The study

Four real changes, two public codebases.

Five repositories were registered before any of the selected revisions were read: OpenZeppelin Contracts, Safe Smart Account, Uniswap v4 core, Aave v3 and eth-infinitism account abstraction. Three had no eligible change in the window and were not replaced. Two of the four selected changes touch executable code; two change only comments.

ChangeKindRequirementsOld verdict reusedNew run
Use unsafeAccess in EnumerableSetOpenZeppelin Contracts · #6774 · 2026-09-17Code change8 carriedrefusedchecks pass
Do not propagate reverts on signaturesSafe Smart Account · #1115 · 2026-06-05Code change6 carried, 2 changedrefusedchecks pass
Document delegatecall interaction with AccessManaged.setAuthorityOpenZeppelin Contracts · #6776 · 2026-09-21Comments only5 carried, 1 addedrefusedchecks pass
Fix secp256r1 signature commentSafe Smart Account · #1121 · 2026-09-03Comments only8 carriedrefusedchecks pass
10 / 10

valid revisions and alternatives accepted, under both check sets.

6 / 6

constructed faults rejected by SCAR’s checks.

4 / 6

rejected by the ported upstream tests alone.

0 / 32

executions inconclusive, across both check sets.

“Ported upstream tests” are selected assertions from each project’s own JavaScript and TypeScript tests, ported to Foundry with attribution. The full upstream suites were not run, and they may already catch these faults. SCAR’s checks are those ports plus properties SCAR added. The faults were constructed to test the checks; they are not upstream bugs or model errors.

What the added checks catch

Two faults the ported tests accept.

Both are small, plausible edits to the current code. Each passes every selected upstream assertion, and each is rejected by a requirement SCAR added, named below.

OpenZeppelin Contracts · #6774

Silently refuses to store the zero value

A constructed fault on the current revision.

contracts/utils/structs/EnumerableSet.sol+1 −0
@@ -73,6 +73,7 @@7373     * already present.7474     */7575    function _add(Set storage set, bytes32 value) private returns (bool) {76+        if (value == bytes32(0)) return false;7677        if (!_contains(set, value)) {7778            set._values.push(value);7879            // The value is stored at length-1, but we add 1 to all indexes
Ported upstream tests
accepted
SCAR checks
rejected

Fails:

  • sequence-membership: Across 32 generated operations over an eight-value domain, membership, cardinality and enumeration agree with an independent Boolean reference model.
  • variable-width-reuse: Empty and 1/31/32/33/64/65/129-byte values retain correct membership across removal, clearing and reuse.
Safe Smart Account · #1115

Accepts a signer response longer than 32 bytes

A constructed fault on the current revision. The code did exactly this before the change; the current requirement forbids it.

contracts/common/SecuredSignatureValidator.sol+1 −1
@@ -20,6 +20,6 @@2020    function validateContractSignature(address owner, bytes32 dataHash, bytes memory signature) internal view returns (bool valid) {2121        bytes memory data = abi.encodeWithSelector(ISignatureValidator.isValidSignature.selector, dataHash, signature);2222        (bool success, bytes memory result) = owner.staticcall(data);23        return success && result.length == 32 && abi.decode(result, (bytes32)) == bytes32(EIP1271_MAGIC_VALUE);23+        return success && result.length >= 32 && abi.decode(result, (bytes32)) == bytes32(EIP1271_MAGIC_VALUE);2424    }2525}
Ported upstream tests
accepted
SCAR checks
rejected

Fails:

  • return-length: A signer response is accepted only when its length is exactly 32 bytes and the word is ABI-encoded ERC-1271 magic.
The four changes

Every requirement, every candidate.

Highlighted rows are faults the ported upstream tests accept. Each change keeps its own requirement version: a parent revision is graded against the behaviour it promised, so a requested change is not counted as an upstream defect.

OpenZeppelin Contracts · Code change#6774 on GitHub

Use unsafeAccess in EnumerableSet

Switches the set’s internal array reads and writes to unchecked access. One contract file changes: EnumerableSet.sol.

Parent229fc7682026-09-17 · its own requirement version
Currentdab7110e2026-09-17 · re-run from scratch
Contract files1 changedcode
Requirements8 carriednone inherits a pass
Parent’s passing resultRefused for this revision“Result belongs to different requested inputs”
Current revisionAll checks passapproval still needs review
CandidateLabelPorted upstream testsSCAR checksFailed requirements
The parent revision, before the changeupstream-parent · 229fc768validacceptedaccepted
The current revision, as mergedupstream-current · dab7110evalidacceptedaccepted
Valid alternative: the current revision with the parent’s checked access restoredchecked-access-alternative · dab7110evalidacceptedaccepted
Constructed fault: records the moved value’s position off by onewrong-moved-position · dab7110efaultyrejectedrejectedmoved-position, sequence-membership, variable-width-reuse
Constructed fault: silently refuses to store the zero valuezero-sentinel-assumption · dab7110efaultyacceptedrejectedsequence-membership, variable-width-reuse
Constructed fault: clearing leaves stale position records behindstale-cleared-position · dab7110efaultyrejectedrejectedclear-reuse, sequence-membership, variable-width-reuse
8 requirements, in plain words
  • Legitimate useClearing removes values and position records; previously held values can be reinserted.clear-reuse
  • Legitimate useA new collection is empty and removing an absent member returns false.empty-set
  • Legitimate useRemoving a non-last member preserves the moved member's position so later removal remains correct.moved-position
  • Legitimate useInsertion preserves unique membership and duplicate insertion returns false.set-insertion
  • Legitimate usePagination clamps its range and returns the selected current values.set-pagination
  • BoundaryEmpty, end and overflowing indices are rejected without changing membership.index-bounds
  • BoundaryAcross 32 generated operations over an eight-value domain, membership, cardinality and enumeration agree with an independent Boolean reference model.SCAR-authoredsequence-membership
  • BoundaryEmpty and 1/31/32/33/64/65/129-byte values retain correct membership across removal, clearing and reuse.SCAR-authoredvariable-width-reuse
Scope and changed files

The three changed storage implementations: Bytes32Set, StringSet and BytesSet. Generated sequences use eight values and 32 operations; asymptotic gas cost and every wrapper type are not proven. All checks are controlled local unit executions. Full upstream JS/TS suites, deployed state and independent labels are outside this experiment.

  • contracts/utils/structs/EnumerableSet.sol · code changed
Safe Smart Account · Code change#1115 on GitHub

Do not propagate reverts on signatures

Moves contract-signature validation into a new SecuredSignatureValidator, which treats a reverting signer as an invalid signature. Two contract files change (Safe.sol, and the new validator), and two requirement definitions change with them.

Parent09fada862026-05-27 · its own requirement version
Current77901a5a2026-06-05 · re-run from scratch
Contract files2 changedcode, new file; SCAR’s review test updated
Requirements6 carried, 2 changednone inherits a pass
Parent’s passing resultRefused for this revision“Result belongs to different requested inputs”
Current revisionAll checks passapproval still needs review
CandidateLabelPorted upstream testsSCAR checksFailed requirements
The parent revision, before the changeupstream-parent · 09fada86validacceptedaccepted
The current revision, as mergedupstream-current · 77901a5avalidacceptedaccepted
Valid alternative: reads the returned word in assembly instead of abi.decodeword-load-alternative · 77901a5avalidacceptedaccepted
Constructed fault: accepts a signer response longer than 32 bytestrailing-data-accepted · 77901a5afaultyacceptedrejectedreturn-length
Constructed fault: accepts any 32-byte response, magic value or notmagic-check-omitted · 77901a5afaultyrejectedrejectedinvalid-contract-signature, signature-inputs
Constructed fault: rejects every contract signatureall-signatures-rejected · 77901a5afaultyrejectedrejectedreturn-length, signature-inputs, valid-contract-signature
8 requirements, in plain words
  • SecurityA reverting signer is reported as the Safe GS024 signature error.changedfailure-contractBefore: A reverting signer propagates its original failure data under the pre-change call contract.
  • SecurityA well-formed response with a non-magic value is rejected.invalid-contract-signature
  • SecurityAn address with no code cannot satisfy a contract-signature check by returning empty data.SCAR-authoredsigner-code
  • SecuritySignature validation cannot commit writes to the signer contract's state.SCAR-authoredstatic-signature-validation
  • Legitimate useThe intended hash and signature bytes are passed unchanged to the contract signer.signature-inputs
  • Legitimate useA well-formed 32-byte ABI-encoded ERC-1271 magic response is accepted.valid-contract-signature
  • BoundaryA signer response is accepted only when its length is exactly 32 bytes and the word is ABI-encoded ERC-1271 magic.changedSCAR-authoredreturn-lengthBefore: The pre-change ABI call accepts the correctly padded magic word with at least 32 returned bytes, including trailing data.
  • BoundaryThe signature offset and length are checked before reading signer data, preserving GS022 and GS023 outcomes.signature-bounds
Scope and changed files

Safe.checkContractSignature through an explicit internal-method adapter and controlled ERC-1271 signers. Owner-list, threshold, transaction execution and P-256 precompile correctness are outside this scoped pack. All checks are controlled local unit executions. Full upstream JS/TS suites, deployed state and independent labels are outside this experiment.

  • contracts/Safe.sol · code changed
  • contracts/common/SecuredSignatureValidator.sol · new file
  • review-tests/current/SignatureRequirements.t.sol · SCAR’s review test, updated with the change
OpenZeppelin Contracts · Comments only#6776 on GitHub

Document delegatecall interaction with AccessManaged.setAuthority

Documentation in AccessManaged.sol and AccessManager.sol. No executable code changes; one requirement is added to cover what the new comment describes.

Parent7c856ec22026-09-18 · its own requirement version
Current9a0211902026-09-21 · re-run from scratch
Contract files2 changedcomments only
Requirements5 carried, 1 addednone inherits a pass
Parent’s passing resultRefused for this revision“Result belongs to different requested inputs”
Current revisionAll checks passapproval still needs review
CandidateLabelPorted upstream testsSCAR checksFailed requirements
The parent revision, before the changeupstream-parent · 7c856ec2validacceptedaccepted
The current revision, as mergedupstream-current · 9a021190validacceptedaccepted
6 requirements, in plain words
  • SecurityA direct authority transfer from a caller other than the current authority is rejected.authority-caller
  • SecurityA direct transfer to an address with no code is rejected.authority-code
  • SecurityThe setAuthority reservation is checked for the direct entry selector; it does not establish an invariant over delegated entry paths.addedSCAR-authoredselector-scope
  • Legitimate useThe current authority can transfer the target to another deployed authority.authority-transfer
  • Legitimate useAn authorized manager execution can invoke the target's restricted application function.SCAR-authoredmanaged-call
  • SetupThe target is initialized with the configured manager as its authority.initial-authority
Scope and changed files

AccessManaged direct authority transfers and a controlled AccessManager application call. The direct-selector check does not establish safety of self-delegated entry paths. All checks are controlled local unit executions. Full upstream JS/TS suites, deployed state and independent labels are outside this experiment.

  • contracts/access/manager/AccessManaged.sol · comments changed
  • contracts/access/manager/AccessManager.sol · comments changed
Safe Smart Account · Comments only#1121 on GitHub

Fix secp256r1 signature comment

A comment in Safe.sol. No executable code changes.

Parentf61064bb2026-09-02 · its own requirement version
Currentd9996a332026-09-03 · re-run from scratch
Contract files1 changedcomments only
Requirements8 carriednone inherits a pass
Parent’s passing resultRefused for this revision“Result belongs to different requested inputs”
Current revisionAll checks passapproval still needs review
CandidateLabelPorted upstream testsSCAR checksFailed requirements
The parent revision, before the changeupstream-parent · f61064bbvalidacceptedaccepted
The current revision, as mergedupstream-current · d9996a33validacceptedaccepted
8 requirements, in plain words
  • SecurityA reverting signer is reported as the Safe GS024 signature error.failure-contract
  • SecurityA well-formed response with a non-magic value is rejected.invalid-contract-signature
  • SecurityAn address with no code cannot satisfy a contract-signature check by returning empty data.SCAR-authoredsigner-code
  • SecuritySignature validation cannot commit writes to the signer contract's state.SCAR-authoredstatic-signature-validation
  • Legitimate useThe intended hash and signature bytes are passed unchanged to the contract signer.signature-inputs
  • Legitimate useA well-formed 32-byte ABI-encoded ERC-1271 magic response is accepted.valid-contract-signature
  • BoundaryA signer response is accepted only when its length is exactly 32 bytes and the word is ABI-encoded ERC-1271 magic.SCAR-authoredreturn-length
  • BoundaryThe signature offset and length are checked before reading signer data, preserving GS022 and GS023 outcomes.signature-bounds
Scope and changed files

Safe.checkContractSignature through an explicit internal-method adapter and controlled ERC-1271 signers. Owner-list, threshold, transaction execution and P-256 precompile correctness are outside this scoped pack. All checks are controlled local unit executions. Full upstream JS/TS suites, deployed state and independent labels are outside this experiment.

  • contracts/Safe.sol · comments changed
Removing a check

Everything passes. The gate still fails.

Safe revision 77901a5a (#1115), unchanged, with one selected check removed from its pack: the return-length requirement. The seven remaining checks pass, so the execution passes. The maintained gate compares against the previous pack, sees the lost coverage, and fails.

ExecutionPassed7 of 7 remaining checks
Coverage gateFailedlost: return-length
Approval inheritedNofrom the previous pack
Review approvedNoexecution is not approval

Repeatability: 4 jobs were re-executed with identical inputs and reproduced their test outcomes and counterexamples. Repeats are engineering checks, not extra cases.

Execution conditions

Pinned, seeded, single-threaded.

Each job runs in a fresh, isolated sandbox with a pinned compiler (0.8.30); credentials never enter it. Fuzz checks run exactly the requested budget, and a mismatch makes the result inconclusive instead of passing.

256 fuzz trials

Requested and verified on the worker. A passing fuzz check must record the full budget; a failing one may stop early at its counterexample.

Seed 0x20260923

Fixed, with zero dictionary weight from the source under review, so a counterexample is repeatable.

One Forge thread

Parallel execution can pick a different failing witness; single-threaded runs make repeats comparable.

Hashes are identities

They show which inputs and evidence a verdict used. They are not third-party signatures or hardware attestations.

Limits

What this preview does not show.

Stated plainly, because the next step is to remove them.

  • Two lineages.Two public project lineages, not five independent families. The target of five independently assessed lineages is not yet met.
  • Authored, not independent.Requirements and labels were written after reading the public source and frozen before execution. Independent review is pending; this is not a sealed holdout.
  • Constructed faults.The six faulty variants were written to test the checks. They are not upstream vulnerabilities and not natural model errors.
  • Selected ports.The comparison uses selected upstream assertions, not the full upstream test suites, and says nothing about other vendors.

Want your next change reviewed this way?

Bring a codebase and its next change. We will write the requirements with you and grade the change against them.