Skip to content

feat: add signed integer constraints and OR predicate composition#11

Open
fichiokaku wants to merge 5 commits into
mainfrom
feat/signed-constraints-and-or-composition
Open

feat: add signed integer constraints and OR predicate composition#11
fichiokaku wants to merge 5 commits into
mainfrom
feat/signed-constraints-and-or-composition

Conversation

@fichiokaku
Copy link
Copy Markdown
Collaborator

@fichiokaku fichiokaku commented Apr 22, 2026

Summary

Community feedback on ERC-8211 surfaced two gaps in the constraint system that this PR addresses, plus a few refinements that came out of code review.

Problem 1 — Unsigned-only comparisons break for int256

GTE and LTE compare raw bytes32 values, which gives wrong results across the sign boundary: int256(-1) encodes as 0xffff…ff, making it appear greater than any positive value under an unsigned comparison. Signed integer comparisons require explicit two's-complement casting.

Fix: added GTE_SIGNED and LTE_SIGNED to ConstraintType. These cast both sides through int256(uint256(...)) before comparing, which correctly handles two's-complement representation.

Problem 2 — No OR composition without helper contracts

All constraints in an InputParam are AND-composed. Users who need "value must satisfy at least one of these conditions" previously had to deploy a helper contract to express that.

Fix: added ConstraintType.OR. Its referenceData is abi.encode(Constraint[]) — a dynamic array of alternative leaf constraints. At least one must pass for the OR to succeed. OR is intentionally single-level (no nesting): a leaf constraint inside an OR cannot itself be another OR. This keeps what the user is signing flat and displayable; the InvalidConstraintType revert path is covered by a dedicated test so future refactors can't silently re-enable nesting.

Changes

  • contracts/types/ComposabilityDataTypes.sol — added GTE_SIGNED, LTE_SIGNED, OR to ConstraintType. Corrected the type-suitability comments on EQ (bitwise; works for signed/unsigned/addresses/bytes32) and IN (works for unsigned ranges and same-sign signed ranges).
  • contracts/ComposableExecutionLib.sol — refactored _validateConstraints into a loop + _checkConstraint(bytes32, Constraint memory) returns (bool) helper. OR is handled inline in _validateConstraints (decoded once, evaluated against the same 32-byte word as the outer entry). Gas: both loops use unchecked { ++i; } on bounded counters. Docs: added an inline NatSpec block that contrasts AND (static 32/64-byte payloads) vs OR (dynamic abi.encode(Constraint[])) with a concrete encoding example.
  • test/mock/DummyContract.sol — added getSignedValue() returns (int256) for STATIC_CALL-path signed tests.
  • test/unit/ComposableExecution_ConstraintsReverts.sol — 6 new test functions, each fanned out across all four account execution paths (MockAccount, MockAccountFallback, MockAccountCaller, MockAccountDelegateCaller):
    • test_inputs_With_GteSigned_Constraints — includes a case proving plain GTE incorrectly accepts int256(-1) >= 0 while GTE_SIGNED correctly rejects it
    • test_inputs_With_LteSigned_Constraints — boundary, above-bound (including positive vs negative bound), and below-bound cases
    • test_inputs_With_GteSigned_StaticCall_Constraints — signed constraints through the STATIC_CALL fetcher
    • test_inputs_With_Or_ConstraintsEQ(0) OR GTE(100) across passing and failing values
    • test_inputs_With_Or_Signed_ConstraintsLTE_SIGNED(-100) OR GTE_SIGNED(0) mixing signed types inside OR
    • test_Nested_Or_Reverts_With_InvalidConstraintType — locks in the "no nesting" design decision

Review feedback addressed

  • Nested OR support: considered; declined — kept single-level for signing-UX reasons (flat payload). Explicit rejection with a dedicated revert test.
  • unchecked on bounded loops: applied to the outer constraint loop and the OR sub-array loop.
  • OR encoding readability: inline NatSpec with a concrete example showing how to build and attach an OR constraint, plus an explicit AND-vs-OR payload-shape comparison.
  • EQ and IN comments: corrected to reflect that they are not strictly unsigned.

forge test passes: 28/28 (existing + 6 new).

@fichiokaku fichiokaku requested a review from vr16x April 22, 2026 21:19
Community feedback on ERC-8211 identified two gaps in the constraint system:
- Unsigned-only GTE/LTE constraints are incorrect for int256 values (two's complement
  makes negative numbers appear greater than positives in raw bytes32 comparison).
- No way to express OR logic without deploying helper contracts.
@fichiokaku fichiokaku force-pushed the feat/signed-constraints-and-or-composition branch from 8a79688 to 92bade9 Compare April 22, 2026 21:22
Comment on lines +25 to +28
EQ, // Equal to (unsigned / bitwise)
GTE, // Greater than or equal to (unsigned)
LTE, // Less than or equal to (unsigned)
IN, // In range (unsigned)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

EQ, and IN can be suitable for signed and unsigned as well. Please update the code comment here

Comment thread contracts/ComposableExecutionLib.sol Outdated
} else {
revert InvalidConstraintType();
uint256 len = constraints.length;
for (uint256 i; i < len; i++) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

For gas optimization, we can also try to use unchecked {} block where the length will be always few and will never exceeds the bound

Comment thread contracts/ComposableExecutionLib.sol Outdated
if (c.constraintType == ConstraintType.OR) {
Constraint[] memory subs = abi.decode(c.referenceData, (Constraint[]));
bool anyMet;
for (uint256 j; j < subs.length; j++) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

For gas optimization, we can also try to use unchecked {} block where the length will be always few and will never exceeds the bound

Comment thread contracts/ComposableExecutionLib.sol Outdated
/// @dev Validate the constraints => compare the value with the reference data
/// @dev Validate the constraints => compare the value with the reference data.
/// Each constraints[i] is checked against the i-th 32-byte word of rawValue (AND semantics).
/// Use ConstraintType.OR to express OR semantics within a single word position.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Given the OR operand can be just 1 level. Should be support deep recursive levels as well.

I mean more than on level

[
  {
     type: "OR",
     constraints: [
         {
             type: "OR",
             constraints: [...] // and so on
         },
         { ... }
     ]
  }
]

Copy link
Copy Markdown

@vr16x vr16x left a comment

Choose a reason for hiding this comment

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

Overall, everything seems good and test coverage also good.

Just left few comments on gas optimizations and extended OR operand support

Copy link
Copy Markdown

@vr16x vr16x left a comment

Choose a reason for hiding this comment

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

Looks good.

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.

2 participants