From af5ccee34c4500bfa6d4110a2843d22b06c7ba11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Mon, 21 Oct 2024 18:50:39 +0200 Subject: [PATCH] Finish fake-evm transaction calls --- packages/evm/jsonrpc/evmchain.go | 58 +++++++++++++------ .../evm/jsonrpc/jsonrpctest/jsonrpc_test.go | 44 ++++++++++++++ packages/evm/jsonrpc/types.go | 13 +++++ 3 files changed, 97 insertions(+), 18 deletions(-) diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index d1b5a3080e..ac744ac095 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -10,6 +10,7 @@ import ( "math" "math/big" "path" + "slices" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -665,12 +666,26 @@ func (e *EVMChain) iscRequestsInBlock(evmBlockNumber uint64) (*blocklog.BlockInf return blocklog.GetRequestsInBlock(blocklogStatePartition, iscBlockIndex) } -func (e *EVMChain) trace(config *tracers.TraceConfig, blockInfo *blocklog.BlockInfo, requestsInBlock []isc.Request, txIndex uint64, txHash common.Hash, blockNumber uint64, blockHash common.Hash) (json.RawMessage, error) { +func (e *EVMChain) isFakeTransaction(tx *types.Transaction) bool { + sender, err := evmutil.GetSender(tx) + + // the error will fire when the transaction is invalid. This is most of the time a fake evm tx we use for internal calls, therefore it's fine to assume both. + if slices.Equal(sender.Bytes(), common.Address{}.Bytes()) || err != nil { + return true + } + + return false +} + +// Trace allows the tracing of EVM transactions and considers "fake" evm transactions that are emitted when ISC internal requests are being made. (Transfer of funds from L1->L2EVM for example) +func (e *EVMChain) trace(config *tracers.TraceConfig, blockInfo *blocklog.BlockInfo, requestsInBlock []isc.Request, evmTxs types.Transactions, txIndex uint64, txHash common.Hash, blockHash common.Hash) (json.RawMessage, error) { tracerType := "callTracer" if config.Tracer != nil { tracerType = *config.Tracer } + blockNumber := uint64(blockInfo.BlockIndex()) + tracer, err := newTracer(tracerType, &tracers.Context{ BlockHash: blockHash, BlockNumber: new(big.Int).SetUint64(blockNumber), @@ -689,11 +704,24 @@ func (e *EVMChain) trace(config *tracers.TraceConfig, blockInfo *blocklog.BlockI &blockNumber, tracer, ) + + result, err := tracer.GetResult() if err != nil { - return nil, err + if err != nil && !errors.Is(err, ErrIncorrectTopLevelCalls) { + return nil, err + } + + tx, ok := lo.Find(evmTxs, func(tx *types.Transaction) bool { return slices.Equal(txHash.Bytes(), tx.Hash().Bytes()) }) + if !ok { + return nil, fmt.Errorf("can not find transaction: %v", txHash.String()) + } + + if e.isFakeTransaction(tx) { + return json.Marshal(RPCMarshalTransactionForFakeTX(tx, tx.GasPrice())) + } } - return tracer.GetResult() + return result, nil } func (e *EVMChain) traceTransaction(config *tracers.TraceConfig, txIndex uint64, txHash common.Hash, blockNumber uint64, blockHash common.Hash) (any, error) { @@ -702,42 +730,36 @@ func (e *EVMChain) traceTransaction(config *tracers.TraceConfig, txIndex uint64, return nil, err } - result, err := e.trace(config, iscBlock, iscRequestsInBlock, txIndex, txHash, blockNumber, blockHash) + blockTxs, err := e.txsByBlockNumber(new(big.Int).SetUint64(blockNumber)) if err != nil { return nil, err } - return result, nil + return e.trace(config, iscBlock, iscRequestsInBlock, blockTxs, txIndex, txHash, blockHash) } -func (e *EVMChain) traceBlock(config *tracers.TraceConfig, blockNumber uint64, blockHash common.Hash) (any, error) { - iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(blockNumber) +func (e *EVMChain) traceBlock(config *tracers.TraceConfig, block *types.Block) (any, error) { + iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(block.NumberU64()) if err != nil { return nil, err } - blockTxs, err := e.txsByBlockNumber(new(big.Int).SetUint64(blockNumber)) + blockTxs, err := e.txsByBlockNumber(new(big.Int).SetUint64(block.NumberU64())) if err != nil { return nil, err } results := make([]TxTraceResult, 0) - for i, tx := range blockTxs { - result, err := e.trace(config, iscBlock, iscRequestsInBlock, uint64(i), tx.Hash(), blockNumber, blockHash) + result, err := e.trace(config, iscBlock, iscRequestsInBlock, blockTxs, uint64(i), tx.Hash(), block.Hash()) + // Transactions which failed tracing will be omitted, so the rest of the block can be returned if err == nil { results = append(results, TxTraceResult{ TxHash: tx.Hash(), Result: result, }) } - - if err != nil && !errors.Is(err, ErrIncorrectTopLevelCalls) { - return nil, err - } - - // Continue the loop for next TXs } return results, nil @@ -765,7 +787,7 @@ func (e *EVMChain) TraceBlockByHash(blockHash common.Hash, config *tracers.Trace return nil, fmt.Errorf("block not found: %s", blockHash.String()) } - return e.traceBlock(config, block.Number().Uint64(), blockHash) + return e.traceBlock(config, block) } func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceConfig) (any, error) { @@ -776,7 +798,7 @@ func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceC return nil, fmt.Errorf("block not found: %d", blockNumber) } - return e.traceBlock(config, blockNumber, block.Hash()) + return e.traceBlock(config, block) } func (e *EVMChain) GetRawBlock(blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index 1daac43941..3ba3632d4c 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -6,6 +6,7 @@ package jsonrpctest import ( "context" "encoding/json" + "fmt" "math/big" "slices" "strings" @@ -607,6 +608,49 @@ func TestRPCTraceTx(t *testing.T) { require.Contains(t, trace2.GasUsed.String(), "0x") } +// Transfer calls produce "fake" Transactions to simulate EVM behavior. +// They are not real in the sense of being persisted to the blockchain, therefore requires additional checks. +func TestRPCTraceEvmDeposit(t *testing.T) { + env := newSoloTestEnv(t) + wallet, _ := env.solo.NewKeyPairWithFunds() + _, evmAddr := env.soloChain.NewEthereumAccountWithL2Funds() + + err := env.soloChain.TransferAllowanceTo( + isc.NewAssetsBaseTokens(1000), + isc.NewEthereumAddressAgentID(env.soloChain.ChainID, evmAddr), + wallet) + + block := env.BlockByNumber(nil) + require.NoError(t, err) + txs := block.Transactions() + tx := txs[0] + + require.Equal(t, evmAddr, *tx.To()) + + rc, err := env.TxReceipt(txs[0].Hash()) + require.NoError(t, err) + require.EqualValues(t, types.ReceiptStatusSuccessful, rc.Status) + + var res1 json.RawMessage + err = env.RawClient.CallContext( + context.Background(), + &res1, + "debug_traceTransaction", + tx.Hash().Hex(), + tracers.TraceConfig{TracerConfig: []byte(`{"tracer": "callTracer"}`)}, + ) + require.NoError(t, err) + + var trace1 jsonrpc.SendTxArgs + err = json.Unmarshal(res1, &trace1) + require.NoError(t, err) + + fmt.Print(hexutil.EncodeUint64(isc.NewAssetsBaseTokens(1000).BaseTokens)) + + require.Equal(t, evmAddr.String(), trace1.To.String()) + require.Equal(t, hexutil.EncodeUint64(isc.NewAssetsBaseTokens(1000).BaseTokens*1e12), trace1.Value.String()) +} + func TestRPCTraceBlock(t *testing.T) { env := newSoloTestEnv(t) creator, creatorAddress := env.soloChain.NewEthereumAccountWithL2Funds() diff --git a/packages/evm/jsonrpc/types.go b/packages/evm/jsonrpc/types.go index 0f0ae1201a..5cd3a6ed52 100644 --- a/packages/evm/jsonrpc/types.go +++ b/packages/evm/jsonrpc/types.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/rpc" iotago "github.com/iotaledger/iota.go/v3" @@ -158,6 +159,18 @@ func parseBlockNumber(bn rpc.BlockNumber) *big.Int { return big.NewInt(n) } +func RPCMarshalTransactionForFakeTX(tx *types.Transaction, effectiveGasPrice *big.Int) map[string]interface{} { + return map[string]interface{}{ + "from": evmutil.MustGetSenderIfTxSigned(tx), + "gas": hexutil.Uint64(tx.Gas()), + "gasUsed": hexutil.Uint64(tx.Gas()), + "to": tx.To(), + "input": hexutil.Bytes(tx.Data()), + "type": vm.OpCode(tx.Type()).String(), + "value": hexutil.Big(*tx.Value()), + } +} + func RPCMarshalReceipt(r *types.Receipt, tx *types.Transaction, effectiveGasPrice *big.Int) map[string]interface{} { // fix for an already fixed bug where some old failed receipts contain non-empty logs if r.Status != types.ReceiptStatusSuccessful {