Tosca CT integration

This commit is contained in:
Simon 2024-05-22 13:41:46 +02:00
parent d6e91e2e05
commit 6b8469282d
3 changed files with 302 additions and 111 deletions

View file

@ -125,6 +125,9 @@ type EVM struct {
// available gas is calculated in gasCall* according to the 63/64 rule and later
// applied in opCall*.
callGasTemp uint64
// An optional override to intercept EVM calls.
CallContext CallContextInterceptor
}
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
@ -181,6 +184,9 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.CallContext != nil {
return evm.CallContext.Call(evm, caller, addr, input, new(big.Int).SetUint64(gas), value.ToBig())
}
// Capture the tracer start/end events in debug mode
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALL, caller.Address(), addr, input, gas, value.ToBig())
@ -253,6 +259,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context.
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.CallContext != nil {
return evm.CallContext.CallCode(evm, caller, addr, input, new(big.Int).SetUint64(gas), value.ToBig())
}
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALLCODE, caller.Address(), addr, input, gas, value.ToBig())
@ -304,6 +313,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
// DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
if evm.CallContext != nil {
return evm.CallContext.DelegateCall(evm, caller, addr, input, new(big.Int).SetUint64(gas))
}
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
// NOTE: caller must, at all times be a contract. It should never happen
@ -349,6 +361,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
// Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications.
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
if evm.CallContext != nil {
return evm.CallContext.StaticCall(evm, caller, addr, input, new(big.Int).SetUint64(gas))
}
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, STATICCALL, caller.Address(), addr, input, gas, nil)
@ -520,6 +535,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
if evm.CallContext != nil {
return evm.CallContext.Create(evm, caller, code, new(big.Int).SetUint64(gas), value.ToBig())
}
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE)
}
@ -529,6 +547,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint2
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
if evm.CallContext != nil {
return evm.CallContext.Create2(evm, caller, code, new(big.Int).SetUint64(gas), endowment.ToBig(), salt)
}
codeAndHash := &codeAndHash{code: code}
contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2)

View file

@ -147,7 +147,11 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
func (in *EVMInterpreter) run(state *InterpreterState, input []byte, readOnly bool) (ret []byte, err error) {
defer func() {
state.finished = true
}()
// Increment the call depth which is restricted to 1024
in.evm.depth++
defer func() { in.evm.depth-- }()
@ -164,38 +168,15 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
in.returnData = nil
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
if len(state.Contract.Code) == 0 {
return nil, nil
}
var (
op OpCode // current opcode
mem = NewMemory() // bound memory
stack = newstack() // local stack
callContext = &ScopeContext{
Memory: mem,
Stack: stack,
Contract: contract,
}
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC
// to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter
cost uint64
// copies used by tracer
pcCopy uint64 // needed for the deferred EVMLogger
gasCopy uint64 // for EVMLogger to log gas remaining before execution
logged bool // deferred EVMLogger should ignore already logged steps
res []byte // result of the opcode execution function
debug = in.evm.Config.Tracer != nil
)
// Don't move this deferred function, it's placed before the OnOpcode-deferred method,
// so that it gets executed _after_: the OnOpcode needs the stacks before
// they are returned to the pools
defer func() {
returnStack(stack)
}()
contract.Input = input
gethState := NewGethState(state.Contract, state.Memory, state.Stack, state.pc)
gethState.Contract.Input = input
debug := in.evm.Config.Tracer != nil
var logged bool
if debug {
defer func() { // this deferred method handles exit-with-error
@ -203,101 +184,125 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
return
}
if !logged && in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
in.evm.Config.Tracer.OnOpcode(gethState.pcCopy, byte(gethState.op), gethState.gasCopy, gethState.cost, gethState.CallContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
}
if logged && in.evm.Config.Tracer.OnFault != nil {
in.evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err))
in.evm.Config.Tracer.OnFault(gethState.pcCopy, byte(gethState.op), gethState.gasCopy, gethState.cost, gethState.CallContext, in.evm.depth, VMErrorFromErr(err))
}
}()
}
// The Interpreter main run loop (contextual). This loop runs until either an
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
// the execution of one of the operations or until the done flag is set by the
// parent context.
steps := 0
for {
if debug {
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas
}
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
op = contract.GetOp(pc)
operation := in.table[op]
cost = operation.constantGas // For tracing
// Validate stack
if sLen := stack.len(); sLen < operation.minStack {
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
} else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
if !contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas
}
if operation.dynamicGas != nil {
// All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, ErrGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
// Consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
}
if !contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas
}
// Do tracing before memory expansion
if debug {
if in.evm.Config.Tracer.OnGasChange != nil {
in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
}
if in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
logged = true
}
}
if memorySize > 0 {
mem.Resize(memorySize)
}
} else if debug {
if in.evm.Config.Tracer.OnGasChange != nil {
in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
}
if in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
logged = true
}
}
// execute the operation
res, err = operation.execute(&pc, in, callContext)
if err != nil {
steps++
if in.evm.abort.Load() {
break
}
if !in.Step(gethState) {
break
}
pc++
}
if err == errStopToken {
err = nil // clear stop token error
if gethState.Err == errStopToken {
gethState.Err = nil // clear stop token error
}
return res, err
return gethState.Result, gethState.Err
}
func (in *EVMInterpreter) Step(state *GethState) bool {
debug := in.evm.Config.Tracer != nil
if debug {
// Capture pre-execution values for tracing.
state.logged, state.pcCopy, state.gasCopy = false, state.Pc, state.Contract.Gas
}
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
state.op = state.Contract.GetOp(state.Pc)
operation := in.table[state.op]
cost := operation.constantGas // For tracing
// Validate stack
if sLen := state.Stack.len(); sLen < operation.minStack {
state.Err = &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
return false
} else if sLen > operation.maxStack {
state.Err = &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
return false
}
if !state.Contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
state.Err = ErrOutOfGas
return false
}
if operation.dynamicGas != nil {
// All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(state.Stack)
if overflow {
state.Err = ErrGasUintOverflow
return false
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
state.Err = ErrGasUintOverflow
return false
}
}
// Consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
var dynamicCost uint64
dynamicCost, state.Err = operation.dynamicGas(in.evm, state.Contract, state.Stack, state.Memory, memorySize)
cost += dynamicCost // for tracing
if state.Err != nil {
state.Err = fmt.Errorf("%w: %v", ErrOutOfGas, state.Err)
return false
}
if !state.Contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
state.Err = ErrOutOfGas
return false
}
// Do tracing before memory expansion
if debug {
if in.evm.Config.Tracer.OnGasChange != nil {
in.evm.Config.Tracer.OnGasChange(state.gasCopy, state.gasCopy-cost, tracing.GasChangeCallOpCode)
}
if in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(state.Pc, byte(state.op), state.gasCopy, cost, state.CallContext, in.returnData, in.evm.depth, VMErrorFromErr(state.Err))
state.logged = true
}
}
if memorySize > 0 {
state.Memory.Resize(memorySize)
}
} else if debug {
if in.evm.Config.Tracer.OnGasChange != nil {
in.evm.Config.Tracer.OnGasChange(state.gasCopy, state.gasCopy-cost, tracing.GasChangeCallOpCode)
}
if in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(state.Pc, byte(state.op), state.gasCopy, cost, state.CallContext, in.returnData, in.evm.depth, VMErrorFromErr(state.Err))
state.logged = true
}
}
// execute the operation
state.Result, state.Err = operation.execute(&state.Pc, in, state.CallContext)
if state.Err != nil {
return false
}
state.Pc++
return state.Err == nil
}

View file

@ -0,0 +1,165 @@
package vm
import (
"math/big"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/holiman/uint256"
)
// Error
var ErrStopToken = errStopToken
// Gas table
func MemoryGasCost(mem *Memory, wordSize uint64) (uint64, error) {
return memoryGasCost(mem, wordSize)
}
// Stack
func NewStack() *Stack {
return newstack()
}
func (st *Stack) Len() int {
return st.len()
}
func (st *Stack) Push(d *uint256.Int) {
st.push(d)
}
// EVM
func (evm *EVM) GetDepth() int {
return evm.depth
}
func (evm *EVM) SetDepth(depth int) {
evm.depth = depth
}
// Interpreter
func (g *EVMInterpreter) SetLastCallReturnData(data []byte) {
g.returnData = data
}
func (g *EVMInterpreter) GetLastCallReturnData() []byte {
return g.returnData
}
func (g *EVMInterpreter) SetReadOnly(readOnly bool) {
g.readOnly = readOnly
}
func (g *EVMInterpreter) IsReadOnly() bool {
return g.readOnly
}
// CallContext Call interceptor
// CallContext provides a basic interface for the EVM calling conventions. The EVM
// depends on this context being implemented for doing subcalls and initialising new EVM contracts.
type CallContextInterceptor interface {
// Call calls another contract.
Call(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, uint64, error)
// CallCode takes another contracts code and execute within our own context
CallCode(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, uint64, error)
// DelegateCall is same as CallCode except sender and value is propagated from parent to child scope
DelegateCall(env *EVM, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, uint64, error)
// Create creates a new contract
Create(env *EVM, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, uint64, error)
StaticCall(env *EVM, me ContractRef, addr common.Address, input []byte, gas *big.Int) ([]byte, uint64, error)
Create2(env *EVM, me ContractRef, code []byte, gas *big.Int, value *big.Int, salt *uint256.Int) ([]byte, common.Address, uint64, error)
}
// Interpreter interface
// EVMInterpreter defines an interface for different interpreter implementations.
type GethEVMInterpreter interface {
// Run the contract's code with the given input data and returns the return byte-slice
// and an error if one occurred.
Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error)
}
type InterpreterFactory func(evm *EVM, cfg Config) GethEVMInterpreter
var interpreter_registry = map[string]InterpreterFactory{}
func RegisterInterpreterFactory(name string, factory InterpreterFactory) {
interpreter_registry[strings.ToLower(name)] = factory
}
func NewInterpreter(name string, evm *EVM, cfg Config) GethEVMInterpreter {
factory, found := interpreter_registry[strings.ToLower(name)]
if !found {
log.Error("no factory for interpreter registered", "name", name)
}
return factory(evm, cfg)
}
func init() {
factory := func(evm *EVM, cfg Config) GethEVMInterpreter {
return NewEVMInterpreter(evm)
}
RegisterInterpreterFactory("", factory)
RegisterInterpreterFactory("geth", factory)
}
// Abstracted interpreter with single step execution.
// GethState represents the internal state of the interpreter.
type GethState struct {
Contract *Contract // processed contract
Memory *Memory // bound memory
Stack *Stack // local stack
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC
// to be uint256. Practically much less so feasible.
Pc uint64 // program counter
Result []byte // result of the opcode execution function
Err error
CallContext *ScopeContext
ReadOnly bool
Halted bool
op OpCode // current opcode
cost uint64
// copies used by tracer
pcCopy uint64 // needed for the deferred Tracer
gasCopy uint64 // for Tracer to log gas remaining before execution
logged bool // deferred Tracer should ignore already logged steps
}
func NewGethState(contract *Contract, memory *Memory, stack *Stack, Pc uint64) *GethState {
return &GethState{
Contract: contract,
Memory: memory,
Stack: stack,
Pc: Pc,
CallContext: &ScopeContext{
Memory: memory,
Stack: stack,
Contract: contract,
},
}
}
type InterpreterState struct {
Contract *Contract
Stack *Stack
Memory *Memory
pc uint64
finished bool
}
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
state := InterpreterState{
Contract: contract,
Stack: NewStack(),
Memory: NewMemory(),
}
defer returnStack(state.Stack)
return in.run(&state, input, readOnly)
}