Skip to content

Commit 6d571b2

Browse files
committed
minimal implementation
1 parent 277c4c0 commit 6d571b2

File tree

1 file changed

+109
-9
lines changed

1 file changed

+109
-9
lines changed
Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// SPDX-License-Identifier: AGPLv3
22
pragma 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";
57
import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol";
68
import { 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

Comments
 (0)