Skip to content

Commit

Permalink
Merge pull request ethereum#279 from nextyio/reverted-log
Browse files Browse the repository at this point in the history
Receipt log for reverted tx
  • Loading branch information
Zergity authored Sep 11, 2019
2 parents ec6e5d8 + 48e5a7b commit 4d0740a
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 15 deletions.
3 changes: 3 additions & 0 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
}
// Set the receipt logs and create a bloom for filtering
receipt.Logs = statedb.GetLogs(tx.Hash())
if len(vmenv.Logs) > 0 {
receipt.Logs = append(receipt.Logs, vmenv.Logs...)
}
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
receipt.BlockHash = statedb.BlockHash()
receipt.BlockNumber = header.Number
Expand Down
5 changes: 5 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
// sufficient balance to make the transfer happen. The first
// balance transfer may never fail.
if vmerr == vm.ErrInsufficientBalance {
if msg.To() == nil {
evm.LogFailure(common.Address{}, params.TopicError, params.ErrorLogInsufficientBalance)
} else {
evm.LogFailure(*msg.To(), params.TopicError, params.ErrorLogInsufficientBalance)
}
return nil, 0, false, vmerr
}
}
Expand Down
3 changes: 2 additions & 1 deletion core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ var PrecompiledContractsCoLoa = map[common.Address]PrecompiledContract{
}

// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
func (evm *EVM) RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.RequiredGas(input)
if contract.UseGas(gas) {
return p.Run(input)
}
evm.LogFailure(contract.Address(), params.TopicError, params.ErrorLogOutOfGas)
return nil, ErrOutOfGas
}

Expand Down
16 changes: 10 additions & 6 deletions core/vm/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@

package vm

import "errors"
import (
"errors"

"github.com/ethereum/go-ethereum/params"
)

// List execution errors
var (
ErrOutOfGas = errors.New("out of gas")
ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas")
ErrDepth = errors.New("max call depth exceeded")
ErrOutOfGas = errors.New(params.ErrorLogOutOfGas)
ErrCodeStoreOutOfGas = errors.New(params.ErrorLogCodeStoreOutOfGas)
ErrDepth = errors.New(params.ErrorLogDepth)
ErrInsufficientBalance = errors.New(params.ErrorLogInsufficientBalance)
ErrContractAddressCollision = errors.New(params.ErrorLogContractAddressCollision)
ErrTraceLimitReached = errors.New("the number of logs reached the specified limit")
ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrContractAddressCollision = errors.New("contract address collision")
ErrNoCompatibleInterpreter = errors.New("no compatible interpreter")
)
31 changes: 30 additions & 1 deletion core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
Expand Down Expand Up @@ -54,7 +55,7 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
precompiles = PrecompiledContractsCoLoa
}
if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract)
return evm.RunPrecompiledContract(p, input, contract)
}
}
for _, interpreter := range evm.interpreters {
Expand Down Expand Up @@ -94,6 +95,9 @@ type Context struct {
BlockNumber *big.Int // Provides information for NUMBER
Time *big.Int // Provides information for TIME
Difficulty *big.Int // Provides information for DIFFICULTY

// extra logs to append to the receipt after the regular logs
Logs []*types.Log
}

// EVM is the Ethereum Virtual Machine base object and provides
Expand Down Expand Up @@ -169,6 +173,20 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon
return evm
}

// LogFailure appends the failure reason or revert message to receipt log
// after the state has be reverted
func (evm *EVM) LogFailure(address common.Address, topic common.Hash, reason string) {
if !evm.chainRules.IsCoLoa {
return
}
log := types.Log{
Address: address,
Topics: []common.Hash{topic},
Data: []byte(reason),
}
evm.Logs = append(evm.Logs, &log)
}

// Cancel cancels any running EVM operation. This may be called concurrently and
// it's safe to be called multiple times.
func (evm *EVM) Cancel() {
Expand Down Expand Up @@ -196,10 +214,12 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas

// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
evm.LogFailure(addr, params.TopicError, params.ErrorLogDepth)
return nil, gas, ErrDepth
}
// Fail if we're trying to transfer more than the available balance
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
evm.LogFailure(addr, params.TopicError, params.ErrorLogInsufficientBalance)
return nil, gas, ErrInsufficientBalance
}

Expand Down Expand Up @@ -276,10 +296,12 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,

// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
evm.LogFailure(addr, params.TopicError, params.ErrorLogDepth)
return nil, gas, ErrDepth
}
// Fail if we're trying to transfer more than the available balance
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
evm.LogFailure(addr, params.TopicError, params.ErrorLogInsufficientBalance)
return nil, gas, ErrInsufficientBalance
}

Expand Down Expand Up @@ -313,6 +335,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
evm.LogFailure(addr, params.TopicError, params.ErrorLogDepth)
return nil, gas, ErrDepth
}

Expand Down Expand Up @@ -345,6 +368,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
evm.LogFailure(addr, params.TopicError, params.ErrorLogDepth)
return nil, gas, ErrDepth
}

Expand Down Expand Up @@ -393,9 +417,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// Depth check execution. Fail if we're trying to execute above the
// limit.
if evm.depth > int(params.CallCreateDepth) {
evm.LogFailure(common.Address{}, params.TopicError, params.ErrorLogDepth)
return nil, common.Address{}, gas, ErrDepth
}
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
evm.LogFailure(common.Address{}, params.TopicError, params.ErrorLogInsufficientBalance)
return nil, common.Address{}, gas, ErrInsufficientBalance
}
nonce := evm.StateDB.GetNonce(caller.Address())
Expand All @@ -404,6 +430,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// 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 != EmptyCodeHash) {
evm.LogFailure(common.Address{}, params.TopicError, params.ErrorLogContractAddressCollision)
return nil, common.Address{}, 0, ErrContractAddressCollision
}
// Create a new account on the state
Expand Down Expand Up @@ -441,6 +468,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if contract.UseGas(createDataGas) {
evm.StateDB.SetCode(address, ret)
} else {
evm.LogFailure(common.Address{}, params.TopicError, params.ErrorLogCodeStoreOutOfGas)
err = ErrCodeStoreOutOfGas
}
}
Expand All @@ -456,6 +484,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
}
// Assign err if contract code size exceeds the max while the err is still empty.
if maxCodeSizeExceeded && err == nil {
evm.LogFailure(common.Address{}, params.TopicError, params.ErrorLogMaxCodeSizeExceeded)
err = errMaxCodeSizeExceeded
}
if evm.vmConfig.Debug && evm.depth == 0 {
Expand Down
11 changes: 7 additions & 4 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ import (
var (
bigZero = new(big.Int)
tt255 = math.BigPow(2, 255)
errWriteProtection = errors.New("evm: write protection")
errReturnDataOutOfBounds = errors.New("evm: return data out of bounds")
errWriteProtection = errors.New("evm: " + params.ErrorLogWriteProtection)
errReturnDataOutOfBounds = errors.New("evm: " + params.ErrorLogReturnDataOutOfBounds)
errMaxCodeSizeExceeded = errors.New("evm: " + params.ErrorLogMaxCodeSizeExceeded)
errInvalidJump = errors.New("evm: " + params.ErrorLogInvalidJump)
errExecutionReverted = errors.New("evm: execution reverted")
errMaxCodeSizeExceeded = errors.New("evm: max code size exceeded")
errInvalidJump = errors.New("evm: invalid jump destination")
)

func opAdd(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
Expand Down Expand Up @@ -468,6 +468,7 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contrac
defer interpreter.intPool.put(memOffset, dataOffset, length, end)

if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() {
interpreter.evm.LogFailure(contract.Address(), params.TopicError, params.ErrorLogReturnDataOutOfBounds)
return nil, errReturnDataOutOfBounds
}
memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()])
Expand Down Expand Up @@ -645,6 +646,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor
func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
pos := stack.pop()
if !contract.validJumpdest(pos) {
interpreter.evm.LogFailure(contract.Address(), params.TopicError, params.ErrorLogInvalidJump)
return nil, errInvalidJump
}
*pc = pos.Uint64()
Expand All @@ -657,6 +659,7 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory
pos, cond := stack.pop(), stack.pop()
if cond.Sign() != 0 {
if !contract.validJumpdest(pos) {
interpreter.evm.LogFailure(contract.Address(), params.TopicError, params.ErrorLogInvalidJump)
return nil, errInvalidJump
}
*pc = pos.Uint64()
Expand Down
22 changes: 19 additions & 3 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
package vm

import (
"errors"
"fmt"
"hash"
"sync/atomic"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)

// Config are the configuration options for the Interpreter
Expand Down Expand Up @@ -190,6 +192,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}
}()
}

// 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
Expand All @@ -205,13 +208,19 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
op = contract.GetOp(pc)
operation := in.cfg.JumpTable[op]
if !operation.valid {
return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
failure := fmt.Sprintf(params.ErrorLogInvalidOpCode, int(op))
in.evm.LogFailure(contract.Address(), params.TopicError, failure)
return nil, errors.New(failure)
}
// Validate stack
if sLen := stack.len(); sLen < operation.minStack {
return nil, fmt.Errorf("stack underflow (%d <=> %d)", sLen, operation.minStack)
failure := fmt.Sprintf(params.ErrorLogStackUnderflow, sLen, operation.minStack)
in.evm.LogFailure(contract.Address(), params.TopicError, failure)
return nil, errors.New(failure)
} else if sLen > operation.maxStack {
return nil, fmt.Errorf("stack limit reached %d (%d)", sLen, operation.maxStack)
failure := fmt.Sprintf(params.ErrorLogStackLimitReached, sLen, operation.maxStack)
in.evm.LogFailure(contract.Address(), params.TopicError, failure)
return nil, errors.New(failure)
}
// If the operation is valid, enforce and write restrictions
if in.readOnly && in.evm.chainRules.IsByzantium {
Expand All @@ -221,12 +230,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// account to the others means the state is modified and should also
// return with an error.
if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
in.evm.LogFailure(contract.Address(), params.TopicError, params.ErrorLogWriteProtection)
return nil, errWriteProtection
}
}
// Static portion of gas
cost = operation.constantGas // For tracing
if !contract.UseGas(operation.constantGas) {
in.evm.LogFailure(contract.Address(), params.TopicError, params.ErrorLogOutOfGas)
return nil, ErrOutOfGas
}

Expand All @@ -238,11 +249,13 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
in.evm.LogFailure(contract.Address(), params.TopicError, params.ErrorLogGasUintOverflow)
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 {
in.evm.LogFailure(contract.Address(), params.TopicError, params.ErrorLogGasUintOverflow)
return nil, errGasUintOverflow
}
}
Expand All @@ -254,6 +267,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // total cost, for debug tracing
if err != nil || !contract.UseGas(dynamicCost) {
in.evm.LogFailure(contract.Address(), params.TopicError, params.ErrorLogOutOfGas)
return nil, ErrOutOfGas
}
}
Expand Down Expand Up @@ -283,6 +297,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
case err != nil:
return nil, err
case operation.reverts:
msg := params.GetSolidityRevertMessage(res)
in.evm.LogFailure(contract.Address(), params.TopicRevert, msg)
return res, errExecutionReverted
case operation.halts:
return res, nil
Expand Down
72 changes: 72 additions & 0 deletions params/log_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2015 The Nexty Authors
// This file is part of the gonex 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 <http://www.gnu.org/licenses/>.

package params

import (
"bytes"
"math/big"

"github.com/ethereum/go-ethereum/common"
)

const (
ErrorLogInvalidOpCode = "invalid opcode 0x%x"
ErrorLogStackUnderflow = "stack underflow (%d <=> %d)"
ErrorLogStackLimitReached = "stack limit reached %d (%d)"
ErrorLogWriteProtection = "write protection"
ErrorLogOutOfGas = "out of gas"
ErrorLogGasUintOverflow = "gas overflow unsigned 64 bit integer"
ErrorLogReturnDataOutOfBounds = "return data out of bounds"
ErrorLogMaxCodeSizeExceeded = "max code size exceeded"
ErrorLogInvalidJump = "invalid jump destination"
ErrorLogCodeStoreOutOfGas = "contract creation code storage out of gas"
ErrorLogDepth = "max call depth exceeded"
ErrorLogInsufficientBalance = "insufficient balance for transfer"
ErrorLogContractAddressCollision = "contract address collision"
)

var (
// TopicRevert is Keccak("REVERT")
TopicRevert = common.HexToHash("e13872d662304a4be4efe6d4425b00781f90609ddf2ef6e5b5e5c8bc7f5ed47f")

// TopicError is Keccak("ERROR")
TopicError = common.HexToHash("6368faa35d5ea15ae80b929d8626383bb91c2157389a6ddb6239282e6aa9005d")
)

var (
// SolidityErrorSignature is Keccak("Error(string)")
SolidityErrorSignature = []byte{0x08, 0xc3, 0x79, 0xa0}
)

// GetSolidityRevertMessage handles Solidity revert and require message.
func GetSolidityRevertMessage(res []byte) string {
if len(res) < 4+32+32 {
return string(res)
}
if bytes.Compare(res[:4], SolidityErrorSignature) != 0 {
return string(res)
}
res = res[4:]
offset := int(new(big.Int).SetBytes(res[:32]).Uint64())
res = res[32:]
size := int(new(big.Int).SetBytes(res[:32]).Uint64())
if len(res) < offset+size {
return string(res)
}
msg := string(res[offset : offset+size])
return msg
}

0 comments on commit 4d0740a

Please sign in to comment.