9dc29bf3f7
* feat: api: improve the correctness of Eth's trace_block (#11609) * Improve the correctness of Eth's trace_block - Improve encoding/decoding of parameters and return values: - Encode "native" parameters and return values with Solidity ABI. - Correctly decode parameters to "create" calls. - Use the correct (ish) output for "create" calls. - Handle all forms of "create". - Make robust with respect to reverts: - Use the actor ID/address from the trace instead of looking it up in the state-tree (may not exist in the state-tree due to a revert). - Gracefully handle failed actor/contract creation. - Improve performance: - We avoid looking anything up in the state-tree when translating the trace, which should significantly improve performance. - Improve code readability: - Remove all "backtracking" logic. - Use an "environment" struct to store temporary state instead of attaching it to the trace. - Fix random bugs: - Fix an allocation bug in the "address" logic (need to set the capacity before modifying the slice). - Improved error checking/handling. - Use correct types for `trace_block` action/results (create, call, etc.). - And use the correct types for Result/Action structs instead of reusing the same "Call" action every time. - Improve error messages. * Make gen Make gen --------- Co-authored-by: Steven Allen <steven@stebalien.com>
606 lines
22 KiB
Go
606 lines
22 KiB
Go
package full
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"github.com/multiformats/go-multicodec"
|
|
cbg "github.com/whyrusleeping/cbor-gen"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
"github.com/filecoin-project/go-state-types/builtin"
|
|
eam12 "github.com/filecoin-project/go-state-types/builtin/v12/eam"
|
|
evm12 "github.com/filecoin-project/go-state-types/builtin/v12/evm"
|
|
init12 "github.com/filecoin-project/go-state-types/builtin/v12/init"
|
|
"github.com/filecoin-project/go-state-types/exitcode"
|
|
|
|
builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin"
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/evm"
|
|
"github.com/filecoin-project/lotus/chain/state"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
|
)
|
|
|
|
// decodePayload is a utility function which decodes the payload using the given codec
|
|
func decodePayload(payload []byte, codec uint64) (ethtypes.EthBytes, error) {
|
|
switch multicodec.Code(codec) {
|
|
case multicodec.Identity:
|
|
return nil, nil
|
|
case multicodec.DagCbor, multicodec.Cbor:
|
|
buf, err := cbg.ReadByteArray(bytes.NewReader(payload), uint64(len(payload)))
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("decodePayload: failed to decode cbor payload: %w", err)
|
|
}
|
|
return buf, nil
|
|
case multicodec.Raw:
|
|
return ethtypes.EthBytes(payload), nil
|
|
}
|
|
|
|
return nil, xerrors.Errorf("decodePayload: unsupported codec: %d", codec)
|
|
}
|
|
|
|
func decodeParams[P any, T interface {
|
|
*P
|
|
cbg.CBORUnmarshaler
|
|
}](msg *types.MessageTrace) (T, error) {
|
|
var params T = new(P)
|
|
switch msg.ParamsCodec {
|
|
case uint64(multicodec.DagCbor), uint64(multicodec.Cbor):
|
|
default:
|
|
return nil, xerrors.Errorf("Method called with unexpected codec %d", msg.ParamsCodec)
|
|
}
|
|
|
|
if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil {
|
|
return nil, xerrors.Errorf("failed to decode params: %w", err)
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
func decodeReturn[R any, T interface {
|
|
*R
|
|
cbg.CBORUnmarshaler
|
|
}](ret *types.ReturnTrace) (T, error) {
|
|
var retval T = new(R)
|
|
switch ret.ReturnCodec {
|
|
case uint64(multicodec.DagCbor), uint64(multicodec.Cbor):
|
|
default:
|
|
return nil, xerrors.Errorf("Method returned an unexpected codec %d", ret.ReturnCodec)
|
|
}
|
|
|
|
if err := retval.UnmarshalCBOR(bytes.NewReader(ret.Return)); err != nil {
|
|
return nil, xerrors.Errorf("failed to decode return value: %w", err)
|
|
}
|
|
|
|
return retval, nil
|
|
}
|
|
|
|
func find[T any](values []T, cb func(t *T) *T) *T {
|
|
for i := range values {
|
|
if o := cb(&values[i]); o != nil {
|
|
return o
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type environment struct {
|
|
caller ethtypes.EthAddress
|
|
isEVM bool
|
|
subtraceCount int
|
|
traces []*ethtypes.EthTrace
|
|
lastByteCode *ethtypes.EthAddress
|
|
}
|
|
|
|
func baseEnvironment(st *state.StateTree, from address.Address) (*environment, error) {
|
|
sender, err := lookupEthAddress(from, st)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("top-level message sender %s s could not be found: %w", from, err)
|
|
}
|
|
return &environment{caller: sender}, nil
|
|
}
|
|
|
|
func traceToAddress(act *types.ActorTrace) ethtypes.EthAddress {
|
|
if act.State.Address != nil {
|
|
if addr, err := ethtypes.EthAddressFromFilecoinAddress(*act.State.Address); err == nil {
|
|
return addr
|
|
}
|
|
}
|
|
return ethtypes.EthAddressFromActorID(act.Id)
|
|
}
|
|
|
|
// traceIsEVMOrEAM returns true if the trace is a call to an EVM or EAM actor.
|
|
func traceIsEVMOrEAM(et *types.ExecutionTrace) bool {
|
|
if et.InvokedActor == nil {
|
|
return false
|
|
}
|
|
return builtinactors.IsEvmActor(et.InvokedActor.State.Code) ||
|
|
et.InvokedActor.Id != abi.ActorID(builtin.EthereumAddressManagerActorID)
|
|
}
|
|
|
|
func traceErrMsg(et *types.ExecutionTrace) string {
|
|
code := et.MsgRct.ExitCode
|
|
|
|
if code.IsSuccess() {
|
|
return ""
|
|
}
|
|
|
|
// EVM tools often expect this literal string.
|
|
if code == exitcode.SysErrOutOfGas {
|
|
return "out of gas"
|
|
}
|
|
|
|
// indicate when we have a "system" error.
|
|
if code < exitcode.FirstActorErrorCode {
|
|
return fmt.Sprintf("vm error: %s", code)
|
|
}
|
|
|
|
// handle special exit codes from the EVM/EAM.
|
|
if traceIsEVMOrEAM(et) {
|
|
switch code {
|
|
case evm.ErrReverted:
|
|
return "Reverted" // capitalized for compatibility
|
|
case evm.ErrInvalidInstruction:
|
|
return "invalid instruction"
|
|
case evm.ErrUndefinedInstruction:
|
|
return "undefined instruction"
|
|
case evm.ErrStackUnderflow:
|
|
return "stack underflow"
|
|
case evm.ErrStackOverflow:
|
|
return "stack overflow"
|
|
case evm.ErrIllegalMemoryAccess:
|
|
return "illegal memory access"
|
|
case evm.ErrBadJumpdest:
|
|
return "invalid jump destination"
|
|
case evm.ErrSelfdestructFailed:
|
|
return "self destruct failed"
|
|
}
|
|
}
|
|
// everything else...
|
|
return fmt.Sprintf("actor error: %s", code.Error())
|
|
}
|
|
|
|
// buildTraces recursively builds the traces for a given ExecutionTrace by walking the subcalls
|
|
func buildTraces(env *environment, addr []int, et *types.ExecutionTrace) error {
|
|
trace, recurseInto, err := buildTrace(env, addr, et)
|
|
if err != nil {
|
|
return xerrors.Errorf("at trace %v: %w", addr, err)
|
|
}
|
|
|
|
if trace != nil {
|
|
env.traces = append(env.traces, trace)
|
|
env.subtraceCount++
|
|
}
|
|
|
|
// Skip if there's nothing more to do and/or `buildTrace` told us to skip this one.
|
|
if recurseInto == nil || recurseInto.InvokedActor == nil || len(recurseInto.Subcalls) == 0 {
|
|
return nil
|
|
}
|
|
|
|
subEnv := &environment{
|
|
caller: traceToAddress(recurseInto.InvokedActor),
|
|
isEVM: builtinactors.IsEvmActor(recurseInto.InvokedActor.State.Code),
|
|
traces: env.traces,
|
|
}
|
|
// Set capacity to the length so each `append` below creates a new slice. Otherwise, we'll
|
|
// end up repeatedly mutating previous paths.
|
|
addr = addr[:len(addr):len(addr)]
|
|
for i := range recurseInto.Subcalls {
|
|
err := buildTraces(subEnv, append(addr, subEnv.subtraceCount), &recurseInto.Subcalls[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
trace.Subtraces = subEnv.subtraceCount
|
|
env.traces = subEnv.traces
|
|
|
|
return nil
|
|
}
|
|
|
|
// buildTrace processes the passed execution trace and updates the environment, if necessary.
|
|
//
|
|
// On success, it returns a trace to add (or nil to skip) and the trace recurse into (or nil to skip).
|
|
func buildTrace(env *environment, addr []int, et *types.ExecutionTrace) (*ethtypes.EthTrace, *types.ExecutionTrace, error) {
|
|
// This function first assumes that the call is a "native" call, then handles all the "not
|
|
// native" cases. If we get any unexpected results in any of these special cases, we just
|
|
// keep the "native" interpretation and move on.
|
|
//
|
|
// 1. If we're invoking a contract (even if the caller is a native account/actor), we
|
|
// attempt to decode the params/return value as a contract invocation.
|
|
// 2. If we're calling the EAM and/or init actor, we try to treat the call as a CREATE.
|
|
// 3. Finally, if the caller is an EVM smart contract and it's calling a "private" (1-1023)
|
|
// method, we know something special is going on. We look for calls related to
|
|
// DELEGATECALL and drop everything else (everything else includes calls triggered by,
|
|
// e.g., EXTCODEHASH).
|
|
|
|
// If we don't have sufficient funds, or we have a fatal error, or we have some
|
|
// other syscall error: skip the entire trace to mimic Ethereum (Ethereum records
|
|
// traces _after_ checking things like this).
|
|
//
|
|
// NOTE: The FFI currently folds all unknown syscall errors into "sys assertion
|
|
// failed" which is turned into SysErrFatal.
|
|
if len(addr) > 0 {
|
|
switch et.MsgRct.ExitCode {
|
|
case exitcode.SysErrInsufficientFunds, exitcode.SysErrFatal:
|
|
return nil, nil, nil
|
|
}
|
|
}
|
|
|
|
// We may fail before we can even invoke the actor. In that case, we have no 100% reliable
|
|
// way of getting its address (e.g., due to reverts) so we're just going to drop the entire
|
|
// trace. This is OK (ish) because the call never really "happened".
|
|
if et.InvokedActor == nil {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// Step 2: Decode as a contract invocation
|
|
//
|
|
// Normal EVM calls. We don't care if the caller/receiver are actually EVM actors, we only
|
|
// care if the call _looks_ like an EVM call. If we fail to decode it as an EVM call, we
|
|
// fallback on interpreting it as a native call.
|
|
if et.Msg.Method == builtin.MethodsEVM.InvokeContract {
|
|
return traceEVMCall(env, addr, et)
|
|
}
|
|
|
|
// Step 3: Decode as a contract deployment
|
|
switch et.Msg.To {
|
|
// NOTE: this will only catch _direct_ calls to the init actor. Calls through the EAM will
|
|
// be caught and _skipped_ below in the next case.
|
|
case builtin.InitActorAddr:
|
|
switch et.Msg.Method {
|
|
case builtin.MethodsInit.Exec, builtin.MethodsInit.Exec4:
|
|
return traceNativeCreate(env, addr, et)
|
|
}
|
|
case builtin.EthereumAddressManagerActorAddr:
|
|
switch et.Msg.Method {
|
|
case builtin.MethodsEAM.Create, builtin.MethodsEAM.Create2, builtin.MethodsEAM.CreateExternal:
|
|
return traceEthCreate(env, addr, et)
|
|
}
|
|
}
|
|
|
|
// Step 4: Handle DELEGATECALL
|
|
//
|
|
// EVM contracts cannot call methods in the range 1-1023, only the EVM itself can. So, if we
|
|
// see a call in this range, we know it's an implementation detail of the EVM and not an
|
|
// explicit call by the user.
|
|
//
|
|
// While the EVM calls several methods in this range (some we've already handled above with
|
|
// respect to the EAM), we only care about the ones relevant DELEGATECALL and can _ignore_
|
|
// all the others.
|
|
if env.isEVM && et.Msg.Method > 0 && et.Msg.Method < 1024 {
|
|
return traceEVMPrivate(env, addr, et)
|
|
}
|
|
|
|
return traceNativeCall(env, addr, et), et, nil
|
|
}
|
|
|
|
// Build an EthTrace for a "call" with the given input & output.
|
|
func traceCall(env *environment, addr []int, et *types.ExecutionTrace, input, output ethtypes.EthBytes) *ethtypes.EthTrace {
|
|
to := traceToAddress(et.InvokedActor)
|
|
callType := "call"
|
|
if et.Msg.ReadOnly {
|
|
callType = "staticcall"
|
|
}
|
|
return ðtypes.EthTrace{
|
|
Type: "call",
|
|
Action: ðtypes.EthCallTraceAction{
|
|
CallType: callType,
|
|
From: env.caller,
|
|
To: to,
|
|
Gas: ethtypes.EthUint64(et.Msg.GasLimit),
|
|
Value: ethtypes.EthBigInt(et.Msg.Value),
|
|
Input: input,
|
|
},
|
|
Result: ðtypes.EthCallTraceResult{
|
|
GasUsed: ethtypes.EthUint64(et.SumGas().TotalGas),
|
|
Output: output,
|
|
},
|
|
TraceAddress: addr,
|
|
Error: traceErrMsg(et),
|
|
}
|
|
}
|
|
|
|
// Build an EthTrace for a "call", parsing the inputs & outputs as a "native" FVM call.
|
|
func traceNativeCall(env *environment, addr []int, et *types.ExecutionTrace) *ethtypes.EthTrace {
|
|
return traceCall(env, addr, et,
|
|
encodeFilecoinParamsAsABI(et.Msg.Method, et.Msg.ParamsCodec, et.Msg.Params),
|
|
encodeFilecoinReturnAsABI(et.MsgRct.ExitCode, et.MsgRct.ReturnCodec, et.MsgRct.Return),
|
|
)
|
|
}
|
|
|
|
// Build an EthTrace for a "call", parsing the inputs & outputs as an EVM call (falling back on
|
|
// treating it as a native call).
|
|
func traceEVMCall(env *environment, addr []int, et *types.ExecutionTrace) (*ethtypes.EthTrace, *types.ExecutionTrace, error) {
|
|
input, err := decodePayload(et.Msg.Params, et.Msg.ParamsCodec)
|
|
if err != nil {
|
|
log.Debugf("failed to decode contract invocation payload: %w", err)
|
|
return traceNativeCall(env, addr, et), et, nil
|
|
}
|
|
output, err := decodePayload(et.MsgRct.Return, et.MsgRct.ReturnCodec)
|
|
if err != nil {
|
|
log.Debugf("failed to decode contract invocation return: %w", err)
|
|
return traceNativeCall(env, addr, et), et, nil
|
|
}
|
|
return traceCall(env, addr, et, input, output), et, nil
|
|
}
|
|
|
|
// Build an EthTrace for a native "create" operation. This should only be called with an
|
|
// ExecutionTrace is an Exec or Exec4 method invocation on the Init actor.
|
|
func traceNativeCreate(env *environment, addr []int, et *types.ExecutionTrace) (*ethtypes.EthTrace, *types.ExecutionTrace, error) {
|
|
if et.Msg.ReadOnly {
|
|
// "create" isn't valid in a staticcall, so we just skip this trace
|
|
// (couldn't have created an actor anyways).
|
|
// This mimic's the EVM: it doesn't trace CREATE calls when in
|
|
// read-only mode.
|
|
return nil, nil, nil
|
|
}
|
|
|
|
subTrace := find(et.Subcalls, func(c *types.ExecutionTrace) *types.ExecutionTrace {
|
|
if c.Msg.Method == builtin.MethodConstructor {
|
|
return c
|
|
}
|
|
return nil
|
|
})
|
|
if subTrace == nil {
|
|
// If we succeed in calling Exec/Exec4 but don't even try to construct
|
|
// something, we have a bug in our tracing logic or a mismatch between our
|
|
// tracing logic and the actors.
|
|
if et.MsgRct.ExitCode.IsSuccess() {
|
|
return nil, nil, xerrors.Errorf("successful Exec/Exec4 call failed to call a constructor")
|
|
}
|
|
// Otherwise, this can happen if creation fails early (bad params,
|
|
// out of gas, contract already exists, etc.). The EVM wouldn't
|
|
// trace such cases, so we don't either.
|
|
//
|
|
// NOTE: It's actually impossible to run out of gas before calling
|
|
// initcode in the EVM (without running out of gas in the calling
|
|
// contract), but this is an equivalent edge-case to InvokedActor
|
|
// being nil, so we treat it the same way and skip the entire
|
|
// operation.
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// Native actors that aren't the EAM can attempt to call Exec4, but such
|
|
// call should fail immediately without ever attempting to construct an
|
|
// actor. I'm catching this here because it likely means that there's a bug
|
|
// in our trace-conversion logic.
|
|
if et.Msg.Method == builtin.MethodsInit.Exec4 {
|
|
return nil, nil, xerrors.Errorf("direct call to Exec4 successfully called a constructor!")
|
|
}
|
|
|
|
var output ethtypes.EthBytes
|
|
var createdAddr *ethtypes.EthAddress
|
|
if et.MsgRct.ExitCode.IsSuccess() {
|
|
// We're supposed to put the "installed bytecode" here. But this
|
|
// isn't an EVM actor, so we just put some invalid bytecode (this is
|
|
// the answer you'd get if you called EXTCODECOPY on a native
|
|
// non-account actor, anyways).
|
|
output = []byte{0xFE}
|
|
|
|
// Extract the address of the created actor from the return value.
|
|
initReturn, err := decodeReturn[init12.ExecReturn](&et.MsgRct)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("failed to decode init params after a successful Init.Exec call: %w", err)
|
|
}
|
|
actorId, err := address.IDFromAddress(initReturn.IDAddress)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("failed to extract created actor ID from address: %w", err)
|
|
}
|
|
ethAddr := ethtypes.EthAddressFromActorID(abi.ActorID(actorId))
|
|
createdAddr = ðAddr
|
|
}
|
|
|
|
return ðtypes.EthTrace{
|
|
Type: "create",
|
|
Action: ðtypes.EthCreateTraceAction{
|
|
From: env.caller,
|
|
Gas: ethtypes.EthUint64(et.Msg.GasLimit),
|
|
Value: ethtypes.EthBigInt(et.Msg.Value),
|
|
// If we get here, this isn't a native EVM create. Those always go through
|
|
// the EAM. So we have no "real" initcode and must use the sentinel value
|
|
// for "invalid" initcode.
|
|
Init: []byte{0xFE},
|
|
},
|
|
Result: ðtypes.EthCreateTraceResult{
|
|
GasUsed: ethtypes.EthUint64(et.SumGas().TotalGas),
|
|
Address: createdAddr,
|
|
Code: output,
|
|
},
|
|
TraceAddress: addr,
|
|
Error: traceErrMsg(et),
|
|
}, subTrace, nil
|
|
}
|
|
|
|
// Assert that these are all identical so we can simplify the below code and decode once.
|
|
var _ *eam12.Return = (*eam12.Return)((*eam12.CreateReturn)(nil))
|
|
var _ *eam12.Return = (*eam12.Return)((*eam12.Create2Return)(nil))
|
|
var _ *eam12.Return = (*eam12.Return)((*eam12.CreateExternalReturn)(nil))
|
|
|
|
// Decode the parameters and return value of an EVM smart contract creation through the EAM. This
|
|
// should only be called with an ExecutionTrace for a Create, Create2, or CreateExternal method
|
|
// invocation on the EAM.
|
|
func decodeCreateViaEAM(et *types.ExecutionTrace) (initcode []byte, addr *ethtypes.EthAddress, err error) {
|
|
switch et.Msg.Method {
|
|
case builtin.MethodsEAM.Create:
|
|
params, err := decodeParams[eam12.CreateParams](&et.Msg)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
initcode = params.Initcode
|
|
case builtin.MethodsEAM.Create2:
|
|
params, err := decodeParams[eam12.Create2Params](&et.Msg)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
initcode = params.Initcode
|
|
case builtin.MethodsEAM.CreateExternal:
|
|
input, err := decodePayload(et.Msg.Params, et.Msg.ParamsCodec)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
initcode = input
|
|
default:
|
|
return nil, nil, xerrors.Errorf("unexpected CREATE method %d", et.Msg.Method)
|
|
}
|
|
ret, err := decodeReturn[eam12.CreateReturn](&et.MsgRct)
|
|
if err != nil {
|
|
return nil, (*ethtypes.EthAddress)(&ret.EthAddress), err
|
|
}
|
|
return initcode, (*ethtypes.EthAddress)(&ret.EthAddress), nil
|
|
}
|
|
|
|
// Build an EthTrace for an EVM "create" operation. This should only be called with an
|
|
// ExecutionTrace for a Create, Create2, or CreateExternal method invocation on the EAM.
|
|
func traceEthCreate(env *environment, addr []int, et *types.ExecutionTrace) (*ethtypes.EthTrace, *types.ExecutionTrace, error) {
|
|
// Same as the Init actor case above, see the comment there.
|
|
if et.Msg.ReadOnly {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// Look for a call to either a constructor or the EVM's resurrect method.
|
|
subTrace := find(et.Subcalls, func(et *types.ExecutionTrace) *types.ExecutionTrace {
|
|
if et.Msg.To == builtinactors.InitActorAddr {
|
|
return find(et.Subcalls, func(et *types.ExecutionTrace) *types.ExecutionTrace {
|
|
if et.Msg.Method == builtinactors.MethodConstructor {
|
|
return et
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
if et.Msg.Method == builtin.MethodsEVM.Resurrect {
|
|
return et
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Same as the Init actor case above, see the comment there.
|
|
if subTrace == nil {
|
|
if et.MsgRct.ExitCode.IsSuccess() {
|
|
return nil, nil, xerrors.Errorf("successful Create/Create2 call failed to call a constructor")
|
|
}
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// Decode inputs & determine create type.
|
|
initcode, createdAddr, err := decodeCreateViaEAM(et)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("EAM called with invalid params or returned an invalid result, but it still tried to construct the contract: %w", err)
|
|
}
|
|
|
|
var output ethtypes.EthBytes
|
|
// Handle the output.
|
|
switch et.MsgRct.ExitCode {
|
|
case 0: // success
|
|
// We're _supposed_ to include the contracts bytecode here, but we
|
|
// can't do that reliably (e.g., if some part of the trace reverts).
|
|
// So we don't try and include a sentinel "impossible bytecode"
|
|
// value (the value specified by EIP-3541).
|
|
output = []byte{0xFE}
|
|
case 33: // Reverted, parse the revert message.
|
|
// If we managed to call the constructor, parse/return its revert message. If we
|
|
// fail, we just return no output.
|
|
output, _ = decodePayload(subTrace.MsgRct.Return, subTrace.MsgRct.ReturnCodec)
|
|
}
|
|
|
|
return ðtypes.EthTrace{
|
|
Type: "create",
|
|
Action: ðtypes.EthCreateTraceAction{
|
|
From: env.caller,
|
|
Gas: ethtypes.EthUint64(et.Msg.GasLimit),
|
|
Value: ethtypes.EthBigInt(et.Msg.Value),
|
|
Init: initcode,
|
|
},
|
|
Result: ðtypes.EthCreateTraceResult{
|
|
GasUsed: ethtypes.EthUint64(et.SumGas().TotalGas),
|
|
Address: createdAddr,
|
|
Code: output,
|
|
},
|
|
TraceAddress: addr,
|
|
Error: traceErrMsg(et),
|
|
}, subTrace, nil
|
|
}
|
|
|
|
// Build an EthTrace for a "private" method invocation from the EVM. This should only be called with
|
|
// an ExecutionTrace from an EVM instance and on a method between 1 and 1023 inclusive.
|
|
func traceEVMPrivate(env *environment, addr []int, et *types.ExecutionTrace) (*ethtypes.EthTrace, *types.ExecutionTrace, error) {
|
|
// The EVM actor implements DELEGATECALL by:
|
|
//
|
|
// 1. Asking the callee for its bytecode by calling it on the GetBytecode method.
|
|
// 2. Recursively invoking the currently executing contract on the
|
|
// InvokeContractDelegate method.
|
|
//
|
|
// The code below "reconstructs" that delegate call by:
|
|
//
|
|
// 1. Remembering the last contract on which we called GetBytecode.
|
|
// 2. Treating the contract invoked in step 1 as the DELEGATECALL receiver.
|
|
//
|
|
// Note, however: GetBytecode will be called, e.g., if the user invokes the
|
|
// EXTCODECOPY instruction. It's not an error to see multiple GetBytecode calls
|
|
// before we see an InvokeContractDelegate.
|
|
switch et.Msg.Method {
|
|
case builtin.MethodsEVM.GetBytecode:
|
|
// NOTE: I'm not checking anything about the receiver here. The EVM won't
|
|
// DELEGATECALL any non-EVM actor, but there's no need to encode that fact
|
|
// here in case we decide to loosen this up in the future.
|
|
if et.MsgRct.ExitCode.IsSuccess() {
|
|
to := traceToAddress(et.InvokedActor)
|
|
env.lastByteCode = &to
|
|
} else {
|
|
env.lastByteCode = nil
|
|
}
|
|
return nil, nil, nil
|
|
case builtin.MethodsEVM.InvokeContractDelegate:
|
|
// NOTE: We return errors in all the failure cases below instead of trying
|
|
// to continue because the caller is an EVM actor. If something goes wrong
|
|
// here, there's a bug in our EVM implementation.
|
|
|
|
// Handle delegate calls
|
|
//
|
|
// 1) Look for trace from an EVM actor to itself on InvokeContractDelegate,
|
|
// method 6.
|
|
// 2) Check that the previous trace calls another actor on method 3
|
|
// (GetByteCode) and they are at the same level (same parent)
|
|
// 3) Treat this as a delegate call to actor A.
|
|
if env.lastByteCode == nil {
|
|
return nil, nil, xerrors.Errorf("unknown bytecode for delegate call")
|
|
}
|
|
|
|
if to := traceToAddress(et.InvokedActor); env.caller != to {
|
|
return nil, nil, xerrors.Errorf("delegate-call not from & to self: %s != %s", env.caller, to)
|
|
}
|
|
|
|
dp, err := decodeParams[evm12.DelegateCallParams](&et.Msg)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("failed to decode delegate-call params: %w", err)
|
|
}
|
|
|
|
output, err := decodePayload(et.MsgRct.Return, et.MsgRct.ReturnCodec)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("failed to decode delegate-call return: %w", err)
|
|
}
|
|
|
|
return ðtypes.EthTrace{
|
|
Type: "call",
|
|
Action: ðtypes.EthCallTraceAction{
|
|
CallType: "delegatecall",
|
|
From: env.caller,
|
|
To: *env.lastByteCode,
|
|
Gas: ethtypes.EthUint64(et.Msg.GasLimit),
|
|
Value: ethtypes.EthBigInt(et.Msg.Value),
|
|
Input: dp.Input,
|
|
},
|
|
Result: ðtypes.EthCallTraceResult{
|
|
GasUsed: ethtypes.EthUint64(et.SumGas().TotalGas),
|
|
Output: output,
|
|
},
|
|
TraceAddress: addr,
|
|
Error: traceErrMsg(et),
|
|
}, et, nil
|
|
}
|
|
// We drop all other "private" calls from FEVM. We _forbid_ explicit calls between 0 and
|
|
// 1024 (exclusive), so any calls in this range must be implementation details.
|
|
return nil, nil, nil
|
|
}
|