@@ -12,6 +12,8 @@ abstract contract Lib4337_Test is Test {
1212 uint256 internal _preValidationData;
1313 uint256 internal _validationRes;
1414 uint256 internal _currentTimestamp;
15+ // Shared block-number-mode operand used by the block mode checkValidation tests.
16+ uint256 internal _blockValidationData;
1517
1618 /// @dev MODE_BIT from Lib4337 - highest bit of uint48
1719 uint48 internal constant MODE_BIT = 0x800000000000 ;
@@ -88,53 +90,132 @@ abstract contract Lib4337_Test is Test {
8890 _;
8991 }
9092
91- function test_GivenValidAfterIsGreaterThanCurrentTimestamp () external whenCallingCheckValidation {
92- uint256 validationData = packValidationData (2000 , 0 , address (0 ));
93+ function test_GivenValidationDataIsExactlyZero () external whenCallingCheckValidation {
94+ // Exact zero validation data is unconditional success.
95+ bool isValid = harness.checkValidation (0 );
96+
97+ assertTrue (isValid, "should return true when validation data is exactly zero " );
98+ }
99+
100+ function test_GivenResultIsTheFailureSentinel () external whenCallingCheckValidation {
101+ // Time bounds satisfied, but result is the failure sentinel address(1).
102+ uint256 validationData = packValidationData (0 , 2000 , address (1 ));
93103
94104 bool isValid = harness.checkValidation (validationData);
95105
96- assertFalse (isValid, "should return false when validAfter is in the future " );
106+ assertFalse (isValid, "should return false when result is the failure sentinel " );
97107 }
98108
99- function test_GivenValidUntilIsLessThanCurrentTimestamp () external whenCallingCheckValidation {
100- vm. warp ( 5000 );
101- uint256 validationData = packValidationData (0 , 1000 , address (0 ));
109+ function test_GivenResultIsAnAggregatorAddress () external whenCallingCheckValidation {
110+ // Time bounds satisfied, but result is an aggregator address (> 1); checkValidation requires res == 0.
111+ uint256 validationData = packValidationData (0 , 2000 , address (0xdeadbeef ));
102112
103113 bool isValid = harness.checkValidation (validationData);
104114
105- assertFalse (isValid, "should return false when validUntil is in the past " );
115+ assertFalse (isValid, "should return false when result is an aggregator address " );
116+ }
117+
118+ function test_GivenRawValidUntilIsZero () external whenCallingCheckValidation {
119+ // Raw validUntil packed as 0 must parse to type(uint48).max (unbounded), so current (1000) is within range.
120+ uint256 validationData = packValidationData (500 , 0 , address (0 ));
121+
122+ bool isValid = harness.checkValidation (validationData);
123+
124+ assertTrue (isValid, "should treat raw zero validUntil as unbounded and return true " );
125+ }
126+
127+ modifier givenTimestampMode () {
128+ // Timestamp mode: bounds carry no MODE_BIT, so comparisons run against block.timestamp warped to 1000.
129+ _currentTimestamp = 1000 ;
130+ vm.warp (_currentTimestamp);
131+ _;
106132 }
107133
108- function test_GivenResultAddressIsNotZero () external whenCallingCheckValidation {
109- uint256 validationData = packValidationData (0 , 0 , address (1 ));
134+ function test_GivenCurrentIsBelowValidAfter () external whenCallingCheckValidation givenTimestampMode {
135+ // current (1000) < validAfter (2000): interval (validAfter, validUntil] not yet open.
136+ uint256 validationData = packValidationData (2000 , 0 , address (0 ));
110137
111138 bool isValid = harness.checkValidation (validationData);
112139
113- assertFalse (isValid, "should return false when result is not address(0) " );
140+ assertFalse (isValid, "should return false when current is below validAfter " );
114141 }
115142
116- function test_GivenTimeBoundsAreValidAndResultIsZero () external whenCallingCheckValidation {
117- uint256 validationData = packValidationData (500 , 2000 , address (0 ));
143+ function test_GivenCurrentEqualsValidAfter () external whenCallingCheckValidation givenTimestampMode {
144+ // Boundary: interval is (validAfter, validUntil] so current == validAfter is EXCLUDED (strict greater-than).
145+ uint256 validationData = packValidationData (uint48 (_currentTimestamp), 0 , address (0 ));
118146
119147 bool isValid = harness.checkValidation (validationData);
120148
121- assertTrue (isValid, "should return true when time bounds are valid and result is zero " );
149+ assertFalse (isValid, "should return false when current equals validAfter (open lower bound) " );
122150 }
123151
124- function test_GivenValidationDataIsZero () external whenCallingCheckValidation {
125- // validationData = 0 means: validAfter=0, validUntil=0 (becomes max), result=address(0) => true
126- bool isValid = harness.checkValidation (0 );
152+ function test_GivenCurrentIsOneAboveValidAfter () external whenCallingCheckValidation givenTimestampMode {
153+ // current (1000) == validAfter (999) + 1: first instant inside the open lower bound.
154+ uint256 validationData = packValidationData (uint48 (_currentTimestamp - 1 ), 0 , address (0 ));
155+
156+ bool isValid = harness.checkValidation (validationData);
127157
128- assertTrue (isValid, "should return true when validation data is zero (all defaults) " );
158+ assertTrue (isValid, "should return true when current is one above validAfter " );
129159 }
130160
131- function test_GivenValidAfterEqualsCurrentTimestamp () external whenCallingCheckValidation {
132- // Boundary: validAfter == block.timestamp should pass (not strictly greater than)
133- uint256 validationData = packValidationData (uint48 (_currentTimestamp), 0 , address (0 ));
161+ function test_GivenCurrentEqualsValidUntil () external whenCallingCheckValidation givenTimestampMode {
162+ // Boundary: interval is (validAfter, validUntil] so current == validUntil is INCLUDED (closed upper bound).
163+ uint256 validationData = packValidationData (0 , uint48 (_currentTimestamp), address (0 ));
164+
165+ bool isValid = harness.checkValidation (validationData);
166+
167+ assertTrue (isValid, "should return true when current equals validUntil (closed upper bound) " );
168+ }
169+
170+ function test_GivenCurrentIsOneAboveValidUntil () external whenCallingCheckValidation givenTimestampMode {
171+ // current (1000) == validUntil (999) + 1: one past the closed upper bound.
172+ uint256 validationData = packValidationData (0 , uint48 (_currentTimestamp - 1 ), address (0 ));
134173
135174 bool isValid = harness.checkValidation (validationData);
136175
137- assertTrue (isValid, "should return true when validAfter equals current timestamp " );
176+ assertFalse (isValid, "should return false when current is one above validUntil " );
177+ }
178+
179+ modifier givenBlockNumberMode () {
180+ // Block number mode: both bounds carry MODE_BIT, masking to validAfter=500, validUntil=2000.
181+ _blockValidationData = packValidationData (MODE_BIT | 500 , MODE_BIT | 2000 , address (0 ));
182+ _;
183+ }
184+
185+ function test_GivenBlockNumberEqualsValidAfter () external whenCallingCheckValidation givenBlockNumberMode {
186+ // block.number == masked validAfter (500): open lower bound excludes equality.
187+ vm.roll (500 );
188+
189+ bool isValid = harness.checkValidation (_blockValidationData);
190+
191+ assertFalse (isValid, "should return false when block.number equals validAfter " );
192+ }
193+
194+ function test_GivenBlockNumberIsOneAboveValidAfter () external whenCallingCheckValidation givenBlockNumberMode {
195+ // block.number == masked validAfter + 1 (501): first block inside the range.
196+ vm.roll (501 );
197+
198+ bool isValid = harness.checkValidation (_blockValidationData);
199+
200+ assertTrue (isValid, "should return true when block.number is one above validAfter " );
201+ }
202+
203+ function test_GivenBlockNumberEqualsValidUntil () external whenCallingCheckValidation givenBlockNumberMode {
204+ // block.number == masked validUntil (2000): closed upper bound includes equality.
205+ vm.roll (2000 );
206+
207+ bool isValid = harness.checkValidation (_blockValidationData);
208+
209+ assertTrue (isValid, "should return true when block.number equals validUntil " );
210+ }
211+
212+ function test_GivenBlockNumberIsOneAboveValidUntil () external whenCallingCheckValidation givenBlockNumberMode {
213+ // block.number == masked validUntil + 1 (2001): one past the closed upper bound.
214+ vm.roll (2001 );
215+
216+ bool isValid = harness.checkValidation (_blockValidationData);
217+
218+ assertFalse (isValid, "should return false when block.number is one above validUntil " );
138219 }
139220
140221 /*//////////////////////////////////////////////////////////////
@@ -322,9 +403,76 @@ abstract contract Lib4337_Test is Test {
322403 }
323404
324405 /*//////////////////////////////////////////////////////////////
325- VALIDITY FORMAT MISMATCH TESTS (block number vs timestamp)
406+ NORMALIZED-ORDERING + BLOCK/TIMESTAMP FORMAT TESTS
326407 //////////////////////////////////////////////////////////////*/
327408
409+ function test_GivenABlockOperandWithRawZeroValidUntilAndATimestampOperand ()
410+ external
411+ whenCallingIntersectValidationData
412+ givenBothValuesAreNon_zero
413+ {
414+ // preValidationData: validAfter has MODE_BIT, validUntil raw 0. After normalization validUntil becomes
415+ // type(uint48).max (MODE_BIT set), so classification-after-normalization makes this a BLOCK operand.
416+ // validationRes is a pure timestamp operand => genuine mismatch => must revert.
417+ uint256 preValidationData = packValidationData (MODE_BIT | 100 , 0 , address (0 ));
418+ uint256 validationRes = packValidationData (100 , 2000 , address (0 ));
419+
420+ vm.expectRevert (ValidityFormatMismatch.selector );
421+ harness.intersectValidationData (preValidationData, validationRes);
422+ }
423+
424+ function test_GivenAnUnboundedBlockRangeIntersectedWithABoundedBlockRange ()
425+ external
426+ whenCallingIntersectValidationData
427+ givenBothValuesAreNon_zero
428+ {
429+ // Unbounded block operand (validUntil raw 0 -> normalized to max) intersected with a bounded block operand.
430+ // Both classify as block after normalization, so no revert; result takes max(validAfter) and min(validUntil).
431+ uint256 unboundedBlock = packValidationData (MODE_BIT | 500 , 0 , address (0 ));
432+ uint256 boundedBlock = packValidationData (MODE_BIT | 300 , MODE_BIT | 2000 , address (0 ));
433+
434+ uint256 result = harness.intersectValidationData (unboundedBlock, boundedBlock);
435+
436+ uint48 resultValidAfter = uint48 (result >> 208 );
437+ uint48 resultValidUntil = uint48 (result >> 160 );
438+ assertEq (resultValidAfter, MODE_BIT | 500 , "should use larger validAfter (MODE_BIT|500) " );
439+ assertEq (resultValidUntil, MODE_BIT | 2000 , "should use smaller validUntil (bounded MODE_BIT|2000) " );
440+ }
441+
442+ function test_GivenABoundedBlockRangeIntersectedWithAnUnboundedBlockRange ()
443+ external
444+ whenCallingIntersectValidationData
445+ givenBothValuesAreNon_zero
446+ {
447+ // Same as above but with argument order reversed; result must be identical.
448+ uint256 boundedBlock = packValidationData (MODE_BIT | 300 , MODE_BIT | 2000 , address (0 ));
449+ uint256 unboundedBlock = packValidationData (MODE_BIT | 500 , 0 , address (0 ));
450+
451+ uint256 result = harness.intersectValidationData (boundedBlock, unboundedBlock);
452+
453+ uint48 resultValidAfter = uint48 (result >> 208 );
454+ uint48 resultValidUntil = uint48 (result >> 160 );
455+ assertEq (resultValidAfter, MODE_BIT | 500 , "should use larger validAfter (MODE_BIT|500) " );
456+ assertEq (resultValidUntil, MODE_BIT | 2000 , "should use smaller validUntil (bounded MODE_BIT|2000) " );
457+ }
458+
459+ function test_GivenABoundExactlyEqualToMODE_BIT ()
460+ external
461+ whenCallingIntersectValidationData
462+ givenBothValuesAreNon_zero
463+ {
464+ // validAfter exactly equal to MODE_BIT (masked value 0) must still classify as block number format.
465+ uint256 preValidationData = packValidationData (MODE_BIT, MODE_BIT | 2000 , address (0 ));
466+ uint256 validationRes = packValidationData (MODE_BIT | 300 , MODE_BIT | 1000 , address (0 ));
467+
468+ uint256 result = harness.intersectValidationData (preValidationData, validationRes);
469+
470+ uint48 resultValidAfter = uint48 (result >> 208 );
471+ uint48 resultValidUntil = uint48 (result >> 160 );
472+ assertEq (resultValidAfter, MODE_BIT | 300 , "should use larger validAfter (MODE_BIT|300) " );
473+ assertEq (resultValidUntil, MODE_BIT | 1000 , "should use smaller validUntil (MODE_BIT|1000) " );
474+ }
475+
328476 function test_GivenPreValidationDataUsesBlockNumberFormatAndValidationResUsesTimestampFormat ()
329477 external
330478 whenCallingIntersectValidationData
@@ -394,6 +542,7 @@ abstract contract Lib4337_Test is Test {
394542 //////////////////////////////////////////////////////////////*/
395543
396544 modifier whenCallingUsesBlockNumberFormat () {
545+ // No shared setup needed; _usesBlockNumberFormat is a pure classifier exercised directly per test.
397546 _;
398547 }
399548
@@ -406,6 +555,12 @@ abstract contract Lib4337_Test is Test {
406555 assertTrue (result, "should return true when both have MODE_BIT " );
407556 }
408557
558+ function test_GivenBothBoundsEqualMODE_BITExactly () external whenCallingUsesBlockNumberFormat {
559+ // Bounds exactly equal to MODE_BIT (masked value 0) still count as block number format (pins equality).
560+ bool result = harness.usesBlockNumberFormat (MODE_BIT, MODE_BIT);
561+ assertTrue (result, "should return true when both bounds equal MODE_BIT exactly " );
562+ }
563+
409564 function test_GivenOnlyValidAfterHasMODE_BITSet () external whenCallingUsesBlockNumberFormat {
410565 uint48 validAfter = MODE_BIT | 100 ;
411566 uint48 validUntil = 2000 ; // no MODE_BIT
0 commit comments