Skip to content

Commit 06d230e

Browse files
committed
added provider auth
1 parent 5ee90cb commit 06d230e

File tree

2 files changed

+44
-22
lines changed

2 files changed

+44
-22
lines changed

packages/ethereum-contracts/contracts/utils/Only712MacroForwarder.sol

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity ^0.8.23;
33

44
import { EIP712 } from "@openzeppelin-v5/contracts/utils/cryptography/EIP712.sol";
55
import { SignatureChecker } from "@openzeppelin-v5/contracts/utils/cryptography/SignatureChecker.sol";
6+
import { IAccessControl } from "@openzeppelin-v5/contracts/access/IAccessControl.sol";
67
import { IUserDefined712Macro } from "../interfaces/utils/IUserDefinedMacro.sol";
78
import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol";
89
import { ForwarderBase } from "./ForwarderBase.sol";
@@ -43,15 +44,15 @@ abstract contract NonceManager {
4344
* Envelope verification, nonce, and registry checks to be added in follow-up.
4445
*
4546
* TODO:
46-
* -[] use SimpleACL as registry
47+
* -[X] use SimpleACL for provider authorization
4748
* -[X] add nonce verification
4849
* -[] add missing fields
4950
* -[] extract interface definition
5051
* -[] review naming
5152
*/
5253
contract Only712MacroForwarder is ForwarderBase, EIP712, NonceManager {
5354

54-
// STRUCTS AND CONSTANTS
55+
// STRUCTS, CONSTANTS, IMMUTABLES
5556

5657
// top-level data structure
5758
// TODO: is "payload" a good name? Does EIP-712 give a good hint for naming this? Something "primary"?
@@ -83,17 +84,21 @@ contract Only712MacroForwarder is ForwarderBase, EIP712, NonceManager {
8384
bytes internal constant _TYPEDEF_SECURITY = "Security(string provider,uint256 nonce)";
8485
bytes32 internal constant _TYPEHASH_SECURITY = keccak256(_TYPEDEF_SECURITY);
8586

87+
IAccessControl internal immutable _providerACL;
88+
8689
// ERRORS
8790

8891
error InvalidPayload(string message);
89-
error InvalidProvider(string provider);
92+
error ProviderNotAuthorized(string provider, address msgSender);
9093
error InvalidSignature();
9194

9295
// INITIALIZATION
9396

9497
// Here EIP712 domain name and version are set.
9598
// TODO: should the name include "Superfluid"?
96-
constructor(ISuperfluid host, address /*registry*/) ForwarderBase(host) EIP712("ClearSigning", "1") {}
99+
constructor(ISuperfluid host, address /*registry*/) ForwarderBase(host) EIP712("ClearSigning", "1") {
100+
_providerACL = IAccessControl(host.getSimpleACL());
101+
}
97102

98103
// PUBLIC FUNCTIONS
99104

@@ -111,10 +116,10 @@ contract Only712MacroForwarder is ForwarderBase, EIP712, NonceManager {
111116
{
112117
// decode the payload
113118
Payload memory payload = abi.decode(params, (Payload));
114-
require(
115-
keccak256(bytes(payload.security.provider)) == keccak256(bytes("macros.superfluid.eth")),
116-
InvalidProvider(payload.security.provider)
117-
);
119+
bytes32 providerRole = keccak256(bytes(payload.security.provider));
120+
if (!_providerACL.hasRole(providerRole, msg.sender)) {
121+
revert ProviderNotAuthorized(payload.security.provider, msg.sender);
122+
}
118123

119124
_validateAndUpdateNonce(signer, payload.security.nonce);
120125

packages/ethereum-contracts/test/foundry/utils/Only712MacroForwarder.t.sol

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity ^0.8.23;
33

44
import { VmSafe } from "forge-std/Vm.sol";
55
import { console } from "forge-std/console.sol";
6+
import { IAccessControl } from "@openzeppelin-v5/contracts/access/IAccessControl.sol";
67
import { ISuperfluid, ISuperfluidToken } from "../../../contracts/interfaces/superfluid/ISuperfluid.sol";
78
import { IUserDefined712Macro } from "../../../contracts/interfaces/utils/IUserDefinedMacro.sol";
89
import { Only712MacroForwarder, NonceManager } from "../../../contracts/utils/Only712MacroForwarder.sol";
@@ -13,10 +14,11 @@ string constant PRIMARY_TYPE_NAME = "MinimalExample";
1314
string constant META_DOMAIN = "minimalmacro.xyz";
1415
string constant META_VERSION = "1";
1516
string constant SECURITY_PROVIDER = "macros.superfluid.eth";
17+
uint256 constant DEFAULT_NONCE = uint256(1) << 64;
1618

1719
// returns the encoded payload for the example macro (nonce = key 1, sequence 0)
1820
function getTestPayload() pure returns (bytes memory) {
19-
return getPayloadWithNonce(uint256(1) << 64);
21+
return getPayloadWithNonce(DEFAULT_NONCE);
2022
}
2123

2224
// returns the encoded payload with the given nonce (for nonce tests)
@@ -79,14 +81,26 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
7981
forwarder = new Only712MacroForwarder(sf.host, address(0));
8082
minimal712Macro = new Minimal712Macro();
8183

84+
IAccessControl acl = IAccessControl(sf.host.getSimpleACL());
85+
vm.prank(address(sfDeployer));
86+
acl.grantRole(keccak256(bytes(SECURITY_PROVIDER)), address(this));
87+
8288
vm.prank(address(sfDeployer));
8389
sf.governance.enableTrustedForwarder(sf.host, ISuperfluidToken(address(0)), address(forwarder));
8490
}
8591

8692
function testRunMacro() external {
8793
VmSafe.Wallet memory signer = vm.createWallet("signer");
88-
(bytes memory params, bytes memory signatureVRS) = _signPayload(signer, uint256(1) << 64);
89-
assertTrue(_runMacroAs(signer.addr, params, signatureVRS));
94+
(bytes memory params, bytes memory signatureVRS) = _signPayload(signer, DEFAULT_NONCE);
95+
assertTrue(_runMacroAs(address(this), signer.addr, params, signatureVRS));
96+
}
97+
98+
function testRevertsWhenCallerMissingProviderRole() external {
99+
VmSafe.Wallet memory signer = vm.createWallet("signer");
100+
(bytes memory params, bytes memory signatureVRS) = _signPayload(signer, DEFAULT_NONCE);
101+
vm.expectRevert(abi.encodeWithSelector(
102+
Only712MacroForwarder.ProviderNotAuthorized.selector, SECURITY_PROVIDER, address(0xbad)));
103+
_runMacroAs(address(0xbad), signer.addr, params, signatureVRS);
90104
}
91105

92106
function testDigestCalculation() external view {
@@ -120,7 +134,7 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
120134
for (uint256 i = 0; i < 10; i++) {
121135
uint256 nonce = forwarder.getNonce(signer.addr, key);
122136
(bytes memory params, bytes memory signatureVRS) = _signPayload(signer, nonce);
123-
assertTrue(_runMacroAs(signer.addr, params, signatureVRS), "runMacro with getNonce() nonce should succeed");
137+
assertTrue(_runMacroAs(address(this), signer.addr, params, signatureVRS), "runMacro with getNonce() nonce should succeed");
124138
}
125139
}
126140

@@ -129,10 +143,10 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
129143

130144
uint256 nonce = forwarder.getNonce(signer.addr, key);
131145
(bytes memory params, bytes memory signatureVRS) = _signPayload(signer, nonce);
132-
assertTrue(_runMacroAs(signer.addr, params, signatureVRS));
146+
assertTrue(_runMacroAs(address(this), signer.addr, params, signatureVRS));
133147

134148
vm.expectRevert(abi.encodeWithSelector(NonceManager.InvalidNonce.selector, signer.addr, nonce));
135-
_runMacroAs(signer.addr, params, signatureVRS);
149+
_runMacroAs(address(this), signer.addr, params, signatureVRS);
136150
}
137151

138152
/// For a given key, nonces must be used in sequence (0, 1, 2, ...). Skipping must revert.
@@ -144,15 +158,15 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
144158
(bytes memory paramsSeq1, bytes memory sig1) = _signPayload(signer, nonceSeq1);
145159

146160
vm.expectRevert(abi.encodeWithSelector(NonceManager.InvalidNonce.selector, signer.addr, nonceSeq1));
147-
_runMacroAs(signer.addr, paramsSeq1, sig1);
161+
_runMacroAs(address(this), signer.addr, paramsSeq1, sig1);
148162

149163
// seq=0 must succeed
150164
uint256 nonceSeq0 = uint256(key) << 64;
151165
(bytes memory paramsSeq0, bytes memory sig0) = _signPayload(signer, nonceSeq0);
152-
assertTrue(_runMacroAs(signer.addr, paramsSeq0, sig0));
166+
assertTrue(_runMacroAs(address(this), signer.addr, paramsSeq0, sig0));
153167

154168
// now seq=1 must succeed
155-
assertTrue(_runMacroAs(signer.addr, paramsSeq1, sig1));
169+
assertTrue(_runMacroAs(address(this), signer.addr, paramsSeq1, sig1));
156170
}
157171

158172
// example: https://github.com/vaquita-fi/vaquita-lisk/blob/c4964af9157c9cca9cfb167ac1a4450e36edb29e/contracts/test/VaquitaPool.t.sol#L142
@@ -255,18 +269,21 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
255269
// Use string for nonce so Foundry's JSON parser accepts 2^64 as uint256 (avoids type mismatch)
256270
return string(abi.encodePacked(
257271
'"provider": "', SECURITY_PROVIDER, '",',
258-
'"nonce": "', vm.toString(uint256(1) << 64), '"'
272+
'"nonce": "', vm.toString(DEFAULT_NONCE), '"'
259273
));
260274
}
261275

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);
276+
function _runMacroAs(address relayer, address signer, bytes memory params, bytes memory signatureVRS)
277+
internal
278+
returns (bool)
279+
{
280+
vm.prank(relayer);
281+
return forwarder.runMacro(minimal712Macro, params, signer, signatureVRS);
265282
}
266283

267284
function _signPayload(VmSafe.Wallet memory signer, uint256 nonce)
268285
internal
269-
returns (bytes memory params, bytes memory signatureVRS)
286+
returns (bytes memory params, bytes memory signatureVRS) {
270287
{
271288
params = getPayloadWithNonce(nonce);
272289
bytes32 digest = forwarder.getDigest(minimal712Macro, params);

0 commit comments

Comments
 (0)