11// SPDX-License-Identifier: AGPLv3
22pragma solidity ^ 0.8.23 ;
33
4- import { IUserDefinedMacro } from "../interfaces/utils/IUserDefinedMacro.sol " ;
4+ import { EIP712 } from "@openzeppelin-v5/contracts/utils/cryptography/EIP712.sol " ;
5+ import { SignatureChecker } from "@openzeppelin-v5/contracts/utils/cryptography/SignatureChecker.sol " ;
6+ import { IUserDefined712Macro } from "../interfaces/utils/IUserDefinedMacro.sol " ;
57import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol " ;
68import { ForwarderBase } from "./ForwarderBase.sol " ;
79
@@ -10,20 +12,118 @@ import { ForwarderBase } from "./ForwarderBase.sol";
1012 * In this minimal iteration: decodes payload as appParams and passes through to the macro.
1113 * Envelope verification, nonce, and registry checks to be added in follow-up.
1214 */
13- contract Only712MacroForwarder is ForwarderBase {
14- constructor (ISuperfluid host , address /*registry*/ ) ForwarderBase (host) {}
15+ contract Only712MacroForwarder is ForwarderBase , EIP712 {
16+
17+ // top-level data structure
18+ struct Payload {
19+ PayloadMeta meta;
20+ PayloadMessage message;
21+ PayloadSecurity security;
22+ }
23+ struct PayloadMeta {
24+ string domain;
25+ string version;
26+ //string language;
27+ //string disclaimer;
28+ }
29+ bytes32 constant TYPEHASH_META = keccak256 ("Meta(string domain,string version) " );
30+ struct PayloadMessage {
31+ string title;
32+ //string description;
33+ bytes customPayload;
34+ }
35+ // the message typehash is user macro specific
36+ struct PayloadSecurity {
37+ string provider;
38+ //uint256 validAfter;
39+ //uint256 validBefore;
40+ uint256 nonce;
41+ }
42+ bytes32 constant TYPEHASH_SECURITY = keccak256 ("Security(string provider,uint256 nonce) " );
43+
44+ error InvalidPayload (string message );
45+ error InvalidProvider (string provider );
46+ error InvalidSignature ();
47+
48+ // TODO: should this be something like "Clear Sign" instead?
49+ constructor (ISuperfluid host , address /*registry*/ ) ForwarderBase (host) EIP712 ("Only712MacroForwarder " , "1 " ) {}
1550
1651 /**
17- * @dev Run the macro with encoded payload (envelope + app params; envelope verification TBD ).
52+ * @dev Run the macro with encoded payload (generic + macro specific fragments ).
1853 * @param m Target macro.
19- * @param params Encoded payload. Minimal format: abi.encode(appParams).
54+ * @param params Encoded payload
2055 */
21- function runMacro (IUserDefinedMacro m , bytes calldata params ) external payable returns (bool ) {
22- bytes memory appParams = abi.decode (params, (bytes ));
56+ function runMacro (IUserDefined712Macro m , bytes calldata params , address signer , bytes calldata signature ) external payable returns (bool ) {
57+ //bytes memory appParams = abi.decode(params, (bytes));
58+
59+ // decode the payload
60+ Payload memory payload = abi.decode (params, (Payload));
61+ require (
62+ keccak256 (bytes (payload.security.provider)) == keccak256 (bytes ("macros.superfluid.eth " )),
63+ InvalidProvider (payload.security.provider)
64+ );
65+ // TODO: verify nonce (replay protection)
66+
67+ bytes32 metaStructHash = getMetaStructHash (payload.meta);
68+
69+ // the message fragment is handled by the user macro.
70+ bytes32 messageStructHash = m.getMessageStructHash (
71+ abi.encode (payload.message.title, payload.message.customPayload)
72+ );
2373
24- ISuperfluid.Operation[] memory operations = m.buildBatchOperations (_host, appParams, msg .sender );
74+ bytes32 securityStructHash = getSecurityStructHash (payload.security);
75+
76+ // get the typehash
77+ bytes32 primaryTypeHash = keccak256 (
78+ abi.encodePacked (
79+ // TODO: shall we name it "ClearSign"?
80+ "Payload(Meta meta,Message message,Security security) " ,
81+ // nested components need to be in alphabetical order
82+ m.getMessageTypeHash (),
83+ TYPEHASH_META,
84+ TYPEHASH_SECURITY
85+ )
86+ );
87+
88+ // calculate the digest of the entire payload
89+ bytes32 digest = _hashTypedDataV4 (
90+ keccak256 (
91+ abi.encode (
92+ primaryTypeHash,
93+ metaStructHash,
94+ messageStructHash,
95+ securityStructHash
96+ )
97+ )
98+ );
99+
100+ // verify the signature - this also works for ERC1271 (contract signatures)
101+ if (! SignatureChecker.isValidSignatureNow (signer, digest, signature)) {
102+ revert InvalidSignature (); // or custom error
103+ }
104+
105+ // get the operations array from the user macro based on the payload message
106+ ISuperfluid.Operation[] memory operations = m.buildBatchOperations (_host, payload.message.customPayload, msg .sender );
107+
108+ // forward the operations
25109 bool retVal = _forwardBatchCallWithValue (operations, msg .value );
26- m.postCheck (_host, appParams , msg .sender );
110+ m.postCheck (_host, payload.message.customPayload , msg .sender );
27111 return retVal;
28112 }
113+
114+ function getMetaStructHash (PayloadMeta memory meta ) internal pure returns (bytes32 ) {
115+ return keccak256 (abi.encode (
116+ TYPEHASH_META,
117+ keccak256 (bytes (meta.domain)),
118+ keccak256 (bytes (meta.version))
119+ ));
120+ }
121+
122+ function getSecurityStructHash (PayloadSecurity memory security ) internal pure returns (bytes32 ) {
123+ return keccak256 (abi.encode (
124+ TYPEHASH_SECURITY,
125+ keccak256 (bytes (security.provider)),
126+ security.nonce
127+ ));
128+ }
29129}
0 commit comments