diff --git a/evmrpc/tx.go b/evmrpc/tx.go index 9d773c872..d4d450e3a 100644 --- a/evmrpc/tx.go +++ b/evmrpc/tx.go @@ -53,6 +53,45 @@ func (t *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. } return nil, err } + // Fill in the receipt if the transaction has failed and used 0 gas + // This case is for when a tx fails before it makes it to the VM + if receipt.Status == 0 && receipt.GasUsed == 0 { + // Get the block + height := int64(receipt.BlockNumber) + block, err := blockByNumberWithRetry(ctx, t.tmClient, &height, 1) + if err != nil { + return nil, err + } + + // Find the transaction in the block + for _, tx := range block.Block.Txs { + etx := getEthTxForTxBz(tx, t.txConfig.TxDecoder()) + if etx != nil && etx.Hash() == hash { + // Get the signer + signer := ethtypes.MakeSigner( + types.DefaultChainConfig().EthereumConfig(t.keeper.ChainID(sdkctx)), + big.NewInt(height), + uint64(block.Block.Time.Unix()), + ) + from, _ := ethtypes.Sender(signer, etx) + + // Update receipt with correct information + receipt.From = from.Hex() + if etx.To() != nil { + receipt.To = etx.To().Hex() + receipt.ContractAddress = "" + } else { + receipt.To = "" + // For contract creation transactions, calculate the contract address + receipt.ContractAddress = crypto.CreateAddress(from, etx.Nonce()).Hex() + } + receipt.TxType = uint32(etx.Type()) + receipt.Status = uint32(ethtypes.ReceiptStatusFailed) + receipt.GasUsed = 0 + break + } + } + } height := int64(receipt.BlockNumber) block, err := blockByNumberWithRetry(ctx, t.tmClient, &height, 1) if err != nil { @@ -278,7 +317,6 @@ func encodeReceipt(receipt *types.Receipt, decoder sdk.TxDecoder, block *coretyp } bloom := ethtypes.Bloom{} bloom.SetBytes(receipt.LogsBloom) - fields := map[string]interface{}{ "blockHash": bh, "blockNumber": hexutil.Uint64(receipt.BlockNumber), diff --git a/evmrpc/tx_test.go b/evmrpc/tx_test.go index b41c07f2c..e2d1896bf 100644 --- a/evmrpc/tx_test.go +++ b/evmrpc/tx_test.go @@ -243,3 +243,49 @@ func TestGetVMError(t *testing.T) { resObj = sendRequestGood(t, "getVMError", "0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872f") require.Equal(t, "not found", resObj["error"].(map[string]interface{})["message"]) } + +func TestGetTransactionReceiptFailedTx(t *testing.T) { + fromAddr := "0x5b4eba929f3811980f5ae0c5d04fa200f837df4e" // Use the actual address from the block + + // Create a failed receipt with 0 gas used for a contract creation transaction + failedReceipt := &types.Receipt{ + Status: 0, // failed status + GasUsed: 0, + BlockNumber: 8, + TransactionIndex: 0, + TxHashHex: "0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e", + From: fromAddr, // Use the actual from address + } + + // Mock the receipt in the keeper + txHash := common.HexToHash("0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e") + EVMKeeper.MockReceipt(Ctx, txHash, failedReceipt) + + // Create JSON-RPC request + body := "{\"jsonrpc\": \"2.0\",\"method\": \"eth_getTransactionReceipt\",\"params\":[\"0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e\"],\"id\":\"test\"}" + + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s:%d", TestAddr, TestPort), strings.NewReader(body)) + require.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + + res, err := http.DefaultClient.Do(req) + require.Nil(t, err) + + resBody, err := io.ReadAll(res.Body) + require.Nil(t, err) + + resObj := map[string]interface{}{} + require.Nil(t, json.Unmarshal(resBody, &resObj)) + resObj = resObj["result"].(map[string]interface{}) + + // Verify the receipt was filled with correct information for failed tx + require.Equal(t, "0x0", resObj["status"].(string)) // Failed status + require.Equal(t, "0x0", resObj["gasUsed"].(string)) // 0 gas used + require.Equal(t, "0x8", resObj["blockNumber"].(string)) + require.Equal(t, "0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e", resObj["transactionHash"].(string)) + require.Equal(t, fromAddr, resObj["from"].(string)) + + // For contract creation transaction + require.Equal(t, "0x0000000000000000000000000000000000010203", resObj["to"].(string)) + require.Nil(t, resObj["contractAddress"]) +}