Skip to content

Commit 4359446

Browse files
committed
added validity time window validation
1 parent 35c55b1 commit 4359446

File tree

3 files changed

+80
-6
lines changed

3 files changed

+80
-6
lines changed

packages/ethereum-contracts/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
77

88
### Added
99

10+
- `Only712MacroForwarder`: a new macro forwarder that executes EIP-712-signed meta-transactions with properties giving it additional security guarantees.
1011
- `SuperToken`: the contract admin can enable/disable a _Yield Backend_ in order to generate a yield on the underlying asset.
1112
- `SuperToken`: added `VERSION()` which returns the version string of the logic contract set for the SuperToken, and inline CHANGELOG.
1213

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ abstract contract NonceManager {
4646
* TODO:
4747
* -[X] use SimpleACL for provider authorization
4848
* -[X] add nonce verification
49+
* -[X] add timeframe (validAfter, validBefore) validation
4950
* -[] add missing fields
5051
* -[] extract interface definition
5152
* -[] review naming
@@ -77,18 +78,21 @@ contract Only712MacroForwarder is ForwarderBase, EIP712, NonceManager {
7778
// the message typehash is user macro specific
7879
struct PayloadSecurity {
7980
string provider;
80-
//uint256 validAfter;
81-
//uint256 validBefore;
81+
uint256 validAfter;
82+
uint256 validBefore;
8283
uint256 nonce;
8384
}
84-
bytes internal constant _TYPEDEF_SECURITY = "Security(string provider,uint256 nonce)";
85+
bytes internal constant _TYPEDEF_SECURITY =
86+
"Security(string provider,uint256 validAfter,uint256 validBefore,uint256 nonce)";
87+
8588
bytes32 internal constant _TYPEHASH_SECURITY = keccak256(_TYPEDEF_SECURITY);
8689

8790
IAccessControl internal immutable _providerACL;
8891

8992
// ERRORS
9093

9194
error InvalidPayload(string message);
95+
error OutsideValidityWindow(uint256 blockTimestamp, uint256 validBefore, uint256 validAfter);
9296
error ProviderNotAuthorized(string provider, address msgSender);
9397
error InvalidSignature();
9498

@@ -123,6 +127,13 @@ contract Only712MacroForwarder is ForwarderBase, EIP712, NonceManager {
123127

124128
_validateAndUpdateNonce(signer, payload.security.nonce);
125129

130+
if (block.timestamp < payload.security.validAfter) {
131+
revert OutsideValidityWindow(block.timestamp, payload.security.validBefore, payload.security.validAfter);
132+
}
133+
if (payload.security.validBefore != 0 && block.timestamp > payload.security.validBefore) {
134+
revert OutsideValidityWindow(block.timestamp, payload.security.validBefore, payload.security.validAfter);
135+
}
136+
126137
bytes32 digest = _getDigest(m, payload);
127138

128139
// verify the signature - this also works for ERC1271 (contract signatures)
@@ -215,6 +226,8 @@ contract Only712MacroForwarder is ForwarderBase, EIP712, NonceManager {
215226
return keccak256(abi.encode(
216227
_TYPEHASH_SECURITY,
217228
keccak256(bytes(security.provider)),
229+
security.validAfter,
230+
security.validBefore,
218231
security.nonce
219232
));
220233
}

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

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,20 @@ function getTestPayload() pure returns (bytes memory) {
2323

2424
// returns the encoded payload with the given nonce (for nonce tests)
2525
function getPayloadWithNonce(uint256 nonce) pure returns (bytes memory) {
26+
return getPayloadWithNonceAndTimeframe(nonce, 0, 0);
27+
}
28+
29+
// returns the encoded payload with the given nonce and timeframe
30+
function getPayloadWithNonceAndTimeframe(uint256 nonce, uint256 validAfter, uint256 validBefore) pure returns (bytes memory) {
2631
Only712MacroForwarder.Payload memory payload = Only712MacroForwarder.Payload({
2732
meta: Only712MacroForwarder.PayloadMeta({ domain: META_DOMAIN, version: META_VERSION }),
2833
message: Only712MacroForwarder.PayloadMessage({ title: MESSAGE_TITLE, customPayload: new bytes(0) }),
29-
security: Only712MacroForwarder.PayloadSecurity({ provider: SECURITY_PROVIDER, nonce: nonce })
34+
security: Only712MacroForwarder.PayloadSecurity({
35+
provider: SECURITY_PROVIDER,
36+
validAfter: validAfter,
37+
validBefore: validBefore,
38+
nonce: nonce
39+
})
3040
});
3141
return abi.encode(payload);
3242
}
@@ -106,7 +116,7 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
106116
function testDigestCalculation() external view {
107117
// check the type definition
108118
string memory typeDefinition = forwarder.getTypeDefinition(minimal712Macro);
109-
string memory expectedTypeDefinition = "MinimalExample(Meta meta,Message message,Security security)Message(string title)Meta(string domain,string version)Security(string provider,uint256 nonce)";
119+
string memory expectedTypeDefinition = "MinimalExample(Meta meta,Message message,Security security)Message(string title)Meta(string domain,string version)Security(string provider,uint256 validAfter,uint256 validBefore,uint256 nonce)";
110120
assertEq(typeDefinition, expectedTypeDefinition, "typeDefinition mismatch");
111121

112122
// check the type hash
@@ -149,6 +159,45 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
149159
_runMacroAs(address(this), signer.addr, params, signatureVRS);
150160
}
151161

162+
function testValidityWindow(uint32 t0_raw, uint32 t1_raw) external {
163+
uint256 t0 = uint256(t0_raw);
164+
uint256 t1 = uint256(t1_raw);
165+
166+
vm.warp(t0);
167+
168+
VmSafe.Wallet memory signer = vm.createWallet("signer");
169+
uint256 nonce = forwarder.getNonce(signer.addr, 0);
170+
(bytes memory params, bytes memory signatureVRS) = _signPayloadWithTimeframe(signer, nonce, t0, t1);
171+
172+
// Before validAfter: revert (skip when t0 == 0 to avoid underflow)
173+
if (t0 > 0) {
174+
vm.warp(t0 - 1);
175+
vm.expectRevert(abi.encodeWithSelector(
176+
Only712MacroForwarder.OutsideValidityWindow.selector, t0 - 1, t1, t0));
177+
_runMacroAs(address(this), signer.addr, params, signatureVRS);
178+
}
179+
180+
// Within window: success when non-empty (t1 == 0 or t1 >= t0); else revert
181+
if (t1 == 0 || t1 >= t0) {
182+
vm.warp(t1 == 0 ? t0 + 100 : t0 + (t1 - t0) / 2);
183+
assertTrue(_runMacroAs(address(this), signer.addr, params, signatureVRS));
184+
} else {
185+
vm.warp(t0);
186+
vm.expectRevert(abi.encodeWithSelector(
187+
Only712MacroForwarder.OutsideValidityWindow.selector, t0, t1, t0));
188+
_runMacroAs(address(this), signer.addr, params, signatureVRS);
189+
}
190+
191+
// After validBefore: revert (use non-zero validBefore so 0 = unbounded is not used here)
192+
uint256 expiry = t0 > 0 ? t0 : 1;
193+
nonce = forwarder.getNonce(signer.addr, 0);
194+
(params, signatureVRS) = _signPayloadWithTimeframe(signer, nonce, 0, expiry);
195+
vm.warp(expiry + 1);
196+
vm.expectRevert(abi.encodeWithSelector(
197+
Only712MacroForwarder.OutsideValidityWindow.selector, expiry + 1, expiry, uint256(0)));
198+
_runMacroAs(address(this), signer.addr, params, signatureVRS);
199+
}
200+
152201
/// For a given key, nonces must be used in sequence (0, 1, 2, ...). Skipping must revert.
153202
function testNonceEnforceInSequence(uint192 key) external {
154203
VmSafe.Wallet memory signer = vm.createWallet("signer");
@@ -238,6 +287,8 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
238287
return string(abi.encodePacked(
239288
'"Security": [',
240289
'{"name": "provider", "type": "string"},',
290+
'{"name": "validAfter", "type": "uint256"},',
291+
'{"name": "validBefore", "type": "uint256"},',
241292
'{"name": "nonce", "type": "uint256"}',
242293
']'
243294
));
@@ -269,6 +320,8 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
269320
// Use string for nonce so Foundry's JSON parser accepts 2^64 as uint256 (avoids type mismatch)
270321
return string(abi.encodePacked(
271322
'"provider": "', SECURITY_PROVIDER, '",',
323+
'"validAfter": "0",',
324+
'"validBefore": "0",',
272325
'"nonce": "', vm.toString(DEFAULT_NONCE), '"'
273326
));
274327
}
@@ -285,7 +338,14 @@ contract Only712MacroForwarderTest is FoundrySuperfluidTester {
285338
internal
286339
returns (bytes memory params, bytes memory signatureVRS)
287340
{
288-
params = getPayloadWithNonce(nonce);
341+
return _signPayloadWithTimeframe(signer, nonce, 0, 0);
342+
}
343+
344+
function _signPayloadWithTimeframe(VmSafe.Wallet memory signer, uint256 nonce, uint256 validAfter, uint256 validBefore)
345+
internal
346+
returns (bytes memory params, bytes memory signatureVRS)
347+
{
348+
params = getPayloadWithNonceAndTimeframe(nonce, validAfter, validBefore);
289349
bytes32 digest = forwarder.getDigest(minimal712Macro, params);
290350
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signer, digest);
291351
signatureVRS = abi.encodePacked(r, s, v);

0 commit comments

Comments
 (0)