|
| 1 | +// SPDX-License-Identifier: UNLICENSED |
| 2 | +pragma solidity ^0.8.13; |
| 3 | + |
| 4 | +import {Test, console} from "forge-std/Test.sol"; |
| 5 | +import {Paymaster} from "../src/Paymaster.sol"; |
| 6 | +import {IEntryPoint} from "@account-abstraction/interfaces/IEntryPoint.sol"; |
| 7 | +import {IStakeManager} from "@account-abstraction/interfaces/IStakeManager.sol"; |
| 8 | +import {EntryPoint} from "@account-abstraction/core/EntryPoint.sol"; |
| 9 | +import {UserOperation} from "@account-abstraction/interfaces/UserOperation.sol"; |
| 10 | +import {SimpleAccountFactory} from "@account-abstraction/samples/SimpleAccountFactory.sol"; |
| 11 | +import {SimpleAccount} from "@account-abstraction/samples/SimpleAccount.sol"; |
| 12 | +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; |
| 13 | + |
| 14 | +contract PaymasterTest is Test { |
| 15 | + EntryPoint public entrypoint; |
| 16 | + Paymaster public paymaster; |
| 17 | + SimpleAccount public account; |
| 18 | + |
| 19 | + uint48 constant MOCK_VALID_UNTIL = 0x00000000deadbeef; |
| 20 | + uint48 constant MOCK_VALID_AFTER = 0x0000000000001234; |
| 21 | + bytes constant MOCK_SIG = "0x1234"; |
| 22 | + address constant PAYMASTER_SIGNER = 0xC3Bf2750F0d47098f487D45b2FB26b32eCbAf9a2; |
| 23 | + uint256 constant PAYMASTER_SIGNER_KEY = 0x6a6c11c6f4703865cc4a88c6ebf0a605fdeeccd8052d66101d1d02730740a3c0; |
| 24 | + address constant ACCOUNT_OWNER = 0x39c0Bb04Bf6B779ac994f6A5211204e3Dbe16741; |
| 25 | + uint256 constant ACCOUNT_OWNER_KEY = 0x4034df11fcc455209edcb8948449a4dff732376dab6d03dc2d099d0084b0f023; |
| 26 | + |
| 27 | + function setUp() public { |
| 28 | + entrypoint = new EntryPoint(); |
| 29 | + paymaster = new Paymaster(entrypoint, PAYMASTER_SIGNER); |
| 30 | + SimpleAccountFactory factory = new SimpleAccountFactory(entrypoint); |
| 31 | + account = factory.createAccount(ACCOUNT_OWNER, 0); |
| 32 | + } |
| 33 | + |
| 34 | + function test_parsePaymasterAndData() public { |
| 35 | + bytes memory paymasterAndData = abi.encodePacked(address(paymaster), abi.encode(MOCK_VALID_UNTIL, MOCK_VALID_AFTER), MOCK_SIG); |
| 36 | + (uint48 validUntil, uint48 validAfter, bytes memory signature) = paymaster.parsePaymasterAndData(paymasterAndData); |
| 37 | + assertEq(validUntil, MOCK_VALID_UNTIL); |
| 38 | + assertEq(validAfter, MOCK_VALID_AFTER); |
| 39 | + assertEq(signature, MOCK_SIG); |
| 40 | + } |
| 41 | + |
| 42 | + function test_validatePaymasterUserOpValidSignature() public { |
| 43 | + UserOperation memory userOp = createUserOp(); |
| 44 | + signUserOp(userOp); |
| 45 | + |
| 46 | + vm.expectRevert(createEncodedValidationResult(false)); |
| 47 | + entrypoint.simulateValidation(userOp); |
| 48 | + } |
| 49 | + |
| 50 | + function test_validatePaymasterUserOpWrongSigner() public { |
| 51 | + UserOperation memory userOp = createUserOp(); |
| 52 | + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ACCOUNT_OWNER_KEY, ECDSA.toEthSignedMessageHash(paymaster.getHash(userOp, MOCK_VALID_UNTIL, MOCK_VALID_AFTER))); |
| 53 | + userOp.paymasterAndData = abi.encodePacked(address(paymaster), abi.encode(MOCK_VALID_UNTIL, MOCK_VALID_AFTER), r, s, v); |
| 54 | + signUserOp(userOp); |
| 55 | + |
| 56 | + vm.expectRevert(createEncodedValidationResult(true)); |
| 57 | + entrypoint.simulateValidation(userOp); |
| 58 | + } |
| 59 | + |
| 60 | + function test_validatePaymasterUserOpNoSignature() public { |
| 61 | + UserOperation memory userOp = createUserOp(); |
| 62 | + userOp.paymasterAndData = abi.encodePacked(address(paymaster), abi.encode(MOCK_VALID_UNTIL, MOCK_VALID_AFTER)); |
| 63 | + signUserOp(userOp); |
| 64 | + |
| 65 | + vm.expectRevert( |
| 66 | + abi.encodeWithSelector( |
| 67 | + IEntryPoint.FailedOp.selector, |
| 68 | + 0, "AA33 reverted: Paymaster: invalid signature length in paymasterAndData" |
| 69 | + ) |
| 70 | + ); |
| 71 | + entrypoint.simulateValidation(userOp); |
| 72 | + } |
| 73 | + |
| 74 | + function test_validatePaymasterUserOpInvalidSignature() public { |
| 75 | + UserOperation memory userOp = createUserOp(); |
| 76 | + userOp.paymasterAndData = abi.encodePacked(address(paymaster), abi.encode(MOCK_VALID_UNTIL, MOCK_VALID_AFTER), bytes32(0), bytes32(0), uint8(0)); |
| 77 | + signUserOp(userOp); |
| 78 | + |
| 79 | + vm.expectRevert( |
| 80 | + abi.encodeWithSelector( |
| 81 | + IEntryPoint.FailedOp.selector, |
| 82 | + 0, "AA33 reverted: ECDSA: invalid signature" |
| 83 | + ) |
| 84 | + ); |
| 85 | + entrypoint.simulateValidation(userOp); |
| 86 | + } |
| 87 | + |
| 88 | + function createUserOp() public view returns (UserOperation memory) { |
| 89 | + UserOperation memory userOp; |
| 90 | + userOp.sender = address(account); |
| 91 | + userOp.verificationGasLimit = 100000; |
| 92 | + (uint8 v, bytes32 r, bytes32 s) = vm.sign(PAYMASTER_SIGNER_KEY, ECDSA.toEthSignedMessageHash(paymaster.getHash(userOp, MOCK_VALID_UNTIL, MOCK_VALID_AFTER))); |
| 93 | + userOp.paymasterAndData = abi.encodePacked(address(paymaster), abi.encode(MOCK_VALID_UNTIL, MOCK_VALID_AFTER), r, s, v); |
| 94 | + return userOp; |
| 95 | + } |
| 96 | + |
| 97 | + function signUserOp(UserOperation memory userOp) public view { |
| 98 | + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ACCOUNT_OWNER_KEY, ECDSA.toEthSignedMessageHash(entrypoint.getUserOpHash(userOp))); |
| 99 | + userOp.signature = abi.encodePacked(r, s, v); |
| 100 | + } |
| 101 | + |
| 102 | + function createEncodedValidationResult(bool sigFailed) public pure returns (bytes memory) { |
| 103 | + uint256 preOpGas = 55114; |
| 104 | + if (sigFailed) { |
| 105 | + preOpGas = 55120; |
| 106 | + } |
| 107 | + uint256 prefund = 0; |
| 108 | + bytes memory paymasterContext = ""; |
| 109 | + return abi.encodeWithSelector( |
| 110 | + IEntryPoint.ValidationResult.selector, |
| 111 | + IEntryPoint.ReturnInfo(preOpGas, prefund, sigFailed, MOCK_VALID_AFTER, MOCK_VALID_UNTIL, paymasterContext), |
| 112 | + IStakeManager.StakeInfo(0, 0), |
| 113 | + IStakeManager.StakeInfo(0, 0), |
| 114 | + IStakeManager.StakeInfo(0, 0) |
| 115 | + ); |
| 116 | + } |
| 117 | +} |
0 commit comments