@trustvc/eip7702 1.1.0-beta.3 → 1.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contracts/PlatformPaymaster.sol +52 -27
- package/package.json +1 -1
|
@@ -23,15 +23,18 @@ interface ITradeTrustToken {
|
|
|
23
23
|
interface IAccessControl {
|
|
24
24
|
function grantRole(bytes32 role, address account) external;
|
|
25
25
|
function renounceRole(bytes32 role, address callerConfirmation) external;
|
|
26
|
+
function hasRole(bytes32 role, address account) external view returns (bool);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* PlatformPaymaster — validates by inspecting the target contract in callData.
|
|
30
31
|
*
|
|
31
|
-
* Validation strategy (
|
|
32
|
+
* Validation strategy (paymaster must be staked → bundler compliant):
|
|
32
33
|
* 1. Decode userOp.callData → must be execute(address to, uint256, bytes)
|
|
33
34
|
* 2. Check `to` is in authorizedRegistries (paymaster's own storage)
|
|
34
|
-
* 3.
|
|
35
|
+
* 3. mintDocument only: sender holds MINTER_ROLE on the registry
|
|
36
|
+
* (registry storage associated with sender)
|
|
37
|
+
* 4. Check daily spend (paymaster's own storage)
|
|
35
38
|
*
|
|
36
39
|
* Access control is enforced by the Storage contract itself:
|
|
37
40
|
* require(msg.sender == beneficiary || msg.sender == holder)
|
|
@@ -168,7 +171,7 @@ contract PlatformPaymaster is BasePaymaster {
|
|
|
168
171
|
|
|
169
172
|
// Mints a TradeTrust document, captures the TitleEscrow, and auto-authorizes it.
|
|
170
173
|
// Paymaster must hold MINTER_ROLE on the registry.
|
|
171
|
-
//
|
|
174
|
+
// Caller must hold MINTER_ROLE on the registry (the deployer, or whoever they granted it to).
|
|
172
175
|
function mintDocument(
|
|
173
176
|
address registry,
|
|
174
177
|
address beneficiary,
|
|
@@ -177,11 +180,10 @@ contract PlatformPaymaster is BasePaymaster {
|
|
|
177
180
|
bytes calldata remark
|
|
178
181
|
) external returns (address titleEscrow) {
|
|
179
182
|
require(authorizedRegistries[registry], "registry not authorized");
|
|
183
|
+
// The registry only sees the paymaster as minter, so check the real caller here
|
|
180
184
|
require(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
msg.sender == owner(),
|
|
184
|
-
"caller not authorized"
|
|
185
|
+
IAccessControl(registry).hasRole(MINTER_ROLE, msg.sender),
|
|
186
|
+
"not registry minter"
|
|
185
187
|
);
|
|
186
188
|
|
|
187
189
|
titleEscrow = ITradeTrustToken(registry).mint(
|
|
@@ -271,14 +273,7 @@ contract PlatformPaymaster is BasePaymaster {
|
|
|
271
273
|
emit UserOpRejected(sender, "caller not authorized");
|
|
272
274
|
return ("", _packValidationData(true, 0, 0));
|
|
273
275
|
}
|
|
274
|
-
|
|
275
|
-
emit UserOpRejected(sender, "daily limit exceeded");
|
|
276
|
-
return ("", _packValidationData(true, 0, 0));
|
|
277
|
-
}
|
|
278
|
-
return (
|
|
279
|
-
abi.encode(sender, maxCost, false),
|
|
280
|
-
_packValidationData(false, 0, 0)
|
|
281
|
-
);
|
|
276
|
+
return _sponsorWithinDailyLimit(sender, maxCost);
|
|
282
277
|
}
|
|
283
278
|
|
|
284
279
|
// Path B — gasless paymaster call (deployRegistry or mintDocument)
|
|
@@ -316,22 +311,25 @@ contract PlatformPaymaster is BasePaymaster {
|
|
|
316
311
|
}
|
|
317
312
|
|
|
318
313
|
if (innerSel == MINT_DOCUMENT_SEL) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
sender
|
|
323
|
-
|
|
324
|
-
|
|
314
|
+
// Mirror mintDocument's checks so non-minters can't get failing ops sponsored.
|
|
315
|
+
// hasRole reads _roles[role][sender] — storage associated with sender (ERC-7562).
|
|
316
|
+
if (innerData.length < 36) {
|
|
317
|
+
emit UserOpRejected(sender, "mint calldata too short");
|
|
318
|
+
return ("", _packValidationData(true, 0, 0));
|
|
319
|
+
}
|
|
320
|
+
address registry;
|
|
321
|
+
assembly {
|
|
322
|
+
registry := mload(add(innerData, 36))
|
|
323
|
+
}
|
|
324
|
+
if (!authorizedRegistries[registry]) {
|
|
325
|
+
emit UserOpRejected(sender, "registry not authorized");
|
|
325
326
|
return ("", _packValidationData(true, 0, 0));
|
|
326
327
|
}
|
|
327
|
-
if (
|
|
328
|
-
emit UserOpRejected(sender, "
|
|
328
|
+
if (!IAccessControl(registry).hasRole(MINTER_ROLE, sender)) {
|
|
329
|
+
emit UserOpRejected(sender, "not registry minter");
|
|
329
330
|
return ("", _packValidationData(true, 0, 0));
|
|
330
331
|
}
|
|
331
|
-
return (
|
|
332
|
-
abi.encode(sender, maxCost, false),
|
|
333
|
-
_packValidationData(false, 0, 0)
|
|
334
|
-
);
|
|
332
|
+
return _sponsorWithinDailyLimit(sender, maxCost);
|
|
335
333
|
}
|
|
336
334
|
|
|
337
335
|
emit UserOpRejected(sender, "unauthorized paymaster call");
|
|
@@ -342,6 +340,33 @@ contract PlatformPaymaster is BasePaymaster {
|
|
|
342
340
|
return ("", _packValidationData(true, 0, 0));
|
|
343
341
|
}
|
|
344
342
|
|
|
343
|
+
// Approves a daily-limit-tracked op. If the sender is over today's limit, the op
|
|
344
|
+
// is approved with validAfter = window end, so it only runs after the window rolls
|
|
345
|
+
// over and _postOp resets the counter. block.timestamp is banned in validation
|
|
346
|
+
// (ERC-7562), so the EntryPoint enforces the time via validAfter.
|
|
347
|
+
function _sponsorWithinDailyLimit(
|
|
348
|
+
address sender,
|
|
349
|
+
uint256 maxCost
|
|
350
|
+
) internal returns (bytes memory context, uint256 validationData) {
|
|
351
|
+
context = abi.encode(sender, maxCost, false);
|
|
352
|
+
|
|
353
|
+
if (dailyLimit > 0 && dailySpend[sender] + maxCost > dailyLimit) {
|
|
354
|
+
// Even a fresh window can't fit this op
|
|
355
|
+
if (maxCost > dailyLimit) {
|
|
356
|
+
emit UserOpRejected(sender, "maxCost exceeds daily limit");
|
|
357
|
+
return ("", _packValidationData(true, 0, 0));
|
|
358
|
+
}
|
|
359
|
+
// EntryPoint runs the op only when block.timestamp > validAfter, which is
|
|
360
|
+
// exactly when _postOp's reset condition becomes true
|
|
361
|
+
return (
|
|
362
|
+
context,
|
|
363
|
+
_packValidationData(false, 0, uint48(lastReset[sender] + 1 days))
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return (context, _packValidationData(false, 0, 0));
|
|
368
|
+
}
|
|
369
|
+
|
|
345
370
|
function _postOp(
|
|
346
371
|
PostOpMode mode,
|
|
347
372
|
bytes calldata context,
|