@@ -8,11 +8,13 @@ const {
8
8
shouldSupportInterfaces,
9
9
} = require ( '@openzeppelin/contracts/test/utils/introspection/SupportsInterface.behavior' ) ;
10
10
11
- function shouldBehaveLikeAnAccountBase ( ) {
11
+ const value = ethers . parseEther ( '0.1' ) ;
12
+
13
+ function shouldBehaveLikeAccountCore ( ) {
12
14
describe ( 'entryPoint' , function ( ) {
13
15
it ( 'should return the canonical entrypoint' , async function ( ) {
14
16
await this . mock . deploy ( ) ;
15
- expect ( await this . mock . entryPoint ( ) ) . to . equal ( entrypoint ) ;
17
+ expect ( this . mock . entryPoint ( ) ) . to . eventually . equal ( entrypoint ) ;
16
18
} ) ;
17
19
} ) ;
18
20
@@ -23,18 +25,8 @@ function shouldBehaveLikeAnAccountBase() {
23
25
} ) ;
24
26
25
27
it ( 'should revert if the caller is not the canonical entrypoint' , async function ( ) {
26
- const selector = this . mock . interface . getFunction ( 'executeUserOp' ) . selector ;
27
- const operation = await this . mock
28
- . createUserOp ( {
29
- callData : ethers . concat ( [
30
- selector ,
31
- ethers . AbiCoder . defaultAbiCoder ( ) . encode (
32
- [ 'address' , 'uint256' , 'bytes' ] ,
33
- [ this . target . target , 0 , this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ] ,
34
- ) ,
35
- ] ) ,
36
- } )
37
- . then ( op => this . signUserOp ( op ) ) ;
28
+ // empty operation (does nothing)
29
+ const operation = await this . mock . createUserOp ( { } ) . then ( op => this . signUserOp ( op ) ) ;
38
30
39
31
await expect ( this . mock . connect ( this . other ) . validateUserOp ( operation . packed , operation . hash ( ) , 0 ) )
40
32
. to . be . revertedWithCustomError ( this . mock , 'AccountUnauthorized' )
@@ -47,18 +39,8 @@ function shouldBehaveLikeAnAccountBase() {
47
39
} ) ;
48
40
49
41
it ( 'should return SIG_VALIDATION_SUCCESS if the signature is valid' , async function ( ) {
50
- const selector = this . mock . interface . getFunction ( 'executeUserOp' ) . selector ;
51
- const operation = await this . mock
52
- . createUserOp ( {
53
- callData : ethers . concat ( [
54
- selector ,
55
- ethers . AbiCoder . defaultAbiCoder ( ) . encode (
56
- [ 'address' , 'uint256' , 'bytes' ] ,
57
- [ this . target . target , 0 , this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ] ,
58
- ) ,
59
- ] ) ,
60
- } )
61
- . then ( op => this . signUserOp ( op ) ) ;
42
+ // empty operation (does nothing)
43
+ const operation = await this . mock . createUserOp ( { } ) . then ( op => this . signUserOp ( op ) ) ;
62
44
63
45
expect (
64
46
await this . mock
@@ -68,17 +50,8 @@ function shouldBehaveLikeAnAccountBase() {
68
50
} ) ;
69
51
70
52
it ( 'should return SIG_VALIDATION_FAILURE if the signature is invalid' , async function ( ) {
71
- const selector = this . mock . interface . getFunction ( 'executeUserOp' ) . selector ;
72
- const operation = await this . mock . createUserOp ( {
73
- callData : ethers . concat ( [
74
- selector ,
75
- ethers . AbiCoder . defaultAbiCoder ( ) . encode (
76
- [ 'address' , 'uint256' , 'bytes' ] ,
77
- [ this . target . target , 0 , this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ] ,
78
- ) ,
79
- ] ) ,
80
- } ) ;
81
-
53
+ // empty operation (does nothing)
54
+ const operation = await this . mock . createUserOp ( { } ) ;
82
55
operation . signature = '0x00' ;
83
56
84
57
expect (
@@ -89,46 +62,24 @@ function shouldBehaveLikeAnAccountBase() {
89
62
} ) ;
90
63
91
64
it ( 'should pay missing account funds for execution' , async function ( ) {
92
- const selector = this . mock . interface . getFunction ( 'executeUserOp' ) . selector ;
93
- const operation = await this . mock
94
- . createUserOp ( {
95
- callData : ethers . concat ( [
96
- selector ,
97
- ethers . AbiCoder . defaultAbiCoder ( ) . encode (
98
- [ 'address' , 'uint256' , 'bytes' ] ,
99
- [ this . target . target , 0 , this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ] ,
100
- ) ,
101
- ] ) ,
102
- } )
103
- . then ( op => this . signUserOp ( op ) ) ;
104
-
105
- const prevAccountBalance = await ethers . provider . getBalance ( this . mock ) ;
106
- const prevEntrypointBalance = await ethers . provider . getBalance ( entrypoint ) ;
107
- const amount = ethers . parseEther ( '0.1' ) ;
108
-
109
- const tx = await this . mock
110
- . connect ( this . entrypointAsSigner )
111
- . validateUserOp ( operation . packed , operation . hash ( ) , amount ) ;
65
+ // empty operation (does nothing)
66
+ const operation = await this . mock . createUserOp ( { } ) . then ( op => this . signUserOp ( op ) ) ;
112
67
113
- const receipt = await tx . wait ( ) ;
114
- const callerFees = receipt . gasUsed * tx . gasPrice ;
115
-
116
- expect ( await ethers . provider . getBalance ( this . mock ) ) . to . equal ( prevAccountBalance - amount ) ;
117
- expect ( await ethers . provider . getBalance ( entrypoint ) ) . to . equal ( prevEntrypointBalance + amount - callerFees ) ;
68
+ await expect (
69
+ this . mock . connect ( this . entrypointAsSigner ) . validateUserOp ( operation . packed , operation . hash ( ) , value ) ,
70
+ ) . to . changeEtherBalances ( [ this . mock , entrypoint ] , [ - value , value ] ) ;
118
71
} ) ;
119
72
} ) ;
120
73
} ) ;
121
74
122
75
describe ( 'fallback' , function ( ) {
123
76
it ( 'should receive ether' , async function ( ) {
124
77
await this . mock . deploy ( ) ;
125
- await setBalance ( this . other . address , ethers . parseEther ( '1' ) ) ;
126
-
127
- const prevBalance = await ethers . provider . getBalance ( this . mock ) ;
128
- const amount = ethers . parseEther ( '0.1' ) ;
129
- await this . other . sendTransaction ( { to : this . mock , value : amount } ) ;
130
78
131
- expect ( await ethers . provider . getBalance ( this . mock ) ) . to . equal ( prevBalance + amount ) ;
79
+ await expect ( this . other . sendTransaction ( { to : this . mock , value } ) ) . to . changeEtherBalances (
80
+ [ this . other , this . mock ] ,
81
+ [ - value , value ] ,
82
+ ) ;
132
83
} ) ;
133
84
} ) ;
134
85
}
@@ -147,76 +98,85 @@ function shouldBehaveLikeAccountHolder() {
147
98
const data = '0x12345678' ;
148
99
149
100
beforeEach ( async function ( ) {
150
- [ this . owner ] = await ethers . getSigners ( ) ;
151
101
this . token = await ethers . deployContract ( '$ERC1155Mock' , [ 'https://somedomain.com/{id}.json' ] ) ;
152
- await this . token . $_mintBatch ( this . owner , ids , values , '0x' ) ;
102
+ await this . token . $_mintBatch ( this . other , ids , values , '0x' ) ;
153
103
} ) ;
154
104
155
105
it ( 'receives ERC1155 tokens from a single ID' , async function ( ) {
156
- await this . token . connect ( this . owner ) . safeTransferFrom ( this . owner , this . mock , ids [ 0 ] , values [ 0 ] , data ) ;
157
- expect ( await this . token . balanceOf ( this . mock , ids [ 0 ] ) ) . to . equal ( values [ 0 ] ) ;
158
- for ( let i = 1 ; i < ids . length ; i ++ ) {
159
- expect ( await this . token . balanceOf ( this . mock , ids [ i ] ) ) . to . equal ( 0n ) ;
160
- }
106
+ await this . token . connect ( this . other ) . safeTransferFrom ( this . other , this . mock , ids [ 0 ] , values [ 0 ] , data ) ;
107
+
108
+ expect (
109
+ this . token . balanceOfBatch (
110
+ ids . map ( ( ) => this . mock ) ,
111
+ ids ,
112
+ ) ,
113
+ ) . to . eventually . deep . equal ( values . map ( ( v , i ) => ( i == 0 ? v : 0n ) ) ) ;
161
114
} ) ;
162
115
163
116
it ( 'receives ERC1155 tokens from a multiple IDs' , async function ( ) {
164
117
expect (
165
- await this . token . balanceOfBatch (
118
+ this . token . balanceOfBatch (
166
119
ids . map ( ( ) => this . mock ) ,
167
120
ids ,
168
121
) ,
169
- ) . to . deep . equal ( ids . map ( ( ) => 0n ) ) ;
170
- await this . token . connect ( this . owner ) . safeBatchTransferFrom ( this . owner , this . mock , ids , values , data ) ;
122
+ ) . to . eventually . deep . equal ( ids . map ( ( ) => 0n ) ) ;
123
+
124
+ await this . token . connect ( this . other ) . safeBatchTransferFrom ( this . other , this . mock , ids , values , data ) ;
171
125
expect (
172
- await this . token . balanceOfBatch (
126
+ this . token . balanceOfBatch (
173
127
ids . map ( ( ) => this . mock ) ,
174
128
ids ,
175
129
) ,
176
- ) . to . deep . equal ( values ) ;
130
+ ) . to . eventually . deep . equal ( values ) ;
177
131
} ) ;
178
132
} ) ;
179
133
180
134
describe ( 'onERC721Received' , function ( ) {
181
- it ( 'receives an ERC721 token' , async function ( ) {
182
- const name = 'Some NFT' ;
183
- const symbol = 'SNFT' ;
184
- const tokenId = 1n ;
135
+ const tokenId = 1n ;
185
136
186
- const [ owner ] = await ethers . getSigners ( ) ;
187
-
188
- const token = await ethers . deployContract ( '$ERC721Mock' , [ name , symbol ] ) ;
189
- await token . $_mint ( owner , tokenId ) ;
137
+ beforeEach ( async function ( ) {
138
+ this . token = await ethers . deployContract ( '$ERC721Mock' , [ 'Some NFT' , 'SNFT' ] ) ;
139
+ await this . token . $_mint ( this . other , tokenId ) ;
140
+ } ) ;
190
141
191
- await token . connect ( owner ) . safeTransferFrom ( owner , this . mock , tokenId ) ;
142
+ it ( 'receives an ERC721 token' , async function ( ) {
143
+ await this . token . connect ( this . other ) . safeTransferFrom ( this . other , this . mock , tokenId ) ;
192
144
193
- expect ( await token . ownerOf ( tokenId ) ) . to . equal ( this . mock ) ;
145
+ expect ( this . token . ownerOf ( tokenId ) ) . to . eventually . equal ( this . mock ) ;
194
146
} ) ;
195
147
} ) ;
196
148
} ) ;
197
149
}
198
150
199
- function shouldBehaveLikeAnAccountBaseExecutor ( { deployable = true } = { } ) {
151
+ function shouldBehaveLikeAccountExecutor ( { deployable = true } = { } ) {
200
152
describe ( 'executeUserOp' , function ( ) {
201
153
beforeEach ( async function ( ) {
154
+ // give eth to the account (before deployment)
202
155
await setBalance ( this . mock . target , ethers . parseEther ( '1' ) ) ;
203
- expect ( await ethers . provider . getCode ( this . mock ) ) . to . equal ( '0x' ) ;
204
- this . entrypointAsSigner = await impersonate ( entrypoint . target ) ;
156
+
157
+ // account is not initially deployed
158
+ expect ( ethers . provider . getCode ( this . mock ) ) . to . eventually . equal ( '0x' ) ;
159
+
160
+ this . encodeUserOpCalldata = ( to , value , calldata ) =>
161
+ ethers . concat ( [
162
+ this . mock . interface . getFunction ( 'executeUserOp' ) . selector ,
163
+ ethers . solidityPacked (
164
+ [ 'address' , 'uint256' , 'bytes' ] ,
165
+ [ to . target ?? to . address ?? to , value ?? 0 , calldata ?? '0x' ] ,
166
+ ) ,
167
+ ] ) ;
205
168
} ) ;
206
169
207
170
it ( 'should revert if the caller is not the canonical entrypoint or the account itself' , async function ( ) {
208
171
await this . mock . deploy ( ) ;
209
172
210
- const selector = this . mock . interface . getFunction ( 'executeUserOp' ) . selector ;
211
173
const operation = await this . mock
212
174
. createUserOp ( {
213
- callData : ethers . concat ( [
214
- selector ,
215
- ethers . AbiCoder . defaultAbiCoder ( ) . encode (
216
- [ 'address' , 'uint256' , 'bytes' ] ,
217
- [ this . target . target , 0 , this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ] ,
218
- ) ,
219
- ] ) ,
175
+ callData : this . encodeUserOpCalldata (
176
+ this . target ,
177
+ 0 ,
178
+ this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ,
179
+ ) ,
220
180
} )
221
181
. then ( op => this . signUserOp ( op ) ) ;
222
182
@@ -228,39 +188,34 @@ function shouldBehaveLikeAnAccountBaseExecutor({ deployable = true } = {}) {
228
188
if ( deployable ) {
229
189
describe ( 'when not deployed' , function ( ) {
230
190
it ( 'should be created with handleOps and increase nonce' , async function ( ) {
231
- const selector = this . mock . interface . getFunction ( 'executeUserOp' ) . selector ;
232
191
const operation = await this . mock
233
192
. createUserOp ( {
234
- callData : ethers . concat ( [
235
- selector ,
236
- ethers . AbiCoder . defaultAbiCoder ( ) . encode (
237
- [ 'address' , 'uint256' , 'bytes' ] ,
238
- [ this . target . target , 17 , this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ] ,
239
- ) ,
240
- ] ) ,
193
+ callData : this . encodeUserOpCalldata (
194
+ this . target ,
195
+ 17 ,
196
+ this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ,
197
+ ) ,
241
198
} )
242
199
. then ( op => op . addInitCode ( ) )
243
200
. then ( op => this . signUserOp ( op ) ) ;
244
201
202
+ expect ( this . mock . getNonce ( ) ) . to . eventually . equal ( 0 ) ;
245
203
await expect ( entrypoint . handleOps ( [ operation . packed ] , this . beneficiary ) )
246
204
. to . emit ( entrypoint , 'AccountDeployed' )
247
205
. withArgs ( operation . hash ( ) , this . mock , this . factory , ethers . ZeroAddress )
248
206
. to . emit ( this . target , 'MockFunctionCalledExtra' )
249
207
. withArgs ( this . mock , 17 ) ;
250
- expect ( await this . mock . getNonce ( ) ) . to . equal ( 1 ) ;
208
+ expect ( this . mock . getNonce ( ) ) . to . eventually . equal ( 1 ) ;
251
209
} ) ;
252
210
253
211
it ( 'should revert if the signature is invalid' , async function ( ) {
254
- const selector = this . mock . interface . getFunction ( 'executeUserOp' ) . selector ;
255
212
const operation = await this . mock
256
213
. createUserOp ( {
257
- callData : ethers . concat ( [
258
- selector ,
259
- ethers . AbiCoder . defaultAbiCoder ( ) . encode (
260
- [ 'address' , 'uint256' , 'bytes' ] ,
261
- [ this . target . target , 17 , this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ] ,
262
- ) ,
263
- ] ) ,
214
+ callData : this . encodeUserOpCalldata (
215
+ this . target ,
216
+ 17 ,
217
+ this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ,
218
+ ) ,
264
219
} )
265
220
. then ( op => op . addInitCode ( ) ) ;
266
221
@@ -277,31 +232,41 @@ function shouldBehaveLikeAnAccountBaseExecutor({ deployable = true } = {}) {
277
232
} ) ;
278
233
279
234
it ( 'should increase nonce and call target' , async function ( ) {
280
- const selector = this . mock . interface . getFunction ( 'executeUserOp' ) . selector ;
281
235
const operation = await this . mock
282
236
. createUserOp ( {
283
- callData : ethers . concat ( [
284
- selector ,
285
- ethers . AbiCoder . defaultAbiCoder ( ) . encode (
286
- [ 'address' , 'uint256' , 'bytes' ] ,
287
- [ this . target . target , 42 , this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ] ,
288
- ) ,
289
- ] ) ,
237
+ callData : this . encodeUserOpCalldata (
238
+ this . target ,
239
+ 42 ,
240
+ this . target . interface . encodeFunctionData ( 'mockFunctionExtra' ) ,
241
+ ) ,
290
242
} )
291
243
. then ( op => this . signUserOp ( op ) ) ;
292
244
293
- expect ( await this . mock . getNonce ( ) ) . to . equal ( 0 ) ;
245
+ expect ( this . mock . getNonce ( ) ) . to . eventually . equal ( 0 ) ;
294
246
await expect ( entrypoint . handleOps ( [ operation . packed ] , this . beneficiary ) )
295
247
. to . emit ( this . target , 'MockFunctionCalledExtra' )
296
248
. withArgs ( this . mock , 42 ) ;
297
- expect ( await this . mock . getNonce ( ) ) . to . equal ( 1 ) ;
249
+ expect ( this . mock . getNonce ( ) ) . to . eventually . equal ( 1 ) ;
250
+ } ) ;
251
+
252
+ it ( 'should support sending eth to an EOA' , async function ( ) {
253
+ const operation = await this . mock
254
+ . createUserOp ( { callData : this . encodeUserOpCalldata ( this . other , value ) } )
255
+ . then ( op => this . signUserOp ( op ) ) ;
256
+
257
+ expect ( this . mock . getNonce ( ) ) . to . eventually . equal ( 0 ) ;
258
+ await expect ( entrypoint . handleOps ( [ operation . packed ] , this . beneficiary ) ) . to . changeEtherBalance (
259
+ this . other ,
260
+ value ,
261
+ ) ;
262
+ expect ( this . mock . getNonce ( ) ) . to . eventually . equal ( 1 ) ;
298
263
} ) ;
299
264
} ) ;
300
265
} ) ;
301
266
}
302
267
303
268
module . exports = {
304
- shouldBehaveLikeAnAccountBase ,
269
+ shouldBehaveLikeAccountCore ,
305
270
shouldBehaveLikeAccountHolder,
306
- shouldBehaveLikeAnAccountBaseExecutor ,
271
+ shouldBehaveLikeAccountExecutor ,
307
272
} ;
0 commit comments