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

Commit ba32cd4

Browse files
authored
Update tracer validation to account for storage struct (#81)
1 parent 421b66a commit ba32cd4

File tree

9 files changed

+443
-375
lines changed

9 files changed

+443
-375
lines changed

internal/config/values.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ func envArrayToAddressSlice(s string) []common.Address {
3939
return slc
4040
}
4141

42-
// GetValues returns config for the bundler that has been read in from env vars.
43-
// See https://docs.stackup.sh/docs/packages/bundler/configure for details.
42+
// GetValues returns config for the bundler that has been read in from env vars. See
43+
// https://docs.stackup.sh/docs/packages/bundler/configure for details.
4444
func GetValues() *Values {
4545
// Default variables
4646
viper.SetDefault("erc4337_bundler_port", 4337)

pkg/entrypoint/callstack.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package entrypoint
2+
3+
import (
4+
"math/big"
5+
"strings"
6+
7+
"github.com/ethereum/go-ethereum/common"
8+
"github.com/stackup-wallet/stackup-bundler/internal/utils"
9+
"github.com/stackup-wallet/stackup-bundler/pkg/tracer"
10+
)
11+
12+
type callEntry struct {
13+
To common.Address
14+
Type string
15+
Method string
16+
Revert any
17+
Return any
18+
Value *big.Int
19+
}
20+
21+
func newCallStack(calls []tracer.CallInfo) []*callEntry {
22+
out := []*callEntry{}
23+
stack := utils.NewStack[tracer.CallInfo]()
24+
for _, call := range calls {
25+
if call.Type == revertOpCode || call.Type == returnOpCode {
26+
top, _ := stack.Pop()
27+
28+
if strings.Contains(top.Type, "CREATE") {
29+
// TODO: implement...
30+
} else if call.Type == revertOpCode {
31+
// TODO: implement...
32+
} else {
33+
out = append(out, &callEntry{
34+
To: top.To,
35+
Type: top.Type,
36+
Method: top.Method,
37+
Return: call.Data,
38+
})
39+
}
40+
} else {
41+
stack.Push(call)
42+
}
43+
}
44+
45+
return out
46+
}

pkg/entrypoint/knownentity.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package entrypoint
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/ethereum/go-ethereum/common"
7+
"github.com/stackup-wallet/stackup-bundler/pkg/tracer"
8+
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
9+
)
10+
11+
type knownEntity map[string]struct {
12+
Address common.Address
13+
Info tracer.NumberLevelInfo
14+
IsStaked bool
15+
}
16+
17+
func newKnownEntity(
18+
op *userop.UserOperation,
19+
res *tracer.BundlerCollectorReturn,
20+
stakes EntityStakes,
21+
) (knownEntity, error) {
22+
if len(res.NumberLevels) != 3 {
23+
return nil, fmt.Errorf("unexpected NumberLevels length in tracing result: %d", len(res.NumberLevels))
24+
}
25+
26+
return knownEntity{
27+
"factory": {
28+
Address: op.GetFactory(),
29+
Info: res.NumberLevels[factoryNumberLevel],
30+
IsStaked: stakes[op.GetFactory()] != nil && stakes[op.GetFactory()].Staked,
31+
},
32+
"account": {
33+
Address: op.Sender,
34+
Info: res.NumberLevels[accountNumberLevel],
35+
IsStaked: stakes[op.Sender] != nil && stakes[op.Sender].Staked,
36+
},
37+
"paymaster": {
38+
Address: op.GetPaymaster(),
39+
Info: res.NumberLevels[paymasterNumberLevel],
40+
IsStaked: stakes[op.GetPaymaster()] != nil && stakes[op.GetPaymaster()].Staked,
41+
},
42+
}, nil
43+
}
44+
45+
func addr2KnownEntity(op *userop.UserOperation, addr common.Address) string {
46+
if addr == op.GetFactory() {
47+
return "factory"
48+
} else if addr == op.Sender {
49+
return "account"
50+
} else if addr == op.GetPaymaster() {
51+
return "paymaster"
52+
} else {
53+
return addr.String()
54+
}
55+
}

pkg/entrypoint/simulateutils.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package entrypoint
2+
3+
import (
4+
mapset "github.com/deckarep/golang-set/v2"
5+
"github.com/ethereum/go-ethereum/common"
6+
"github.com/ethereum/go-ethereum/common/hexutil"
7+
"github.com/ethereum/go-ethereum/crypto"
8+
)
9+
10+
// EntityStakes provides a mapping for encountered entity addresses and their stake info on the EntryPoint.
11+
type EntityStakes map[common.Address]*IStakeManagerDepositInfo
12+
13+
type traceCallReq struct {
14+
From common.Address `json:"from"`
15+
To common.Address `json:"to"`
16+
Data hexutil.Bytes `json:"data"`
17+
}
18+
19+
type traceCallOpts struct {
20+
Tracer string `json:"tracer"`
21+
}
22+
23+
var (
24+
// A dummy private key used to build *bind.TransactOpts for simulation.
25+
dummyPk, _ = crypto.GenerateKey()
26+
27+
// Up to the first number marker represents factory validation.
28+
factoryNumberLevel = 0
29+
30+
// After the first number marker and before the second represents account validation.
31+
accountNumberLevel = 1
32+
33+
// After the second number marker represents paymaster validation.
34+
paymasterNumberLevel = 2
35+
36+
// Only one create2 opcode is allowed if these two conditions are met:
37+
// 1. op.initcode.length != 0
38+
// 2. During account simulation (i.e. before markerOpCode)
39+
create2OpCode = "CREATE2"
40+
41+
// List of opcodes not allowed during simulation for depth > 1 (i.e. account, paymaster, or contracts
42+
// called by them).
43+
bannedOpCodes = mapset.NewSet(
44+
"GASPRICE",
45+
"GASLIMIT",
46+
"DIFFICULTY",
47+
"TIMESTAMP",
48+
"BASEFEE",
49+
"BLOCKHASH",
50+
"NUMBER",
51+
"SELFBALANCE",
52+
"BALANCE",
53+
"ORIGIN",
54+
"GAS",
55+
"CREATE",
56+
"COINBASE",
57+
)
58+
59+
revertOpCode = "REVERT"
60+
returnOpCode = "RETURN"
61+
)

pkg/entrypoint/simulatevalidation.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package entrypoint
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/ethereum/go-ethereum/common"
8+
"github.com/ethereum/go-ethereum/ethclient"
9+
"github.com/ethereum/go-ethereum/rpc"
10+
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
11+
)
12+
13+
// SimulateValidation makes a static call to Entrypoint.simulateValidation(userop) and returns the
14+
// results without any state changes.
15+
func SimulateValidation(
16+
rpc *rpc.Client,
17+
entryPoint common.Address,
18+
op *userop.UserOperation,
19+
) (*ValidationResultRevert, error) {
20+
ep, err := NewEntrypoint(entryPoint, ethclient.NewClient(rpc))
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
var res []interface{}
26+
rawCaller := &EntrypointRaw{Contract: ep}
27+
err = rawCaller.Call(nil, &res, "simulateValidation", UserOperation(*op))
28+
if err == nil {
29+
return nil, errors.New("unexpected result from simulateValidation")
30+
}
31+
32+
sim, simErr := newValidationResultRevert(err)
33+
if simErr != nil {
34+
fo, foErr := newFailedOpRevert(err)
35+
if foErr != nil {
36+
return nil, fmt.Errorf("%s, %s", simErr, foErr)
37+
}
38+
return nil, errors.New(fo.Reason)
39+
}
40+
41+
return sim, nil
42+
}

0 commit comments

Comments
 (0)