Skip to content

Commit 9aaafa2

Browse files
4337 Account Abstraction UnitTests, IntegrationTests
1 parent ce0e537 commit 9aaafa2

28 files changed

Lines changed: 9327 additions & 2 deletions

tests/Nethereum.AccountAbstraction.IntegrationTests/Bundler/BundleExecutorTests.cs

Lines changed: 390 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
using Nethereum.AccountAbstraction.Bundler;
2+
using Nethereum.AccountAbstraction.SimpleAccount.SimpleAccount.ContractDefinition;
3+
using Nethereum.AccountAbstraction.Structs;
4+
using Nethereum.Contracts;
5+
using Nethereum.Hex.HexConvertors.Extensions;
6+
using Nethereum.Signer;
7+
using Nethereum.XUnitEthereumClients;
8+
using System.Numerics;
9+
using Xunit;
10+
11+
namespace Nethereum.AccountAbstraction.IntegrationTests.Bundler
12+
{
13+
[Collection(BundlerTestFixture.BUNDLER_COLLECTION)]
14+
public class BundlerServiceTests
15+
{
16+
private readonly BundlerTestFixture _fixture;
17+
18+
public BundlerServiceTests(BundlerTestFixture fixture)
19+
{
20+
_fixture = fixture;
21+
}
22+
23+
[Fact]
24+
public async Task SendUserOperation_WithValidOperation_ReturnsUserOpHash()
25+
{
26+
var salt = (ulong)Random.Shared.NextInt64();
27+
var (accountAddress, accountKey) = await _fixture.CreateFundedAccountAsync(salt);
28+
29+
var executeFunction = new ExecuteFunction
30+
{
31+
Target = accountAddress,
32+
Value = 0,
33+
Data = Array.Empty<byte>()
34+
};
35+
36+
var userOp = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
37+
new UserOperation
38+
{
39+
Sender = accountAddress,
40+
CallData = executeFunction.GetCallData(),
41+
CallGasLimit = 100_000,
42+
VerificationGasLimit = 100_000
43+
},
44+
accountKey);
45+
46+
var userOpHash = await _fixture.BundlerService.SendUserOperationAsync(
47+
userOp,
48+
_fixture.EntryPointService.ContractAddress);
49+
50+
Assert.NotNull(userOpHash);
51+
Assert.StartsWith("0x", userOpHash);
52+
Assert.Equal(66, userOpHash.Length);
53+
}
54+
55+
[Fact]
56+
public async Task SendUserOperation_WithBlacklistedSender_ThrowsException()
57+
{
58+
var salt = (ulong)Random.Shared.NextInt64();
59+
var (accountAddress, accountKey) = await _fixture.CreateFundedAccountAsync(salt);
60+
61+
var config = new BundlerConfig
62+
{
63+
SupportedEntryPoints = new[] { _fixture.EntryPointService.ContractAddress },
64+
BeneficiaryAddress = _fixture.BeneficiaryAddress,
65+
AutoBundleIntervalMs = 0,
66+
StrictValidation = false,
67+
SimulateValidation = false,
68+
BlacklistedAddresses = new HashSet<string> { accountAddress.ToLowerInvariant() }
69+
};
70+
71+
using var bundler = new BundlerService(_fixture.Web3, config);
72+
73+
var executeFunction = new ExecuteFunction
74+
{
75+
Target = accountAddress,
76+
Value = 0,
77+
Data = Array.Empty<byte>()
78+
};
79+
80+
var userOp = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
81+
new UserOperation
82+
{
83+
Sender = accountAddress,
84+
CallData = executeFunction.GetCallData(),
85+
CallGasLimit = 100_000,
86+
VerificationGasLimit = 100_000
87+
},
88+
accountKey);
89+
90+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
91+
bundler.SendUserOperationAsync(userOp, _fixture.EntryPointService.ContractAddress));
92+
93+
Assert.Contains("blacklisted", ex.Message.ToLowerInvariant());
94+
}
95+
96+
[Fact]
97+
public async Task SendUserOperation_WithUnsupportedEntryPoint_ThrowsException()
98+
{
99+
var salt = (ulong)Random.Shared.NextInt64();
100+
var (accountAddress, accountKey) = await _fixture.CreateFundedAccountAsync(salt);
101+
102+
var executeFunction = new ExecuteFunction
103+
{
104+
Target = accountAddress,
105+
Value = 0,
106+
Data = Array.Empty<byte>()
107+
};
108+
109+
var userOp = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
110+
new UserOperation
111+
{
112+
Sender = accountAddress,
113+
CallData = executeFunction.GetCallData(),
114+
CallGasLimit = 100_000,
115+
VerificationGasLimit = 100_000
116+
},
117+
accountKey);
118+
119+
var fakeEntryPoint = "0x0000000000000000000000000000000000000001";
120+
121+
var ex = await Assert.ThrowsAsync<ArgumentException>(() =>
122+
_fixture.BundlerService.SendUserOperationAsync(userOp, fakeEntryPoint));
123+
124+
Assert.Contains("Unsupported EntryPoint", ex.Message);
125+
}
126+
127+
[Fact]
128+
public async Task SendUserOperation_Duplicate_ThrowsException()
129+
{
130+
var salt = (ulong)Random.Shared.NextInt64();
131+
var (accountAddress, accountKey) = await _fixture.CreateFundedAccountAsync(salt);
132+
133+
var executeFunction = new ExecuteFunction
134+
{
135+
Target = accountAddress,
136+
Value = 0,
137+
Data = Array.Empty<byte>()
138+
};
139+
140+
var userOp = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
141+
new UserOperation
142+
{
143+
Sender = accountAddress,
144+
CallData = executeFunction.GetCallData(),
145+
CallGasLimit = 100_000,
146+
VerificationGasLimit = 100_000
147+
},
148+
accountKey);
149+
150+
using var bundler = _fixture.CreateNewBundlerService();
151+
152+
var hash1 = await bundler.SendUserOperationAsync(
153+
userOp,
154+
_fixture.EntryPointService.ContractAddress);
155+
156+
Assert.NotNull(hash1);
157+
158+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
159+
bundler.SendUserOperationAsync(userOp, _fixture.EntryPointService.ContractAddress));
160+
161+
Assert.Contains("duplicate", ex.Message.ToLowerInvariant());
162+
}
163+
164+
[Fact]
165+
public async Task GetUserOperationByHash_AfterSubmission_ReturnsOperation()
166+
{
167+
var salt = (ulong)Random.Shared.NextInt64();
168+
var (accountAddress, accountKey) = await _fixture.CreateFundedAccountAsync(salt);
169+
170+
var executeFunction = new ExecuteFunction
171+
{
172+
Target = accountAddress,
173+
Value = 0,
174+
Data = Array.Empty<byte>()
175+
};
176+
177+
var userOp = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
178+
new UserOperation
179+
{
180+
Sender = accountAddress,
181+
CallData = executeFunction.GetCallData(),
182+
CallGasLimit = 100_000,
183+
VerificationGasLimit = 100_000
184+
},
185+
accountKey);
186+
187+
using var bundler = _fixture.CreateNewBundlerService();
188+
189+
var userOpHash = await bundler.SendUserOperationAsync(
190+
userOp,
191+
_fixture.EntryPointService.ContractAddress);
192+
193+
var retrievedOp = await bundler.GetUserOperationByHashAsync(userOpHash);
194+
195+
Assert.NotNull(retrievedOp);
196+
Assert.Equal(userOpHash, retrievedOp.UserOpHash);
197+
Assert.Equal(accountAddress.ToLowerInvariant(), retrievedOp.UserOperation.Sender?.ToLowerInvariant());
198+
}
199+
200+
[Fact]
201+
public async Task GetUserOperationByHash_WithUnknownHash_ReturnsNull()
202+
{
203+
var unknownHash = "0x" + new string('0', 64);
204+
205+
var result = await _fixture.BundlerService.GetUserOperationByHashAsync(unknownHash);
206+
207+
Assert.Null(result);
208+
}
209+
210+
[Fact]
211+
public async Task GetUserOperationStatus_AfterSubmission_ReturnsPendingState()
212+
{
213+
var salt = (ulong)Random.Shared.NextInt64();
214+
var (accountAddress, accountKey) = await _fixture.CreateFundedAccountAsync(salt);
215+
216+
var executeFunction = new ExecuteFunction
217+
{
218+
Target = accountAddress,
219+
Value = 0,
220+
Data = Array.Empty<byte>()
221+
};
222+
223+
var userOp = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
224+
new UserOperation
225+
{
226+
Sender = accountAddress,
227+
CallData = executeFunction.GetCallData(),
228+
CallGasLimit = 100_000,
229+
VerificationGasLimit = 100_000
230+
},
231+
accountKey);
232+
233+
using var bundler = _fixture.CreateNewBundlerService();
234+
235+
var userOpHash = await bundler.SendUserOperationAsync(
236+
userOp,
237+
_fixture.EntryPointService.ContractAddress);
238+
239+
var status = await bundler.GetUserOperationStatusAsync(userOpHash);
240+
241+
Assert.NotNull(status);
242+
Assert.Equal(userOpHash, status.UserOpHash);
243+
Assert.Equal(UserOpState.Pending, status.State);
244+
}
245+
246+
[Fact]
247+
public async Task GetPendingUserOperations_AfterSubmissions_ReturnsAllPending()
248+
{
249+
using var bundler = _fixture.CreateNewBundlerService();
250+
251+
var pendingBefore = await bundler.GetPendingUserOperationsAsync();
252+
var initialCount = pendingBefore.Length;
253+
254+
var salt1 = (ulong)Random.Shared.NextInt64();
255+
var (account1, key1) = await _fixture.CreateFundedAccountAsync(salt1);
256+
257+
var salt2 = (ulong)Random.Shared.NextInt64();
258+
var key2 = new EthECKey(TestAccounts.Account3PrivateKey);
259+
var owner2 = key2.GetPublicAddress();
260+
await _fixture.FundAccountAsync(owner2, 0.1m);
261+
var result2 = await _fixture.AccountFactoryService.CreateAndDeployAccountAsync(
262+
owner2, owner2, _fixture.EntryPointService.ContractAddress, key2, 0.01m, salt2);
263+
264+
var execute1 = new ExecuteFunction { Target = account1, Value = 0, Data = Array.Empty<byte>() };
265+
var userOp1 = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
266+
new UserOperation { Sender = account1, CallData = execute1.GetCallData(), CallGasLimit = 100_000, VerificationGasLimit = 100_000 },
267+
key1);
268+
269+
var execute2 = new ExecuteFunction { Target = result2.AccountAddress, Value = 0, Data = Array.Empty<byte>() };
270+
var userOp2 = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
271+
new UserOperation { Sender = result2.AccountAddress, CallData = execute2.GetCallData(), CallGasLimit = 100_000, VerificationGasLimit = 100_000 },
272+
key2);
273+
274+
await bundler.SendUserOperationAsync(userOp1, _fixture.EntryPointService.ContractAddress);
275+
await bundler.SendUserOperationAsync(userOp2, _fixture.EntryPointService.ContractAddress);
276+
277+
var pending = await bundler.GetPendingUserOperationsAsync();
278+
279+
Assert.Equal(initialCount + 2, pending.Length);
280+
}
281+
282+
[Fact]
283+
public async Task DropUserOperation_WithValidHash_RemovesFromMempool()
284+
{
285+
var salt = (ulong)Random.Shared.NextInt64();
286+
var (accountAddress, accountKey) = await _fixture.CreateFundedAccountAsync(salt);
287+
288+
var executeFunction = new ExecuteFunction
289+
{
290+
Target = accountAddress,
291+
Value = 0,
292+
Data = Array.Empty<byte>()
293+
};
294+
295+
var userOp = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
296+
new UserOperation
297+
{
298+
Sender = accountAddress,
299+
CallData = executeFunction.GetCallData(),
300+
CallGasLimit = 100_000,
301+
VerificationGasLimit = 100_000
302+
},
303+
accountKey);
304+
305+
using var bundler = _fixture.CreateNewBundlerService();
306+
307+
var userOpHash = await bundler.SendUserOperationAsync(
308+
userOp,
309+
_fixture.EntryPointService.ContractAddress);
310+
311+
var dropped = await bundler.DropUserOperationAsync(userOpHash);
312+
Assert.True(dropped);
313+
314+
var status = await bundler.GetUserOperationStatusAsync(userOpHash);
315+
Assert.Equal(UserOpState.Dropped, status.State);
316+
}
317+
318+
[Fact]
319+
public async Task SupportedEntryPoints_ReturnsConfiguredEntryPoints()
320+
{
321+
var entryPoints = await _fixture.BundlerService.SupportedEntryPointsAsync();
322+
323+
Assert.NotNull(entryPoints);
324+
Assert.Single(entryPoints);
325+
Assert.Equal(
326+
_fixture.EntryPointService.ContractAddress.ToLowerInvariant(),
327+
entryPoints[0].ToLowerInvariant());
328+
}
329+
330+
[Fact]
331+
public async Task ChainId_ReturnsConfiguredChainId()
332+
{
333+
var chainId = await _fixture.BundlerService.ChainIdAsync();
334+
335+
Assert.Equal(_fixture.ChainId, chainId);
336+
}
337+
338+
[Fact]
339+
public async Task GetStats_ReturnsValidStatistics()
340+
{
341+
var stats = await _fixture.BundlerService.GetStatsAsync();
342+
343+
Assert.NotNull(stats);
344+
Assert.True(stats.StartedAt <= DateTimeOffset.UtcNow);
345+
}
346+
347+
[Fact]
348+
public async Task Flush_ExecutesPendingOperations()
349+
{
350+
var salt = (ulong)Random.Shared.NextInt64();
351+
var (accountAddress, accountKey) = await _fixture.CreateFundedAccountAsync(salt, 0.1m);
352+
353+
var executeFunction = new ExecuteFunction
354+
{
355+
Target = accountAddress,
356+
Value = 0,
357+
Data = Array.Empty<byte>()
358+
};
359+
360+
var userOp = await _fixture.EntryPointService.SignAndInitialiseUserOperationAsync(
361+
new UserOperation
362+
{
363+
Sender = accountAddress,
364+
CallData = executeFunction.GetCallData(),
365+
CallGasLimit = 200_000,
366+
VerificationGasLimit = 200_000
367+
},
368+
accountKey);
369+
370+
using var bundler = _fixture.CreateNewBundlerService();
371+
372+
var userOpHash = await bundler.SendUserOperationAsync(
373+
userOp,
374+
_fixture.EntryPointService.ContractAddress);
375+
376+
var statusBefore = await bundler.GetUserOperationStatusAsync(userOpHash);
377+
Assert.Equal(UserOpState.Pending, statusBefore.State);
378+
379+
var txHash = await bundler.FlushAsync();
380+
381+
Assert.NotNull(txHash);
382+
Assert.StartsWith("0x", txHash);
383+
384+
var statusAfter = await bundler.GetUserOperationStatusAsync(userOpHash);
385+
Assert.True(statusAfter.State == UserOpState.Included || statusAfter.State == UserOpState.Failed);
386+
}
387+
}
388+
}

0 commit comments

Comments
 (0)