diff --git a/core/state_transition.go b/core/state_transition.go
index df8c679a7a..2ddd735005 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -457,14 +457,16 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
)
// Check clauses 4-5, subtract intrinsic gas if everything is correct
- gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules)
- if err != nil {
- return nil, err
- }
- if st.gasRemaining < gas {
- return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
+ if !st.evm.Config.IgnoreGas {
+ gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules)
+ if err != nil {
+ return nil, err
+ }
+ if st.gasRemaining < gas {
+ return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
+ }
+ st.gasRemaining -= gas
}
- st.gasRemaining -= gas
// Check clause 6
value, overflow := uint256.FromBig(msg.Value)
@@ -476,7 +478,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
// Check whether the init code size has been exceeded.
- if rules.IsDurango && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
+ if !st.evm.Config.IgnoreCodeSizeLimit && rules.IsDurango && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
return nil, fmt.Errorf("%w: code size %v limit %v", vmerrs.ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize)
}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 0eb7ec12df..cf0742f56b 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -514,6 +514,16 @@ func (c *codeAndHash) Hash() common.Hash {
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
// Depth check execution. Fail if we're trying to execute above the
// limit.
+ if evm.Config.CreateAddressOverride != nil {
+ address = *evm.Config.CreateAddressOverride
+ }
+ if evm.Config.CreationCodeOverrides != nil {
+ if code, ok := evm.Config.CreationCodeOverrides[address]; ok {
+ codeAndHash.code = code
+ codeAndHash.hash = common.Hash{}
+ _ = codeAndHash.Hash()
+ }
+ }
if evm.depth > int(params.CallCreateDepth) {
return nil, common.Address{}, gas, vmerrs.ErrDepth
}
@@ -538,10 +548,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.chainRules.IsApricotPhase2 {
evm.StateDB.AddAddressToAccessList(address)
}
- // Ensure there's no existing contract already at the designated address
- contractHash := evm.StateDB.GetCodeHash(address)
- if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
- return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision
+ if evm.Config.CreateAddressOverride == nil {
+ // Ensure there's no existing contract already at the designated address
+ contractHash := evm.StateDB.GetCodeHash(address)
+ if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
+ return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision
+ }
}
// Create a new account on the state
snapshot := evm.StateDB.Snapshot()
@@ -567,7 +579,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
ret, err := evm.interpreter.Run(contract, nil, false)
// Check whether the max code size has been exceeded, assign err if the case.
- if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
+ if err == nil && !evm.Config.IgnoreCodeSizeLimit && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
err = vmerrs.ErrMaxCodeSizeExceeded
}
@@ -582,6 +594,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// by the error checking condition below.
if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas
+ if evm.Config.IgnoreGas {
+ createDataGas = 0
+ }
if contract.UseGas(createDataGas) {
evm.StateDB.SetCode(address, ret)
} else {
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 72047c260d..8904e7d93e 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -588,7 +588,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas
)
- if interpreter.evm.chainRules.IsEIP150 {
+ if !interpreter.evm.Config.IgnoreGas && interpreter.evm.chainRules.IsEIP150 {
gas -= gas / 64
}
// reuse size int for stackvalue
@@ -631,7 +631,9 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
gas = scope.Contract.Gas
)
// Apply EIP150
- gas -= gas / 64
+ if !interpreter.evm.Config.IgnoreGas {
+ gas -= gas / 64
+ }
scope.Contract.UseGas(gas)
// reuse size int for stackvalue
stackvalue := size
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 5d495f0af6..f64a6e13e0 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -29,6 +29,7 @@ package vm
import (
"github.com/ava-labs/coreth/vmerrs"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
@@ -40,6 +41,11 @@ type Config struct {
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
ExtraEips []int // Additional EIPS that are to be enabled
+
+ CreationCodeOverrides map[common.Address]hexutil.Bytes
+ CreateAddressOverride *common.Address
+ IgnoreGas bool
+ IgnoreCodeSizeLimit bool
}
// ScopeContext contains the things that are per-call, such as stack and memory,
@@ -106,6 +112,22 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
}
}
evm.Config.ExtraEips = extraEips
+ if evm.Config.IgnoreGas {
+ table = copyJumpTable(table)
+ for i, op := range table {
+ opCode := OpCode(i)
+ // retain call costs to prevent call stack from going too deep
+ // some contracts use a loop to burn gas
+ // if all codes in the loop have zero cost, it will run forever
+ if opCode == CALL || opCode == STATICCALL || opCode == CALLCODE || opCode == DELEGATECALL || opCode == GAS {
+ continue
+ }
+ op.constantGas = 0
+ op.dynamicGas = func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) {
+ return 0, nil
+ }
+ }
+ }
return &EVMInterpreter{evm: evm, table: table}
}
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index 605a2495fe..9059e483f9 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -184,13 +184,18 @@ type TraceConfig struct {
// Config specific to given tracer. Note struct logger
// config are historically embedded in main object.
TracerConfig json.RawMessage
+
+ StateOverrides *ethapi.StateOverride
+ IgnoreGas *bool
+ IgnoreCodeSizeLimit *bool
+ CreationCodeOverrides map[common.Address]hexutil.Bytes
+ CreateAddressOverride *common.Address
}
// TraceCallConfig is the config for traceCall API. It holds one more
// field to override the state for tracing.
type TraceCallConfig struct {
TraceConfig
- StateOverrides *ethapi.StateOverride
BlockOverrides *ethapi.BlockOverrides
TxIndex *hexutil.Uint
}
@@ -906,6 +911,12 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
TxIndex: int(index),
TxHash: hash,
}
+
+ if config != nil {
+ if err := config.StateOverrides.Apply(statedb); err != nil {
+ return nil, err
+ }
+ }
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config)
}
@@ -1008,7 +1019,21 @@ func (api *baseAPI) traceTx(ctx context.Context, message *core.Message, txctx *C
return nil, err
}
}
- vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true})
+ vmConfig := vm.Config{
+ Tracer: tracer,
+ NoBaseFee: true,
+ }
+ if config != nil {
+ vmConfig.CreateAddressOverride = config.CreateAddressOverride
+ vmConfig.CreationCodeOverrides = config.CreationCodeOverrides
+ if config.IgnoreCodeSizeLimit != nil {
+ vmConfig.IgnoreCodeSizeLimit = *config.IgnoreCodeSizeLimit
+ }
+ if config.IgnoreGas != nil {
+ vmConfig.IgnoreGas = *config.IgnoreGas
+ }
+ }
+ vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vmConfig)
// Define a meaningful timeout of a single transaction trace
if config.Timeout != nil {
@@ -1116,3 +1141,116 @@ func overrideConfig(original *params.ChainConfig, override *params.ChainConfig)
return copy, canon
}
+
+type Bundle struct {
+ Transactions []*ethapi.TransactionArgs `json:"transactions"`
+ BlockOverride ethapi.BlockOverrides `json:"blockOverride"`
+}
+
+type StateContext struct {
+ BlockNumber *rpc.BlockNumberOrHash `json:"blockNumber"`
+ TransactionIndex int `json:"transactionIndex"`
+}
+
+type FailedTrace struct {
+ Failed string `json:"failed,omitempty"`
+}
+
+func (api *API) TraceCallMany(ctx context.Context, bundles []*Bundle, simulateContext *StateContext, config *TraceCallConfig) (interface{}, error) {
+ if len(bundles) == 0 {
+ return nil, errors.New("empty bundles")
+ }
+ var result []interface{}
+ for _, bundle := range bundles {
+ r, err := api.traceBundle(ctx, bundle, simulateContext, config)
+ if err != nil {
+ if r != nil {
+ // return partial results
+ r = append(r, &FailedTrace{Failed: err.Error()})
+ result = append(result, r)
+ return result, nil
+ }
+ return nil, err
+ }
+ result = append(result, r)
+ }
+ return result, nil
+}
+
+func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext *StateContext, config *TraceCallConfig) ([]interface{}, error) {
+ var result []interface{}
+ // Try to retrieve the specified block
+ var (
+ err error
+ block *types.Block
+ )
+ if hash, ok := simulateContext.BlockNumber.Hash(); ok {
+ block, err = api.blockByHash(ctx, hash)
+ } else if number, ok := simulateContext.BlockNumber.Number(); ok {
+ if number == rpc.PendingBlockNumber {
+ // We don't have access to the miner here. For tracing 'future' transactions,
+ // it can be done with block- and state-overrides instead, which offers
+ // more flexibility and stability than trying to trace on 'pending', since
+ // the contents of 'pending' is unstable and probably not a true representation
+ // of what the next actual block is likely to contain.
+ return nil, errors.New("tracing on top of pending is not supported")
+ }
+ block, err = api.blockByNumber(ctx, number)
+ } else {
+ return nil, errors.New("invalid arguments; neither block nor hash specified")
+ }
+ if err != nil {
+ return nil, err
+ }
+ // try to recompute the state
+ reexec := defaultTraceReexec
+ if config != nil && config.Reexec != nil {
+ reexec = *config.Reexec
+ }
+ is158 := api.backend.ChainConfig().IsEIP158(block.Number())
+
+ if err != nil {
+ return nil, err
+ }
+ _, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, simulateContext.TransactionIndex, reexec)
+ if err != nil {
+ return nil, err
+ }
+ defer release()
+
+ // Apply the customization rules if required.
+ if config != nil {
+ if err := config.StateOverrides.Apply(statedb); err != nil {
+ return nil, err
+ }
+ config.BlockOverrides.Apply(&vmctx)
+ }
+ // Execute the trace
+ for idx, args := range bundle.Transactions {
+ if args.Gas == nil {
+ gasCap := api.backend.RPCGasCap()
+ args.Gas = (*hexutil.Uint64)(&gasCap)
+ }
+ msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee())
+ if err != nil {
+ return result, err
+ }
+
+ var traceConfig *TraceConfig
+ if config != nil {
+ traceConfig = &config.TraceConfig
+ }
+ txctx := &Context{
+ BlockHash: block.Hash(),
+ BlockNumber: block.Number(),
+ TxIndex: simulateContext.TransactionIndex + idx,
+ }
+ r, err := api.traceTx(ctx, msg, txctx, vmctx, statedb, traceConfig)
+ if err != nil {
+ return result, err
+ }
+ result = append(result, r)
+ statedb.Finalise(is158)
+ }
+ return result, nil
+}
diff --git a/eth/tracers/internal/tracetest/util.go b/eth/tracers/internal/tracetest/util.go
index 3cecbf84ab..d36a40cae9 100644
--- a/eth/tracers/internal/tracetest/util.go
+++ b/eth/tracers/internal/tracetest/util.go
@@ -16,6 +16,7 @@ import (
// Force-load native and js packages, to trigger registration
_ "github.com/ava-labs/coreth/eth/tracers/js"
_ "github.com/ava-labs/coreth/eth/tracers/native"
+ _ "github.com/ava-labs/coreth/eth/tracers/sentio"
)
// To generate a new callTracer test, copy paste the makeTest method below into
diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go
index c41d569a96..d753824435 100644
--- a/eth/tracers/logger/gen_structlog.go
+++ b/eth/tracers/logger/gen_structlog.go
@@ -22,6 +22,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
Gas math.HexOrDecimal64 `json:"gas"`
GasCost math.HexOrDecimal64 `json:"gasCost"`
Memory hexutil.Bytes `json:"memory,omitempty"`
+ Meq *int `json:"meq,omitempty"`
MemorySize int `json:"memSize"`
Stack []hexutil.U256 `json:"stack"`
ReturnData hexutil.Bytes `json:"returnData,omitempty"`
@@ -38,6 +39,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
enc.Gas = math.HexOrDecimal64(s.Gas)
enc.GasCost = math.HexOrDecimal64(s.GasCost)
enc.Memory = s.Memory
+ enc.Meq = s.Meq
enc.MemorySize = s.MemorySize
if s.Stack != nil {
enc.Stack = make([]hexutil.U256, len(s.Stack))
@@ -63,6 +65,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
Gas *math.HexOrDecimal64 `json:"gas"`
GasCost *math.HexOrDecimal64 `json:"gasCost"`
Memory *hexutil.Bytes `json:"memory,omitempty"`
+ Meq *int `json:"meq,omitempty"`
MemorySize *int `json:"memSize"`
Stack []hexutil.U256 `json:"stack"`
ReturnData *hexutil.Bytes `json:"returnData,omitempty"`
@@ -90,6 +93,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
if dec.Memory != nil {
s.Memory = *dec.Memory
}
+ if dec.Meq != nil {
+ s.Meq = dec.Meq
+ }
if dec.MemorySize != nil {
s.MemorySize = *dec.MemorySize
}
diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go
index d94ffd3cf7..fd8ac4f973 100644
--- a/eth/tracers/logger/logger.go
+++ b/eth/tracers/logger/logger.go
@@ -17,6 +17,7 @@
package logger
import (
+ "bytes"
"encoding/hex"
"encoding/json"
"fmt"
@@ -49,12 +50,13 @@ func (s Storage) Copy() Storage {
// Config are the configuration options for structured logger the EVM
type Config struct {
- EnableMemory bool // enable memory capture
- DisableStack bool // disable stack capture
- DisableStorage bool // disable storage capture
- EnableReturnData bool // enable return data capture
- Debug bool // print output during capture end
- Limit int // maximum length of output, but zero means unlimited
+ EnableMemory bool // enable memory capture
+ DisableStack bool // disable stack capture
+ DisableStorage bool // disable storage capture
+ EnableReturnData bool // enable return data capture
+ Debug bool // print output during capture end
+ Limit int // maximum length of output, but zero means unlimited
+ MemoryCompressionWindow int
// Chain overrides, can be used to execute a trace using future fork rules
Overrides *params.ChainConfig `json:"overrides,omitempty"`
}
@@ -69,6 +71,7 @@ type StructLog struct {
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Memory []byte `json:"memory,omitempty"`
+ Meq *int `json:"meq,omitempty"`
MemorySize int `json:"memSize"`
Stack []uint256.Int `json:"stack"`
ReturnData []byte `json:"returnData,omitempty"`
@@ -83,6 +86,7 @@ type structLogMarshaling struct {
Gas math.HexOrDecimal64
GasCost math.HexOrDecimal64
Memory hexutil.Bytes
+ Meq *int `json:"meq,omitempty"`
ReturnData hexutil.Bytes
Stack []hexutil.U256
OpName string `json:"opName"` // adds call to OpName() in MarshalJSON
@@ -118,6 +122,10 @@ type StructLogger struct {
gasLimit uint64
usedGas uint64
+ prevMem [][]byte
+ prevMemWindow int
+ prevMemIdx int
+
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
}
@@ -129,6 +137,9 @@ func NewStructLogger(cfg *Config) *StructLogger {
}
if cfg != nil {
logger.cfg = *cfg
+ logger.prevMemWindow = cfg.MemoryCompressionWindow
+ logger.prevMemIdx = 0
+ logger.prevMem = make([][]byte, cfg.MemoryCompressionWindow)
}
return logger
}
@@ -164,9 +175,36 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
contract := scope.Contract
// Copy a snapshot of the current memory state to a new buffer
var mem []byte
+ var meq *int
if l.cfg.EnableMemory {
mem = make([]byte, len(memory.Data()))
copy(mem, memory.Data())
+
+ foundEq := false
+ if l.prevMemWindow > 0 {
+ i := l.prevMemIdx
+ for dist := 1; dist <= l.prevMemWindow; dist++ {
+ if i--; i < 0 {
+ i = l.prevMemWindow - 1
+ }
+ if len(l.prevMem[i]) == len(mem) && bytes.Equal(l.prevMem[i], mem) {
+ foundEq = true
+ meq = new(int)
+ *meq = dist
+ mem = nil
+ break
+ }
+ }
+ if l.prevMemIdx++; l.prevMemIdx == l.prevMemWindow {
+ l.prevMemIdx = 0
+ }
+ if foundEq {
+ l.prevMem[l.prevMemIdx] = l.prevMem[i]
+ } else {
+ l.prevMem[l.prevMemIdx] = make([]byte, len(mem))
+ copy(l.prevMem[l.prevMemIdx], mem)
+ }
+ }
}
// Copy a snapshot of the current stack state to a new buffer
var stck []uint256.Int
@@ -210,7 +248,7 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
copy(rdata, rData)
}
// create a new snapshot of the EVM.
- log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
+ log := StructLog{pc, op, gas, cost, mem, meq, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log)
}
@@ -422,6 +460,7 @@ type StructLogRes struct {
Stack *[]string `json:"stack,omitempty"`
ReturnData string `json:"returnData,omitempty"`
Memory *[]string `json:"memory,omitempty"`
+ Meq *int `json:"meq,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
RefundCounter uint64 `json:"refund,omitempty"`
}
@@ -438,6 +477,7 @@ func formatLogs(logs []StructLog) []StructLogRes {
Depth: trace.Depth,
Error: trace.ErrorString(),
RefundCounter: trace.RefundCounter,
+ Meq: trace.Meq,
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
diff --git a/eth/tracers/sentio/gen_account_json.go b/eth/tracers/sentio/gen_account_json.go
new file mode 100644
index 0000000000..e5498234a6
--- /dev/null
+++ b/eth/tracers/sentio/gen_account_json.go
@@ -0,0 +1,74 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package sentio
+
+import (
+ "encoding/json"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*accountMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (a account) MarshalJSON() ([]byte, error) {
+ type account struct {
+ Balance *hexutil.Big `json:"balance,omitempty"`
+ Code hexutil.Bytes `json:"code,omitempty"`
+ Nonce uint64 `json:"nonce,omitempty"`
+ Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
+ CodeAddress *common.Address `json:"codeAddress,omitempty"`
+ CodeAddressBySlot map[common.Hash]*common.Address `json:"codeAddressBySlot,omitempty"`
+ MappingKeys map[string]string `json:"mappingKeys,omitempty"`
+ }
+ var enc account
+ enc.Balance = (*hexutil.Big)(a.Balance)
+ enc.Code = a.Code
+ enc.Nonce = a.Nonce
+ enc.Storage = a.Storage
+ enc.CodeAddress = a.CodeAddress
+ enc.CodeAddressBySlot = a.CodeAddressBySlot
+ enc.MappingKeys = a.MappingKeys
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (a *account) UnmarshalJSON(input []byte) error {
+ type account struct {
+ Balance *hexutil.Big `json:"balance,omitempty"`
+ Code *hexutil.Bytes `json:"code,omitempty"`
+ Nonce *uint64 `json:"nonce,omitempty"`
+ Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
+ CodeAddress *common.Address `json:"codeAddress,omitempty"`
+ CodeAddressBySlot map[common.Hash]*common.Address `json:"codeAddressBySlot,omitempty"`
+ MappingKeys map[string]string `json:"mappingKeys,omitempty"`
+ }
+ var dec account
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.Balance != nil {
+ a.Balance = (*big.Int)(dec.Balance)
+ }
+ if dec.Code != nil {
+ a.Code = *dec.Code
+ }
+ if dec.Nonce != nil {
+ a.Nonce = *dec.Nonce
+ }
+ if dec.Storage != nil {
+ a.Storage = dec.Storage
+ }
+ if dec.CodeAddress != nil {
+ a.CodeAddress = dec.CodeAddress
+ }
+ if dec.CodeAddressBySlot != nil {
+ a.CodeAddressBySlot = dec.CodeAddressBySlot
+ }
+ if dec.MappingKeys != nil {
+ a.MappingKeys = dec.MappingKeys
+ }
+ return nil
+}
diff --git a/eth/tracers/sentio/prestate.go b/eth/tracers/sentio/prestate.go
new file mode 100644
index 0000000000..9d159e0fd8
--- /dev/null
+++ b/eth/tracers/sentio/prestate.go
@@ -0,0 +1,323 @@
+// 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
+// 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 sentio
+
+import (
+ "bytes"
+ "encoding/json"
+ "math/big"
+ "sync/atomic"
+
+ "github.com/ava-labs/coreth/core/vm"
+ "github.com/ava-labs/coreth/eth/tracers"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go
+
+func init() {
+ tracers.DefaultDirectory.Register("sentioPrestateTracer", newSentioPrestateTracer, false)
+}
+
+type state = map[common.Address]*account
+
+type account struct {
+ Balance *big.Int `json:"balance,omitempty"`
+ Code []byte `json:"code,omitempty"`
+ Nonce uint64 `json:"nonce,omitempty"`
+ Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
+ CodeAddress *common.Address `json:"codeAddress,omitempty"`
+ CodeAddressBySlot map[common.Hash]*common.Address `json:"codeAddressBySlot,omitempty"`
+ MappingKeys map[string]string `json:"mappingKeys,omitempty"`
+}
+
+func (a *account) exists() bool {
+ return a.Nonce > 0 || len(a.Code) > 0 || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0)
+}
+
+type accountMarshaling struct {
+ Balance *hexutil.Big
+ Code hexutil.Bytes
+}
+
+type sentioPrestateTracer struct {
+ env *vm.EVM
+ pre state
+ post state
+ create bool
+ to common.Address
+ gasLimit uint64 // Amount of gas bought for the whole tx
+ config prestateTracerConfig
+ interrupt uint32 // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+ created map[common.Address]bool
+ deleted map[common.Address]bool
+}
+
+func (t *sentioPrestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+ //TODO implement me
+}
+
+func (t *sentioPrestateTracer) CaptureExit(output []byte, usedGas uint64, err error) {
+ //TODO implement me
+}
+
+func (t *sentioPrestateTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+ //TODO implement me
+}
+
+type prestateTracerConfig struct {
+ DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications
+}
+
+func newSentioPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
+ var config prestateTracerConfig
+ if cfg != nil {
+ if err := json.Unmarshal(cfg, &config); err != nil {
+ return nil, err
+ }
+ }
+ return &sentioPrestateTracer{
+ pre: state{},
+ post: state{},
+ config: config,
+ created: make(map[common.Address]bool),
+ deleted: make(map[common.Address]bool),
+ }, nil
+}
+
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
+func (t *sentioPrestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ t.env = env
+ t.create = create
+ t.to = to
+
+ t.lookupAccount(from)
+ t.lookupAccount(to)
+ t.lookupAccount(env.Context.Coinbase)
+
+ // The recipient balance includes the value transferred.
+ toBal := new(big.Int).Sub(t.pre[to].Balance, value)
+ t.pre[to].Balance = toBal
+
+ // The sender balance is after reducing: value and gasLimit.
+ // We need to re-add them to get the pre-tx balance.
+ fromBal := new(big.Int).Set(t.pre[from].Balance)
+ gasPrice := env.TxContext.GasPrice
+ consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
+ fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
+ t.pre[from].Balance = fromBal
+ t.pre[from].Nonce--
+
+ if create && t.config.DiffMode {
+ t.created[to] = true
+ }
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (t *sentioPrestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
+ if t.config.DiffMode {
+ return
+ }
+
+ if t.create {
+ // Keep existing account prior to contract creation at that address
+ if s := t.pre[t.to]; s != nil && !s.exists() {
+ // Exclude newly created contract.
+ delete(t.pre, t.to)
+ }
+ }
+}
+
+// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
+func (t *sentioPrestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+ stack := scope.Stack
+ stackData := stack.Data()
+ stackLen := len(stackData)
+ caller := scope.Contract.Address()
+ switch {
+ case stackLen >= 2 && op == vm.KECCAK256:
+ size := stackData[stackLen-2]
+ if size.Uint64() == 64 {
+ offset := stackData[stackLen-1]
+ rawkey := scope.Memory.GetCopy(int64(offset.Uint64()), 64)
+
+ // only cares 64 bytes for mapping key
+ hashOfKey := crypto.Keccak256(rawkey)
+ t.pre[caller].MappingKeys[common.Bytes2Hex(rawkey)] = "0x" + common.Bytes2Hex(hashOfKey)
+
+ baseSlot := rawkey[32:]
+ t.pre[caller].CodeAddressBySlot[common.BytesToHash(baseSlot)] = scope.Contract.CodeAddr
+ t.pre[caller].CodeAddressBySlot[common.BytesToHash(hashOfKey)] = scope.Contract.CodeAddr
+ }
+ case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE):
+ slot := common.Hash(stackData[stackLen-1].Bytes32())
+ t.pre[caller].CodeAddress = scope.Contract.CodeAddr
+ t.pre[caller].CodeAddressBySlot[slot] = scope.Contract.CodeAddr
+ t.lookupStorage(caller, slot)
+ case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT):
+ addr := common.Address(stackData[stackLen-1].Bytes20())
+ t.lookupAccount(addr)
+ if op == vm.SELFDESTRUCT {
+ t.deleted[caller] = true
+ }
+ case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE):
+ addr := common.Address(stackData[stackLen-2].Bytes20())
+ t.lookupAccount(addr)
+ case op == vm.CREATE:
+ nonce := t.env.StateDB.GetNonce(caller)
+ addr := crypto.CreateAddress(caller, nonce)
+ t.lookupAccount(addr)
+ t.created[addr] = true
+ case stackLen >= 4 && op == vm.CREATE2:
+ offset := stackData[stackLen-2]
+ size := stackData[stackLen-3]
+ init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
+ inithash := crypto.Keccak256(init)
+ salt := stackData[stackLen-4]
+ addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash)
+ t.lookupAccount(addr)
+ t.created[addr] = true
+ }
+}
+
+func (t *sentioPrestateTracer) CaptureTxStart(gasLimit uint64) {
+ t.gasLimit = gasLimit
+}
+
+func (t *sentioPrestateTracer) CaptureTxEnd(restGas uint64) {
+ if !t.config.DiffMode {
+ return
+ }
+
+ for addr, state := range t.pre {
+ // The deleted account's state is pruned from `post` but kept in `pre`
+ if _, ok := t.deleted[addr]; ok {
+ continue
+ }
+ modified := false
+ postAccount := &account{Storage: make(map[common.Hash]common.Hash)}
+ newBalance := t.env.StateDB.GetBalance(addr)
+ newNonce := t.env.StateDB.GetNonce(addr)
+ newCode := t.env.StateDB.GetCode(addr)
+ postAccount.CodeAddress = state.CodeAddress
+ postAccount.MappingKeys = t.pre[addr].MappingKeys
+
+ if newBalance.Cmp(t.pre[addr].Balance) != 0 {
+ modified = true
+ postAccount.Balance = newBalance
+ }
+ if newNonce != t.pre[addr].Nonce {
+ modified = true
+ postAccount.Nonce = newNonce
+ }
+ if !bytes.Equal(newCode, t.pre[addr].Code) {
+ modified = true
+ postAccount.Code = newCode
+ }
+
+ for key, val := range state.Storage {
+ // don't include the empty slot
+ if val == (common.Hash{}) {
+ delete(t.pre[addr].Storage, key)
+ }
+
+ newVal := t.env.StateDB.GetState(addr, key)
+ if val == newVal {
+ // Omit unchanged slots
+ delete(t.pre[addr].Storage, key)
+ } else {
+ modified = true
+ if newVal != (common.Hash{}) {
+ postAccount.Storage[key] = newVal
+ }
+ }
+ }
+
+ if modified {
+ t.post[addr] = postAccount
+ } else {
+ // if state is not modified, then no need to include into the pre state
+ delete(t.pre, addr)
+ }
+ }
+ // the new created contracts' prestate were empty, so delete them
+ for a := range t.created {
+ // the created contract maybe exists in statedb before the creating tx
+ if s := t.pre[a]; s != nil && !s.exists() {
+ delete(t.pre, a)
+ }
+ }
+}
+
+// GetResult returns the json-encoded nested list of call traces, and any
+// error arising from the encoding or forceful termination (via `Stop`).
+func (t *sentioPrestateTracer) GetResult() (json.RawMessage, error) {
+ var res []byte
+ var err error
+ if t.config.DiffMode {
+ res, err = json.Marshal(struct {
+ Post state `json:"post"`
+ Pre state `json:"pre"`
+ }{t.post, t.pre})
+ } else {
+ res, err = json.Marshal(struct {
+ Pre state `json:"pre"`
+ }{t.pre})
+ }
+ if err != nil {
+ return nil, err
+ }
+ return json.RawMessage(res), t.reason
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (t *sentioPrestateTracer) Stop(err error) {
+ t.reason = err
+ atomic.StoreUint32(&t.interrupt, 1)
+}
+
+// lookupAccount fetches details of an account and adds it to the prestate
+// if it doesn't exist there.
+func (t *sentioPrestateTracer) lookupAccount(addr common.Address) {
+ if _, ok := t.pre[addr]; ok {
+ return
+ }
+
+ t.pre[addr] = &account{
+ Balance: t.env.StateDB.GetBalance(addr),
+ Nonce: t.env.StateDB.GetNonce(addr),
+ Code: t.env.StateDB.GetCode(addr),
+ Storage: make(map[common.Hash]common.Hash),
+ MappingKeys: make(map[string]string),
+ CodeAddressBySlot: make(map[common.Hash]*common.Address),
+ }
+}
+
+// lookupStorage fetches the requested storage slot and adds
+// it to the prestate of the given contract. It assumes `lookupAccount`
+// has been performed on the contract before.
+func (t *sentioPrestateTracer) lookupStorage(addr common.Address, key common.Hash) {
+ if _, ok := t.pre[addr].Storage[key]; ok {
+ return
+ }
+ t.pre[addr].Storage[key] = t.env.StateDB.GetState(addr, key)
+}
+
+func (*sentioPrestateTracer) CapturePreimage(pc uint64, hash common.Hash, preimage []byte) {}
diff --git a/eth/tracers/sentio/tracer.go b/eth/tracers/sentio/tracer.go
new file mode 100644
index 0000000000..2303ddf2b3
--- /dev/null
+++ b/eth/tracers/sentio/tracer.go
@@ -0,0 +1,678 @@
+// 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
+// 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 sentio
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math/big"
+ "sync/atomic"
+
+ "github.com/ava-labs/coreth/accounts/abi"
+ "github.com/ava-labs/coreth/core/vm"
+ "github.com/ava-labs/coreth/eth/tracers"
+ "github.com/ava-labs/coreth/vmerrs"
+ corestate "github.com/ava-labs/coreth/core/state"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/holiman/uint256"
+)
+
+type functionInfo struct {
+ address string
+ Name string `json:"name"`
+ SignatureHash string `json:"signatureHash"`
+
+ Pc uint64 `json:"pc"`
+ InputSize int `json:"inputSize"`
+ InputMemory bool `json:"inputMemory"`
+ OutputSize int `json:"outputSize"`
+ OutputMemory bool `json:"outputMemory"`
+}
+
+type sentioTracerConfig struct {
+ Functions map[string][]functionInfo `json:"functions"`
+ Calls map[string][]uint64 `json:"calls"`
+ Debug bool `json:"debug"`
+ WithInternalCalls bool `json:"withInternalCalls"`
+}
+
+func init() {
+ tracers.DefaultDirectory.Register("sentioTracer", NewSentioTracer, false)
+}
+
+type Trace struct {
+ // only in debug mode
+ Name string `json:"name,omitempty"`
+
+ Type string `json:"type"`
+ Pc uint64 `json:"pc"`
+ // Global index of the trace
+ StartIndex int `json:"startIndex"`
+ EndIndex int `json:"endIndex"`
+
+ // Gas remaining before the OP
+ Gas math.HexOrDecimal64 `json:"gas"`
+ // Gas for the entire call
+ GasUsed math.HexOrDecimal64 `json:"gasUsed"`
+
+ From *common.Address `json:"from,omitempty"`
+ // Used by call
+ To *common.Address `json:"to,omitempty"`
+ // Input
+ Input string `json:"input,omitempty"` // TODO better struct it and make it bytes
+ // Ether transfered
+ Value *hexutil.Big `json:"value,omitempty"`
+ // Return for calls
+ Output hexutil.Bytes `json:"output,omitempty"`
+ Error string `json:"error,omitempty"`
+ Revertal string `json:"revertReason,omitempty"`
+
+ // Used by jump
+ InputStack []uint256.Int `json:"inputStack,omitempty"`
+ InputMemory *[]string `json:"inputMemory,omitempty"`
+ OutputStack []uint256.Int `json:"outputStack,omitempty"`
+ OutputMemory *[]string `json:"outputMemory,omitempty"`
+ FunctionPc uint64 `json:"functionPc,omitempty"`
+
+ // Used by log
+ Address *common.Address `json:"address,omitempty"`
+ CodeAddress *common.Address `json:"codeAddress,omitempty"`
+ Data hexutil.Bytes `json:"data,omitempty"`
+
+ Topics []common.Hash `json:"topics,omitempty"`
+
+ // Only used by root
+ Traces []Trace `json:"traces,omitempty"`
+
+ // Use for internal call stack organization
+ // The jump to go into the function
+ //enterPc uint64
+ exitPc uint64
+
+ // the function get called
+ function *functionInfo
+}
+
+type Receipt struct {
+ Nonce uint64 `json:"nonce"`
+ TxHash *common.Hash `json:"transactionHash,omitempty"`
+ BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
+ BlockHash *common.Hash `json:"blockHash,omitempty"`
+ TransactionIndex uint `json:"transactionIndex"`
+}
+
+type sentioTracer struct {
+ config sentioTracerConfig
+ env *vm.EVM
+ activePrecompiles []common.Address // Updated on CaptureStart based on given rules
+
+ functionMap map[string]map[uint64]functionInfo
+ callMap map[string]map[uint64]bool
+ receipt Receipt
+
+ previousJump *Trace
+ index int
+ entryPc map[uint64]bool
+
+ callstack []Trace
+ gasLimit uint64
+
+ interrupt uint32 // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+}
+
+func (t *sentioTracer) CaptureTxStart(gasLimit uint64) {
+ t.gasLimit = gasLimit
+}
+
+func (t *sentioTracer) CaptureTxEnd(restGas uint64) {
+ if len(t.callstack) == 0 {
+ return
+ }
+ t.callstack[0].EndIndex = t.index
+ t.callstack[0].GasUsed = math.HexOrDecimal64(t.gasLimit - restGas)
+ if t.callstack[0].StartIndex == -1 {
+ // It's possible that we can't correctly locate the PC that match the entry function (check why), in this case we need to 0 for the user
+ t.callstack[0].StartIndex = 0
+ }
+}
+
+func (t *sentioTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ t.env = env
+ t.receipt.BlockNumber = (*hexutil.Big)(env.Context.BlockNumber)
+ // TODO this current will block the tracer
+
+ // TODO bockHash & txHash
+ t.receipt.Nonce = env.StateDB.GetNonce(from) - 1
+ if ibs, ok := env.StateDB.(*corestate.StateDB); ok {
+ t.receipt.TransactionIndex = uint(ibs.TxIndex())
+ }
+
+ rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Time)
+ t.activePrecompiles = vm.ActivePrecompiles(rules)
+
+ root := Trace{
+ StartIndex: -1,
+ Type: vm.CALL.String(),
+ From: &from,
+ To: &to,
+ Gas: math.HexOrDecimal64(gas),
+ Input: hexutil.Bytes(input).String(),
+ }
+ if value != nil {
+ root.Value = (*hexutil.Big)(value)
+ }
+ if create {
+ root.Type = vm.CREATE.String()
+ }
+
+ if !create && !t.isPrecompiled(to) && len(input) >= 4 {
+ m, ok := t.functionMap[to.String()]
+ if ok {
+ sigHash := "0x" + common.Bytes2Hex(input[0:4])
+ for pc, fn := range m {
+ if fn.SignatureHash == sigHash {
+ t.entryPc[pc] = true
+ }
+ }
+ log.Info(fmt.Sprintf("entry pc match %s (%d times) ", sigHash, len(t.entryPc)))
+ }
+ }
+ t.callstack = append(t.callstack, root)
+}
+
+func (t *sentioTracer) CaptureEnd(output []byte, usedGas uint64, err error) {
+ t.callstack[0].EndIndex = t.index
+ t.callstack[0].GasUsed = math.HexOrDecimal64(usedGas)
+ t.callstack[0].Output = common.CopyBytes(output)
+
+ stackSize := len(t.callstack)
+ t.popStack(1, output, uint64(t.callstack[stackSize-1].Gas)-usedGas, err)
+
+ t.callstack[0].processError(output, err)
+}
+
+func (t *sentioTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+ // Skip if tracing was interrupted
+ if atomic.LoadUint32(&t.interrupt) > 0 {
+ return
+ }
+
+ if typ == vm.CALL || typ == vm.CALLCODE {
+ // After enter, make the assumped transfer as function call
+ topElementTraces := t.callstack[len(t.callstack)-1].Traces
+ call := topElementTraces[len(topElementTraces)-1]
+ topElementTraces = topElementTraces[:len(topElementTraces)-1]
+ t.callstack[len(t.callstack)-1].Traces = topElementTraces
+ t.callstack = append(t.callstack, call)
+ }
+
+ size := len(t.callstack)
+
+ t.callstack[size-1].From = &from
+ t.callstack[size-1].To = &to
+ t.callstack[size-1].Input = hexutil.Bytes(input).String()
+ t.callstack[size-1].Gas = math.HexOrDecimal64(gas)
+
+ if value != nil {
+ t.callstack[size-1].Value = (*hexutil.Big)(value)
+ }
+}
+
+func (t *sentioTracer) CaptureExit(output []byte, usedGas uint64, err error) {
+ size := len(t.callstack)
+ if size <= 1 {
+ return
+ }
+
+ //log.Info(fmt.Sprintf("CaptureExit pop frame %s", t.callstack[size-1].Type))
+
+ stackSize := len(t.callstack)
+ for i := stackSize - 1; i >= 0; i-- {
+ if t.callstack[i].function != nil {
+ continue
+ }
+
+ if stackSize-i > 1 {
+ log.Info(fmt.Sprintf("tail call optimization [external] size %d", stackSize-i))
+ }
+
+ call := &t.callstack[i]
+ //call.EndIndex = t.index
+ //call.GasUsed = math.HexOrDecimal64(usedGas)
+ call.processError(output, err)
+
+ t.popStack(i, output, uint64(call.Gas)-usedGas, err)
+ return
+ }
+
+ log.Error(fmt.Sprintf("failed to pop stack"))
+}
+
+func (t *sentioTracer) popStack(to int, output []byte, currentGas uint64, err error) { // , scope *vm.ScopeContext
+ stackSize := len(t.callstack)
+ for j := stackSize - 1; j >= to; j-- {
+ t.callstack[j].Output = common.CopyBytes(output)
+ t.callstack[j].EndIndex = t.index
+ t.callstack[j].GasUsed = math.HexOrDecimal64(uint64(t.callstack[j].Gas) - currentGas)
+
+ // TODO consider pass scopeContext so that popStack also record this
+ //if t.callstack[j].function != nil {
+ // t.callstack[j].OutputStack = copyStack(scope.Stack, t.callstack[j].function.OutputSize)
+ // if t.callstack[j].function.OutputMemory {
+ // t.callstack[j].OutputMemory = formatMemory(scope.Memory)
+ // }
+ //}
+ //if err != nil {
+ // t.callstack[j].Error = err.Error()
+ //}
+ t.callstack[j-1].Traces = append(t.callstack[j-1].Traces, t.callstack[j])
+ }
+
+ t.callstack = t.callstack[:to]
+}
+
+func (t *sentioTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+ // Skip if tracing was interrupted
+ if atomic.LoadUint32(&t.interrupt) > 0 {
+ return
+ }
+ t.index++
+
+ if t.callstack[0].StartIndex == -1 && t.entryPc[pc] {
+ //fillback the index and PC for root
+ t.callstack[0].Pc = pc
+ t.callstack[0].StartIndex = t.index - 1
+ t.previousJump = nil
+ return
+ }
+
+ var mergeBase = func(trace Trace) Trace {
+ trace.Pc = pc
+ trace.Type = op.String()
+ trace.Gas = math.HexOrDecimal64(gas)
+ trace.StartIndex = t.index - 1
+ trace.EndIndex = t.index
+
+ // Assume it's single instruction, adjust it for jump and call
+ trace.GasUsed = math.HexOrDecimal64(cost)
+ if err != nil {
+ // set error for instruction
+ trace.Error = err.Error()
+ }
+ return trace
+ }
+
+ switch op {
+ case vm.CALL, vm.CALLCODE:
+ call := mergeBase(Trace{})
+ call.Gas = math.HexOrDecimal64(scope.Stack.Back(0).Uint64())
+ from := scope.Contract.Address()
+ call.From = &from
+ call.CodeAddress = scope.Contract.CodeAddr
+ to := common.BigToAddress(scope.Stack.Back(1).ToBig())
+ call.To = &to
+ call.Value = (*hexutil.Big)(scope.Stack.Back(2).ToBig())
+
+ v := call.Value.ToInt()
+ if v.BitLen() != 0 && !t.env.Context.CanTransfer(t.env.StateDB, from, v) {
+ if call.Error == "" {
+ call.Error = "insufficient funds for transfer"
+ }
+ }
+
+ // Treat this call as pure transfer until it enters the CaptureEnter
+ t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, call)
+ case vm.CREATE, vm.CREATE2, vm.DELEGATECALL, vm.STATICCALL, vm.SELFDESTRUCT:
+ // more info to be add at CaptureEnter
+ call := mergeBase(Trace{})
+ t.callstack = append(t.callstack, call)
+ case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4:
+ topicCount := int(op - vm.LOG0)
+ logOffset := scope.Stack.Back(0)
+ logSize := scope.Stack.Back(1)
+ data := copyMemory(scope.Memory, logOffset, logSize)
+ var topics []common.Hash
+ //stackLen := scope.Stack.Len()
+ for i := 0; i < topicCount; i++ {
+ topics = append(topics, scope.Stack.Back(2+i).Bytes32())
+ }
+ addr := scope.Contract.Address()
+ l := mergeBase(Trace{
+ Address: &addr,
+ CodeAddress: scope.Contract.CodeAddr,
+ Data: data,
+ Topics: topics,
+ })
+ t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, l)
+ case vm.JUMP:
+ if !t.config.WithInternalCalls {
+ break
+ }
+ from := scope.Contract.CodeAddr
+ codeAddress := scope.Contract.CodeAddr
+
+ jump := mergeBase(Trace{
+ From: from,
+ CodeAddress: codeAddress,
+ //InputStack: append([]uint256.Int(nil), scope.Stack.Data...), // TODO only need partial
+ })
+ if t.previousJump != nil {
+ log.Error("Unexpected previous jump", t.previousJump)
+ }
+ if err == nil {
+ t.previousJump = &jump
+ } else {
+ log.Error("error in jump", "err", err)
+ // error happend, attach to current frame
+ t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, jump)
+ }
+ case vm.JUMPDEST:
+ if !t.config.WithInternalCalls {
+ break
+ }
+ from := scope.Contract.CodeAddr
+ fromStr := from.String()
+
+ if t.previousJump != nil { // vm.JumpDest and match with a previous jump (otherwise it's a jumpi)
+ defer func() {
+ t.previousJump = nil
+ }()
+ // Check if this is return
+ // TODO pontentially maintain a map for fast filtering
+ //log.Info("fromStr" + fromStr + ", callstack size" + fmt.Sprint(len(t.callStack)))
+ stackSize := len(t.callstack)
+
+ // Part 1: try process the trace as function call exit
+ for i := stackSize - 1; i >= 0; i-- {
+ // process internal call within the same contract
+ // no function info means another external call
+ functionInfo := t.callstack[i].function
+ if functionInfo == nil {
+ break
+ }
+
+ if functionInfo.address != fromStr {
+ break
+ }
+
+ // find a match
+ if t.callstack[i].exitPc == pc {
+ // find a match, pop the stack, copy memory if needed
+
+ if stackSize-i > 1 {
+ log.Info(fmt.Sprintf("tail call optimization size %d", stackSize-i))
+ }
+
+ // TODO maybe don't need return all
+ for j := stackSize - 1; j >= i; j-- {
+ call := &t.callstack[j]
+ functionJ := call.function
+ call.EndIndex = t.index - 1 // EndIndex should before the jumpdest
+ call.GasUsed = math.HexOrDecimal64(uint64(t.callstack[j].Gas) - gas)
+ if functionJ.OutputSize > len(scope.Stack.Data()) {
+ log.Error(fmt.Sprintf("stack size not enough (%d vs %d) for function %s %s. pc: %d",
+ len(scope.Stack.Data()), functionJ.OutputSize, functionJ.address, functionJ.Name, pc))
+ if err == nil {
+ log.Error("stack size not enough has error", "err", err)
+ }
+ } else {
+ call.OutputStack = copyStack(scope.Stack, t.callstack[j].function.OutputSize)
+ }
+ if call.function.OutputMemory {
+ call.OutputMemory = formatMemory(scope.Memory)
+ }
+ //if err != nil {
+ // call.Error = err.Error()
+ //}
+ t.callstack[j-1].Traces = append(t.callstack[j-1].Traces, *call)
+ }
+ t.callstack = t.callstack[:i]
+ return
+ }
+ }
+
+ // Part 2: try process the trace as function call entry
+ funcInfo := t.getFunctionInfo(fromStr, pc)
+ //log.Info("function info" + fmt.Sprint(funcInfo))
+
+ if funcInfo != nil {
+ // filter those jump are not call site
+ if !t.isCall(t.previousJump.From.String(), t.previousJump.Pc) {
+ return
+ }
+
+ if funcInfo.InputSize >= len(scope.Stack.Data()) {
+ // TODO this check should not needed after frist check
+ log.Error("Unexpected stack size for function:" + fmt.Sprint(funcInfo) + ", stack" + fmt.Sprint(scope.Stack.Data))
+ log.Error("previous jump" + fmt.Sprint(*t.previousJump))
+ return
+ }
+
+ // confirmed that we are in an internal call
+ //t.internalCallStack = append(t.internalCallStack, internalCallStack{
+ // enterPc: t.previousJump.Pc,
+ // exitPc: scope.Stack.Back(funcInfo.InputSize).Uint64(),
+ // function: funcInfo,
+ //})
+ //jump.enterPc = t.previousJump.Pc
+ t.previousJump.exitPc = scope.Stack.Back(funcInfo.InputSize).Uint64()
+ t.previousJump.function = funcInfo
+ t.previousJump.FunctionPc = pc
+ t.previousJump.InputStack = copyStack(scope.Stack, funcInfo.InputSize)
+ if t.config.Debug {
+ t.previousJump.Name = funcInfo.Name
+ }
+ if funcInfo.InputMemory {
+ t.previousJump.InputMemory = formatMemory(scope.Memory)
+ }
+ t.callstack = append(t.callstack, *t.previousJump)
+ //t.callstack = append(t.callstack, callStack{
+ }
+ }
+ case vm.REVERT:
+ if !t.config.WithInternalCalls {
+ break
+ }
+ logOffset := scope.Stack.Back(0)
+ logSize := scope.Stack.Back(1)
+ output := scope.Memory.GetPtr(int64(logOffset.Uint64()), int64(logSize.Uint64()))
+ //data := copyMemory(logOffset, logSize)
+
+ trace := mergeBase(Trace{
+ Output: output,
+ Error: "execution reverted",
+ })
+ if unpacked, err := abi.UnpackRevert(output); err == nil {
+ trace.Revertal = unpacked
+ }
+ t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, trace)
+ default:
+ if !t.config.WithInternalCalls {
+ break
+ }
+ if err != nil {
+ // Error happen, attach the error OP if not already processed
+ t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, mergeBase(Trace{}))
+ }
+ }
+}
+func (t *sentioTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+}
+
+// CapturePreimage records a SHA3 preimage discovered during execution.
+func (t *sentioTracer) CapturePreimage(pc uint64, hash common.Hash, preimage []byte) {}
+
+func (t *sentioTracer) GetResult() (json.RawMessage, error) {
+ type RootTrace struct {
+ Trace
+ TracerConfig *sentioTracerConfig `json:"tracerConfig,omitempty"`
+ Receipt Receipt `json:"receipt"`
+ }
+ root := RootTrace{
+ Trace: t.callstack[0],
+ Receipt: t.receipt,
+ }
+ if t.config.Debug {
+ root.TracerConfig = &t.config
+ }
+
+ if len(t.callstack) != 1 {
+ log.Error("callstack length is not 1, is " + fmt.Sprint(len(t.callstack)))
+ }
+
+ res, err := json.Marshal(root)
+ if err != nil {
+ return nil, err
+ }
+ return res, t.reason
+}
+
+func (t *sentioTracer) Stop(err error) {
+ t.reason = err
+ atomic.StoreUint32(&t.interrupt, 1)
+}
+
+func NewSentioTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
+ //if name != "sentioTracer" {
+ // return nil, errors.New("no tracer found")
+ //}
+
+ var config sentioTracerConfig
+ functionMap := map[string]map[uint64]functionInfo{}
+ callMap := map[string]map[uint64]bool{}
+
+ if cfg != nil {
+ if err := json.Unmarshal(cfg, &config); err != nil {
+ return nil, err
+ }
+
+ for address, functions := range config.Functions {
+ checkSumAddress := common.HexToAddress(address).String()
+ functionMap[checkSumAddress] = make(map[uint64]functionInfo)
+
+ for _, function := range functions {
+ function.address = checkSumAddress
+ functionMap[checkSumAddress][function.Pc] = function
+ }
+ }
+
+ for address, calls := range config.Calls {
+ checkSumAddress := common.HexToAddress(address).String()
+ callMap[checkSumAddress] = make(map[uint64]bool)
+
+ for _, call := range calls {
+ callMap[checkSumAddress][call] = true
+ }
+ }
+
+ log.Info(fmt.Sprintf("create sentioTracer config with %d functions, %d calls", len(functionMap), len(callMap)))
+ }
+
+ return &sentioTracer{
+ config: config,
+ functionMap: functionMap,
+ callMap: callMap,
+ entryPc: map[uint64]bool{},
+ }, nil
+}
+
+func (t *sentioTracer) isPrecompiled(addr common.Address) bool {
+ for _, p := range t.activePrecompiles {
+ if p == addr {
+ return true
+ }
+ }
+ return false
+}
+
+func (t *sentioTracer) getFunctionInfo(address string, pc uint64) *functionInfo {
+ m, ok := t.functionMap[address]
+ if !ok || m == nil {
+ return nil
+ }
+ info, ok := m[pc]
+ if ok {
+ return &info
+ }
+
+ return nil
+}
+
+func (t *sentioTracer) isCall(address string, pc uint64) bool {
+ m, ok := t.callMap[address]
+ if !ok || m == nil {
+ return false
+ }
+ info, ok := m[pc]
+ if ok {
+ return info
+ }
+ return false
+}
+
+// Only used in non detail mode
+func (f *Trace) processError(output []byte, err error) {
+ //output = common.CopyBytes(output)
+ if err == nil {
+ //f.Output = output
+ return
+ }
+ f.Error = err.Error()
+ if f.Type == vm.CREATE.String() || f.Type == vm.CREATE2.String() {
+ f.To = &common.Address{}
+ }
+ if !errors.Is(err, vmerrs.ErrExecutionReverted) || len(output) == 0 {
+ return
+ }
+ //f.Output = output
+ if len(output) < 4 {
+ return
+ }
+ if unpacked, err := abi.UnpackRevert(output); err == nil {
+ f.Revertal = unpacked
+ }
+}
+
+func copyMemory(m *vm.Memory, offset *uint256.Int, size *uint256.Int) hexutil.Bytes {
+ // it's important to get copy
+ return m.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
+}
+
+func formatMemory(m *vm.Memory) *[]string {
+ res := make([]string, 0, (m.Len()+31)/32)
+ for i := 0; i+32 <= m.Len(); i += 32 {
+ res = append(res, fmt.Sprintf("%x", m.GetPtr(int64(i), 32)))
+ }
+ return &res
+}
+
+func copyStack(s *vm.Stack, copySize int) []uint256.Int {
+ if copySize == 0 {
+ return nil
+ }
+ stackSize := len(s.Data())
+ res := make([]uint256.Int, stackSize)
+ for i := stackSize - copySize; i < stackSize; i++ {
+ res[i] = s.Data()[i]
+ }
+ return res
+}
diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index 50606b0af7..1ab6cf1ab9 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -59,6 +59,7 @@ import (
// inside of cmd/geth.
_ "github.com/ava-labs/coreth/eth/tracers/js"
_ "github.com/ava-labs/coreth/eth/tracers/native"
+ _ "github.com/ava-labs/coreth/eth/tracers/sentio"
"github.com/ava-labs/coreth/precompile/precompileconfig"
// Force-load precompiles to trigger registration
diff --git a/rpc/client.go b/rpc/client.go
index 6c11365560..6871de012b 100644
--- a/rpc/client.go
+++ b/rpc/client.go
@@ -378,6 +378,7 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str
resp := batchresp[0]
switch {
case resp.Error != nil:
+ _ = json.Unmarshal(resp.Result, result)
return resp.Error
case len(resp.Result) == 0:
return ErrNoResult