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

Commit 87a6d2d

Browse files
authored
Enable optional JSON-RPC parameter for state override (#346)
1 parent 0d6192f commit 87a6d2d

File tree

10 files changed

+97
-27
lines changed

10 files changed

+97
-27
lines changed

.github/workflows/compliance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
- name: Setup Go
2626
uses: actions/setup-go@v3
2727
with:
28-
go-version: 1.19
28+
go-version: "1.20"
2929
cache: true
3030

3131
- name: Checkout bundler spec test

.github/workflows/core.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Set up Go
1818
uses: actions/setup-go@v3
1919
with:
20-
go-version: 1.19
20+
go-version: "1.20"
2121
cache: true
2222

2323
- name: Install dependencies
@@ -36,7 +36,7 @@ jobs:
3636
- name: Set up Go
3737
uses: actions/setup-go@v3
3838
with:
39-
go-version: 1.19
39+
go-version: "1.20"
4040
cache: true
4141

4242
- name: Lint

.github/workflows/e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
- name: Use Go
4545
uses: actions/setup-go@v3
4646
with:
47-
go-version: 1.19
47+
go-version: "1.20"
4848
cache: true
4949

5050
- name: Checkout account-abstraction

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.19-buster as builder
1+
FROM golang:1.20-buster as builder
22

33
# Create and change to the app directory.
44
WORKDIR /app

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ See the `Bundler` documentation at [docs.stackup.sh](https://docs.stackup.sh/doc
1717

1818
## Prerequisites
1919

20-
- Go 1.19 or later
20+
- Go 1.20 or later
2121
- Access to a node with `debug` API enabled for custom tracing.
2222

2323
## Setup

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/stackup-wallet/stackup-bundler
22

3-
go 1.19
3+
go 1.20
44

55
require (
66
github.com/deckarep/golang-set/v2 v2.3.0

pkg/client/rpc.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import (
77
"github.com/stackup-wallet/stackup-bundler/pkg/gas"
88
)
99

10+
// Named UserOperation type for jsonrpc package.
11+
type userOperation map[string]any
12+
13+
// Named StateOverride type for jsonrpc package.
14+
type optional_stateOverride map[string]any
15+
1016
// RpcAdapter is an adapter for routing JSON-RPC method calls to the correct client functions.
1117
type RpcAdapter struct {
1218
client *Client
@@ -19,12 +25,16 @@ func NewRpcAdapter(client *Client, debug *Debug) *RpcAdapter {
1925
}
2026

2127
// Eth_sendUserOperation routes method calls to *Client.SendUserOperation.
22-
func (r *RpcAdapter) Eth_sendUserOperation(op map[string]any, ep string) (string, error) {
28+
func (r *RpcAdapter) Eth_sendUserOperation(op userOperation, ep string) (string, error) {
2329
return r.client.SendUserOperation(op, ep)
2430
}
2531

2632
// Eth_estimateUserOperationGas routes method calls to *Client.EstimateUserOperationGas.
27-
func (r *RpcAdapter) Eth_estimateUserOperationGas(op map[string]any, ep string) (*gas.GasEstimates, error) {
33+
func (r *RpcAdapter) Eth_estimateUserOperationGas(
34+
op userOperation,
35+
ep string,
36+
ov optional_stateOverride,
37+
) (*gas.GasEstimates, error) {
2838
return r.client.EstimateUserOperationGas(op, ep)
2939
}
3040

pkg/jsonrpc/handler.go

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@ import (
77
"io"
88
"net/http"
99
"reflect"
10+
"strings"
1011

1112
"github.com/gin-gonic/gin"
1213
"github.com/stackup-wallet/stackup-bundler/pkg/errors"
1314
"golang.org/x/text/cases"
1415
"golang.org/x/text/language"
1516
)
1617

18+
var (
19+
optionalTypePrefix = "optional_"
20+
)
21+
22+
func formatConversionErrMsg(i int, call *reflect.Value) string {
23+
s, _ := strings.CutPrefix(call.Type().In(i).Name(), optionalTypePrefix)
24+
return fmt.Sprintf("Param [%d] can't be converted to %s", i, s)
25+
}
26+
1727
func jsonrpcError(c *gin.Context, code int, message string, data any, id any) {
1828
c.JSON(http.StatusOK, gin.H{
1929
"jsonrpc": "2.0",
@@ -39,6 +49,27 @@ func parseRequestId(data map[string]any) (any, bool) {
3949
return nil, false
4050
}
4151

52+
// hasOptionalInput checks if the API method has defined an optional final input:
53+
// 1. The input must start with the "optional_" prefix in its name.
54+
// 2. The input must be of kind Map.
55+
func hasOptionalInput(numIn int, call *reflect.Value) bool {
56+
return numIn > 0 &&
57+
strings.HasPrefix(call.Type().In(numIn-1).Name(), optionalTypePrefix) &&
58+
call.Type().In(numIn-1).Kind() == reflect.Map
59+
}
60+
61+
// hasValidParamLength checks if the number of parameters in the request is correct:
62+
// 1. Ok if the number of params equals number of method inputs.
63+
// 2. Ok if optional input is defined and number of params is one less the number of method inputs.
64+
func hasValidParamLength(numParams, numIn int, hasOptional bool) bool {
65+
return numParams == numIn || (hasOptional && numParams == numIn-1)
66+
}
67+
68+
// isOptionalParamUndefined checks if the optional input has been left unset in the request.
69+
func isOptionalParamUndefined(numParams, numIn int, hasOptional bool) bool {
70+
return hasOptional && numParams == numIn-1
71+
}
72+
4273
// Controller returns a custom Gin middleware that handles incoming JSON-RPC requests via HTTP. It maps the
4374
// RPC method name to struct methods on the given api. For example, if the RPC request has the method field
4475
// set to "namespace_methodName" then the controller will make a call to api.Namespace_methodName with the
@@ -99,12 +130,19 @@ func Controller(api interface{}) gin.HandlerFunc {
99130
return
100131
}
101132

102-
if call.Type().NumIn() != len(params) {
133+
numIn := call.Type().NumIn()
134+
numParams := len(params)
135+
hasOptional := hasOptionalInput(numIn, &call)
136+
if !hasValidParamLength(numParams, numIn, hasOptional) {
103137
jsonrpcError(c, -32602, "Invalid params", "Invalid number of params", &id)
104138
return
105139
}
140+
if isOptionalParamUndefined(numParams, numIn, hasOptional) {
141+
params = append(params, map[string]any{})
142+
numParams++
143+
}
106144

107-
args := make([]reflect.Value, len(params))
145+
args := make([]reflect.Value, numParams)
108146
for i, arg := range params {
109147
switch call.Type().In(i).Kind() {
110148
case reflect.Float32:
@@ -114,7 +152,7 @@ func Controller(api interface{}) gin.HandlerFunc {
114152
c,
115153
-32602,
116154
"Invalid params",
117-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
155+
formatConversionErrMsg(i, &call),
118156
&id,
119157
)
120158
return
@@ -128,7 +166,7 @@ func Controller(api interface{}) gin.HandlerFunc {
128166
c,
129167
-32602,
130168
"Invalid params",
131-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
169+
formatConversionErrMsg(i, &call),
132170
&id,
133171
)
134172
return
@@ -150,7 +188,7 @@ func Controller(api interface{}) gin.HandlerFunc {
150188
c,
151189
-32602,
152190
"Invalid params",
153-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
191+
formatConversionErrMsg(i, &call),
154192
&id,
155193
)
156194
return
@@ -171,7 +209,7 @@ func Controller(api interface{}) gin.HandlerFunc {
171209
c,
172210
-32602,
173211
"Invalid params",
174-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
212+
formatConversionErrMsg(i, &call),
175213
&id,
176214
)
177215
return
@@ -192,7 +230,7 @@ func Controller(api interface{}) gin.HandlerFunc {
192230
c,
193231
-32602,
194232
"Invalid params",
195-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
233+
formatConversionErrMsg(i, &call),
196234
&id,
197235
)
198236
return
@@ -213,7 +251,7 @@ func Controller(api interface{}) gin.HandlerFunc {
213251
c,
214252
-32602,
215253
"Invalid params",
216-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
254+
formatConversionErrMsg(i, &call),
217255
&id,
218256
)
219257
return
@@ -234,7 +272,7 @@ func Controller(api interface{}) gin.HandlerFunc {
234272
c,
235273
-32602,
236274
"Invalid params",
237-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
275+
formatConversionErrMsg(i, &call),
238276
&id,
239277
)
240278
return
@@ -251,7 +289,7 @@ func Controller(api interface{}) gin.HandlerFunc {
251289
c,
252290
-32602,
253291
"Invalid params",
254-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
292+
formatConversionErrMsg(i, &call),
255293
&id,
256294
)
257295
return
@@ -265,7 +303,7 @@ func Controller(api interface{}) gin.HandlerFunc {
265303
c,
266304
-32602,
267305
"Invalid params",
268-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
306+
formatConversionErrMsg(i, &call),
269307
&id,
270308
)
271309
return
@@ -279,7 +317,7 @@ func Controller(api interface{}) gin.HandlerFunc {
279317
c,
280318
-32602,
281319
"Invalid params",
282-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
320+
formatConversionErrMsg(i, &call),
283321
&id,
284322
)
285323
return
@@ -300,7 +338,7 @@ func Controller(api interface{}) gin.HandlerFunc {
300338
c,
301339
-32602,
302340
"Invalid params",
303-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
341+
formatConversionErrMsg(i, &call),
304342
&id,
305343
)
306344
return
@@ -321,7 +359,7 @@ func Controller(api interface{}) gin.HandlerFunc {
321359
c,
322360
-32602,
323361
"Invalid params",
324-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
362+
formatConversionErrMsg(i, &call),
325363
&id,
326364
)
327365
return
@@ -342,7 +380,7 @@ func Controller(api interface{}) gin.HandlerFunc {
342380
c,
343381
-32602,
344382
"Invalid params",
345-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
383+
formatConversionErrMsg(i, &call),
346384
&id,
347385
)
348386
return
@@ -363,7 +401,7 @@ func Controller(api interface{}) gin.HandlerFunc {
363401
c,
364402
-32602,
365403
"Invalid params",
366-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
404+
formatConversionErrMsg(i, &call),
367405
&id,
368406
)
369407
return
@@ -384,7 +422,7 @@ func Controller(api interface{}) gin.HandlerFunc {
384422
c,
385423
-32602,
386424
"Invalid params",
387-
fmt.Sprintf("Param [%d] can't be converted to %v", i, call.Type().In(i).String()),
425+
formatConversionErrMsg(i, &call),
388426
&id,
389427
)
390428
return

pkg/state/override.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package state
2+
3+
import (
4+
"github.com/ethereum/go-ethereum/common"
5+
"github.com/ethereum/go-ethereum/common/hexutil"
6+
)
7+
8+
type OverrideAccount struct {
9+
Nonce *hexutil.Uint64 `json:"nonce"`
10+
Code *hexutil.Bytes `json:"code"`
11+
Balance **hexutil.Big `json:"balance"`
12+
State *map[common.Hash]common.Hash `json:"state"`
13+
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
14+
}
15+
16+
// OverrideSet is a set of accounts with customized state that can be applied during eth_call or
17+
// debug_traceCall.
18+
type OverrideSet map[common.Address]OverrideAccount

pkg/userop/parse.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"math/big"
77
"reflect"
8+
"strings"
89
"sync"
910

1011
"github.com/ethereum/go-ethereum/common"
@@ -15,6 +16,9 @@ import (
1516
var (
1617
validate = validator.New()
1718
onlyOnce = sync.Once{}
19+
20+
replaceErrSubStrOld = "\n\n* ''"
21+
replaceErrSubStrNew = " UserOperation"
1822
)
1923

2024
func exactFieldMatch(mapKey, fieldName string) bool {
@@ -100,7 +104,7 @@ func New(data map[string]any) (*UserOperation, error) {
100104
return nil, err
101105
}
102106
if err := decoder.Decode(data); err != nil {
103-
return nil, err
107+
return nil, errors.New(strings.Replace(err.Error(), replaceErrSubStrOld, replaceErrSubStrNew, 1))
104108
}
105109

106110
// Validate struct

0 commit comments

Comments
 (0)