# Inverted comparison guard accepts wrong terms

Guard whose operator, operand order, or measured quantity is reversed, accepting what it should reject

_Area: Smart contract access control and control flow. Seen in 5 disclosed reports; latest 2025-04-10. Updated 2026-10-23._

## How it works
A guard is written so its comparison accepts the inputs it was meant to reject. The failure is operator or operand inversion: `<` where `<=` was needed, the two sides of the comparison swapped, or a boolean connective (`||` vs `&&`) that lets the wrong branch through. The code reads like a guard and passes happy-path tests, but the direction or boundary semantics are inverted. The broken assumption is that "the condition looks like a guard, therefore it guards."

## Where it shows up
- Time/height windows: expiry, dispute, unlock, finalization checks.
- Boundary operators (`<` vs `<=`) when wrapping an external cumulative counter.
- Operand order in a named predicate: an `is_expired` helper whose sides are swapped.
- Boolean connectives in access checks where a default (zero) mapping value can slip through [E1](https://bugbountylibrary.com/r/c4%3A2021-06-gro%3AH-03.md).
- Implication direction in constraint/boolean helpers (`conditionally_enforce_true(a, b)` enforces `a=>b`).
- The same invariant enforced in several loops with different operators or reference timestamps.

## How to review
For each guard, state the invariant in words, then name each side's semantic role ("is the request older than the finalized watermark?") and confirm the operator matches that sentence. Check the code at deadline-1, deadline, deadline+1. For external wrappers, mirror the upstream boundary exactly. For access predicates, trace the default/zero value through each branch of the connective. For constraint helpers, confirm which operand is the guard and which the obligation. When one invariant appears in several places, diff the operators and reference values across all of them.

## Variants seen
- Reversed `>=` in `is_proposal_expired` marks active proposals expired and expired ones active [E5](https://bugbountylibrary.com/r/c4%3A2025-01-initia-move%3AM-03.md).
- Inverted `<` vs `<=` against a cumulative finalization counter bricks all older claims [E2](https://bugbountylibrary.com/r/sherlock%3A2024-05-napier-update%3AH-1.md).
- `||` instead of `&&` in an access check lets the default-zero mapping value pass, then underflow routes into a mint path [E1](https://bugbountylibrary.com/r/c4%3A2021-06-gro%3AH-03.md).
- Swapped operands to `conditionally_enforce_true` invert the implication, so the version hash is never enforced; this is a ZK constraint, not a runtime guard [E3](https://bugbountylibrary.com/r/c4%3A2023-10-zksync%3AM-10.md).
- Inverted LTV comparison binds the wrong party against the documented ordering [E6](https://bugbountylibrary.com/r/c4%3A2022-04-abranft%3AH-05.md).
- Adjacent, not inversion: a require on a variable only populated in one branch silently disables a feature [E4](https://bugbountylibrary.com/r/c4%3A2022-08-olympus%3AM-12.md); a helper's range check rejects a caller's wider input domain [E8](https://bugbountylibrary.com/r/c4%3A2021-04-vader%3AH-04.md); a limit check compares a post-trade recomputed rate instead of the rate used to price the trade [E7](https://bugbountylibrary.com/r/c4%3A2022-01-insure%3AH-05.md).

## Why fixes fail
Boundary fixes must mirror the upstream operator exactly, not just the direction: EtherFi finalizes cumulatively with `<=`, so a local `<` still bricks every older request even after the direction is corrected [E2](https://bugbountylibrary.com/r/sherlock%3A2024-05-napier-update%3AH-1.md).

## Impact and severity
Ranges from permanent denial of withdrawal [E2](https://bugbountylibrary.com/r/sherlock%3A2024-05-napier-update%3AH-1.md) and blocked governance execution [E5](https://bugbountylibrary.com/r/c4%3A2025-01-initia-move%3AM-03.md) to arbitrary token minting [E1](https://bugbountylibrary.com/r/c4%3A2021-06-gro%3AH-03.md). Severity rises when the check gates money movement or is irreversible; cumulative counters and one-way windows make errors permanent. Adjacent cases show the same blast radius: griefed withdrawals [E8](https://bugbountylibrary.com/r/c4%3A2021-04-vader%3AH-04.md) and trades executed beyond the user's limit [E7](https://bugbountylibrary.com/r/c4%3A2022-01-insure%3AH-05.md).

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2021-06-gro%3AH-03.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2024-05-napier-update%3AH-1.md
- E3: https://bugbountylibrary.com/r/c4%3A2023-10-zksync%3AM-10.md
- E4: https://bugbountylibrary.com/r/c4%3A2022-08-olympus%3AM-12.md
- E5: https://bugbountylibrary.com/r/c4%3A2025-01-initia-move%3AM-03.md
- E6: https://bugbountylibrary.com/r/c4%3A2022-04-abranft%3AH-05.md
- E7: https://bugbountylibrary.com/r/c4%3A2022-01-insure%3AH-05.md
- E8: https://bugbountylibrary.com/r/c4%3A2021-04-vader%3AH-04.md
