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

Commit b3cd3a0

Browse files
authored
1 parent 19c6ad4 commit b3cd3a0

File tree

8 files changed

+468
-41
lines changed

8 files changed

+468
-41
lines changed

abi/entrypoint.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@
213213
"name": "AccountDeployed",
214214
"type": "event"
215215
},
216+
{
217+
"anonymous": false,
218+
"inputs": [],
219+
"name": "BeforeExecution",
220+
"type": "event"
221+
},
216222
{
217223
"anonymous": false,
218224
"inputs": [
@@ -503,6 +509,18 @@
503509
"stateMutability": "view",
504510
"type": "function"
505511
},
512+
{
513+
"inputs": [
514+
{ "internalType": "address", "name": "sender", "type": "address" },
515+
{ "internalType": "uint192", "name": "key", "type": "uint192" }
516+
],
517+
"name": "getNonce",
518+
"outputs": [
519+
{ "internalType": "uint256", "name": "nonce", "type": "uint256" }
520+
],
521+
"stateMutability": "view",
522+
"type": "function"
523+
},
506524
{
507525
"inputs": [
508526
{ "internalType": "bytes", "name": "initCode", "type": "bytes" }
@@ -689,6 +707,13 @@
689707
"stateMutability": "nonpayable",
690708
"type": "function"
691709
},
710+
{
711+
"inputs": [{ "internalType": "uint192", "name": "key", "type": "uint192" }],
712+
"name": "incrementNonce",
713+
"outputs": [],
714+
"stateMutability": "nonpayable",
715+
"type": "function"
716+
},
692717
{
693718
"inputs": [
694719
{ "internalType": "bytes", "name": "callData", "type": "bytes" },
@@ -763,6 +788,16 @@
763788
"stateMutability": "nonpayable",
764789
"type": "function"
765790
},
791+
{
792+
"inputs": [
793+
{ "internalType": "address", "name": "", "type": "address" },
794+
{ "internalType": "uint192", "name": "", "type": "uint192" }
795+
],
796+
"name": "nonceSequenceNumber",
797+
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
798+
"stateMutability": "view",
799+
"type": "function"
800+
},
766801
{
767802
"inputs": [
768803
{

internal/config/values.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func GetValues() *Values {
5858
// Default variables
5959
viper.SetDefault("erc4337_bundler_port", 4337)
6060
viper.SetDefault("erc4337_bundler_data_directory", "/tmp/stackup_bundler")
61-
viper.SetDefault("erc4337_bundler_supported_entry_points", "0x0576a174D229E3cFA37253523E645A78A0C91B57")
61+
viper.SetDefault("erc4337_bundler_supported_entry_points", "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")
6262
viper.SetDefault("erc4337_bundler_max_verification_gas", 1500000)
6363
viper.SetDefault("erc4337_bundler_max_ops_for_unstaked_sender", 4)
6464
viper.SetDefault("erc4337_bundler_blocks_in_the_future", 25)

pkg/entrypoint/bindings.go

Lines changed: 217 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/entrypoint/execution/simulate.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package execution
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/ethereum/go-ethereum/common"
7+
"github.com/ethereum/go-ethereum/ethclient"
8+
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint"
9+
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts"
10+
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
11+
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
12+
)
13+
14+
func SimulateHandleOp(
15+
eth *ethclient.Client,
16+
entryPoint common.Address,
17+
op *userop.UserOperation,
18+
target common.Address,
19+
data []byte,
20+
) (*reverts.ExecutionResultRevert, error) {
21+
ep, err := entrypoint.NewEntrypoint(entryPoint, eth)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
rawCaller := &entrypoint.EntrypointRaw{Contract: ep}
27+
err = rawCaller.Call(
28+
nil,
29+
nil,
30+
"simulateHandleOp",
31+
entrypoint.UserOperation(*op),
32+
target,
33+
data,
34+
)
35+
36+
sim, simErr := reverts.NewExecutionResult(err)
37+
if simErr != nil {
38+
fo, foErr := reverts.NewFailedOp(err)
39+
if foErr != nil {
40+
return nil, fmt.Errorf("%s, %s", simErr, foErr)
41+
}
42+
return nil, errors.NewRPCError(errors.REJECTED_BY_EP_OR_ACCOUNT, fo.Reason, fo)
43+
}
44+
45+
return sim, nil
46+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package reverts
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"math/big"
7+
8+
"github.com/ethereum/go-ethereum/accounts/abi"
9+
"github.com/ethereum/go-ethereum/common"
10+
"github.com/ethereum/go-ethereum/rpc"
11+
)
12+
13+
type ExecutionResultRevert struct {
14+
PreOpGas *big.Int
15+
Paid *big.Int
16+
ValidAfter *big.Int
17+
ValidUntil *big.Int
18+
TargetSuccess bool
19+
TargetResult []byte
20+
}
21+
22+
func executionResult() abi.Error {
23+
uint256, _ := abi.NewType("uint256", "", nil)
24+
uint48, _ := abi.NewType("uint48", "", nil)
25+
boolean, _ := abi.NewType("bool", "", nil)
26+
bytes, _ := abi.NewType("bytes", "", nil)
27+
return abi.NewError("ExecutionResult", abi.Arguments{
28+
{Name: "preOpGas", Type: uint256},
29+
{Name: "paid", Type: uint256},
30+
{Name: "validAfter", Type: uint48},
31+
{Name: "validUntil", Type: uint48},
32+
{Name: "targetSuccess", Type: boolean},
33+
{Name: "targetResult", Type: bytes},
34+
})
35+
}
36+
37+
func NewExecutionResult(err error) (*ExecutionResultRevert, error) {
38+
rpcErr, ok := err.(rpc.DataError)
39+
if !ok {
40+
return nil, errors.New("executionResult: cannot assert type: error is not of type rpc.DataError")
41+
}
42+
43+
data, ok := rpcErr.ErrorData().(string)
44+
if !ok {
45+
return nil, errors.New("executionResult: cannot assert type: data is not of type string")
46+
}
47+
48+
sim := executionResult()
49+
revert, err := sim.Unpack(common.Hex2Bytes(data[2:]))
50+
if err != nil {
51+
return nil, fmt.Errorf("executionResult: %s", err)
52+
}
53+
54+
args, ok := revert.([]any)
55+
if !ok {
56+
return nil, errors.New("executionResult: cannot assert type: args is not of type []any")
57+
}
58+
if len(args) != 6 {
59+
return nil, fmt.Errorf("executionResult: invalid args length: expected 6, got %d", len(args))
60+
}
61+
62+
return &ExecutionResultRevert{
63+
PreOpGas: args[0].(*big.Int),
64+
Paid: args[1].(*big.Int),
65+
ValidAfter: args[2].(*big.Int),
66+
ValidUntil: args[3].(*big.Int),
67+
TargetSuccess: args[4].(bool),
68+
TargetResult: args[5].([]byte),
69+
}, nil
70+
}

pkg/errors/revert.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package errors
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/ethereum/go-ethereum/accounts/abi"
8+
)
9+
10+
func revertError() abi.Error {
11+
reason, _ := abi.NewType("string", "string", nil)
12+
return abi.NewError("Error", abi.Arguments{
13+
{Name: "reason", Type: reason},
14+
})
15+
}
16+
17+
func DecodeRevert(data []byte) (string, error) {
18+
abi := revertError()
19+
revert, err := abi.Unpack(data)
20+
if err != nil {
21+
return "", fmt.Errorf("revert: %s", err)
22+
}
23+
24+
args, ok := revert.([]any)
25+
if !ok {
26+
return "", errors.New("revert: cannot assert type: args is not of type []any")
27+
}
28+
if len(args) != 1 {
29+
return "", fmt.Errorf("revert: invalid args length: expected 1, got %d", len(args))
30+
}
31+
32+
return args[0].(string), nil
33+
}

pkg/gas/callgas.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package gas
22

33
import (
4-
"context"
4+
"math/big"
55

6-
"github.com/ethereum/go-ethereum"
76
"github.com/ethereum/go-ethereum/common"
7+
"github.com/ethereum/go-ethereum/common/hexutil"
88
"github.com/ethereum/go-ethereum/ethclient"
9+
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/execution"
910
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
1011
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1112
)
@@ -15,14 +16,31 @@ func CallGasEstimate(
1516
from common.Address,
1617
op *userop.UserOperation,
1718
) (uint64, error) {
18-
est, err := eth.EstimateGas(context.Background(), ethereum.CallMsg{
19-
From: from,
20-
To: &op.Sender,
21-
Data: op.CallData,
22-
})
19+
data, err := op.ToMap()
2320
if err != nil {
24-
return 0, errors.NewRPCError(errors.EXECUTION_REVERTED, err.Error(), err.Error())
21+
return 0, err
2522
}
2623

27-
return est, nil
24+
// Set MaxPriorityFeePerGas = MaxFeePerGas to simplify callGasLimit calculation.
25+
data["maxPriorityFeePerGas"] = hexutil.EncodeBig(op.MaxFeePerGas)
26+
simOp, err := userop.New(data)
27+
if err != nil {
28+
return 0, err
29+
}
30+
31+
sim, err := execution.SimulateHandleOp(eth, from, simOp, op.Sender, op.CallData)
32+
if err != nil {
33+
return 0, err
34+
}
35+
if !sim.TargetSuccess {
36+
reason, err := errors.DecodeRevert(sim.TargetResult)
37+
if err != nil {
38+
return 0, err
39+
}
40+
return 0, errors.NewRPCError(errors.EXECUTION_REVERTED, reason, reason)
41+
}
42+
43+
tg := big.NewInt(0).Div(sim.Paid, op.MaxFeePerGas)
44+
cgl := big.NewInt(0).Sub(tg, sim.PreOpGas)
45+
return cgl.Uint64(), nil
2846
}

pkg/userop/object.go

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import (
1212
)
1313

1414
var (
15+
address, _ = abi.NewType("address", "", nil)
16+
uint256, _ = abi.NewType("uint256", "", nil)
17+
bytes32, _ = abi.NewType("bytes32", "", nil)
18+
1519
// UserOpPrimitives is the primitive ABI types for each UserOperation field.
1620
UserOpPrimitives = []abi.ArgumentMarshaling{
1721
{Name: "sender", InternalType: "Sender", Type: "address"},
@@ -34,12 +38,6 @@ var (
3438
UserOpArr, _ = abi.NewType("tuple[]", "ops", UserOpPrimitives)
3539
)
3640

37-
func getAbiArgs() abi.Arguments {
38-
return abi.Arguments{
39-
{Name: "UserOp", Type: UserOpType},
40-
}
41-
}
42-
4341
// UserOperation represents an EIP-4337 style transaction for a smart contract account.
4442
type UserOperation struct {
4543
Sender common.Address `json:"sender" mapstructure:"sender" validate:"required"`
@@ -92,7 +90,9 @@ func (op *UserOperation) GetMaxPrefund() *big.Int {
9290

9391
// Pack returns a standard message of the userOp. This cannot be used to generate a userOpHash.
9492
func (op *UserOperation) Pack() []byte {
95-
args := getAbiArgs()
93+
args := abi.Arguments{
94+
{Name: "UserOp", Type: UserOpType},
95+
}
9696
packed, _ := args.Pack(&struct {
9797
Sender common.Address
9898
Nonce *big.Int
@@ -126,37 +126,32 @@ func (op *UserOperation) Pack() []byte {
126126

127127
// PackForSignature returns a minimal message of the userOp. This can be used to generate a userOpHash.
128128
func (op *UserOperation) PackForSignature() []byte {
129-
args := getAbiArgs()
130-
packed, _ := args.Pack(&struct {
131-
Sender common.Address
132-
Nonce *big.Int
133-
InitCode []byte
134-
CallData []byte
135-
CallGasLimit *big.Int
136-
VerificationGasLimit *big.Int
137-
PreVerificationGas *big.Int
138-
MaxFeePerGas *big.Int
139-
MaxPriorityFeePerGas *big.Int
140-
PaymasterAndData []byte
141-
Signature []byte
142-
}{
129+
args := abi.Arguments{
130+
{Name: "sender", Type: address},
131+
{Name: "nonce", Type: uint256},
132+
{Name: "hashInitCode", Type: bytes32},
133+
{Name: "hashCallData", Type: bytes32},
134+
{Name: "callGasLimit", Type: uint256},
135+
{Name: "verificationGasLimit", Type: uint256},
136+
{Name: "preVerificationGas", Type: uint256},
137+
{Name: "maxFeePerGas", Type: uint256},
138+
{Name: "maxPriorityFeePerGas", Type: uint256},
139+
{Name: "hashPaymasterAndData", Type: bytes32},
140+
}
141+
packed, _ := args.Pack(
143142
op.Sender,
144143
op.Nonce,
145-
op.InitCode,
146-
op.CallData,
144+
crypto.Keccak256Hash(op.InitCode),
145+
crypto.Keccak256Hash(op.CallData),
147146
op.CallGasLimit,
148147
op.VerificationGasLimit,
149148
op.PreVerificationGas,
150149
op.MaxFeePerGas,
151150
op.MaxPriorityFeePerGas,
152-
op.PaymasterAndData,
153-
[]byte{},
154-
})
151+
crypto.Keccak256Hash(op.PaymasterAndData),
152+
)
155153

156-
// Return with stripped leading word (total length) and trailing word (zero-length signature).
157-
enc := hexutil.Encode(packed)
158-
enc = "0x" + enc[66:len(enc)-64]
159-
return (hexutil.MustDecode(enc))
154+
return packed
160155
}
161156

162157
// GetUserOpHash returns the hash of the userOp + entryPoint address + chainID.
@@ -196,3 +191,17 @@ func (op *UserOperation) MarshalJSON() ([]byte, error) {
196191
Signature: hexutil.Encode(op.Signature),
197192
})
198193
}
194+
195+
// ToMap returns the current UserOp struct as a map type.
196+
func (op *UserOperation) ToMap() (map[string]any, error) {
197+
data, err := op.MarshalJSON()
198+
if err != nil {
199+
return nil, err
200+
}
201+
202+
var opData map[string]any
203+
if err := json.Unmarshal(data, &opData); err != nil {
204+
return nil, err
205+
}
206+
return opData, nil
207+
}

0 commit comments

Comments
 (0)