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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- chore(deps): update of quic-go to v0.54.1 and go-libp2p to v0.43.0 ([filecoin-project/lotus#13361](https://github.com/filecoin-project/lotus/pull/13361))
- feat(spcli): add a `deposit-margin-factor` option to `lotus-miner actor new` and `lotus-shed miner create` so the sent deposit still covers the on-chain requirement if it rises between lookup and execution
- feat(cli): lotus evm deploy prints message CID ([filecoin-project/lotus#13378](https://github.com/filecoin-project/lotus/pull/13378))
- fix(eth): properly return vm error in all gas estimation methods ([filecoin-project/lotus#13389](https://github.com/filecoin-project/lotus/pull/13389))

# Node and Miner v1.34.1 / 2025-09-15

Expand Down
16 changes: 16 additions & 0 deletions api/api_errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"bytes"
"errors"
"fmt"
"reflect"
Expand All @@ -10,6 +11,8 @@ import (
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/exitcode"

"github.com/filecoin-project/lotus/chain/types/ethtypes"
)

var invalidExecutionRevertedMsg = xerrors.New("invalid execution reverted error")
Expand Down Expand Up @@ -173,6 +176,19 @@ func NewErrExecutionReverted(exitCode exitcode.ExitCode, error, reason string, d
}
}

// NewErrExecutionRevertedFromResult creates an ErrExecutionReverted from an InvocResult.
// It decodes the CBOR-encoded return data and parses any Ethereum revert reason.
func NewErrExecutionRevertedFromResult(res *InvocResult) error {
reason := "none"
var cbytes abi.CborBytes
if err := cbytes.UnmarshalCBOR(bytes.NewReader(res.MsgRct.Return)); err != nil {
reason = "ERROR: revert reason is not cbor encoded bytes"
} else if len(cbytes) > 0 {
reason = ethtypes.ParseEthRevert(cbytes)
}
return NewErrExecutionReverted(res.MsgRct.ExitCode, res.Error, reason, cbytes)
}

type ErrNullRound struct {
Epoch abi.ChainEpoch
Message string
Expand Down
70 changes: 70 additions & 0 deletions chain/types/ethtypes/eth_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1278,3 +1278,73 @@ type EthTxReceipt struct {
Logs []EthLog `json:"logs"`
Type EthUint64 `json:"type"`
}

const errorFunctionSelector = "\x08\xc3\x79\xa0" // Error(string)
const panicFunctionSelector = "\x4e\x48\x7b\x71" // Panic(uint256)

// panicErrorCodes maps Solidity panic codes to human-readable descriptions.
var panicErrorCodes = map[uint64]string{
0x00: "Panic()",
0x01: "Assert()",
0x11: "ArithmeticOverflow()",
Comment thread
rvagg marked this conversation as resolved.
0x12: "DivideByZero()",
0x21: "InvalidEnumVariant()",
0x22: "InvalidStorageArray()",
0x31: "PopEmptyArray()",
0x32: "ArrayIndexOutOfBounds()",
0x41: "OutOfMemory()",
0x51: "CalledUninitializedFunction()",
}

// ParseEthRevert decodes an Ethereum ABI-encoded revert reason.
// This handles both Error(string) and Panic(uint256) revert types.
//
// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
func ParseEthRevert(ret []byte) string {
// If it's not long enough to contain an ABI encoded response, return immediately.
if len(ret) < 4+32 {
return EthBytes(ret).String()
}
switch string(ret[:4]) {
case panicFunctionSelector:
ret := ret[4 : 4+32]
// Read and check the code.
code, err := EthUint64FromBytes(ret)
if err != nil {
// If it's too big, just return the raw value.
codeInt := big.PositiveFromUnsignedBytes(ret)
return fmt.Sprintf("Panic(%s)", EthBigInt(codeInt).String())
}
if s, ok := panicErrorCodes[uint64(code)]; ok {
return s
}
return fmt.Sprintf("Panic(0x%x)", code)
case errorFunctionSelector:
ret := ret[4:]
retLen := EthUint64(len(ret))
// Read and check the offset.
offset, err := EthUint64FromBytes(ret[:32])
if err != nil {
break
}
if retLen < offset {
break
}

// Read and check the length.
if retLen-offset < 32 {
break
}
start := offset + 32
length, err := EthUint64FromBytes(ret[offset : offset+32])
if err != nil {
break
}
if retLen-start < length {
break
}
// Slice the error message.
return fmt.Sprintf("Error(%s)", ret[start:start+length])
}
return EthBytes(ret).String()
}
13 changes: 2 additions & 11 deletions node/impl/eth/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,16 +289,7 @@ func (e *ethGas) applyMessage(ctx context.Context, msg *types.Message, tsk types
}

if res.MsgRct.ExitCode.IsError() {
reason := "none"
var cbytes abi.CborBytes
if err := cbytes.UnmarshalCBOR(bytes.NewReader(res.MsgRct.Return)); err != nil {
log.Warnw("failed to unmarshal cbor bytes from message receipt return", "error", err)
reason = "ERROR: revert reason is not cbor encoded bytes"
} // else leave as empty bytes
if len(cbytes) > 0 {
reason = parseEthRevert(cbytes)
}
return nil, api.NewErrExecutionReverted(res.MsgRct.ExitCode, reason, res.Error, cbytes)
return nil, api.NewErrExecutionRevertedFromResult(res)
}

return res, nil
Expand Down Expand Up @@ -337,7 +328,7 @@ func ethGasSearch(
return ret, nil
}

return -1, xerrors.Errorf("message execution failed: exit %s, reason: %s", res.MsgRct.ExitCode, res.Error)
return -1, api.NewErrExecutionRevertedFromResult(res)
}

func traceContainsExitCode(et types.ExecutionTrace, ex exitcode.ExitCode) bool {
Expand Down
69 changes: 0 additions & 69 deletions node/impl/eth/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,75 +142,6 @@ func executeTipset(ctx context.Context, ts *types.TipSet, cs ChainStore, sm Stat
return stRoot, msgs, rcpts, nil
}

const errorFunctionSelector = "\x08\xc3\x79\xa0" // Error(string)
const panicFunctionSelector = "\x4e\x48\x7b\x71" // Panic(uint256)
// Eth ABI (solidity) panic codes.
var panicErrorCodes = map[uint64]string{
0x00: "Panic()",
0x01: "Assert()",
0x11: "ArithmeticOverflow()",
0x12: "DivideByZero()",
0x21: "InvalidEnumVariant()",
0x22: "InvalidStorageArray()",
0x31: "PopEmptyArray()",
0x32: "ArrayIndexOutOfBounds()",
0x41: "OutOfMemory()",
0x51: "CalledUninitializedFunction()",
}

// Parse an ABI encoded revert reason. This reason should be encoded as if it were the parameters to
// an `Error(string)` function call.
//
// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
func parseEthRevert(ret []byte) string {
// If it's not long enough to contain an ABI encoded response, return immediately.
if len(ret) < 4+32 {
return ethtypes.EthBytes(ret).String()
}
switch string(ret[:4]) {
case panicFunctionSelector:
ret := ret[4 : 4+32]
// Read the and check the code.
code, err := ethtypes.EthUint64FromBytes(ret)
if err != nil {
// If it's too big, just return the raw value.
codeInt := big.PositiveFromUnsignedBytes(ret)
return fmt.Sprintf("Panic(%s)", ethtypes.EthBigInt(codeInt).String())
}
if s, ok := panicErrorCodes[uint64(code)]; ok {
return s
}
return fmt.Sprintf("Panic(0x%x)", code)
case errorFunctionSelector:
ret := ret[4:]
retLen := ethtypes.EthUint64(len(ret))
// Read the and check the offset.
offset, err := ethtypes.EthUint64FromBytes(ret[:32])
if err != nil {
break
}
if retLen < offset {
break
}

// Read and check the length.
if retLen-offset < 32 {
break
}
start := offset + 32
length, err := ethtypes.EthUint64FromBytes(ret[offset : offset+32])
if err != nil {
break
}
if retLen-start < length {
break
}
// Slice the error message.
return fmt.Sprintf("Error(%s)", ret[start:start+length])
}
return ethtypes.EthBytes(ret).String()
}

// lookupEthAddress makes its best effort at finding the Ethereum address for a
// Filecoin address. It does the following:
//
Expand Down
2 changes: 1 addition & 1 deletion node/impl/gasutils/gasutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func GasEstimateGasLimit(
}

if res.MsgRct.ExitCode != exitcode.Ok {
return -1, xerrors.Errorf("message execution failed: exit %s, reason: %s", res.MsgRct.ExitCode, res.Error)
return -1, api.NewErrExecutionRevertedFromResult(res)
}

ret := res.MsgRct.GasUsed
Expand Down
Loading