Skip to content

Commit fdb658f

Browse files
committed
test: cover Lib4337 EntryPoint v0.9 validity parity
1 parent 17e22ca commit fdb658f

4 files changed

Lines changed: 482 additions & 40 deletions

File tree

test/IntersectValidationData.t.sol

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -204,17 +204,19 @@ contract IntersectValidationDataTest is Test {
204204
}
205205

206206
function test_MaxTimeBounds() public {
207+
// RP-01: after normalizing raw validUntil=0 to type(uint48).max BEFORE format classification
208+
// (canonical EntryPoint v0.9), `res` has validAfter=max and validUntil=max — both carry MODE_BIT,
209+
// so it is a block-number-mode operand. `pre` has validAfter=0 (no MODE_BIT), so it is a
210+
// timestamp-mode operand. That is a genuine format mismatch and must revert. The prior expectation
211+
// (max/max/aggregator) reflected the old pre-normalization misclassification of `res` as timestamp.
207212
uint256 pre = createValidationData(0, type(uint48).max, BLS_AGGREGATOR);
208213
uint256 res = createValidationData(type(uint48).max, 0, address(0));
209214

210-
uint256 result = Lib4337._intersectValidationData(pre, res);
211-
212-
uint48 validAfter = uint48(result >> 208);
213-
uint48 validUntil = uint48(result >> 160);
214-
215-
assertEq(validAfter, type(uint48).max);
216-
assertEq(validUntil, type(uint48).max);
217-
assertEq(uint160(result), uint160(BLS_AGGREGATOR));
215+
try this.callIntersect(pre, res) {
216+
fail("Should have reverted with ValidityFormatMismatch");
217+
} catch (bytes memory reason) {
218+
assertEq(bytes4(reason), bytes4(keccak256("ValidityFormatMismatch()")));
219+
}
218220
}
219221

220222
/**

test/btt/Lib4337.t.sol

Lines changed: 177 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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

test/btt/Lib4337.tree

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,34 @@ Lib4337_Test
99
│ └── given result is a non-zero address
1010
│ └── it should return the exact address
1111
├── when calling checkValidation
12-
│ ├── given validAfter is greater than current timestamp
13-
│ │ └── it should return false
14-
│ ├── given validUntil is less than current timestamp
12+
│ ├── given validation data is exactly zero
13+
│ │ └── it should return true
14+
│ ├── given result is the failure sentinel
1515
│ │ └── it should return false
16-
│ ├── given result address is not zero
16+
│ ├── given result is an aggregator address
1717
│ │ └── it should return false
18-
│ ├── given time bounds are valid and result is zero
19-
│ │ └── it should return true
20-
│ ├── given validation data is zero
21-
│ │ └── it should return true
22-
│ └── given validAfter equals current timestamp
23-
│ └── it should return true
18+
│ ├── given raw validUntil is zero
19+
│ │ └── it should treat validUntil as unbounded and return true
20+
│ ├── given timestamp mode
21+
│ │ ├── given current is below validAfter
22+
│ │ │ └── it should return false
23+
│ │ ├── given current equals validAfter
24+
│ │ │ └── it should return false
25+
│ │ ├── given current is one above validAfter
26+
│ │ │ └── it should return true
27+
│ │ ├── given current equals validUntil
28+
│ │ │ └── it should return true
29+
│ │ └── given current is one above validUntil
30+
│ │ └── it should return false
31+
│ └── given block number mode
32+
│ ├── given block number equals validAfter
33+
│ │ └── it should return false
34+
│ ├── given block number is one above validAfter
35+
│ │ └── it should return true
36+
│ ├── given block number equals validUntil
37+
│ │ └── it should return true
38+
│ └── given block number is one above validUntil
39+
│ └── it should return false
2440
├── when calling intersectValidationData
2541
│ ├── given preValidationData is zero
2642
│ │ └── it should return validationRes via short circuit
@@ -51,6 +67,14 @@ Lib4337_Test
5167
│ │ │ └── it should return that aggregator
5268
│ │ ├── given both have different aggregators
5369
│ │ │ └── it should return 1 as the result address
70+
│ │ ├── given a block operand with raw zero validUntil and a timestamp operand
71+
│ │ │ └── it should normalize before classification and revert with ValidityFormatMismatch
72+
│ │ ├── given an unbounded block range intersected with a bounded block range
73+
│ │ │ └── it should intersect without reverting using max and min
74+
│ │ ├── given a bounded block range intersected with an unbounded block range
75+
│ │ │ └── it should intersect without reverting using max and min
76+
│ │ ├── given a bound exactly equal to MODE_BIT
77+
│ │ │ └── it should classify as block number format and intersect
5478
│ │ ├── given preValidationData uses block number format and validationRes uses timestamp format
5579
│ │ │ └── it should revert with ValidityFormatMismatch
5680
│ │ ├── given preValidationData uses timestamp format and validationRes uses block number format
@@ -62,6 +86,8 @@ Lib4337_Test
6286
└── when calling usesBlockNumberFormat
6387
├── given both validAfter and validUntil have MODE_BIT set
6488
│ └── it should return true
89+
├── given both bounds equal MODE_BIT exactly
90+
│ └── it should return true
6591
├── given only validAfter has MODE_BIT set
6692
│ └── it should return false
6793
├── given only validUntil has MODE_BIT set

0 commit comments

Comments
 (0)