Skip to content
This repository was archived by the owner on Oct 20, 2024. It is now read-only.

Commit d3a9216

Browse files
authored
Improve results for eth_estimateUserOperationGas (#82)
1 parent ba32cd4 commit d3a9216

File tree

6 files changed

+120
-22
lines changed

6 files changed

+120
-22
lines changed

internal/start/private.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ func PrivateMode() {
8787
paymaster := paymaster.New(db)
8888

8989
// Init Client
90-
c := client.New(mem, chain, conf.SupportedEntryPoints, conf.MaxVerificationGas)
90+
c := client.New(mem, chain, conf.SupportedEntryPoints)
9191
c.SetGetUserOpReceiptFunc(client.GetUserOpReceiptWithEthClient(eth))
92+
c.SetGetSimulateValidationFunc(client.GetSimulateValidationWithRpcClient(rpc))
93+
c.SetGetCallGasEstimateFunc(client.GetCallGasEstimateWithEthClient(eth))
9294
c.UseLogger(logr)
9395
c.UseModules(
9496
check.ValidateOpValues(),

pkg/client/client.go

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ import (
2020
// Client controls the end to end process of adding incoming UserOperations to the mempool. It also
2121
// implements the required RPC methods as specified in EIP-4337.
2222
type Client struct {
23-
mempool *mempool.Mempool
24-
chainID *big.Int
25-
supportedEntryPoints []common.Address
26-
maxVerificationGas *big.Int
27-
userOpHandler modules.UserOpHandlerFunc
28-
logger logr.Logger
29-
getUserOpReceipt GetUserOpReceiptFunc
23+
mempool *mempool.Mempool
24+
chainID *big.Int
25+
supportedEntryPoints []common.Address
26+
userOpHandler modules.UserOpHandlerFunc
27+
logger logr.Logger
28+
getUserOpReceipt GetUserOpReceiptFunc
29+
getSimulateValidation GetSimulateValidationFunc
30+
getCallGasEstimate GetCallGasEstimateFunc
3031
}
3132

3233
// New initializes a new ERC-4337 client which can be extended with modules for validating UserOperations
@@ -35,16 +36,16 @@ func New(
3536
mempool *mempool.Mempool,
3637
chainID *big.Int,
3738
supportedEntryPoints []common.Address,
38-
maxVerificationGas *big.Int,
3939
) *Client {
4040
return &Client{
41-
mempool: mempool,
42-
chainID: chainID,
43-
supportedEntryPoints: supportedEntryPoints,
44-
maxVerificationGas: maxVerificationGas,
45-
userOpHandler: noop.UserOpHandler,
46-
logger: logger.NewZeroLogr().WithName("client"),
47-
getUserOpReceipt: getUserOpReceiptNoop(),
41+
mempool: mempool,
42+
chainID: chainID,
43+
supportedEntryPoints: supportedEntryPoints,
44+
userOpHandler: noop.UserOpHandler,
45+
logger: logger.NewZeroLogr().WithName("client"),
46+
getUserOpReceipt: getUserOpReceiptNoop(),
47+
getSimulateValidation: getSimulateValidationNoop(),
48+
getCallGasEstimate: getCallGasEstimateNoop(),
4849
}
4950
}
5051

@@ -74,6 +75,18 @@ func (i *Client) SetGetUserOpReceiptFunc(fn GetUserOpReceiptFunc) {
7475
i.getUserOpReceipt = fn
7576
}
7677

78+
// SetGetSimulateValidationFunc defines a general function for fetching simulateValidation results given a
79+
// userOp and EntryPoint address. This function is called in *Client.EstimateUserOperationGas.
80+
func (i *Client) SetGetSimulateValidationFunc(fn GetSimulateValidationFunc) {
81+
i.getSimulateValidation = fn
82+
}
83+
84+
// SetGetCallGasEstimateFunc defines a general function for fetching an estimate for callGasLimit given a
85+
// userOp and EntryPoint address. This function is called in *Client.EstimateUserOperationGas.
86+
func (i *Client) SetGetCallGasEstimateFunc(fn GetCallGasEstimateFunc) {
87+
i.getCallGasEstimate = fn
88+
}
89+
7790
// SendUserOperation implements the method call for eth_sendUserOperation.
7891
// It returns true if userOp was accepted otherwise returns an error.
7992
func (i *Client) SendUserOperation(op map[string]any, ep string) (string, error) {
@@ -148,12 +161,23 @@ func (i *Client) EstimateUserOperationGas(op map[string]any, ep string) (*gas.Ga
148161
hash := userOp.GetUserOpHash(epAddr, i.chainID)
149162
l = l.WithValues("userop_hash", hash)
150163

164+
sim, err := i.getSimulateValidation(epAddr, userOp)
165+
if err != nil {
166+
l.Error(err, "eth_estimateUserOperationGas error")
167+
return nil, err
168+
}
169+
170+
cg, err := i.getCallGasEstimate(epAddr, userOp)
171+
if err != nil {
172+
l.Error(err, "eth_estimateUserOperationGas error")
173+
return nil, err
174+
}
175+
151176
l.Info("eth_estimateUserOperationGas ok")
152-
// TODO: Return more reliable values
153177
return &gas.GasEstimates{
154178
PreVerificationGas: gas.NewDefaultOverhead().CalcPreVerificationGas(userOp),
155-
VerificationGas: i.maxVerificationGas,
156-
CallGasLimit: gas.NewDefaultOverhead().NonZeroValueCall(),
179+
VerificationGas: sim.ReturnInfo.PreOpGas,
180+
CallGasLimit: big.NewInt(int64(cg)),
157181
}, nil
158182
}
159183

pkg/client/utils.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import (
55

66
"github.com/ethereum/go-ethereum/common"
77
"github.com/ethereum/go-ethereum/ethclient"
8+
"github.com/ethereum/go-ethereum/rpc"
89
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint"
10+
"github.com/stackup-wallet/stackup-bundler/pkg/gas"
11+
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
912
)
1013

1114
// GetUserOpReceiptFunc is a general interface for fetching a UserOperationReceipt given a userOpHash and
@@ -26,3 +29,41 @@ func GetUserOpReceiptWithEthClient(eth *ethclient.Client) GetUserOpReceiptFunc {
2629
return entrypoint.GetUserOperationReceipt(eth, hash, ep)
2730
}
2831
}
32+
33+
// GetSimulateValidationFunc is a general interface for fetching simulateValidation results given a userOp
34+
// and EntryPoint address.
35+
type GetSimulateValidationFunc = func(ep common.Address, op *userop.UserOperation) (*entrypoint.ValidationResultRevert, error)
36+
37+
func getSimulateValidationNoop() GetSimulateValidationFunc {
38+
return func(ep common.Address, op *userop.UserOperation) (*entrypoint.ValidationResultRevert, error) {
39+
//lint:ignore ST1005 This needs to match the bundler test spec.
40+
return nil, errors.New("Missing/invalid userOpHash")
41+
}
42+
}
43+
44+
// GetSimulateValidationWithRpcClient returns an implementation of GetSimulateValidationFunc that relies on a
45+
// rpc client to fetch simulateValidation results.
46+
func GetSimulateValidationWithRpcClient(rpc *rpc.Client) GetSimulateValidationFunc {
47+
return func(ep common.Address, op *userop.UserOperation) (*entrypoint.ValidationResultRevert, error) {
48+
return entrypoint.SimulateValidation(rpc, ep, op)
49+
}
50+
}
51+
52+
// GetCallGasEstimateFunc is a general interface for fetching an estimate for callGasLimit given a userOp and
53+
// EntryPoint address.
54+
type GetCallGasEstimateFunc = func(ep common.Address, op *userop.UserOperation) (uint64, error)
55+
56+
func getCallGasEstimateNoop() GetCallGasEstimateFunc {
57+
return func(ep common.Address, op *userop.UserOperation) (uint64, error) {
58+
//lint:ignore ST1005 This needs to match the bundler test spec.
59+
return 0, errors.New("Missing/invalid userOpHash")
60+
}
61+
}
62+
63+
// GetCallGasEstimateWithEthClient returns an implementation of GetCallGasEstimateFunc that relies on an eth
64+
// client to fetch an estimate for callGasLimit.
65+
func GetCallGasEstimateWithEthClient(eth *ethclient.Client) GetCallGasEstimateFunc {
66+
return func(ep common.Address, op *userop.UserOperation) (uint64, error) {
67+
return gas.CallGasEstimate(eth, ep, op)
68+
}
69+
}

pkg/entrypoint/simulatevalidation.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package entrypoint
22

33
import (
4-
"errors"
4+
stdError "errors"
55
"fmt"
66

77
"github.com/ethereum/go-ethereum/common"
88
"github.com/ethereum/go-ethereum/ethclient"
99
"github.com/ethereum/go-ethereum/rpc"
10+
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
1011
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1112
)
1213

@@ -26,7 +27,7 @@ func SimulateValidation(
2627
rawCaller := &EntrypointRaw{Contract: ep}
2728
err = rawCaller.Call(nil, &res, "simulateValidation", UserOperation(*op))
2829
if err == nil {
29-
return nil, errors.New("unexpected result from simulateValidation")
30+
return nil, stdError.New("unexpected result from simulateValidation")
3031
}
3132

3233
sim, simErr := newValidationResultRevert(err)
@@ -35,7 +36,7 @@ func SimulateValidation(
3536
if foErr != nil {
3637
return nil, fmt.Errorf("%s, %s", simErr, foErr)
3738
}
38-
return nil, errors.New(fo.Reason)
39+
return nil, errors.NewRPCError(errors.REJECTED_BY_EP_OR_ACCOUNT, fo.Reason, fo)
3940
}
4041

4142
return sim, nil

pkg/errors/rpc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ var (
99
INVALID_PAYMASTER_STAKE = -32505
1010
INVALID_AGGREGATOR = -32506
1111
INVALID_FIELDS = -32602
12+
13+
EXECUTION_REVERTED = -32521
1214
)
1315

1416
// RPCError is a custom error that fits the JSON-RPC error spec.

pkg/gas/callgas.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package gas
2+
3+
import (
4+
"context"
5+
6+
"github.com/ethereum/go-ethereum"
7+
"github.com/ethereum/go-ethereum/common"
8+
"github.com/ethereum/go-ethereum/ethclient"
9+
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
10+
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
11+
)
12+
13+
func CallGasEstimate(
14+
eth *ethclient.Client,
15+
from common.Address,
16+
op *userop.UserOperation,
17+
) (uint64, error) {
18+
est, err := eth.EstimateGas(context.Background(), ethereum.CallMsg{
19+
From: from,
20+
To: &op.Sender,
21+
Data: op.CallData,
22+
})
23+
if err != nil {
24+
return 0, errors.NewRPCError(errors.EXECUTION_REVERTED, err.Error(), err.Error())
25+
}
26+
27+
return est, nil
28+
}

0 commit comments

Comments
 (0)