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
+ }
0 commit comments