@@ -5,7 +5,7 @@ import { VmSafe } from "forge-std/Vm.sol";
55import { console } from "forge-std/console.sol " ;
66import { ISuperfluid, ISuperfluidToken } from "../../../contracts/interfaces/superfluid/ISuperfluid.sol " ;
77import { IUserDefined712Macro } from "../../../contracts/interfaces/utils/IUserDefinedMacro.sol " ;
8- import { Only712MacroForwarder } from "../../../contracts/utils/Only712MacroForwarder.sol " ;
8+ import { Only712MacroForwarder, NonceManager } from "../../../contracts/utils/Only712MacroForwarder.sol " ;
99import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.t.sol " ;
1010
1111string constant MESSAGE_TITLE = "Hello 712 " ;
@@ -14,12 +14,17 @@ string constant META_DOMAIN = "minimalmacro.xyz";
1414string constant META_VERSION = "1 " ;
1515string constant SECURITY_PROVIDER = "macros.superfluid.eth " ;
1616
17- // returns the encoded payload for the example macro
17+ // returns the encoded payload for the example macro (nonce = key 1, sequence 0)
1818function getTestPayload () pure returns (bytes memory ) {
19+ return getPayloadWithNonce (uint256 (1 ) << 64 );
20+ }
21+
22+ // returns the encoded payload with the given nonce (for nonce tests)
23+ function getPayloadWithNonce (uint256 nonce ) pure returns (bytes memory ) {
1924 Only712MacroForwarder.Payload memory payload = Only712MacroForwarder.Payload ({
2025 meta: Only712MacroForwarder.PayloadMeta ({ domain: META_DOMAIN, version: META_VERSION }),
2126 message: Only712MacroForwarder.PayloadMessage ({ title: MESSAGE_TITLE, customPayload: new bytes (0 ) }),
22- security: Only712MacroForwarder.PayloadSecurity ({ provider: SECURITY_PROVIDER, nonce: 1 })
27+ security: Only712MacroForwarder.PayloadSecurity ({ provider: SECURITY_PROVIDER, nonce: nonce })
2328 });
2429 return abi.encode (payload);
2530}
@@ -78,20 +83,10 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
7883 sf.governance.enableTrustedForwarder (sf.host, ISuperfluidToken (address (0 )), address (forwarder));
7984 }
8085
81- /**
82- * @dev Smoke test: build payload, get digest via getDigest(), sign with vm.createWallet + vm.sign,
83- * call runMacro(m, params, signer, signature), assert success.
84- */
8586 function testRunMacro () external {
8687 VmSafe.Wallet memory signer = vm.createWallet ("signer " );
87- bytes memory params = getTestPayload ();
88- bytes32 digest = forwarder.getDigest (IUserDefined712Macro (address (minimal712Macro)), params);
89- (uint8 v , bytes32 r , bytes32 s ) = vm.sign (signer, digest);
90- bytes memory signatureVRS = abi.encodePacked (r, s, v);
91-
92- vm.prank (signer.addr);
93- bool ok = forwarder.runMacro (IUserDefined712Macro (address (minimal712Macro)), params, signer.addr, signatureVRS);
94- assertTrue (ok);
88+ (bytes memory params , bytes memory signatureVRS ) = _signPayload (signer, uint256 (1 ) << 64 );
89+ assertTrue (_runMacroAs (signer.addr, params, signatureVRS));
9590 }
9691
9792 function testDigestCalculation () external view {
@@ -119,6 +114,47 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
119114 assertEq (digest, expectedDigest, "digest mismatch " );
120115 }
121116
117+ function testGetNonce (uint192 key ) external {
118+ VmSafe.Wallet memory signer = vm.createWallet ("signer " );
119+
120+ for (uint256 i = 0 ; i < 10 ; i++ ) {
121+ uint256 nonce = forwarder.getNonce (signer.addr, key);
122+ (bytes memory params , bytes memory signatureVRS ) = _signPayload (signer, nonce);
123+ assertTrue (_runMacroAs (signer.addr, params, signatureVRS), "runMacro with getNonce() nonce should succeed " );
124+ }
125+ }
126+
127+ function testCannotReuseNonce (uint192 key ) external {
128+ VmSafe.Wallet memory signer = vm.createWallet ("signer " );
129+
130+ uint256 nonce = forwarder.getNonce (signer.addr, key);
131+ (bytes memory params , bytes memory signatureVRS ) = _signPayload (signer, nonce);
132+ assertTrue (_runMacroAs (signer.addr, params, signatureVRS));
133+
134+ vm.expectRevert (abi.encodeWithSelector (NonceManager.InvalidNonce.selector , signer.addr, nonce));
135+ _runMacroAs (signer.addr, params, signatureVRS);
136+ }
137+
138+ /// For a given key, nonces must be used in sequence (0, 1, 2, ...). Skipping must revert.
139+ function testNonceEnforceInSequence (uint192 key ) external {
140+ VmSafe.Wallet memory signer = vm.createWallet ("signer " );
141+
142+ // Using seq=1 before seq=0 must revert
143+ uint256 nonceSeq1 = (uint256 (key) << 64 ) | 1 ;
144+ (bytes memory paramsSeq1 , bytes memory sig1 ) = _signPayload (signer, nonceSeq1);
145+
146+ vm.expectRevert (abi.encodeWithSelector (NonceManager.InvalidNonce.selector , signer.addr, nonceSeq1));
147+ _runMacroAs (signer.addr, paramsSeq1, sig1);
148+
149+ // seq=0 must succeed
150+ uint256 nonceSeq0 = uint256 (key) << 64 ;
151+ (bytes memory paramsSeq0 , bytes memory sig0 ) = _signPayload (signer, nonceSeq0);
152+ assertTrue (_runMacroAs (signer.addr, paramsSeq0, sig0));
153+
154+ // now seq=1 must succeed
155+ assertTrue (_runMacroAs (signer.addr, paramsSeq1, sig1));
156+ }
157+
122158 // example: https://github.com/vaquita-fi/vaquita-lisk/blob/c4964af9157c9cca9cfb167ac1a4450e36edb29e/contracts/test/VaquitaPool.t.sol#L142
123159 // The splitting up into many functions avoids stack too deep error.
124160 function getDataToBeSignedJson () internal view returns (string memory ) {
@@ -216,9 +252,25 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
216252 }
217253
218254 function _getSecurityJson () internal pure returns (string memory ) {
255+ // Use string for nonce so Foundry's JSON parser accepts 2^64 as uint256 (avoids type mismatch)
219256 return string (abi.encodePacked (
220257 '"provider": " ' , SECURITY_PROVIDER, '", ' ,
221- '"nonce": ' , ' 1 '
258+ '"nonce": " ' , vm. toString ( uint256 ( 1 ) << 64 ), ' " '
222259 ));
223260 }
261+
262+ function _runMacroAs (address from , bytes memory params , bytes memory signatureVRS ) internal returns (bool ) {
263+ vm.prank (from);
264+ return forwarder.runMacro (minimal712Macro, params, from, signatureVRS);
265+ }
266+
267+ function _signPayload (VmSafe.Wallet memory signer , uint256 nonce )
268+ internal
269+ returns (bytes memory params , bytes memory signatureVRS )
270+ {
271+ params = getPayloadWithNonce (nonce);
272+ bytes32 digest = forwarder.getDigest (minimal712Macro, params);
273+ (uint8 v , bytes32 r , bytes32 s ) = vm.sign (signer, digest);
274+ signatureVRS = abi.encodePacked (r, s, v);
275+ }
224276}
0 commit comments