Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,15 @@ func applyTransaction(
tx *types.Transaction,
usedGas *uint64, evm *vm.EVM,
receiptProcessor ReceiptProcessor,
) (*types.Receipt, *ExecutionResult, error) {
) (receipt *types.Receipt, result *ExecutionResult, err error) {
if evm.Config.LiveTracer != nil && evm.Config.LiveTracer.OnTxStart != nil {
evm.Config.LiveTracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
if evm.Config.LiveTracer.OnTxEnd != nil {
defer func() {
evm.Config.LiveTracer.OnTxEnd(receipt, err)
}()
}
}
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb)
Expand All @@ -174,7 +182,7 @@ func applyTransaction(
}

// Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, msg, gp)
result, err = ApplyMessage(evm, msg, gp)
if err != nil {
return nil, nil, err
}
Expand All @@ -190,7 +198,7 @@ func applyTransaction(

// Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
receipt = &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
if result.Failed() {
receipt.Status = types.ReceiptStatusFailed
} else {
Expand Down
15 changes: 15 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ func (st *StateTransition) buyGas() error {
if err := st.gp.SubGas(msg.GasLimit); err != nil {
return err
}

if st.evm.Config.LiveTracer != nil && st.evm.Config.LiveTracer.OnGasChange != nil {
st.evm.Config.LiveTracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
}
st.gas += msg.GasLimit

st.initialGas = msg.GasLimit
Expand Down Expand Up @@ -524,6 +528,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st.gas < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas)
}
if t := st.evm.Config.LiveTracer; t != nil && t.OnGasChange != nil {
t.OnGasChange(st.gas, st.gas-gas, tracing.GasChangeTxIntrinsicGas)
}
st.gas -= gas
}

Expand Down Expand Up @@ -601,12 +608,20 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
if refund > st.state.GetRefund() {
refund = st.state.GetRefund()
}

if st.evm.Config.LiveTracer != nil && st.evm.Config.LiveTracer.OnGasChange != nil && refund > 0 {
st.evm.Config.LiveTracer.OnGasChange(st.gas, st.gas+refund, tracing.GasChangeTxRefunds)
}
st.gas += refund

// Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
st.state.AddBalance(st.msg.Payer, remaining, tracing.BalanceIncreaseGasReturn)

if st.evm.Config.LiveTracer != nil && st.evm.Config.LiveTracer.OnGasChange != nil && st.gas > 0 {
st.evm.Config.LiveTracer.OnGasChange(st.gas, 0, tracing.GasChangeTxLeftOverReturned)
}

// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st.gp.AddGas(st.gas)
Expand Down
8 changes: 4 additions & 4 deletions core/tracing/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,11 @@ type OpContext interface {
Address() common.Address
CallValue() *uint256.Int
CallInput() []byte
ContractCode() []byte
}

// StateDB gives tracers access to the whole state.
type StateDB interface {
GetBalance(common.Address) *uint256.Int
GetBalance(common.Address) *big.Int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently, the original state.StateDB defines GetBalance(common.Address) return big.Int instead of uint256 so I think it's neccessary to keep it for more clarify, and we will let PR changes big.Int to uint256.Int do it later

GetNonce(common.Address) uint64
GetCode(common.Address) []byte
GetCodeHash(common.Address) common.Hash
Expand All @@ -62,8 +61,9 @@ type VMContext struct {
Coinbase common.Address
BlockNumber *big.Int
Time uint64
Random *common.Hash
BaseFee *big.Int
// Effective tx gas price
GasPrice *big.Int
ChainConfig *params.ChainConfig
StateDB StateDB
}

Expand Down
17 changes: 16 additions & 1 deletion core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/holiman/uint256"
)

Expand Down Expand Up @@ -164,14 +165,28 @@ 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 *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) {
if c.Gas < gas {
return false
}
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
logger.OnGasChange(c.Gas, c.Gas-gas, reason)
}
c.Gas -= gas
return true
}

// RefundGas refunds gas to the contract
func (c *Contract) RefundGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) {
if gas == 0 {
return
}
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
logger.OnGasChange(c.Gas, c.Gas+gas, reason)
}
c.Gas += gas
}

// Address returns the contracts address
func (c *Contract) Address() common.Address {
return c.self.Address()
Expand Down
6 changes: 5 additions & 1 deletion core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/blake2b"
"github.com/ethereum/go-ethereum/crypto/bls12381"
Expand Down Expand Up @@ -206,11 +207,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 *tracing.Hooks) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas
}
if logger != nil && logger.OnGasChange != nil {
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract)
}
suppliedGas -= gasCost
output, err := p.Run(input)
return output, suppliedGas, err
Expand Down
8 changes: 4 additions & 4 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,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))
Expand All @@ -152,7 +152,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)
}
Expand All @@ -169,7 +169,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)
}
Expand Down Expand Up @@ -201,7 +201,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))
Expand Down
109 changes: 109 additions & 0 deletions core/vm/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package vm
import (
"errors"
"fmt"
"math"
)

// List evm execution errors
Expand Down Expand Up @@ -67,3 +68,111 @@ type ErrInvalidOpCode struct {
}

func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) }

// VMError wraps a VM error with an additional stable error code. The error
// field is the original error that caused the VM error and must be one of the
// VM error defined at the top of this file.
//
// If the error is not one of the known error above, the error code will be
// set to VMErrorCodeUnknown.
type VMError struct {
error
code int
}

func VMErrorFromErr(err error) error {
if err == nil {
return nil
}

return &VMError{
error: err,
code: vmErrorCodeFromErr(err),
}
}

func (e *VMError) Error() string {
return e.error.Error()
}

func (e *VMError) Unwrap() error {
return e.error
}

func (e *VMError) ErrorCode() int {
return e.code
}

const (
// We start the error code at 1 so that we can use 0 later for some possible extension. There
// is no unspecified value for the code today because it should always be set to a valid value
// that could be VMErrorCodeUnknown if the error is not mapped to a known error code.

VMErrorCodeOutOfGas = 1 + iota
VMErrorCodeCodeStoreOutOfGas
VMErrorCodeDepth
VMErrorCodeInsufficientBalance
VMErrorCodeContractAddressCollision
VMErrorCodeExecutionReverted
VMErrorCodeMaxCodeSizeExceeded
VMErrorCodeInvalidJump
VMErrorCodeWriteProtection
VMErrorCodeReturnDataOutOfBounds
VMErrorCodeGasUintOverflow
VMErrorCodeInvalidCode
VMErrorCodeNonceUintOverflow
VMErrorCodeStackUnderflow
VMErrorCodeStackOverflow
VMErrorCodeInvalidOpCode

// VMErrorCodeUnknown explicitly marks an error as unknown, this is useful when error is converted
// from an actual `error` in which case if the mapping is not known, we can use this value to indicate that.
VMErrorCodeUnknown = math.MaxInt - 1
)

func vmErrorCodeFromErr(err error) int {
switch {
case errors.Is(err, ErrOutOfGas):
return VMErrorCodeOutOfGas
case errors.Is(err, ErrCodeStoreOutOfGas):
return VMErrorCodeCodeStoreOutOfGas
case errors.Is(err, ErrDepth):
return VMErrorCodeDepth
case errors.Is(err, ErrInsufficientBalance):
return VMErrorCodeInsufficientBalance
case errors.Is(err, ErrContractAddressCollision):
return VMErrorCodeContractAddressCollision
case errors.Is(err, ErrExecutionReverted):
return VMErrorCodeExecutionReverted
case errors.Is(err, ErrMaxCodeSizeExceeded):
return VMErrorCodeMaxCodeSizeExceeded
case errors.Is(err, ErrInvalidJump):
return VMErrorCodeInvalidJump
case errors.Is(err, ErrWriteProtection):
return VMErrorCodeWriteProtection
case errors.Is(err, ErrReturnDataOutOfBounds):
return VMErrorCodeReturnDataOutOfBounds
case errors.Is(err, ErrGasUintOverflow):
return VMErrorCodeGasUintOverflow
case errors.Is(err, ErrInvalidCode):
return VMErrorCodeInvalidCode
case errors.Is(err, ErrNonceUintOverflow):
return VMErrorCodeNonceUintOverflow

default:
// Dynamic errors
if v := (*ErrStackUnderflow)(nil); errors.As(err, &v) {
return VMErrorCodeStackUnderflow
}

if v := (*ErrStackOverflow)(nil); errors.As(err, &v) {
return VMErrorCodeStackOverflow
}

if v := (*ErrInvalidOpCode)(nil); errors.As(err, &v) {
return VMErrorCodeInvalidOpCode
}

return VMErrorCodeUnknown
}
}
Loading