add gas consumption hook

This commit is contained in:
Sina Mahmoodi 2023-06-23 16:40:58 +02:00
parent 84d6432708
commit ee791b2b20
20 changed files with 73 additions and 42 deletions

View file

@ -348,6 +348,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st.gasRemaining < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
}
if t := st.evm.Config.Tracer; t != nil {
t.OnGasConsumed(st.gasRemaining, gas)
}
st.gasRemaining -= gas
// Check clause 6

View file

@ -159,10 +159,13 @@ func (c *Contract) Caller() common.Address {
}
// UseGas attempts the use gas and subtracts it and returns true on success
func (c *Contract) UseGas(gas uint64) (ok bool) {
func (c *Contract) UseGas(gas uint64, logger EVMLogger) (ok bool) {
if c.Gas < gas {
return false
}
if logger != nil {
logger.OnGasConsumed(c.Gas, gas)
}
c.Gas -= gas
return true
}

View file

@ -168,11 +168,14 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
// - the returned bytes,
// - the _remaining_ gas,
// - any error that occurred
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger EVMLogger) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas
}
if logger != nil {
logger.OnGasConsumed(suppliedGas, gasCost)
}
suppliedGas -= gasCost
output, err := p.Run(input)
return output, suppliedGas, err

View file

@ -97,7 +97,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
if res, _, err := RunPrecompiledContract(p, in, gas); err != nil {
if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil {
t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@ -119,7 +119,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
gas := p.RequiredGas(in) - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
_, _, err := RunPrecompiledContract(p, in, gas)
_, _, err := RunPrecompiledContract(p, in, gas, nil)
if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err)
}
@ -136,7 +136,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) {
_, _, err := RunPrecompiledContract(p, in, gas)
_, _, err := RunPrecompiledContract(p, in, gas, nil)
if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
}
@ -168,7 +168,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
copy(data, in)
res, _, err = RunPrecompiledContract(p, data, reqGas)
res, _, err = RunPrecompiledContract(p, data, reqGas, nil)
}
bench.StopTimer()
elapsed := uint64(time.Since(start))

View file

@ -222,7 +222,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
}
if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
@ -285,7 +285,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
} else {
addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM.
@ -330,7 +330,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
} else {
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
@ -379,7 +379,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
}
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
} else {
// At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract'
@ -480,7 +480,7 @@ 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 contract.UseGas(createDataGas) {
if contract.UseGas(createDataGas, evm.Config.Tracer) {
evm.StateDB.SetCode(address, ret)
} else {
err = ErrCodeStoreOutOfGas
@ -493,7 +493,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
contract.UseGas(contract.Gas)
contract.UseGas(contract.Gas, evm.Config.Tracer)
}
}

View file

@ -591,7 +591,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
// reuse size int for stackvalue
stackvalue := size
scope.Contract.UseGas(gas)
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer)
//TODO: use uint256.Int instead of converting with toBig()
var bigVal = big0
if !value.IsZero() {
@ -634,7 +634,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
)
// Apply EIP150
gas -= gas / 64
scope.Contract.UseGas(gas)
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer)
// reuse size int for stackvalue
stackvalue := size
//TODO: use uint256.Int instead of converting with toBig()

View file

@ -185,7 +185,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
} else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
if !contract.UseGas(cost) {
if !contract.UseGas(cost, in.evm.Config.Tracer) {
return nil, ErrOutOfGas
}
if operation.dynamicGas != nil {
@ -211,7 +211,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing
if err != nil || !contract.UseGas(dynamicCost) {
if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer) {
return nil, ErrOutOfGas
}
// Do tracing before memory expansion

View file

@ -41,4 +41,6 @@ type EVMLogger interface {
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
CaptureKeccakPreimage(hash common.Hash, data []byte)
// Misc
OnGasConsumed(gas, amount uint64)
}

View file

@ -169,7 +169,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
evm.StateDB.AddAddressToAccessList(addr)
// Charge the remaining difference here already, to correctly calculate available
// gas for call
if !contract.UseGas(coldCost) {
if !contract.UseGas(coldCost, evm.Config.Tracer) {
return 0, ErrOutOfGas
}
}

View file

@ -95,6 +95,8 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b
// jsTracer is an implementation of the Tracer interface which evaluates
// JS functions on the relevant EVM hooks. It uses Goja as its JS engine.
type jsTracer struct {
tracers.NoopTracer
vm *goja.Runtime
env *vm.EVM
toBig toBigFn // Converts a hex string into a JS bigint
@ -286,9 +288,6 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope
}
}
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
func (t *jsTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.ctx["output"] = t.vm.ToValue(output)

View file

@ -163,6 +163,8 @@ func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64,
func (*AccessListTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
func (*AccessListTracer) OnGasConsumed(gas, amount uint64) {}
func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {}
func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {

View file

@ -220,6 +220,8 @@ func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, s
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
func (l *StructLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
func (l *StructLogger) OnGasConsumed(gas, amount uint64) {}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
l.output = output
@ -389,6 +391,8 @@ func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope
func (t *mdLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
func (t *mdLogger) OnGasConsumed(gas, amount uint64) {}
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err)

View file

@ -81,6 +81,8 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
func (l *JSONLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
func (l *JSONLogger) OnGasConsumed(gas, amount uint64) {}
// CaptureEnd is triggered at end of execution.
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
type endLog struct {

View file

@ -46,7 +46,7 @@ func init() {
// 0xc281d19e-0: 1
// }
type fourByteTracer struct {
noopTracer
tracers.NoopTracer
ids map[string]int // ids aggregates the 4byte ids found
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption

View file

@ -98,7 +98,7 @@ type callFrameMarshaling struct {
}
type callTracer struct {
noopTracer
tracers.NoopTracer
callstack []callFrame
config callTracerConfig
gasLimit uint64

View file

@ -108,7 +108,7 @@ type flatCallResultMarshaling struct {
// flatCallTracer reports call frame information of a tx in a flat format, i.e.
// as opposed to the nested format of `callTracer`.
type flatCallTracer struct {
noopTracer
tracers.NoopTracer
tracer *callTracer
config flatCallTracerConfig
ctx *tracers.Context // Holds tracer context data

View file

@ -93,6 +93,13 @@ func (t *muxTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {
}
}
// CaptureGasConsumed is called when gas is consumed.
func (t *muxTracer) OnGasConsumed(gas, amount uint64) {
for _, t := range t.tracers {
t.OnGasConsumed(gas, amount)
}
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
for _, t := range t.tracers {

View file

@ -54,7 +54,7 @@ type accountMarshaling struct {
}
type prestateTracer struct {
noopTracer
tracers.NoopTracer
env *vm.EVM
pre state
post state

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package native
package tracers
import (
"encoding/json"
@ -22,59 +22,61 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
)
func init() {
tracers.DefaultDirectory.Register("noopTracer", newNoopTracer, false)
DefaultDirectory.Register("noopTracer", newNoopTracer, false)
}
// noopTracer is a go implementation of the Tracer interface which
// NoopTracer is a go implementation of the Tracer interface which
// performs no action. It's mostly useful for testing purposes.
type noopTracer struct{}
type NoopTracer struct{}
// newNoopTracer returns a new noop tracer.
func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
return &noopTracer{}, nil
func newNoopTracer(ctx *Context, _ json.RawMessage) (Tracer, error) {
return &NoopTracer{}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
func (t *NoopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
func (t *NoopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
}
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
func (t *NoopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
func (t *NoopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
}
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
func (t *noopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
func (t *NoopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
// OnGasConsumed is called when gas is consumed.
func (t *NoopTracer) OnGasConsumed(gas, amount uint64) {}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
func (t *NoopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
// CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code.
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
func (t *NoopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
}
func (*noopTracer) CaptureTxStart(gasLimit uint64) {}
func (*NoopTracer) CaptureTxStart(gasLimit uint64) {}
func (*noopTracer) CaptureTxEnd(restGas uint64) {}
func (*NoopTracer) CaptureTxEnd(restGas uint64) {}
// GetResult returns an empty json object.
func (t *noopTracer) GetResult() (json.RawMessage, error) {
func (t *NoopTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(`{}`), nil
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *noopTracer) Stop(err error) {
func (t *NoopTracer) Stop(err error) {
}

View file

@ -93,3 +93,7 @@ func (p *Printer) OnLog(l *types.Log) {
func (p *Printer) OnNewAccount(a common.Address) {
fmt.Printf("OnNewAccount: a=%v\n", a)
}
func (p *Printer) OnGasConsumed(gas, amount uint64) {
fmt.Printf("OnGasConsumed: gas=%v, amount=%v\n", gas, amount)
}