Skip to content

Commit 1341373

Browse files
authored
feat(userop): add NewUnsignedUserOp, test (#479)
* feat(userop): add NewUnsignedUserOp, test * refactor(userop): call NewUnsignedUserOp in NewUserOp
1 parent da00650 commit 1341373

File tree

2 files changed

+115
-15
lines changed

2 files changed

+115
-15
lines changed

pkg/userop/client.go

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type Client interface {
4040
// - error - if failed to calculate it.
4141
GetAccountAddress(ctx context.Context, owner common.Address, index decimal.Decimal) (common.Address, error)
4242

43-
// NewUserOp builds a new UserOperation and fills all the fields.
43+
// NewUserOp builds and signs a new UserOperation and fills all the fields.
4444
//
4545
// NOTE: only `executeBatch` is supported for now.
4646
//
@@ -53,8 +53,9 @@ type Client interface {
5353
// - overrides - are the overrides for the middleware during the user operation creation. Can be nil.
5454
//
5555
// Returns:
56-
// - UserOperation - user operation with all fields filled in.
56+
// - UserOperation - signed user operation with all fields filled in.
5757
// - error - if failed to build the user operation.
58+
// TODO: rename to NewSignedUserOp when this pkg is extracted.
5859
NewUserOp(
5960
ctx context.Context,
6061
sender common.Address,
@@ -64,6 +65,29 @@ type Client interface {
6465
overrides *Overrides,
6566
) (UserOperation, error)
6667

68+
// NewUserOp builds a new UserOperation and fills all the fields.
69+
//
70+
// NOTE: only `executeBatch` is supported for now.
71+
//
72+
// Parameters:
73+
// - ctx - is the context of the operation.
74+
// - smartWallet - is the address of the smart wallet that will execute the user operation.
75+
// - calls - is the list of calls to be executed in the user operation. Must not be empty.
76+
// - walletDeploymentOpts - are the options for the smart wallet deployment. Can be nil if the smart wallet is already deployed.
77+
// - overrides - are the overrides for the middleware during the user operation creation. Can be nil.
78+
//
79+
// Returns:
80+
// - UserOperation - user operation with all fields filled in.
81+
// - error - if failed to build the user operation.
82+
// TODO: rename to NewUserOp when this pkg is extracted.
83+
NewUnsignedUserOp(
84+
ctx context.Context,
85+
sender common.Address,
86+
calls smart_wallet.Calls,
87+
walletDeploymentOpts *WalletDeploymentOpts,
88+
overrides *Overrides,
89+
) (UserOperation, error)
90+
6791
// SignUserOp signs the user operation with the provided signer.
6892
//
6993
// Parameters:
@@ -261,12 +285,39 @@ func (c *backend) NewUserOp(
261285
return UserOperation{}, ErrNoSigner
262286
}
263287

288+
ctx = context.WithValue(ctx, ctxKeySigner, signer)
289+
290+
op, err := c.NewUnsignedUserOp(ctx, smartWallet, calls, walletDeploymentOpts, overrides)
291+
if err != nil {
292+
return UserOperation{}, err
293+
}
294+
295+
// sign
296+
err = c.sign(ctx, &op)
297+
if err != nil {
298+
return UserOperation{}, err
299+
}
300+
301+
b, err := op.MarshalJSON()
302+
if err != nil {
303+
logger.Error("failed to marshal user operation", "error", err)
304+
} else {
305+
logger.Debug("signed successfully", "userop", string(b))
306+
}
307+
return op, nil
308+
}
309+
310+
func (c *backend) NewUnsignedUserOp(
311+
ctx context.Context,
312+
smartWallet common.Address,
313+
calls smart_wallet.Calls,
314+
walletDeploymentOpts *WalletDeploymentOpts,
315+
overrides *Overrides,
316+
) (UserOperation, error) {
264317
if len(calls) == 0 {
265318
return UserOperation{}, ErrNoCalls
266319
}
267320

268-
ctx = context.WithValue(ctx, ctxKeySigner, signer)
269-
270321
callData, err := smart_wallet.BuildCallData(*c.smartWallet.Type, calls)
271322
if err != nil {
272323
return UserOperation{}, fmt.Errorf("failed to build call data: %w", err)
@@ -328,13 +379,13 @@ func (c *backend) NewUserOp(
328379
return UserOperation{}, err
329380
}
330381

331-
// sign before estimating gas limits, so that signature is well-formed.
332-
// If signature is corrupted, this can cause SmartWallet estimation to fail,
333-
// and the bundler will return an error.
334-
err = c.sign(ctx, &op)
382+
// Use stub signature before estimating gas limits. If signature is corrupted,
383+
// this can cause SmartWallet estimation to fail, and the bundler will return an error.
384+
stubSig, err := smart_wallet.GetStubSignature(*c.smartWallet.Type)
335385
if err != nil {
336386
return UserOperation{}, err
337387
}
388+
op.Signature = stubSig
338389

339390
// getGasLimits
340391
if overridesPresent && overrides.GasLimits != nil {
@@ -353,12 +404,6 @@ func (c *backend) NewUserOp(
353404
return UserOperation{}, err
354405
}
355406

356-
// sign
357-
err = c.sign(ctx, &op)
358-
if err != nil {
359-
return UserOperation{}, err
360-
}
361-
362407
b, err := op.MarshalJSON()
363408
if err != nil {
364409
logger.Error("failed to marshal user operation", "error", err)

pkg/userop/testing/local_blockchain/main_test.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func setLogLevel(level slog.Level) {
2525
slog.SetDefault(logger)
2626
}
2727

28-
func TestSimulatedRPC(t *testing.T) {
28+
func TestNewSend(t *testing.T) {
2929
setLogLevel(slog.LevelDebug)
3030
ctx := context.Background()
3131

@@ -76,6 +76,61 @@ func TestSimulatedRPC(t *testing.T) {
7676
require.Equal(t, transferAmount, receiverBalance, "new balance should equal the transfer amount")
7777
}
7878

79+
func TestNewUnsignedSignSend(t *testing.T) {
80+
setLogLevel(slog.LevelDebug)
81+
ctx := context.Background()
82+
83+
// 1. Start a local Ethereum node
84+
for i := 0; i < 3; i++ { // starting multiple nodes to test reusing existing nodes
85+
ethNode := NewEthNode(ctx, t)
86+
slog.Info("connecting to Ethereum node", "rpcURL", ethNode.LocalURL.String())
87+
}
88+
node := NewEthNode(ctx, t)
89+
slog.Info("connecting to Ethereum node", "rpcURL", node.LocalURL.String())
90+
91+
// 2. Deploy the required contracts
92+
addresses := SetupContracts(ctx, t, node)
93+
94+
// 3. Start the bundler
95+
for i := 0; i < 3; i++ { // starting multiple bundlers to test reusing existing bundlers
96+
bundler := NewBundler(ctx, t, node, addresses.EntryPoint)
97+
slog.Info("connecting to bundler", "bundlerURL", bundler.LocalURL.String())
98+
}
99+
bundler := NewBundler(ctx, t, node, addresses.EntryPoint)
100+
101+
// 4. Build client
102+
client := BuildClient(t, node.LocalURL, bundler.LocalURL, addresses, userop.PaymasterConfig{
103+
Type: &userop.PaymasterDisabled,
104+
})
105+
106+
// 5. Create and fund smart account
107+
eoa, receiver, swAddress := setupAccounts(ctx, t, client, node)
108+
109+
// 6. Submit user operation
110+
signer := userop.SignerForKernel(signer.NewLocalSigner(eoa.PrivateKey))
111+
transferAmount := decimal.NewFromInt(1 /* 1 wei */).BigInt()
112+
calls := smart_wallet.Calls{{To: receiver.Address, Value: transferAmount}}
113+
params := &userop.WalletDeploymentOpts{Index: decimal.Zero, Owner: eoa.Address}
114+
op, err := client.NewUnsignedUserOp(ctx, swAddress, calls, params, nil)
115+
require.NoError(t, err, "failed to create new user operation")
116+
117+
op, err = client.SignUserOp(ctx, op, signer)
118+
require.NoError(t, err, "failed to sign user operation")
119+
120+
slog.Info("ready to send", "userop", op)
121+
122+
done, err := client.SendUserOp(ctx, op)
123+
require.NoError(t, err, "failed to send user operation")
124+
125+
receipt := <-done
126+
slog.Info("transaction mined", "receipt", receipt)
127+
require.True(t, receipt.Success)
128+
129+
receiverBalance, err := node.Client.BalanceAt(ctx, receiver.Address, nil)
130+
require.NoError(t, err, "failed to fetch receiver new balance")
131+
require.Equal(t, transferAmount, receiverBalance, "new balance should equal the transfer amount")
132+
}
133+
79134
func TestSimulatedPaymaster(t *testing.T) {
80135
setLogLevel(slog.LevelDebug)
81136
ctx := context.Background()

0 commit comments

Comments
 (0)