@@ -23,10 +23,20 @@ function getTestPayload() pure returns (bytes memory) {
2323
2424// returns the encoded payload with the given nonce (for nonce tests)
2525function getPayloadWithNonce (uint256 nonce ) pure returns (bytes memory ) {
26+ return getPayloadWithNonceAndTimeframe (nonce, 0 , 0 );
27+ }
28+
29+ // returns the encoded payload with the given nonce and timeframe
30+ function getPayloadWithNonceAndTimeframe (uint256 nonce , uint256 validAfter , uint256 validBefore ) pure returns (bytes memory ) {
2631 Only712MacroForwarder.Payload memory payload = Only712MacroForwarder.Payload ({
2732 meta: Only712MacroForwarder.PayloadMeta ({ domain: META_DOMAIN, version: META_VERSION }),
2833 message: Only712MacroForwarder.PayloadMessage ({ title: MESSAGE_TITLE, customPayload: new bytes (0 ) }),
29- security: Only712MacroForwarder.PayloadSecurity ({ provider: SECURITY_PROVIDER, nonce: nonce })
34+ security: Only712MacroForwarder.PayloadSecurity ({
35+ provider: SECURITY_PROVIDER,
36+ validAfter: validAfter,
37+ validBefore: validBefore,
38+ nonce: nonce
39+ })
3040 });
3141 return abi.encode (payload);
3242}
@@ -106,7 +116,7 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
106116 function testDigestCalculation () external view {
107117 // check the type definition
108118 string memory typeDefinition = forwarder.getTypeDefinition (minimal712Macro);
109- string memory expectedTypeDefinition = "MinimalExample(Meta meta,Message message,Security security)Message(string title)Meta(string domain,string version)Security(string provider,uint256 nonce) " ;
119+ string memory expectedTypeDefinition = "MinimalExample(Meta meta,Message message,Security security)Message(string title)Meta(string domain,string version)Security(string provider,uint256 validAfter,uint256 validBefore,uint256 nonce) " ;
110120 assertEq (typeDefinition, expectedTypeDefinition, "typeDefinition mismatch " );
111121
112122 // check the type hash
@@ -149,6 +159,45 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
149159 _runMacroAs (address (this ), signer.addr, params, signatureVRS);
150160 }
151161
162+ function testValidityWindow (uint32 t0_raw , uint32 t1_raw ) external {
163+ uint256 t0 = uint256 (t0_raw);
164+ uint256 t1 = uint256 (t1_raw);
165+
166+ vm.warp (t0);
167+
168+ VmSafe.Wallet memory signer = vm.createWallet ("signer " );
169+ uint256 nonce = forwarder.getNonce (signer.addr, 0 );
170+ (bytes memory params , bytes memory signatureVRS ) = _signPayloadWithTimeframe (signer, nonce, t0, t1);
171+
172+ // Before validAfter: revert (skip when t0 == 0 to avoid underflow)
173+ if (t0 > 0 ) {
174+ vm.warp (t0 - 1 );
175+ vm.expectRevert (abi.encodeWithSelector (
176+ Only712MacroForwarder.OutsideValidityWindow.selector , t0 - 1 , t1, t0));
177+ _runMacroAs (address (this ), signer.addr, params, signatureVRS);
178+ }
179+
180+ // Within window: success when non-empty (t1 == 0 or t1 >= t0); else revert
181+ if (t1 == 0 || t1 >= t0) {
182+ vm.warp (t1 == 0 ? t0 + 100 : t0 + (t1 - t0) / 2 );
183+ assertTrue (_runMacroAs (address (this ), signer.addr, params, signatureVRS));
184+ } else {
185+ vm.warp (t0);
186+ vm.expectRevert (abi.encodeWithSelector (
187+ Only712MacroForwarder.OutsideValidityWindow.selector , t0, t1, t0));
188+ _runMacroAs (address (this ), signer.addr, params, signatureVRS);
189+ }
190+
191+ // After validBefore: revert (use non-zero validBefore so 0 = unbounded is not used here)
192+ uint256 expiry = t0 > 0 ? t0 : 1 ;
193+ nonce = forwarder.getNonce (signer.addr, 0 );
194+ (params, signatureVRS) = _signPayloadWithTimeframe (signer, nonce, 0 , expiry);
195+ vm.warp (expiry + 1 );
196+ vm.expectRevert (abi.encodeWithSelector (
197+ Only712MacroForwarder.OutsideValidityWindow.selector , expiry + 1 , expiry, uint256 (0 )));
198+ _runMacroAs (address (this ), signer.addr, params, signatureVRS);
199+ }
200+
152201 /// For a given key, nonces must be used in sequence (0, 1, 2, ...). Skipping must revert.
153202 function testNonceEnforceInSequence (uint192 key ) external {
154203 VmSafe.Wallet memory signer = vm.createWallet ("signer " );
@@ -238,6 +287,8 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
238287 return string (abi.encodePacked (
239288 '"Security": [ ' ,
240289 '{"name": "provider", "type": "string"}, ' ,
290+ '{"name": "validAfter", "type": "uint256"}, ' ,
291+ '{"name": "validBefore", "type": "uint256"}, ' ,
241292 '{"name": "nonce", "type": "uint256"} ' ,
242293 '] '
243294 ));
@@ -269,6 +320,8 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
269320 // Use string for nonce so Foundry's JSON parser accepts 2^64 as uint256 (avoids type mismatch)
270321 return string (abi.encodePacked (
271322 '"provider": " ' , SECURITY_PROVIDER, '", ' ,
323+ '"validAfter": "0", ' ,
324+ '"validBefore": "0", ' ,
272325 '"nonce": " ' , vm.toString (DEFAULT_NONCE), '" '
273326 ));
274327 }
@@ -285,7 +338,14 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
285338 internal
286339 returns (bytes memory params , bytes memory signatureVRS )
287340 {
288- params = getPayloadWithNonce (nonce);
341+ return _signPayloadWithTimeframe (signer, nonce, 0 , 0 );
342+ }
343+
344+ function _signPayloadWithTimeframe (VmSafe.Wallet memory signer , uint256 nonce , uint256 validAfter , uint256 validBefore )
345+ internal
346+ returns (bytes memory params , bytes memory signatureVRS )
347+ {
348+ params = getPayloadWithNonceAndTimeframe (nonce, validAfter, validBefore);
289349 bytes32 digest = forwarder.getDigest (minimal712Macro, params);
290350 (uint8 v , bytes32 r , bytes32 s ) = vm.sign (signer, digest);
291351 signatureVRS = abi.encodePacked (r, s, v);
0 commit comments