diff --git a/core/state/statedb.go b/core/state/statedb.go index c76358bbce..2b681c98f6 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -800,6 +800,20 @@ func (s *StateDB) GetUnexpectedBalanceDelta() *big.Int { return new(big.Int).Set(s.unexpectedBalanceDelta) } +func (s *StateDB) GetSuicides() []common.Address { + suicides := []common.Address{} + for addr := range s.journal.dirties { + obj, exist := s.stateObjects[addr] + if !exist { + continue + } + if obj.suicided { + suicides = append(suicides, addr) + } + } + return suicides +} + // Finalise finalises the state by removing the s destructed objects and clears // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. diff --git a/core/state_transition.go b/core/state_transition.go index bc52a265dc..c3a2f08bf5 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -216,6 +216,13 @@ func (st *StateTransition) buyGas() error { st.initialGas = st.msg.Gas() st.state.SubBalance(st.msg.From(), mgval) + + // Arbitrum: record fee payment + if st.evm.Config.Debug { + from := st.msg.From() + st.evm.Config.Tracer.CaptureArbitrumTransfer(st.evm, &from, nil, mgval, true, "feePayment") + } + return nil } @@ -370,8 +377,21 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } st.state.AddBalance(*tipRecipient, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) + // Arbitrum: record the tip if nonzero (this should never happen in L2) + if st.evm.Config.Debug && effectiveTip.Sign() != 0 { + st.evm.Config.Tracer.CaptureArbitrumTransfer(st.evm, nil, tipRecipient, effectiveTip, false, "tip") + } + st.evm.ProcessingHook.EndTxHook(st.gas, vmerr == nil) + // Arbitrum: record self destructs + if st.evm.Config.Debug { + for _, address := range st.evm.StateDB.GetSuicides() { + balance := st.evm.StateDB.GetBalance(address) + st.evm.Config.Tracer.CaptureArbitrumTransfer(st.evm, &address, nil, balance, false, "destroy") + } + } + return &ExecutionResult{ UsedGas: st.gasUsed(), Err: vmerr, @@ -398,6 +418,12 @@ func (st *StateTransition) refundGas(refundQuotient uint64) { remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) st.state.AddBalance(st.msg.From(), remaining) + // Arbitrum: record the gas refund + if st.evm.Config.Debug { + from := st.msg.From() + st.evm.Config.Tracer.CaptureArbitrumTransfer(st.evm, nil, &from, remaining, false, "gasRefund") + } + // Also return remaining gas to the block gas counter so it is // available for the next transaction. st.gp.AddGas(st.gas) diff --git a/core/vm/evm_arbitrum.go b/core/vm/evm_arbitrum.go index 66d025eee1..208e585179 100644 --- a/core/vm/evm_arbitrum.go +++ b/core/vm/evm_arbitrum.go @@ -31,7 +31,7 @@ func (evm *EVM) IncrementDepth() { } func (evm *EVM) DecrementDepth() { - evm.depth += 1 + evm.depth -= 1 } type TxProcessingHook interface { diff --git a/core/vm/interface.go b/core/vm/interface.go index a55c15ef09..496a1ddbca 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -50,6 +50,7 @@ type StateDB interface { Suicide(common.Address) bool HasSuicided(common.Address) bool + GetSuicides() []common.Address // Exist reports whether the given account exists in state. // Notably this should also return true for suicided accounts. diff --git a/core/vm/logger.go b/core/vm/logger.go index 434bc8143d..4711bb874f 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -37,7 +37,7 @@ type EVMLogger interface { CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) // Arbitrum: capture a transfer, mint, or burn that happens outside of EVM exectuion - CaptureArbitrumTransfer(env *EVM, from, to *common.Address, amount *big.Int, before bool) + CaptureArbitrumTransfer(env *EVM, from, to *common.Address, value *big.Int, before bool, purpose string) CaptureArbitrumStorageGet(key common.Hash, depth int, before bool) CaptureArbitrumStorageSet(key, value common.Hash, depth int, before bool) } diff --git a/eth/tracers/js/tracer.go b/eth/tracers/js/tracer.go index a7700b9947..8b7b5b1158 100644 --- a/eth/tracers/js/tracer.go +++ b/eth/tracers/js/tracer.go @@ -868,6 +868,8 @@ func (jst *jsTracer) addToObj(obj int, key string, val interface{}) { func pushValue(ctx *duktape.Context, val interface{}) { switch val := val.(type) { + case bool: + ctx.PushBoolean(val) case uint64: ctx.PushUint(uint(val)) case string: diff --git a/eth/tracers/js/tracer_arbitrum.go b/eth/tracers/js/tracer_arbitrum.go index 3149ad808c..5260dc5056 100644 --- a/eth/tracers/js/tracer_arbitrum.go +++ b/eth/tracers/js/tracer_arbitrum.go @@ -1,4 +1,4 @@ -// Copyright 2017 The go-ethereum Authors +// Copyright 2022 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -23,8 +23,42 @@ import ( "github.com/ethereum/go-ethereum/core/vm" ) -func (*jsTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, amount *big.Int, before bool) { +func (jst *jsTracer) CaptureArbitrumTransfer( + env *vm.EVM, from, to *common.Address, value *big.Int, before bool, purpose string, +) { + traceTransfers := jst.vm.GetPropString(jst.tracerObject, "captureArbitrumTransfer") + jst.vm.Pop() + if !traceTransfers { + return + } + + obj := jst.vm.PushObject() + if from != nil { + jst.addToObj(obj, "from", from.String()) + } else { + jst.addNull(obj, "from") + } + if to != nil { + jst.addToObj(obj, "to", to.String()) + } else { + jst.addNull(obj, "to") + } + + jst.addToObj(obj, "value", value) + jst.addToObj(obj, "before", before) + jst.addToObj(obj, "purpose", purpose) + jst.vm.PutPropString(jst.stateObject, "transfer") + + if _, err := jst.call(true, "captureArbitrumTransfer", "transfer"); err != nil { + jst.err = wrapError("captureArbitrumTransfer", err) + } } func (*jsTracer) CaptureArbitrumStorageGet(key common.Hash, depth int, before bool) {} func (*jsTracer) CaptureArbitrumStorageSet(key, value common.Hash, depth int, before bool) {} + +// addNull pushes a null field to a JS object. +func (jst *jsTracer) addNull(obj int, key string) { + jst.vm.PushNull() + jst.vm.PutPropString(obj, key) +} diff --git a/eth/tracers/logger/access_list_tracer_arbitrum.go b/eth/tracers/logger/access_list_tracer_arbitrum.go deleted file mode 100644 index 097fb9532c..0000000000 --- a/eth/tracers/logger/access_list_tracer_arbitrum.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package logger diff --git a/eth/tracers/logger/logger_arbitrum.go b/eth/tracers/logger/logger_arbitrum.go index 3a204e3a8a..e1c8962202 100644 --- a/eth/tracers/logger/logger_arbitrum.go +++ b/eth/tracers/logger/logger_arbitrum.go @@ -23,11 +23,11 @@ import ( "github.com/ethereum/go-ethereum/core/vm" ) -func (*AccessListTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, amount *big.Int, before bool) { +func (*AccessListTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, value *big.Int, before bool, purpose string) { } -func (*JSONLogger) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, amount *big.Int, before bool) { +func (*JSONLogger) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, value *big.Int, before bool, purpose string) { } -func (*StructLogger) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, amount *big.Int, before bool) { +func (*StructLogger) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, value *big.Int, before bool, purpose string) { } func (*AccessListTracer) CaptureArbitrumStorageGet(key common.Hash, depth int, before bool) {} diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 08dc76aa61..097d6080b2 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -35,6 +35,10 @@ func init() { } type callFrame struct { + // Arbitrum: we add these here due to the tracer returning the top frame + BeforeEVMTransfers *[]arbitrumTransfer `json:"beforeEVMTransfers,omitempty"` + AfterEVMTransfers *[]arbitrumTransfer `json:"afterEVMTransfers,omitempty"` + Type string `json:"type"` From string `json:"from"` To string `json:"to,omitempty"` @@ -48,6 +52,10 @@ type callFrame struct { } type callTracer struct { + // Arbitrum: capture transfers occuring outside of evm execution + beforeEVMTransfers []arbitrumTransfer + afterEVMTransfers []arbitrumTransfer + env *vm.EVM callstack []callFrame interrupt uint32 // Atomic flag to signal execution interruption @@ -59,7 +67,11 @@ type callTracer struct { func newCallTracer() tracers.Tracer { // First callframe contains tx context info // and is populated on start and end. - return &callTracer{callstack: make([]callFrame, 1)} + return &callTracer{ + callstack: make([]callFrame, 1), + beforeEVMTransfers: []arbitrumTransfer{}, + afterEVMTransfers: []arbitrumTransfer{}, + } } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. @@ -148,7 +160,13 @@ func (t *callTracer) GetResult() (json.RawMessage, error) { if len(t.callstack) != 1 { return nil, errors.New("incorrect number of top-level calls") } - res, err := json.Marshal(t.callstack[0]) + + // Arbitrum: populate the top-level call with additional info + call := t.callstack[0] + call.BeforeEVMTransfers = &t.beforeEVMTransfers + call.AfterEVMTransfers = &t.afterEVMTransfers + + res, err := json.Marshal(call) if err != nil { return nil, err } diff --git a/eth/tracers/native/tracer_arbitrum.go b/eth/tracers/native/tracer_arbitrum.go index 972cc3a054..f5b195c68a 100644 --- a/eth/tracers/native/tracer_arbitrum.go +++ b/eth/tracers/native/tracer_arbitrum.go @@ -23,13 +23,40 @@ import ( "github.com/ethereum/go-ethereum/core/vm" ) -func (*callTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, amount *big.Int, before bool) { +type arbitrumTransfer struct { + Purpose string `json:"purpose"` + From *string `json:"from"` + To *string `json:"to"` + Value string `json:"value"` } -func (*fourByteTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, amount *big.Int, before bool) { + +func (t *callTracer) CaptureArbitrumTransfer( + env *vm.EVM, from, to *common.Address, value *big.Int, before bool, purpose string, +) { + transfer := arbitrumTransfer{ + Purpose: purpose, + Value: bigToHex(value), + } + if from != nil { + from := from.String() + transfer.From = &from + } + if to != nil { + to := to.String() + transfer.To = &to + } + if before { + t.beforeEVMTransfers = append(t.beforeEVMTransfers, transfer) + } else { + t.afterEVMTransfers = append(t.afterEVMTransfers, transfer) + } +} + +func (*fourByteTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, value *big.Int, before bool, purpose string) { } -func (*noopTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, amount *big.Int, before bool) { +func (*noopTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, value *big.Int, before bool, purpose string) { } -func (*prestateTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, amount *big.Int, before bool) { +func (*prestateTracer) CaptureArbitrumTransfer(env *vm.EVM, from, to *common.Address, value *big.Int, before bool, purpose string) { } func (*callTracer) CaptureArbitrumStorageGet(key common.Hash, depth int, before bool) {}