22pragma solidity ^ 0.8.23 ;
33
44import { VmSafe } from "forge-std/Vm.sol " ;
5+ import { console } from "forge-std/console.sol " ;
56import { ISuperfluid, ISuperfluidToken } from "../../../contracts/interfaces/superfluid/ISuperfluid.sol " ;
67import { IUserDefined712Macro } from "../../../contracts/interfaces/utils/IUserDefinedMacro.sol " ;
78import { Only712MacroForwarder } from "../../../contracts/utils/Only712MacroForwarder.sol " ;
89import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.t.sol " ;
910
10- // ============== Minimal mock macro for Only712MacroForwarder ==============
11+ string constant MESSAGE_TITLE = "Hello 712 " ;
12+ string constant PRIMARY_TYPE_NAME = "MinimalExample " ;
13+ string constant META_DOMAIN = "minimalmacro.xyz " ;
14+ string constant META_VERSION = "1 " ;
15+ string constant SECURITY_PROVIDER = "macros.superfluid.eth " ;
16+
17+ // returns the encoded payload for the example macro
18+ function getTestPayload () pure returns (bytes memory ) {
19+ Only712MacroForwarder.Payload memory payload = Only712MacroForwarder.Payload ({
20+ meta: Only712MacroForwarder.PayloadMeta ({ domain: META_DOMAIN, version: META_VERSION }),
21+ message: Only712MacroForwarder.PayloadMessage ({ title: MESSAGE_TITLE, customPayload: new bytes (0 ) }),
22+ security: Only712MacroForwarder.PayloadSecurity ({ provider: SECURITY_PROVIDER, nonce: 1 })
23+ });
24+ return abi.encode (payload);
25+ }
26+
27+ // ============== Minimal macro for Only712MacroForwarder ==============
1128// Implements IUserDefined712Macro and uses *no* postCheck logic.
1229// Message has only the required `title`; `customPayload` is expected to be empty.
1330contract Minimal712Macro is IUserDefined712Macro {
14- bytes32 internal constant MESSAGE_TYPEHASH = keccak256 ( " Message(string title) " );
15- bytes32 internal constant EXPECTED_TITLE_HASH = keccak256 ( bytes ( " Hello 712 " )) ;
31+
32+ string public constant MESSAGE_TYPE_DEFINITION = " Message(string title) " ;
1633
1734 function buildBatchOperations (ISuperfluid, bytes memory , address )
1835 external
@@ -27,15 +44,20 @@ contract Minimal712Macro is IUserDefined712Macro {
2744 // intentionally empty
2845 }
2946
30- function getMessageTypeHash () external pure override returns (bytes32 ) {
31- return MESSAGE_TYPEHASH;
47+ function getMessageTypeDefinition () external pure override returns (string memory ) {
48+ return MESSAGE_TYPE_DEFINITION;
49+ }
50+
51+ function getPrimaryTypeName () external pure override returns (string memory ) {
52+ return PRIMARY_TYPE_NAME;
3253 }
3354
3455 function getMessageStructHash (bytes memory message ) external pure override returns (bytes32 ) {
3556 (string memory title , bytes memory customPayload ) = abi.decode (message, (string , bytes ));
36- require (keccak256 (bytes (title)) == EXPECTED_TITLE_HASH , "wrong title " );
57+ require (keccak256 (bytes (title)) == keccak256 ( bytes (MESSAGE_TITLE)) , "wrong title " );
3758 require (customPayload.length == 0 , "customPayload not empty " );
38- return keccak256 (abi.encode (MESSAGE_TYPEHASH, keccak256 (bytes (title))));
59+ bytes32 messageTypeHash = keccak256 (abi.encodePacked (MESSAGE_TYPE_DEFINITION));
60+ return keccak256 (abi.encode (messageTypeHash, keccak256 (bytes (title))));
3961 }
4062}
4163
@@ -60,9 +82,9 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
6082 * @dev Smoke test: build payload, get digest via getDigest(), sign with vm.createWallet + vm.sign,
6183 * call runMacro(m, params, signer, signature), assert success.
6284 */
63- function test_Minimal712Macro_runMacro_smoke () external {
85+ function testRunMacro () external {
6486 VmSafe.Wallet memory signer = vm.createWallet ("signer " );
65- bytes memory params = _buildPayload ();
87+ bytes memory params = getTestPayload ();
6688 bytes32 digest = forwarder.getDigest (IUserDefined712Macro (address (minimal712Macro)), params);
6789 (uint8 v , bytes32 r , bytes32 s ) = vm.sign (signer, digest);
6890 bytes memory signatureVRS = abi.encodePacked (r, s, v);
@@ -72,12 +94,131 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
7294 assertTrue (ok);
7395 }
7496
75- function _buildPayload () internal pure returns (bytes memory ) {
76- Only712MacroForwarder.Payload memory payload = Only712MacroForwarder.Payload ({
77- meta: Only712MacroForwarder.PayloadMeta ({ domain: "test.xyz " , version: "1 " }),
78- message: Only712MacroForwarder.PayloadMessage ({ title: "Hello 712 " , customPayload: new bytes (0 ) }),
79- security: Only712MacroForwarder.PayloadSecurity ({ provider: "macros.superfluid.eth " , nonce: 1 })
80- });
81- return abi.encode (payload);
97+ function testDigestCalculation () external view {
98+ // check the type definition
99+ string memory typeDefinition = forwarder.getTypeDefinition (minimal712Macro);
100+ string memory expectedTypeDefinition = "MinimalExample(Meta meta,Message message,Security security)Message(string title)Meta(string domain,string version)Security(string provider,uint256 nonce) " ;
101+ assertEq (typeDefinition, expectedTypeDefinition, "typeDefinition mismatch " );
102+
103+ // check the type hash
104+ bytes32 typeHash = forwarder.getTypeHash (minimal712Macro);
105+ bytes32 expectedTypeHash = vm.eip712HashType (expectedTypeDefinition);
106+ assertEq (typeHash, expectedTypeHash, "typeHash mismatch " );
107+
108+ // check the struct hash (includes type hash and the struct data)
109+ bytes memory payload = getTestPayload ();
110+ bytes32 structHash = forwarder.getStructHash (minimal712Macro, payload);
111+ bytes32 expectedStructHash = vm.eip712HashStruct (typeDefinition, payload);
112+ assertEq (structHash, expectedStructHash, "structHash mismatch " );
113+
114+ // check the digest
115+ bytes32 digest = forwarder.getDigest (minimal712Macro, payload);
116+ string memory dataToBeSignedJson = getDataToBeSignedJson ();
117+ console.log (dataToBeSignedJson);
118+ bytes32 expectedDigest = vm.eip712HashTypedData (dataToBeSignedJson);
119+ assertEq (digest, expectedDigest, "digest mismatch " );
120+ }
121+
122+ // example: https://github.com/vaquita-fi/vaquita-lisk/blob/c4964af9157c9cca9cfb167ac1a4450e36edb29e/contracts/test/VaquitaPool.t.sol#L142
123+ // The splitting up into many functions avoids stack too deep error.
124+ function getDataToBeSignedJson () internal view returns (string memory ) {
125+ return string (abi.encodePacked (
126+ '{ ' ,
127+ '"types": { ' , _getTypesJson (), '}, ' ,
128+ '"primaryType": "MinimalExample", ' , // leaving this as literal in order to fit onto the stack
129+ '"domain": { ' , _getDomainJson (), '}, ' ,
130+ '"message": { ' ,
131+ '"meta": { ' , _getMetaJson (), '}, ' ,
132+ '"message": { ' , _getMessageJson (), '}, ' ,
133+ '"security": { ' , _getSecurityJson (), '} ' ,
134+ '} ' ,
135+ '} '
136+ ));
137+ }
138+
139+ function _getTypesJson () internal pure returns (string memory ) {
140+ return string (abi.encodePacked (
141+ _getEIP712DomainTypeJson (),
142+ _getMinimalExampleTypeJson (),
143+ _getMessageTypeJson (),
144+ _getMetaTypeJson (),
145+ _getSecurityTypeJson ()
146+ ));
147+ }
148+
149+ function _getEIP712DomainTypeJson () internal pure returns (string memory ) {
150+ return string (abi.encodePacked (
151+ '"EIP712Domain": [ ' ,
152+ '{"name": "name", "type": "string"}, ' ,
153+ '{"name": "version", "type": "string"}, ' ,
154+ '{"name": "chainId", "type": "uint256"}, ' ,
155+ '{"name": "verifyingContract", "type": "address"} ' ,
156+ '], '
157+ ));
158+ }
159+
160+ function _getMinimalExampleTypeJson () internal pure returns (string memory ) {
161+ return string (abi.encodePacked (
162+ '"MinimalExample": [ ' ,
163+ '{"name": "meta", "type": "Meta"}, ' ,
164+ '{"name": "message", "type": "Message"}, ' ,
165+ '{"name": "security", "type": "Security"} ' ,
166+ '], '
167+ ));
168+ }
169+
170+ function _getMessageTypeJson () internal pure returns (string memory ) {
171+ return string (abi.encodePacked (
172+ '"Message": [ ' ,
173+ '{"name": "title", "type": "string"} ' ,
174+ '], '
175+ ));
176+ }
177+
178+ function _getMetaTypeJson () internal pure returns (string memory ) {
179+ return string (abi.encodePacked (
180+ '"Meta": [ ' ,
181+ '{"name": "domain", "type": "string"}, ' ,
182+ '{"name": "version", "type": "string"} ' ,
183+ '], '
184+ ));
185+ }
186+
187+ function _getSecurityTypeJson () internal pure returns (string memory ) {
188+ return string (abi.encodePacked (
189+ '"Security": [ ' ,
190+ '{"name": "provider", "type": "string"}, ' ,
191+ '{"name": "nonce", "type": "uint256"} ' ,
192+ '] '
193+ ));
194+ }
195+
196+ function _getDomainJson () internal view returns (string memory ) {
197+ return string (abi.encodePacked (
198+ '"name": "ClearSigning", ' ,
199+ '"version": "1", ' ,
200+ '"chainId": ' , vm.toString (block .chainid ), ', ' ,
201+ '"verifyingContract": " ' , vm.toString (address (forwarder)), '" '
202+ ));
203+ }
204+
205+ function _getMetaJson () internal pure returns (string memory ) {
206+ return string (abi.encodePacked (
207+ '"domain": " ' , META_DOMAIN, '", ' ,
208+ '"version": " ' , META_VERSION, '" '
209+ ));
210+ }
211+
212+ function _getMessageJson () internal pure returns (string memory ) {
213+ return string (abi.encodePacked (
214+ '"title": " ' , MESSAGE_TITLE, '" '
215+ ));
216+ }
217+
218+ function _getSecurityJson () internal pure returns (string memory ) {
219+ return string (abi.encodePacked (
220+ '"provider": " ' , SECURITY_PROVIDER, '", ' ,
221+ '"nonce": ' , '1 '
222+ ));
82223 }
83224}
0 commit comments