Skip to content

Commit b80dbc8

Browse files
committed
remove ecdsa sig verification from paymaster
1 parent e2ee1f3 commit b80dbc8

File tree

3 files changed

+25
-45
lines changed

3 files changed

+25
-45
lines changed

contracts/src/Paymaster.sol

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ pragma solidity 0.8.24;
44
import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol";
55
import {IPaymaster} from "account-abstraction/interfaces/IPaymaster.sol";
66
import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol";
7-
import {ECDSA} from "solady/utils/ECDSA.sol";
87
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
98

109
import {IUserOpPrecheck} from "./interfaces/IUserOpPrecheck.sol";
@@ -16,7 +15,6 @@ import {IUserOpPrecheck} from "./interfaces/IUserOpPrecheck.sol";
1615
/// @notice This contract is used as a hook for fulfillers to provide funds for requested transactions when the
1716
/// cross-chain call(s) are ERC-4337 User Operations
1817
abstract contract Paymaster is IPaymaster {
19-
using ECDSA for bytes32;
2018
using SafeTransferLib for address payable;
2119

2220
/// @notice The context structure returned by validatePaymasterUserOp
@@ -229,10 +227,9 @@ abstract contract Paymaster is IPaymaster {
229227
returns (bytes memory context, uint256 validationData)
230228
{
231229
_settleBalanceDiff(maxCost);
232-
(uint256 ethAmount, bytes memory signature, address precheckContract) =
233-
abi.decode(userOp.paymasterAndData[52:], (uint256, bytes, address));
230+
(uint256 ethAmount, address precheckContract) = abi.decode(userOp.paymasterAndData[52:], (uint256, address));
234231

235-
address fulfiller = _genDigest(userOp, ethAmount).toEthSignedMessageHash().recover(signature);
232+
address fulfiller = tx.origin;
236233
uint256 balance = _magicSpendBalance[fulfiller];
237234
uint256 gasBalance = _ethForGas[fulfiller];
238235

@@ -397,15 +394,4 @@ abstract contract Paymaster is IPaymaster {
397394

398395
return totalTrackedEthBalance_ - entryPointBalance;
399396
}
400-
401-
/// @notice A function that generates the digest that the fulfiller's signature is for
402-
///
403-
/// @param userOp The User Operation to generate a digest for
404-
/// @param ethAmount The amount of eth in the User Operation
405-
///
406-
/// @return digest The digest for the fulfiller's signature
407-
function _genDigest(PackedUserOperation calldata userOp, uint256 ethAmount) private view returns (bytes32) {
408-
uint256 dstChainId = block.chainid;
409-
return keccak256(abi.encode(userOp.sender, userOp.nonce, userOp.callData, ethAmount, dstChainId));
410-
}
411397
}

contracts/test/Paymaster.t.sol

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ pragma solidity 0.8.24;
33

44
import {EntryPoint, IEntryPoint, PackedUserOperation, UserOperationLib} from "account-abstraction/core/EntryPoint.sol";
55
import {Vm} from "forge-std/Vm.sol";
6-
import {ECDSA} from "solady/utils/ECDSA.sol";
76
import {Ownable} from "solady/auth/Ownable.sol";
87

98
import {BaseTest} from "./BaseTest.t.sol";
@@ -15,7 +14,6 @@ import {MockUserOpPrecheck} from "./mocks/MockUserOpPrecheck.sol";
1514

1615
contract PaymasterTest is BaseTest, MockEndpoint {
1716
using UserOperationLib for PackedUserOperation;
18-
using ECDSA for bytes32;
1917

2018
IEntryPoint entryPoint;
2119
MockAccount mockAccount;
@@ -317,7 +315,7 @@ contract PaymasterTest is BaseTest, MockEndpoint {
317315
fundAccount(signer.addr, amount)
318316
fundPaymaster(signer.addr, amount)
319317
{
320-
PackedUserOperation[] memory userOps = _generateUserOps(signer.privateKey, amount, address(0), 0);
318+
PackedUserOperation[] memory userOps = _generateUserOps(amount, address(0), 0);
321319
uint256 maxCost = this.calculateMaxCost(userOps[0]);
322320

323321
_deposit(amount);
@@ -332,6 +330,7 @@ contract PaymasterTest is BaseTest, MockEndpoint {
332330
abi.encodeWithSelector(Paymaster.InsufficientMagicSpendBalance.selector, signer.addr, 0, amount)
333331
)
334332
);
333+
vm.prank(signer.addr, signer.addr);
335334
entryPoint.handleOps(userOps, payable(BUNDLER));
336335
}
337336

@@ -340,7 +339,7 @@ contract PaymasterTest is BaseTest, MockEndpoint {
340339
fundAccount(signer.addr, amount)
341340
fundPaymaster(signer.addr, amount)
342341
{
343-
PackedUserOperation[] memory userOps = _generateUserOps(otherSigner.privateKey, amount, address(0), 0);
342+
PackedUserOperation[] memory userOps = _generateUserOps(amount, address(0), 0);
344343
uint256 maxCost = this.calculateMaxCost(userOps[0]);
345344

346345
vm.assume(maxCost <= amount && amount <= type(uint256).max - maxCost);
@@ -355,22 +354,24 @@ contract PaymasterTest is BaseTest, MockEndpoint {
355354
abi.encodeWithSelector(Paymaster.InsufficientGasBalance.selector, otherSigner.addr, 0, maxCost)
356355
)
357356
);
357+
vm.prank(otherSigner.addr, otherSigner.addr);
358358
entryPoint.handleOps(userOps, payable(BUNDLER));
359359
}
360360

361361
function test_validatePaymasterUserOp_revertsIfFulfillerHasInsufficientGasBalanceOnSecondTry(
362362
uint256 amount,
363363
uint256 ethAmount
364364
) public fundAccount(signer.addr, amount) fundPaymaster(signer.addr, amount) {
365-
PackedUserOperation[] memory userOps = _generateUserOps(signer.privateKey, ethAmount, address(0), 0);
365+
PackedUserOperation[] memory userOps = _generateUserOps(ethAmount, address(0), 0);
366366
uint256 maxCost = this.calculateMaxCost(userOps[0]);
367367

368368
vm.assume(ethAmount < type(uint256).max - maxCost * 2 && ethAmount + maxCost * 2 < amount);
369369
_deposit(maxCost);
370370

371+
vm.prank(signer.addr, signer.addr);
371372
entryPoint.handleOps(userOps, payable(BUNDLER));
372373

373-
userOps = _generateUserOps(otherSigner.privateKey, ethAmount, address(0), 1);
374+
userOps = _generateUserOps(ethAmount, address(0), 1);
374375
maxCost = this.calculateMaxCost(userOps[0]);
375376
_deposit(maxCost);
376377

@@ -382,6 +383,7 @@ contract PaymasterTest is BaseTest, MockEndpoint {
382383
abi.encodeWithSelector(Paymaster.InsufficientGasBalance.selector, otherSigner.addr, 0, maxCost)
383384
)
384385
);
386+
vm.prank(otherSigner.addr, otherSigner.addr);
385387
entryPoint.handleOps(userOps, payable(BUNDLER));
386388
}
387389

@@ -391,12 +393,13 @@ contract PaymasterTest is BaseTest, MockEndpoint {
391393
fundPaymaster(signer.addr, amount)
392394
{
393395
assertEq(paymaster.getGasBalance(signer.addr), address(entryPoint).balance);
394-
PackedUserOperation[] memory userOps = _generateUserOps(signer.privateKey, ethAmount, address(0), 0);
396+
PackedUserOperation[] memory userOps = _generateUserOps(ethAmount, address(0), 0);
395397
uint256 maxCost = this.calculateMaxCost(userOps[0]);
396398

397399
vm.assume(ethAmount < type(uint256).max - maxCost && ethAmount + maxCost < amount);
398400
_deposit(maxCost);
399401

402+
vm.prank(signer.addr, signer.addr);
400403
entryPoint.handleOps(userOps, payable(BUNDLER));
401404

402405
assertEq(paymaster.getGasBalance(signer.addr), address(entryPoint).balance);
@@ -409,13 +412,14 @@ contract PaymasterTest is BaseTest, MockEndpoint {
409412
{
410413
vm.assume(ethAmount > 0);
411414

412-
PackedUserOperation[] memory userOps = _generateUserOps(signer.privateKey, ethAmount, precheckAddress, 0);
415+
PackedUserOperation[] memory userOps = _generateUserOps(ethAmount, precheckAddress, 0);
413416
uint256 maxCost = this.calculateMaxCost(userOps[0]);
414417

415418
vm.assume(ethAmount < type(uint256).max - maxCost && ethAmount + maxCost < amount);
416419
_deposit(maxCost);
417420

418421
vm.expectRevert(abi.encodeWithSelector(IEntryPoint.FailedOpWithRevert.selector, 0, "AA33 reverted", ""));
422+
vm.prank(signer.addr, signer.addr);
419423
entryPoint.handleOps(userOps, payable(BUNDLER));
420424
}
421425

@@ -426,12 +430,13 @@ contract PaymasterTest is BaseTest, MockEndpoint {
426430
{
427431
vm.assume(ethAmount > 0);
428432

429-
PackedUserOperation[] memory userOps = _generateUserOps(signer.privateKey, ethAmount, address(0), 0);
433+
PackedUserOperation[] memory userOps = _generateUserOps(ethAmount, address(0), 0);
430434
uint256 maxCost = this.calculateMaxCost(userOps[0]);
431435

432436
vm.assume(ethAmount < type(uint256).max - maxCost && ethAmount + maxCost < amount);
433437
_deposit(maxCost);
434438

439+
vm.prank(signer.addr, signer.addr);
435440
entryPoint.handleOps(userOps, payable(BUNDLER));
436441

437442
assertEq(paymaster.requestHash(), entryPoint.getUserOpHash(userOps[0]));
@@ -444,12 +449,13 @@ contract PaymasterTest is BaseTest, MockEndpoint {
444449
fundPaymaster(signer.addr, amount)
445450
{
446451
uint256 ethAmount = 0;
447-
PackedUserOperation[] memory userOps = _generateUserOps(signer.privateKey, ethAmount, address(0), 0);
452+
PackedUserOperation[] memory userOps = _generateUserOps(ethAmount, address(0), 0);
448453
uint256 maxCost = this.calculateMaxCost(userOps[0]);
449454

450455
vm.assume(ethAmount < type(uint256).max - maxCost && ethAmount + maxCost < amount);
451456
_deposit(maxCost);
452457

458+
vm.prank(signer.addr, signer.addr);
453459
entryPoint.handleOps(userOps, payable(BUNDLER));
454460

455461
assertEq(paymaster.requestHash(), bytes32(0));
@@ -463,19 +469,20 @@ contract PaymasterTest is BaseTest, MockEndpoint {
463469
{
464470
vm.assume(ethAmount > 0);
465471

466-
PackedUserOperation[] memory userOps = _generateUserOps(signer.privateKey, ethAmount, address(0), 0);
472+
PackedUserOperation[] memory userOps = _generateUserOps(ethAmount, address(0), 0);
467473
uint256 maxCost = this.calculateMaxCost(userOps[0]);
468474

469475
vm.assume(ethAmount < type(uint256).max - maxCost && ethAmount + maxCost < amount);
470476
_deposit(maxCost);
471477
uint256 initialBalance = paymaster.getMagicSpendBalance(signer.addr);
472478

479+
vm.prank(signer.addr, signer.addr);
473480
entryPoint.handleOps(userOps, payable(BUNDLER));
474481

475482
assertEq(paymaster.getMagicSpendBalance(signer.addr), initialBalance - ethAmount);
476483
}
477484

478-
function _generateUserOps(uint256 signerKey, uint256 ethAmount, address precheck, uint256 nonce)
485+
function _generateUserOps(uint256 ethAmount, address precheck, uint256 nonce)
479486
private
480487
view
481488
returns (PackedUserOperation[] memory)
@@ -489,27 +496,14 @@ contract PaymasterTest is BaseTest, MockEndpoint {
489496
accountGasLimits: bytes32(abi.encodePacked(uint128(1000000), uint128(1000000))),
490497
preVerificationGas: 100000,
491498
gasFees: bytes32(abi.encodePacked(uint128(1000000), uint128(1000000))),
492-
paymasterAndData: "",
499+
paymasterAndData: _encodePaymasterAndData(ethAmount, precheck),
493500
signature: abi.encode(0)
494501
});
495-
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerKey, _genDigest(userOps[0], ethAmount).toEthSignedMessageHash());
496-
userOps[0].paymasterAndData = _encodePaymasterAndData(abi.encodePacked(r, s, v), ethAmount, precheck);
497502
return userOps;
498503
}
499504

500-
function _encodePaymasterAndData(bytes memory signature, uint256 ethAmount, address precheck)
501-
private
502-
view
503-
returns (bytes memory)
504-
{
505-
return abi.encodePacked(
506-
address(paymaster), uint128(1000000), uint128(1000000), abi.encode(ethAmount, signature, precheck)
507-
);
508-
}
509-
510-
function _genDigest(PackedUserOperation memory userOp, uint256 ethAmount) private view returns (bytes32) {
511-
uint256 dstChainId = block.chainid;
512-
return keccak256(abi.encode(userOp.sender, userOp.nonce, userOp.callData, ethAmount, dstChainId));
505+
function _encodePaymasterAndData(uint256 ethAmount, address precheck) private view returns (bytes memory) {
506+
return abi.encodePacked(address(paymaster), uint128(1000000), uint128(1000000), abi.encode(ethAmount, precheck));
513507
}
514508

515509
function calculateMaxCost(PackedUserOperation calldata userOp) public view returns (uint256) {

contracts/test/mocks/MockUserOpPrecheck.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {IUserOpPrecheck} from "../../src/interfaces/IUserOpPrecheck.sol";
77

88
contract MockUserOpPrecheck is IUserOpPrecheck {
99
function precheckUserOp(PackedUserOperation calldata userOp, address fulfiller) external pure {
10-
address expectedCaller = abi.decode(userOp.paymasterAndData[188:], (address));
10+
address expectedCaller = abi.decode(userOp.paymasterAndData[104:], (address));
1111

1212
if (expectedCaller != fulfiller) {
1313
revert();

0 commit comments

Comments
 (0)