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

Commit 68c0c1b

Browse files
authored
Use state override set in eth_estimateUserOperationGas (#348)
1 parent 6e16726 commit 68c0c1b

File tree

13 files changed

+174
-34
lines changed

13 files changed

+174
-34
lines changed

e2e/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
},
1414
"dependencies": {
1515
"ethers": "^5.7.2",
16-
"userop": "^0.3.3"
16+
"userop": "^0.3.7"
1717
},
1818
"devDependencies": {
1919
"@types/jest": "^29.5.2",

e2e/src/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const errorCodes = {
2+
rejectedByEpOrAccount: -32500,
23
executionReverted: -32521,
34
invalidFields: -32602,
45
};

e2e/test/verification.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { Client } from "userop";
1+
import { ethers } from "ethers";
2+
import { Client, Presets } from "userop";
3+
import { errorCodes } from "../src/errors";
24
import { TestAccount } from "../src/testAccount";
35
import config from "../config";
46

@@ -56,4 +58,50 @@ describe("During the verification phase", () => {
5658
});
5759
});
5860
});
61+
62+
describe("With state overrides", () => {
63+
test("Sender with zero funds can successfully estimate UserOperation gas", async () => {
64+
expect.assertions(4);
65+
const randAcc = await Presets.Builder.SimpleAccount.init(
66+
new ethers.Wallet(ethers.utils.randomBytes(32)),
67+
config.nodeUrl,
68+
{
69+
overrideBundlerRpc: config.bundlerUrl,
70+
}
71+
);
72+
randAcc
73+
.setPreVerificationGas(0)
74+
.setVerificationGasLimit(0)
75+
.setCallGasLimit(0);
76+
77+
try {
78+
await client.sendUserOperation(
79+
randAcc.execute(randAcc.getSender(), ethers.constants.Zero, "0x")
80+
);
81+
} catch (error: any) {
82+
expect(error?.error.code).toBe(errorCodes.rejectedByEpOrAccount);
83+
}
84+
85+
await client.sendUserOperation(
86+
randAcc.execute(randAcc.getSender(), ethers.constants.Zero, "0x"),
87+
{
88+
dryRun: true,
89+
stateOverrides: {
90+
[randAcc.getSender()]: {
91+
balance: ethers.constants.MaxUint256.toHexString(),
92+
},
93+
},
94+
onBuild(op) {
95+
expect(ethers.BigNumber.from(op.preVerificationGas).gte(0)).toBe(
96+
true
97+
);
98+
expect(ethers.BigNumber.from(op.verificationGasLimit).gte(0)).toBe(
99+
true
100+
);
101+
expect(ethers.BigNumber.from(op.callGasLimit).gte(0)).toBe(true);
102+
},
103+
}
104+
);
105+
});
106+
});
59107
});

e2e/yarn.lock

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,7 +1620,7 @@ electron-to-chromium@^1.4.431:
16201620
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz#774dc7cb5e58576d0125939ec34a4182f3ccc87d"
16211621
integrity sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ==
16221622

1623-
1623+
[email protected], elliptic@^6.5.4:
16241624
version "6.5.4"
16251625
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
16261626
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
@@ -2580,6 +2580,11 @@ jest@^29.6.1:
25802580
import-local "^3.0.2"
25812581
jest-cli "^29.6.1"
25822582

2583+
js-base64@^3.7.5:
2584+
version "3.7.5"
2585+
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
2586+
integrity sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==
2587+
25832588
25842589
version "0.8.0"
25852590
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
@@ -3264,12 +3269,14 @@ uri-js@^4.2.2:
32643269
dependencies:
32653270
punycode "^2.1.0"
32663271

3267-
userop@^0.3.3:
3268-
version "0.3.3"
3269-
resolved "https://registry.yarnpkg.com/userop/-/userop-0.3.3.tgz#e78e97fa2bcc9682d532766ee6c4dc086aa24ddf"
3270-
integrity sha512-7rPYqo5SJVVzUg9CPF3m03iXcMlXGRdkwtmhBlkHIroX8ftOSjlgwX7qDDv+uU1pumKQoqE5gNh3ydet9oFJ1Q==
3272+
userop@^0.3.7:
3273+
version "0.3.7"
3274+
resolved "https://registry.yarnpkg.com/userop/-/userop-0.3.7.tgz#cd2fe1fca2f438db6bce186a96918684cb9facb3"
3275+
integrity sha512-T4amKAySvJx5z5WLWf2pTtN6scLDqza4yODrWuNx7v9TIc/RGdHgY+5r3rvQ3W6om42heIW1Tnmw+B4LVL/Mgg==
32713276
dependencies:
3277+
elliptic "^6.5.4"
32723278
ethers "^5.7.2"
3279+
js-base64 "^3.7.5"
32733280

32743281
v8-compile-cache-lib@^3.0.1:
32753282
version "3.0.1"

pkg/client/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,14 @@ func (i *Client) EstimateUserOperationGas(
170170
hash := userOp.GetUserOpHash(epAddr, i.chainID)
171171
l = l.WithValues("userop_hash", hash)
172172

173-
_, err = state.ParseOverrideData(os)
173+
sos, err := state.ParseOverrideData(os)
174174
if err != nil {
175175
l.Error(err, "eth_estimateUserOperationGas error")
176176
return nil, err
177177
}
178178

179179
// Estimate gas limits
180-
vg, cg, err := i.getGasEstimate(epAddr, userOp)
180+
vg, cg, err := i.getGasEstimate(epAddr, userOp, state.WithZeroAddressOverride(sos))
181181
if err != nil {
182182
l.Error(err, "eth_estimateUserOperationGas error")
183183
return nil, err

pkg/client/utils.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/ethereum/go-ethereum/rpc"
1010
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/filter"
1111
"github.com/stackup-wallet/stackup-bundler/pkg/gas"
12+
"github.com/stackup-wallet/stackup-bundler/pkg/state"
1213
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1314
)
1415

@@ -33,10 +34,18 @@ func GetUserOpReceiptWithEthClient(eth *ethclient.Client) GetUserOpReceiptFunc {
3334

3435
// GetGasEstimateFunc is a general interface for fetching an estimate for verificationGasLimit and
3536
// callGasLimit given a userOp and EntryPoint address.
36-
type GetGasEstimateFunc = func(ep common.Address, op *userop.UserOperation) (verificationGas uint64, callGas uint64, err error)
37+
type GetGasEstimateFunc = func(
38+
ep common.Address,
39+
op *userop.UserOperation,
40+
sos state.OverrideSet,
41+
) (verificationGas uint64, callGas uint64, err error)
3742

3843
func getGasEstimateNoop() GetGasEstimateFunc {
39-
return func(ep common.Address, op *userop.UserOperation) (verificationGas uint64, callGas uint64, err error) {
44+
return func(
45+
ep common.Address,
46+
op *userop.UserOperation,
47+
sos state.OverrideSet,
48+
) (verificationGas uint64, callGas uint64, err error) {
4049
//lint:ignore ST1005 This needs to match the bundler test spec.
4150
return 0, 0, errors.New("Missing/invalid userOpHash")
4251
}
@@ -50,11 +59,16 @@ func GetGasEstimateWithEthClient(
5059
chain *big.Int,
5160
maxGasLimit *big.Int,
5261
) GetGasEstimateFunc {
53-
return func(ep common.Address, op *userop.UserOperation) (verificationGas uint64, callGas uint64, err error) {
62+
return func(
63+
ep common.Address,
64+
op *userop.UserOperation,
65+
sos state.OverrideSet,
66+
) (verificationGas uint64, callGas uint64, err error) {
5467
return gas.EstimateGas(&gas.EstimateInput{
5568
Rpc: rpc,
5669
EntryPoint: ep,
5770
Op: op,
71+
Sos: sos,
5872
Ov: ov,
5973
ChainID: chain,
6074
MaxGasLimit: maxGasLimit,

pkg/entrypoint/execution/simulate.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
package execution
22

33
import (
4+
"context"
45
"fmt"
6+
"math"
7+
"math/big"
58

9+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
610
"github.com/ethereum/go-ethereum/common"
711
"github.com/ethereum/go-ethereum/ethclient"
812
"github.com/ethereum/go-ethereum/rpc"
913
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint"
1014
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts"
15+
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/utils"
1116
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
17+
"github.com/stackup-wallet/stackup-bundler/pkg/state"
1218
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1319
)
1420

1521
type SimulateInput struct {
1622
Rpc *rpc.Client
1723
EntryPoint common.Address
1824
Op *userop.UserOperation
25+
Sos state.OverrideSet
26+
ChainID *big.Int
1927

2028
// Optional params for simulateHandleOps
2129
Target common.Address
@@ -27,16 +35,23 @@ func SimulateHandleOp(in *SimulateInput) (*reverts.ExecutionResultRevert, error)
2735
if err != nil {
2836
return nil, err
2937
}
38+
auth, err := bind.NewKeyedTransactorWithChainID(utils.DummyPk, in.ChainID)
39+
if err != nil {
40+
return nil, err
41+
}
42+
auth.GasLimit = math.MaxUint64
43+
auth.NoSend = true
44+
tx, err := ep.SimulateHandleOp(auth, entrypoint.UserOperation(*in.Op), in.Target, in.Data)
45+
if err != nil {
46+
return nil, err
47+
}
3048

31-
rawCaller := &entrypoint.EntrypointRaw{Contract: ep}
32-
err = rawCaller.Call(
33-
nil,
34-
nil,
35-
"simulateHandleOp",
36-
entrypoint.UserOperation(*in.Op),
37-
in.Target,
38-
in.Data,
39-
)
49+
req := utils.EthCallReq{
50+
From: common.HexToAddress("0x"),
51+
To: in.EntryPoint,
52+
Data: tx.Data(),
53+
}
54+
err = in.Rpc.CallContext(context.Background(), nil, "eth_call", &req, "latest", in.Sos)
4055

4156
sim, simErr := reverts.NewExecutionResult(err)
4257
if simErr != nil {

pkg/entrypoint/execution/trace.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts"
1717
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/utils"
1818
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
19+
"github.com/stackup-wallet/stackup-bundler/pkg/state"
1920
"github.com/stackup-wallet/stackup-bundler/pkg/tracer"
2021
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
2122
)
@@ -24,6 +25,7 @@ type TraceInput struct {
2425
Rpc *ethRpc.Client
2526
EntryPoint common.Address
2627
Op *userop.UserOperation
28+
Sos state.OverrideSet
2729
ChainID *big.Int
2830

2931
// Optional params for simulateHandleOps
@@ -98,7 +100,7 @@ func TraceSimulateHandleOp(in *TraceInput) (*TraceOutput, error) {
98100
}
99101
opts := utils.TraceCallOpts{
100102
Tracer: tracer.Loaded.BundlerExecutionTracer,
101-
StateOverrides: utils.DefaultStateOverrides,
103+
StateOverrides: in.Sos,
102104
}
103105
if err := in.Rpc.CallContext(context.Background(), &res, "debug_traceCall", &req, "latest", &opts); err != nil {
104106
return nil, err

pkg/entrypoint/simulation/tracevalidation.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint"
1818
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/methods"
1919
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/utils"
20+
"github.com/stackup-wallet/stackup-bundler/pkg/state"
2021
"github.com/stackup-wallet/stackup-bundler/pkg/tracer"
2122
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
2223
)
@@ -62,7 +63,7 @@ func TraceSimulateValidation(in *TraceInput) (*TraceOutput, error) {
6263
}
6364
opts := utils.TraceCallOpts{
6465
Tracer: tracer.Loaded.BundlerCollectorTracer,
65-
StateOverrides: utils.DefaultStateOverrides,
66+
StateOverrides: state.WithZeroAddressOverride(state.OverrideSet{}),
6667
}
6768
if err := in.Rpc.CallContext(context.Background(), &res, "debug_traceCall", &req, "latest", &opts); err != nil {
6869
return nil, err
Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package utils
22

33
import (
4-
"math/big"
5-
64
"github.com/ethereum/go-ethereum/common"
75
"github.com/ethereum/go-ethereum/common/hexutil"
86
"github.com/ethereum/go-ethereum/crypto"
7+
"github.com/stackup-wallet/stackup-bundler/pkg/state"
98
)
109

10+
type EthCallReq struct {
11+
From common.Address `json:"from"`
12+
To common.Address `json:"to"`
13+
Data hexutil.Bytes `json:"data"`
14+
}
15+
1116
type TraceCallReq struct {
1217
From common.Address `json:"from"`
1318
To common.Address `json:"to"`
@@ -20,18 +25,11 @@ type TraceStateOverrides struct {
2025
}
2126

2227
type TraceCallOpts struct {
23-
Tracer string `json:"tracer"`
24-
StateOverrides map[string]TraceStateOverrides `json:"stateOverrides"`
28+
Tracer string `json:"tracer"`
29+
StateOverrides state.OverrideSet `json:"stateOverrides"`
2530
}
2631

2732
var (
2833
// A dummy private key used to build *bind.TransactOpts for simulation.
2934
DummyPk, _ = crypto.GenerateKey()
30-
31-
maxUint96, _ = big.NewInt(0).SetString("79228162514264337593543950335", 10)
32-
33-
// A default state override to ensure the zero address always has sufficient funds.
34-
DefaultStateOverrides = map[string]TraceStateOverrides{
35-
common.HexToAddress("0x").Hex(): {Balance: hexutil.Big(*maxUint96)},
36-
}
3735
)

0 commit comments

Comments
 (0)