Skip to content

Commit a52f344

Browse files
committed
(test) FullCycleUserOp
1 parent e4e9f3e commit a52f344

File tree

5 files changed

+408
-6
lines changed

5 files changed

+408
-6
lines changed

test/foundry/paymasterV3/BasePaymasterTest.t.sol

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.29;
33

4-
import {MockERC20} from "test/foundry/paymasterV3/mocks/MockERC20.sol";
54
import {console2 as console} from "lib/forge-std/src/test.sol";
5+
import {MockERC20} from "test/foundry/paymasterV3/mocks/MockERC20.sol";
6+
import {Simple7702Account} from "test/foundry/paymasterV3/mocks/Simple7702Account.sol";
67
import {PaymasterDataTest as Data} from "test/foundry/paymasterV3/PaymasterDataTest.t.sol";
78
import {OPFPaymasterV3 as Paymaster} from "contracts/paymaster/PaymasterV3/OPFPaymasterV3.sol";
89
import {PackedUserOperation} from "@account-abstraction-v8/interfaces/PackedUserOperation.sol";
910

1011
contract BasePaymasterTest is Data {
1112
Paymaster PM;
1213
MockERC20 mockERC20;
14+
Simple7702Account account;
15+
Simple7702Account implementation;
1316

1417
function setUp() public virtual override {
1518
super.setUp();
1619
PM = new Paymaster(owner, manager, signers);
1720
mockERC20 = new MockERC20();
21+
22+
account = new Simple7702Account();
23+
implementation = account;
24+
_etch();
25+
1826
_deal();
1927
}
2028

@@ -96,4 +104,9 @@ contract BasePaymasterTest is Data {
96104
function _packGasFees(uint256 maxFeePerGas, uint256 maxPriorityFeePerGas) internal pure returns (bytes32) {
97105
return bytes32((maxFeePerGas << 128) | maxPriorityFeePerGas);
98106
}
107+
108+
function _etch() internal {
109+
vm.etch(sender, abi.encodePacked(bytes3(0xef0100), address(implementation)));
110+
account = Simple7702Account(payable(sender));
111+
}
99112
}
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.29;
4+
5+
import {IERC20} from "@oz-v5.4.0/token/ERC20/IERC20.sol";
6+
import {MockERC20} from "test/foundry/paymasterV3/mocks/MockERC20.sol";
7+
import {PackedUserOperation} from "@account-abstraction-v8/interfaces/PackedUserOperation.sol";
8+
import {PaymasterHelpers} from "test/foundry/paymasterV3/paymaster-helpers/PaymasterHelpers.sol";
9+
10+
uint256 constant mintTokens = 30e18;
11+
uint256 constant sendTokens = 5e18;
12+
13+
contract FullCycleUserOp is PaymasterHelpers {
14+
modifier mint() {
15+
_mint(sender, mintTokens);
16+
_;
17+
}
18+
19+
modifier approvePM() {
20+
vm.prank(sender);
21+
mockERC20.approve(address(PM), type(uint256).max);
22+
_;
23+
}
24+
25+
modifier depositAndStakeEP() {
26+
vm.startPrank(owner);
27+
PM.deposit{value: 1 ether}();
28+
PM.addStake{value: 1 ether}(860);
29+
vm.stopPrank();
30+
_;
31+
}
32+
33+
function test_FullCycleUserOpModeVERIFYING_MODE() public depositAndStakeEP {
34+
uint256 balanceSener = sender.balance;
35+
assertEq(balanceSener, 0);
36+
37+
PackedUserOperation memory userOp = _getFreshUserOp();
38+
userOp.nonce = ENTRY_POINT_V8.getNonce(userOp.sender, 0);
39+
userOp.initCode = hex"7702";
40+
41+
address target = address(mockERC20);
42+
uint256 value = 0;
43+
bytes memory data = abi.encodeWithSelector(MockERC20.mint.selector, sender, mintTokens);
44+
45+
bytes memory callData = abi.encodeWithSelector(bytes4(keccak256("execute(address,uint256,bytes)")), target, value, data);
46+
userOp.callData = callData;
47+
48+
userOp.accountGasLimits = _packAccountGasLimits(600_000, 400_000);
49+
userOp.preVerificationGas = preVerificationGas;
50+
userOp.gasFees = _packGasFees(80 gwei, 15 gwei);
51+
52+
userOp.paymasterAndData = _createPaymasterDataMode(userOp, VERIFYING_MODE, 0);
53+
bytes memory paymasterSignature = this._signPaymasterData(VERIFYING_MODE, userOp, 1);
54+
userOp.paymasterAndData = abi.encodePacked(userOp.paymasterAndData, paymasterSignature);
55+
56+
bytes32 userOpHash = ENTRY_POINT_V8.getUserOpHash(userOp);
57+
userOp.signature = _signUserOp(userOpHash);
58+
59+
60+
PackedUserOperation[] memory ops = new PackedUserOperation[](1);
61+
ops[0] = userOp;
62+
63+
_etch();
64+
65+
uint256 senderBalanceBefore = IERC20(address(mockERC20)).balanceOf(sender);
66+
67+
vm.prank(owner);
68+
ENTRY_POINT_V8.handleOps(ops, payable(owner));
69+
70+
uint256 senderBalanceAfter = IERC20(address(mockERC20)).balanceOf(sender);
71+
72+
assertEq(senderBalanceBefore + mintTokens, senderBalanceAfter);
73+
}
74+
75+
function test_FullCycleUserOpERC20_MODE_combinedByteBasic() public depositAndStakeEP mint approvePM {
76+
uint256 balanceSener = sender.balance;
77+
assertEq(balanceSener, 0);
78+
address random = makeAddr("random");
79+
80+
PackedUserOperation memory userOp = _getFreshUserOp();
81+
userOp.nonce = ENTRY_POINT_V8.getNonce(userOp.sender, 0);
82+
userOp.initCode = hex"7702";
83+
84+
address target = address(mockERC20);
85+
uint256 value = 0;
86+
bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, random, sendTokens);
87+
88+
bytes memory callData = abi.encodeWithSelector(bytes4(keccak256("execute(address,uint256,bytes)")), target, value, data);
89+
userOp.callData = callData;
90+
91+
userOp.accountGasLimits = _packAccountGasLimits(600_000, 400_000);
92+
userOp.preVerificationGas = preVerificationGas;
93+
userOp.gasFees = _packGasFees(80 gwei, 15 gwei);
94+
95+
userOp.paymasterAndData = _createPaymasterDataMode(userOp, ERC20_MODE, combinedByteBasic);
96+
bytes memory paymasterSignature = this._signPaymasterData(ERC20_MODE, userOp, 1);
97+
userOp.paymasterAndData = abi.encodePacked(userOp.paymasterAndData, paymasterSignature);
98+
99+
bytes32 userOpHash = ENTRY_POINT_V8.getUserOpHash(userOp);
100+
userOp.signature = _signUserOp(userOpHash);
101+
102+
103+
PackedUserOperation[] memory ops = new PackedUserOperation[](1);
104+
ops[0] = userOp;
105+
106+
_etch();
107+
108+
uint256 randomBalanceBefore = IERC20(address(mockERC20)).balanceOf(random);
109+
uint256 treasuryBalanceBefore = IERC20(address(mockERC20)).balanceOf(treasury);
110+
uint256 senderBalanceBefore = IERC20(address(mockERC20)).balanceOf(sender);
111+
112+
vm.prank(owner);
113+
ENTRY_POINT_V8.handleOps(ops, payable(owner));
114+
115+
uint256 randomBalanceAfter = IERC20(address(mockERC20)).balanceOf(random);
116+
uint256 treasuryBalanceAfter = IERC20(address(mockERC20)).balanceOf(treasury);
117+
uint256 senderBalanceAfter = IERC20(address(mockERC20)).balanceOf(sender);
118+
119+
assertEq(randomBalanceBefore + sendTokens, randomBalanceAfter);
120+
assertEq(senderBalanceBefore - (15156 + sendTokens), senderBalanceAfter);
121+
assertNotEq(treasuryBalanceBefore, treasuryBalanceAfter);
122+
}
123+
124+
function test_FullCycleUserOpERC20_MODE_combinedByteFee() public depositAndStakeEP mint approvePM {
125+
uint256 balanceSener = sender.balance;
126+
assertEq(balanceSener, 0);
127+
address random = makeAddr("random");
128+
129+
PackedUserOperation memory userOp = _getFreshUserOp();
130+
userOp.nonce = ENTRY_POINT_V8.getNonce(userOp.sender, 0);
131+
userOp.initCode = hex"7702";
132+
133+
address target = address(mockERC20);
134+
uint256 value = 0;
135+
bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, random, sendTokens);
136+
137+
bytes memory callData = abi.encodeWithSelector(bytes4(keccak256("execute(address,uint256,bytes)")), target, value, data);
138+
userOp.callData = callData;
139+
140+
userOp.accountGasLimits = _packAccountGasLimits(600_000, 400_000);
141+
userOp.preVerificationGas = preVerificationGas;
142+
userOp.gasFees = _packGasFees(80 gwei, 15 gwei);
143+
144+
userOp.paymasterAndData = _createPaymasterDataMode(userOp, ERC20_MODE, combinedByteFee);
145+
bytes memory paymasterSignature = this._signPaymasterData(ERC20_MODE, userOp, 1);
146+
userOp.paymasterAndData = abi.encodePacked(userOp.paymasterAndData, paymasterSignature);
147+
148+
bytes32 userOpHash = ENTRY_POINT_V8.getUserOpHash(userOp);
149+
userOp.signature = _signUserOp(userOpHash);
150+
151+
152+
PackedUserOperation[] memory ops = new PackedUserOperation[](1);
153+
ops[0] = userOp;
154+
155+
_etch();
156+
157+
uint256 randomBalanceBefore = IERC20(address(mockERC20)).balanceOf(random);
158+
uint256 treasuryBalanceBefore = IERC20(address(mockERC20)).balanceOf(treasury);
159+
uint256 senderBalanceBefore = IERC20(address(mockERC20)).balanceOf(sender);
160+
161+
vm.prank(owner);
162+
ENTRY_POINT_V8.handleOps(ops, payable(owner));
163+
164+
uint256 randomBalanceAfter = IERC20(address(mockERC20)).balanceOf(random);
165+
uint256 treasuryBalanceAfter = IERC20(address(mockERC20)).balanceOf(treasury);
166+
uint256 senderBalanceAfter = IERC20(address(mockERC20)).balanceOf(sender);
167+
168+
assertEq(randomBalanceBefore + sendTokens, randomBalanceAfter);
169+
assertEq(senderBalanceBefore - (25167 + sendTokens), senderBalanceAfter);
170+
assertNotEq(treasuryBalanceBefore, treasuryBalanceAfter);
171+
}
172+
173+
function test_FullCycleUserOpERC20_MODE_combinedByteRecipient() public depositAndStakeEP mint approvePM {
174+
uint256 balanceSener = sender.balance;
175+
assertEq(balanceSener, 0);
176+
address random = makeAddr("random");
177+
178+
PackedUserOperation memory userOp = _getFreshUserOp();
179+
userOp.nonce = ENTRY_POINT_V8.getNonce(userOp.sender, 0);
180+
userOp.initCode = hex"7702";
181+
182+
address target = address(mockERC20);
183+
uint256 value = 0;
184+
bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, random, sendTokens);
185+
186+
bytes memory callData = abi.encodeWithSelector(bytes4(keccak256("execute(address,uint256,bytes)")), target, value, data);
187+
userOp.callData = callData;
188+
189+
userOp.accountGasLimits = _packAccountGasLimits(600_000, 400_000);
190+
userOp.preVerificationGas = preVerificationGas;
191+
userOp.gasFees = _packGasFees(80 gwei, 15 gwei);
192+
193+
userOp.paymasterAndData = _createPaymasterDataMode(userOp, ERC20_MODE, combinedByteRecipient);
194+
bytes memory paymasterSignature = this._signPaymasterData(ERC20_MODE, userOp, 1);
195+
userOp.paymasterAndData = abi.encodePacked(userOp.paymasterAndData, paymasterSignature);
196+
197+
bytes32 userOpHash = ENTRY_POINT_V8.getUserOpHash(userOp);
198+
userOp.signature = _signUserOp(userOpHash);
199+
200+
201+
PackedUserOperation[] memory ops = new PackedUserOperation[](1);
202+
ops[0] = userOp;
203+
204+
_etch();
205+
206+
uint256 randomBalanceBefore = IERC20(address(mockERC20)).balanceOf(random);
207+
uint256 treasuryBalanceBefore = IERC20(address(mockERC20)).balanceOf(treasury);
208+
uint256 senderBalanceBefore = IERC20(address(mockERC20)).balanceOf(sender);
209+
210+
vm.prank(owner);
211+
ENTRY_POINT_V8.handleOps(ops, payable(owner));
212+
213+
uint256 randomBalanceAfter = IERC20(address(mockERC20)).balanceOf(random);
214+
uint256 treasuryBalanceAfter = IERC20(address(mockERC20)).balanceOf(treasury);
215+
uint256 senderBalanceAfter = IERC20(address(mockERC20)).balanceOf(sender);
216+
217+
assertEq(randomBalanceBefore + sendTokens, randomBalanceAfter);
218+
assertEq(senderBalanceBefore - (15167 + sendTokens), senderBalanceAfter);
219+
assertNotEq(treasuryBalanceBefore, treasuryBalanceAfter);
220+
}
221+
222+
function test_FullCycleUserOpERC20_MODE_combinedBytePreFund() public depositAndStakeEP mint approvePM {
223+
uint256 balanceSener = sender.balance;
224+
assertEq(balanceSener, 0);
225+
address random = makeAddr("random");
226+
227+
PackedUserOperation memory userOp = _getFreshUserOp();
228+
userOp.nonce = ENTRY_POINT_V8.getNonce(userOp.sender, 0);
229+
userOp.initCode = hex"7702";
230+
231+
address target = address(mockERC20);
232+
uint256 value = 0;
233+
bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, random, sendTokens);
234+
235+
bytes memory callData = abi.encodeWithSelector(bytes4(keccak256("execute(address,uint256,bytes)")), target, value, data);
236+
userOp.callData = callData;
237+
238+
userOp.accountGasLimits = _packAccountGasLimits(600_000, 400_000);
239+
userOp.preVerificationGas = preVerificationGas;
240+
userOp.gasFees = _packGasFees(80 gwei, 15 gwei);
241+
242+
userOp.paymasterAndData = _createPaymasterDataMode(userOp, ERC20_MODE, combinedBytePreFund);
243+
bytes memory paymasterSignature = this._signPaymasterData(ERC20_MODE, userOp, 1);
244+
userOp.paymasterAndData = abi.encodePacked(userOp.paymasterAndData, paymasterSignature);
245+
246+
bytes32 userOpHash = ENTRY_POINT_V8.getUserOpHash(userOp);
247+
userOp.signature = _signUserOp(userOpHash);
248+
249+
250+
PackedUserOperation[] memory ops = new PackedUserOperation[](1);
251+
ops[0] = userOp;
252+
253+
_etch();
254+
255+
uint256 randomBalanceBefore = IERC20(address(mockERC20)).balanceOf(random);
256+
uint256 treasuryBalanceBefore = IERC20(address(mockERC20)).balanceOf(treasury);
257+
uint256 senderBalanceBefore = IERC20(address(mockERC20)).balanceOf(sender);
258+
259+
vm.prank(owner);
260+
ENTRY_POINT_V8.handleOps(ops, payable(owner));
261+
262+
uint256 randomBalanceAfter = IERC20(address(mockERC20)).balanceOf(random);
263+
uint256 treasuryBalanceAfter = IERC20(address(mockERC20)).balanceOf(treasury);
264+
uint256 senderBalanceAfter = IERC20(address(mockERC20)).balanceOf(sender);
265+
266+
assertEq(randomBalanceBefore + sendTokens, randomBalanceAfter);
267+
assertEq(senderBalanceBefore - (15167 + sendTokens), senderBalanceAfter);
268+
assertNotEq(treasuryBalanceBefore, treasuryBalanceAfter);
269+
}
270+
271+
function test_FullCycleUserOpERC20_MODE_combinedByteAll() public depositAndStakeEP mint approvePM {
272+
uint256 balanceSener = sender.balance;
273+
assertEq(balanceSener, 0);
274+
address random = makeAddr("random");
275+
276+
PackedUserOperation memory userOp = _getFreshUserOp();
277+
userOp.nonce = ENTRY_POINT_V8.getNonce(userOp.sender, 0);
278+
userOp.initCode = hex"7702";
279+
280+
address target = address(mockERC20);
281+
uint256 value = 0;
282+
bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, random, sendTokens);
283+
284+
bytes memory callData = abi.encodeWithSelector(bytes4(keccak256("execute(address,uint256,bytes)")), target, value, data);
285+
userOp.callData = callData;
286+
287+
userOp.accountGasLimits = _packAccountGasLimits(600_000, 400_000);
288+
userOp.preVerificationGas = preVerificationGas;
289+
userOp.gasFees = _packGasFees(80 gwei, 15 gwei);
290+
291+
userOp.paymasterAndData = _createPaymasterDataMode(userOp, ERC20_MODE, combinedByteAll);
292+
bytes memory paymasterSignature = this._signPaymasterData(ERC20_MODE, userOp, 1);
293+
userOp.paymasterAndData = abi.encodePacked(userOp.paymasterAndData, paymasterSignature);
294+
295+
bytes32 userOpHash = ENTRY_POINT_V8.getUserOpHash(userOp);
296+
userOp.signature = _signUserOp(userOpHash);
297+
298+
299+
PackedUserOperation[] memory ops = new PackedUserOperation[](1);
300+
ops[0] = userOp;
301+
302+
_etch();
303+
304+
uint256 randomBalanceBefore = IERC20(address(mockERC20)).balanceOf(random);
305+
uint256 treasuryBalanceBefore = IERC20(address(mockERC20)).balanceOf(treasury);
306+
uint256 senderBalanceBefore = IERC20(address(mockERC20)).balanceOf(sender);
307+
308+
vm.prank(owner);
309+
ENTRY_POINT_V8.handleOps(ops, payable(owner));
310+
311+
uint256 randomBalanceAfter = IERC20(address(mockERC20)).balanceOf(random);
312+
uint256 treasuryBalanceAfter = IERC20(address(mockERC20)).balanceOf(treasury);
313+
uint256 senderBalanceAfter = IERC20(address(mockERC20)).balanceOf(sender);
314+
315+
assertEq(randomBalanceBefore + sendTokens, randomBalanceAfter);
316+
assertEq(senderBalanceBefore - (25189 + sendTokens), senderBalanceAfter);
317+
assertNotEq(treasuryBalanceBefore, treasuryBalanceAfter);
318+
}
319+
}

test/foundry/paymasterV3/PaymasterPostOp.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ contract PaymasterPostOp is PaymasterHelpers {
2020
uint256 balance = IERC20(address(mockERC20)).balanceOf(sender);
2121
assertEq(mintTokens, balance);
2222
}
23-
23+
2424
function test_postOpERC20_MODE_combinedByteBasic() public mint {
2525
PackedUserOperation memory userOp = _getFreshUserOp();
2626
userOp.nonce = ENTRY_POINT_V8.getNonce(userOp.sender, 0);

0 commit comments

Comments
 (0)