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

Commit 05c2770

Browse files
committed
Bump Geth to v1.14.0
1 parent 691df5d commit 05c2770

File tree

1 file changed

+387
-0
lines changed

1 file changed

+387
-0
lines changed

Diff for: tracers/bundler_collector_next.go.template

+387
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
package native
2+
3+
import (
4+
"encoding/json"
5+
"math/big"
6+
"regexp"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/ethereum/go-ethereum/common"
11+
"github.com/ethereum/go-ethereum/common/hexutil"
12+
"github.com/ethereum/go-ethereum/core/tracing"
13+
"github.com/ethereum/go-ethereum/core/types"
14+
"github.com/ethereum/go-ethereum/core/vm"
15+
"github.com/ethereum/go-ethereum/eth/tracers"
16+
"github.com/holiman/uint256"
17+
)
18+
19+
func init() {
20+
tracers.DefaultDirectory.Register("bundlerCollectorTracer", newBundlerCollector, false)
21+
}
22+
23+
type partialStack = []*uint256.Int
24+
25+
type contractSizeVal struct {
26+
ContractSize int `json:"contractSize"`
27+
Opcode string `json:"opcode"`
28+
}
29+
30+
type access struct {
31+
Reads map[string]string `json:"reads"`
32+
Writes map[string]uint64 `json:"writes"`
33+
}
34+
35+
type entryPointCall struct {
36+
TopLevelMethodSig hexutil.Bytes `json:"topLevelMethodSig"`
37+
TopLevelTargetAddress common.Address `json:"topLevelTargetAddress"`
38+
Access map[common.Address]*access `json:"access"`
39+
Opcodes map[string]uint64 `json:"opcodes"`
40+
ExtCodeAccessInfo map[common.Address]string `json:"extCodeAccessInfo"`
41+
ContractSize map[common.Address]*contractSizeVal `json:"contractSize"`
42+
OOG bool `json:"oog"`
43+
}
44+
45+
type callsItem struct {
46+
// Common
47+
Type string `json:"type"`
48+
49+
// Enter info
50+
From common.Address `json:"from"`
51+
To common.Address `json:"to"`
52+
Method hexutil.Bytes `json:"method"`
53+
Value *hexutil.Big `json:"value"`
54+
Gas uint64 `json:"gas"`
55+
56+
// Exit info
57+
GasUsed uint64 `json:"gasUsed"`
58+
Data hexutil.Bytes `json:"data"`
59+
}
60+
61+
type logsItem struct {
62+
Data hexutil.Bytes `json:"data"`
63+
Topic []hexutil.Bytes `json:"topic"`
64+
}
65+
66+
type lastThreeOpCodesItem struct {
67+
Opcode string
68+
StackTop3 partialStack
69+
}
70+
71+
type bundlerCollectorResults struct {
72+
CallsFromEntryPoint []*entryPointCall `json:"callsFromEntryPoint"`
73+
Keccak []hexutil.Bytes `json:"keccak"`
74+
Logs []*logsItem `json:"logs"`
75+
Calls []*callsItem `json:"calls"`
76+
}
77+
78+
type bundlerCollector struct {
79+
vm *tracing.VMContext
80+
81+
CallsFromEntryPoint []*entryPointCall
82+
CurrentLevel *entryPointCall
83+
Keccak []hexutil.Bytes
84+
Calls []*callsItem
85+
Logs []*logsItem
86+
lastOp string
87+
lastThreeOpCodes []*lastThreeOpCodesItem
88+
allowedOpcodeRegex *regexp.Regexp
89+
stopCollectingTopic string
90+
stopCollecting bool
91+
}
92+
93+
func newBundlerCollector(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
94+
rgx, err := regexp.Compile(
95+
`^(DUP\d+|PUSH\d+|SWAP\d+|POP|ADD|SUB|MUL|DIV|EQ|LTE?|S?GTE?|SLT|SH[LR]|AND|OR|NOT|ISZERO)$`,
96+
)
97+
if err != nil {
98+
return nil, err
99+
}
100+
// event sent after all validations are done: keccak("BeforeExecution()")
101+
stopCollectingTopic := "0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972"
102+
103+
t := &bundlerCollector{
104+
CallsFromEntryPoint: []*entryPointCall{},
105+
CurrentLevel: nil,
106+
Keccak: []hexutil.Bytes{},
107+
Calls: []*callsItem{},
108+
Logs: []*logsItem{},
109+
lastOp: "",
110+
lastThreeOpCodes: []*lastThreeOpCodesItem{},
111+
allowedOpcodeRegex: rgx,
112+
stopCollectingTopic: stopCollectingTopic,
113+
stopCollecting: false,
114+
}
115+
116+
return &tracers.Tracer{
117+
Hooks: &tracing.Hooks{
118+
OnTxStart: t.OnTxStart,
119+
OnEnter: t.OnEnter,
120+
OnExit: t.OnExit,
121+
OnOpcode: t.OnOpcode,
122+
},
123+
GetResult: t.GetResult,
124+
}, nil
125+
}
126+
127+
func (b *bundlerCollector) isEXTorCALL(opcode string) bool {
128+
return strings.HasPrefix(opcode, "EXT") ||
129+
opcode == "CALL" ||
130+
opcode == "CALLCODE" ||
131+
opcode == "DELEGATECALL" ||
132+
opcode == "STATICCALL"
133+
}
134+
135+
// not using 'isPrecompiled' to only allow the ones defined by the ERC-4337 as stateless precompiles
136+
// [OP-062]
137+
func (b *bundlerCollector) isAllowedPrecompile(addr common.Address) bool {
138+
addrInt := addr.Big()
139+
return addrInt.Cmp(big.NewInt(0)) == 1 && addrInt.Cmp(big.NewInt(10)) == -1
140+
}
141+
142+
func (b *bundlerCollector) incrementCount(m map[string]uint64, k string) {
143+
if _, ok := m[k]; !ok {
144+
m[k] = 0
145+
}
146+
m[k]++
147+
}
148+
149+
func (b *bundlerCollector) OnTxStart(
150+
vm *tracing.VMContext,
151+
tx *types.Transaction,
152+
from common.Address,
153+
) {
154+
b.vm = vm
155+
}
156+
157+
func (b *bundlerCollector) GetResult() (json.RawMessage, error) {
158+
bcr := bundlerCollectorResults{
159+
CallsFromEntryPoint: b.CallsFromEntryPoint,
160+
Keccak: b.Keccak,
161+
Logs: b.Logs,
162+
Calls: b.Calls,
163+
}
164+
165+
r, err := json.Marshal(bcr)
166+
if err != nil {
167+
return nil, err
168+
}
169+
return r, nil
170+
}
171+
172+
func (b *bundlerCollector) OnEnter(
173+
depth int,
174+
typ byte,
175+
from common.Address,
176+
to common.Address,
177+
input []byte,
178+
gas uint64,
179+
value *big.Int,
180+
) {
181+
if b.stopCollecting {
182+
return
183+
}
184+
185+
op := vm.OpCode(typ)
186+
method := []byte{}
187+
if len(input) >= 4 {
188+
method = append(method, input[:4]...)
189+
}
190+
b.Calls = append(b.Calls, &callsItem{
191+
Type: op.String(),
192+
From: from,
193+
To: to,
194+
Method: method,
195+
Gas: gas,
196+
Value: (*hexutil.Big)(value),
197+
})
198+
}
199+
200+
func (b *bundlerCollector) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
201+
if b.stopCollecting {
202+
return
203+
}
204+
205+
rt := "RETURN"
206+
if err != nil {
207+
rt = "REVERT"
208+
}
209+
b.Calls = append(b.Calls, &callsItem{
210+
Type: rt,
211+
GasUsed: gasUsed,
212+
Data: output,
213+
})
214+
}
215+
216+
func (b *bundlerCollector) OnOpcode(
217+
pc uint64,
218+
op byte,
219+
gas, cost uint64,
220+
scope tracing.OpContext,
221+
rData []byte,
222+
depth int,
223+
err error,
224+
) {
225+
if b.stopCollecting {
226+
return
227+
}
228+
opcode := vm.OpCode(op).String()
229+
230+
stackSize := len(scope.StackData())
231+
stackLast := stackSize - 1
232+
stackTop3 := partialStack{}
233+
for i := 0; i < 3 && i < stackSize; i++ {
234+
stackTop3 = append(stackTop3, scope.StackData()[stackLast-i].Clone())
235+
}
236+
b.lastThreeOpCodes = append(b.lastThreeOpCodes, &lastThreeOpCodesItem{
237+
Opcode: opcode,
238+
StackTop3: stackTop3,
239+
})
240+
if len(b.lastThreeOpCodes) > 3 {
241+
b.lastThreeOpCodes = b.lastThreeOpCodes[1:]
242+
}
243+
244+
if gas < cost || (opcode == "SSTORE" && gas < 2300) {
245+
b.CurrentLevel.OOG = true
246+
}
247+
248+
if opcode == "REVERT" || opcode == "RETURN" {
249+
// exit() is not called on top-level return/revert, so we reconstruct it from opcode
250+
if depth == 1 {
251+
ofs := scope.StackData()[stackLast].ToBig().Int64()
252+
len := scope.StackData()[stackLast-1].ToBig().Int64()
253+
data := make([]byte, len)
254+
copy(data, scope.MemoryData()[ofs:ofs+len])
255+
b.Calls = append(b.Calls, &callsItem{
256+
Type: opcode,
257+
GasUsed: 0,
258+
Data: data,
259+
})
260+
}
261+
// NOTE: flushing all history after RETURN
262+
b.lastThreeOpCodes = []*lastThreeOpCodesItem{}
263+
}
264+
265+
if depth == 1 {
266+
if opcode == "CALL" || opcode == "STATICCALL" {
267+
addr := common.HexToAddress(scope.StackData()[stackLast-1].Hex())
268+
ofs := scope.StackData()[stackLast-3].ToBig().Int64()
269+
sig := make([]byte, 4)
270+
copy(sig, scope.MemoryData()[ofs:ofs+4])
271+
272+
b.CurrentLevel = &entryPointCall{
273+
TopLevelMethodSig: sig,
274+
TopLevelTargetAddress: addr,
275+
Access: map[common.Address]*access{},
276+
Opcodes: map[string]uint64{},
277+
ExtCodeAccessInfo: map[common.Address]string{},
278+
ContractSize: map[common.Address]*contractSizeVal{},
279+
OOG: false,
280+
}
281+
b.CallsFromEntryPoint = append(b.CallsFromEntryPoint, b.CurrentLevel)
282+
} else if opcode == "LOG1" && scope.StackData()[stackLast-2].Hex() == b.stopCollectingTopic {
283+
b.stopCollecting = true
284+
}
285+
b.lastOp = ""
286+
return
287+
}
288+
289+
var lastOpInfo *lastThreeOpCodesItem
290+
if len(b.lastThreeOpCodes) >= 2 {
291+
lastOpInfo = b.lastThreeOpCodes[len(b.lastThreeOpCodes)-2]
292+
}
293+
// store all addresses touched by EXTCODE* opcodes
294+
if lastOpInfo != nil && strings.HasPrefix(lastOpInfo.Opcode, "EXT") {
295+
addr := common.HexToAddress(lastOpInfo.StackTop3[0].Hex())
296+
ops := []string{}
297+
for _, item := range b.lastThreeOpCodes {
298+
ops = append(ops, item.Opcode)
299+
}
300+
last3OpcodeStr := strings.Join(ops, ",")
301+
302+
// only store the last EXTCODE* opcode per address - could even be a boolean for our current use-case
303+
// [OP-051]
304+
if !strings.Contains(last3OpcodeStr, ",EXTCODESIZE,ISZERO") {
305+
b.CurrentLevel.ExtCodeAccessInfo[addr] = opcode
306+
}
307+
}
308+
309+
// [OP-041]
310+
if b.isEXTorCALL(opcode) {
311+
n := 0
312+
if !strings.HasPrefix(opcode, "EXT") {
313+
n = 1
314+
}
315+
addr := common.BytesToAddress(scope.StackData()[stackLast-n].Bytes())
316+
317+
if _, ok := b.CurrentLevel.ContractSize[addr]; !ok && !b.isAllowedPrecompile(addr) {
318+
b.CurrentLevel.ContractSize[addr] = &contractSizeVal{
319+
ContractSize: len(b.vm.StateDB.GetCode(addr)),
320+
Opcode: opcode,
321+
}
322+
}
323+
}
324+
325+
// [OP-012]
326+
if b.lastOp == "GAS" && !strings.Contains(opcode, "CALL") {
327+
b.incrementCount(b.CurrentLevel.Opcodes, "GAS")
328+
}
329+
// ignore "unimportant" opcodes
330+
if opcode != "GAS" && !b.allowedOpcodeRegex.MatchString(opcode) {
331+
b.incrementCount(b.CurrentLevel.Opcodes, opcode)
332+
}
333+
b.lastOp = opcode
334+
335+
if opcode == "SLOAD" || opcode == "SSTORE" {
336+
slot := common.BytesToHash(scope.StackData()[stackLast].Bytes())
337+
slotHex := slot.Hex()
338+
addr := scope.Address()
339+
if _, ok := b.CurrentLevel.Access[addr]; !ok {
340+
b.CurrentLevel.Access[addr] = &access{
341+
Reads: map[string]string{},
342+
Writes: map[string]uint64{},
343+
}
344+
}
345+
access := *b.CurrentLevel.Access[addr]
346+
347+
if opcode == "SLOAD" {
348+
// read slot values before this UserOp was created
349+
// (so saving it if it was written before the first read)
350+
_, rOk := access.Reads[slotHex]
351+
_, wOk := access.Writes[slotHex]
352+
if !rOk && !wOk {
353+
access.Reads[slotHex] = string(b.vm.StateDB.GetState(addr, slot).Hex())
354+
}
355+
} else {
356+
b.incrementCount(access.Writes, slotHex)
357+
}
358+
}
359+
360+
if opcode == "KECCAK256" {
361+
// collect keccak on 64-byte blocks
362+
ofs := scope.StackData()[stackLast].ToBig().Int64()
363+
len := scope.StackData()[stackLast-1].ToBig().Int64()
364+
// currently, solidity uses only 2-word (6-byte) for a key. this might change..still, no need to
365+
// return too much
366+
if len > 20 && len < 512 {
367+
data := make([]byte, len)
368+
copy(data, scope.MemoryData()[ofs:ofs+len])
369+
b.Keccak = append(b.Keccak, data)
370+
}
371+
} else if strings.HasPrefix(opcode, "LOG") {
372+
count, _ := strconv.Atoi(opcode[3:])
373+
ofs := scope.StackData()[stackLast].ToBig().Int64()
374+
len := scope.StackData()[stackLast-1].ToBig().Int64()
375+
topics := []hexutil.Bytes{}
376+
for i := 0; i < count; i++ {
377+
topics = append(topics, scope.StackData()[stackLast-2+i].Bytes())
378+
}
379+
380+
data := make([]byte, len)
381+
copy(data, scope.MemoryData()[ofs:ofs+len])
382+
b.Logs = append(b.Logs, &logsItem{
383+
Data: data,
384+
Topic: topics,
385+
})
386+
}
387+
}

0 commit comments

Comments
 (0)