diff --git a/cmd/evm/testdata/evmrun/8.out.2.txt b/cmd/evm/testdata/evmrun/8.out.2.txt index 3e6e162e29f4..0a4d069f52a3 100644 --- a/cmd/evm/testdata/evmrun/8.out.2.txt +++ b/cmd/evm/testdata/evmrun/8.out.2.txt @@ -239,7 +239,7 @@ {"pc":2597,"op":97,"gas":"0xae7b2","gasCost":"0x3","memSize":2240,"stack":[],"depth":1,"refund":0,"opName":"PUSH2"} {"pc":2600,"op":96,"gas":"0xae7af","gasCost":"0x3","memSize":2240,"stack":["0x8a6"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":2602,"op":96,"gas":"0xae7ac","gasCost":"0x3","memSize":2240,"stack":["0x8a6","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} -{"pc":2604,"op":240,"gas":"0xae7a9","gasCost":"0x7d00","memSize":2240,"stack":["0x8a6","0x0","0x0"],"depth":1,"refund":0,"opName":"CREATE"} +{"pc":2604,"op":240,"gas":"0xae7a9","gasCost":"0x7d00","memSize":2240,"stack":["0x8a6","0x0","0x0"],"createAddr":"0x7dce2faf43218578e3fcf2ad22df9918a89e2fba","depth":1,"refund":0,"opName":"CREATE"} {"pc":0,"op":96,"gas":"0xa40ff","gasCost":"0x3","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"} {"pc":2,"op":84,"gas":"0xa40fc","gasCost":"0x834","memSize":0,"stack":["0x2"],"depth":2,"refund":0,"opName":"SLOAD"} {"pc":3,"op":80,"gas":"0xa38c8","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":2,"refund":0,"opName":"POP"} @@ -703,7 +703,7 @@ {"pc":1765,"op":97,"gas":"0x47e06","gasCost":"0x3","memSize":1472,"stack":["0x0"],"depth":2,"refund":0,"opName":"PUSH2"} {"pc":1768,"op":96,"gas":"0x47e03","gasCost":"0x3","memSize":1472,"stack":["0x0","0x5b2"],"depth":2,"refund":0,"opName":"PUSH1"} {"pc":1770,"op":96,"gas":"0x47e00","gasCost":"0x3","memSize":1472,"stack":["0x0","0x5b2","0x0"],"depth":2,"refund":0,"opName":"PUSH1"} -{"pc":1772,"op":245,"gas":"0x47dfd","gasCost":"0x7e14","memSize":1472,"stack":["0x0","0x5b2","0x0","0x0"],"depth":2,"refund":0,"opName":"CREATE2"} +{"pc":1772,"op":245,"gas":"0x47dfd","gasCost":"0x7e14","memSize":1472,"stack":["0x0","0x5b2","0x0","0x0"],"createAddr":"0x94a843a7335fc63be036fbdecc40b1365f3c5f26","depth":2,"refund":0,"opName":"CREATE2"} {"pc":0,"op":96,"gas":"0x3efea","gasCost":"0x3","memSize":0,"stack":[],"depth":3,"refund":0,"opName":"PUSH1"} {"pc":2,"op":84,"gas":"0x3efe7","gasCost":"0x834","memSize":0,"stack":["0x8"],"depth":3,"refund":0,"opName":"SLOAD"} {"pc":3,"op":80,"gas":"0x3e7b3","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":3,"refund":0,"opName":"POP"} diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go index b406cb344546..b4a7b6fb2645 100644 --- a/eth/tracers/logger/gen_structlog.go +++ b/eth/tracers/logger/gen_structlog.go @@ -26,6 +26,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { Stack []hexutil.U256 `json:"stack"` ReturnData hexutil.Bytes `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` + CreateAddr *common.Address `json:"createAddr,omitempty"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` Err error `json:"-"` @@ -47,6 +48,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { } enc.ReturnData = s.ReturnData enc.Storage = s.Storage + enc.CreateAddr = s.CreateAddr enc.Depth = s.Depth enc.RefundCounter = s.RefundCounter enc.Err = s.Err @@ -67,6 +69,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { Stack []hexutil.U256 `json:"stack"` ReturnData *hexutil.Bytes `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` + CreateAddr *common.Address `json:"createAddr,omitempty"` Depth *int `json:"depth"` RefundCounter *uint64 `json:"refund"` Err error `json:"-"` @@ -105,6 +108,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { if dec.Storage != nil { s.Storage = dec.Storage } + if dec.CreateAddr != nil { + s.CreateAddr = dec.CreateAddr + } if dec.Depth != nil { s.Depth = *dec.Depth } diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 963282ca6610..71c5ebb0ef59 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -33,6 +33,9 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/internal" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -78,6 +81,7 @@ type StructLog struct { Stack []uint256.Int `json:"stack"` ReturnData []byte `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` + CreateAddr *common.Address `json:"createAddr,omitempty"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` Err error `json:"-"` @@ -113,6 +117,9 @@ func (s *StructLog) Write(writer io.Writer) { if s.Err != nil { fmt.Fprintf(writer, " ERROR: %v", s.Err) } + if s.CreateAddr != nil { + fmt.Fprintf(writer, " createAddr=%v", s.CreateAddr) + } fmt.Fprintln(writer) if len(s.Stack) > 0 { @@ -166,6 +173,7 @@ type structLogLegacy struct { ReturnData string `json:"returnData,omitempty"` Memory *[]string `json:"memory,omitempty"` Storage *map[string]string `json:"storage,omitempty"` + CreateAddr *common.Address `json:"createAddr,omitempty"` RefundCounter uint64 `json:"refund,omitempty"` } @@ -187,6 +195,7 @@ func (s *StructLog) toLegacyJSON() json.RawMessage { GasCost: s.GasCost, Depth: s.Depth, Error: s.ErrorString(), + CreateAddr: s.CreateAddr, RefundCounter: s.RefundCounter, } if s.Stack != nil { @@ -221,6 +230,43 @@ func (s *StructLog) toLegacyJSON() json.RawMessage { return element } +func projectedCreateAddress(op vm.OpCode, scope tracing.OpContext, state tracing.StateDB) *common.Address { + stack := scope.StackData() + switch op { + case vm.CREATE: + addr := crypto.CreateAddress(scope.Address(), state.GetNonce(scope.Address())) + return &addr + case vm.CREATE2: + if len(stack) < 4 { + return nil + } + size, sizeOverflow := stack[len(stack)-3].Uint64WithOverflow() + const maxInt64 = uint64(1<<63 - 1) + if sizeOverflow || size > maxInt64 { + log.Warn("failed to copy CREATE2 input", "err", "offset or size overflow", "offset", stack[len(stack)-2], "size", stack[len(stack)-3]) + return nil + } + var input []byte + if size > 0 { + offset, overflow := stack[len(stack)-2].Uint64WithOverflow() + if overflow || offset > maxInt64-size { + log.Warn("failed to copy CREATE2 input", "err", "offset or size overflow", "offset", stack[len(stack)-2], "size", size) + return nil + } + var err error + input, err = internal.GetMemoryCopyPadded(scope.MemoryData(), int64(offset), int64(size)) + if err != nil { + log.Warn("failed to copy CREATE2 input", "err", err, "offset", offset, "size", size) + return nil + } + } + addr := crypto.CreateAddress2(scope.Address(), stack[len(stack)-4].Bytes32(), crypto.Keccak256(input)) + return &addr + default: + return nil + } +} + // StructLogger is an EVM state logger and implements EVMLogger. // // StructLogger can capture state based on the given Log configuration and also keeps @@ -300,7 +346,17 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope stack = scope.StackData() stackLen = len(stack) ) - log := StructLog{pc, op, gas, cost, nil, len(memory), nil, nil, nil, depth, l.env.StateDB.GetRefund(), err} + log := StructLog{ + Pc: pc, + Op: op, + Gas: gas, + GasCost: cost, + MemorySize: len(memory), + CreateAddr: projectedCreateAddress(op, scope, l.env.StateDB), + Depth: depth, + RefundCounter: l.env.StateDB.GetRefund(), + Err: err, + } if l.cfg.EnableMemory { log.Memory = memory } diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 530f21b534a5..9d78e7f05c5d 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -117,6 +117,7 @@ func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracin Gas: gas, GasCost: cost, MemorySize: len(memory), + CreateAddr: projectedCreateAddress(vm.OpCode(op), scope, l.env.StateDB), Depth: depth, RefundCounter: l.env.StateDB.GetRefund(), Err: err, diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index 73868d22e031..3391a8a9603a 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -17,6 +17,7 @@ package logger import ( + "bytes" "encoding/json" "errors" "math/big" @@ -24,16 +25,20 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) type dummyStatedb struct { state.StateDB + nonce uint64 } func (*dummyStatedb) GetRefund() uint64 { return 1337 } +func (db *dummyStatedb) GetNonce(common.Address) uint64 { return db.nonce } func (*dummyStatedb) GetState(_ common.Address, _ common.Hash) common.Hash { return common.Hash{} } func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) common.Hash { return common.Hash{} @@ -43,6 +48,108 @@ func (*dummyStatedb) GetStateAndCommittedState(common.Address, common.Hash) (com return common.Hash{}, common.Hash{} } +type testOpContext struct { + memory []byte + stack []uint256.Int + address common.Address +} + +func (c testOpContext) MemoryData() []byte { return c.memory } +func (c testOpContext) StackData() []uint256.Int { return c.stack } +func (c testOpContext) Caller() common.Address { return common.Address{} } +func (c testOpContext) Address() common.Address { return c.address } +func (c testOpContext) CallValue() *uint256.Int { return new(uint256.Int) } +func (c testOpContext) CallInput() []byte { return nil } +func (c testOpContext) ContractCode() []byte { return nil } + +func testStack(values ...uint64) []uint256.Int { + stack := make([]uint256.Int, len(values)) + for i, value := range values { + stack[i].SetUint64(value) + } + return stack +} + +func TestStructLoggerCapturesCreateAddress(t *testing.T) { + var ( + caller = common.HexToAddress("0x1234") + db = &dummyStatedb{nonce: 7} + logger = NewStructLogger(nil) + ) + logger.OnTxStart(&tracing.VMContext{StateDB: db}, nil, common.Address{}) + logger.OnOpcode(0, byte(vm.CREATE), 0, 0, testOpContext{ + address: caller, + stack: testStack(0, 0, 0), + }, nil, 0, nil) + + var result struct { + CreateAddr *common.Address `json:"createAddr"` + } + if err := json.Unmarshal(logger.logs[0], &result); err != nil { + t.Fatal(err) + } + want := crypto.CreateAddress(caller, db.nonce) + if result.CreateAddr == nil || *result.CreateAddr != want { + t.Fatalf("unexpected create address: have %v want %v", result.CreateAddr, want) + } +} + +func TestJSONLoggerCapturesCreate2Address(t *testing.T) { + var ( + caller = common.HexToAddress("0x1234") + memory = []byte{0xaa, 0xbb} + salt = uint64(42) + output bytes.Buffer + db = new(dummyStatedb) + hooks = NewJSONLogger(nil, &output) + ) + hooks.OnTxStart(&tracing.VMContext{StateDB: db}, nil, common.Address{}) + hooks.OnOpcode(0, byte(vm.CREATE2), 0, 0, testOpContext{ + address: caller, + memory: memory, + stack: testStack(salt, uint64(len(memory)), 0, 0), + }, nil, 0, nil) + + var result struct { + CreateAddr *common.Address `json:"createAddr"` + } + if err := json.Unmarshal(output.Bytes(), &result); err != nil { + t.Fatal(err) + } + want := crypto.CreateAddress2(caller, testStack(salt)[0].Bytes32(), crypto.Keccak256(memory)) + if result.CreateAddr == nil || *result.CreateAddr != want { + t.Fatalf("unexpected create2 address: have %v want %v", result.CreateAddr, want) + } +} + +func TestJSONLoggerCapturesCreate2AddressWithZeroSize(t *testing.T) { + var ( + caller = common.HexToAddress("0x1234") + salt = uint64(42) + output bytes.Buffer + db = new(dummyStatedb) + hooks = NewJSONLogger(nil, &output) + stack = testStack(salt, 0, 0, 0) + ) + stack[2].SetBytes(bytes.Repeat([]byte{0xff}, 32)) + hooks.OnTxStart(&tracing.VMContext{StateDB: db}, nil, common.Address{}) + hooks.OnOpcode(0, byte(vm.CREATE2), 0, 0, testOpContext{ + address: caller, + stack: stack, + }, nil, 0, nil) + + var result struct { + CreateAddr *common.Address `json:"createAddr"` + } + if err := json.Unmarshal(output.Bytes(), &result); err != nil { + t.Fatal(err) + } + want := crypto.CreateAddress2(caller, stack[0].Bytes32(), crypto.Keccak256(nil)) + if result.CreateAddr == nil || *result.CreateAddr != want { + t.Fatalf("unexpected create2 address: have %v want %v", result.CreateAddr, want) + } +} + func TestStoreCapture(t *testing.T) { var ( logger = NewStructLogger(nil)