Skip to content

Commit

Permalink
Return txHash of failed tx
Browse files Browse the repository at this point in the history
  • Loading branch information
fvictorio committed Apr 21, 2021
1 parent 1830ef4 commit 2fbd769
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ProviderError,
} from "../../core/providers/errors";
import {
FailedJsonRpcResponse,
isSuccessfulJsonResponse,
isValidJsonRequest,
isValidJsonResponse,
Expand Down Expand Up @@ -229,12 +230,20 @@ const _handleError = (error: any): JsonRpcResponse => {
error = new InternalError(error);
}

return {
const response: FailedJsonRpcResponse = {
jsonrpc: "2.0",
id: null,
error: {
code: error.code,
message: error.message,
},
};

if (error.transactionHash !== undefined) {
response.error.data = {
txHash: error.transactionHash,
};
}

return response;
};
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,16 @@ You can use them by running Hardhat Network with 'hardfork' ${ACCESS_LIST_MIN_HA
result = [result];
}

await this._handleMineBlockResults(result, tx);
try {
await this._handleMineBlockResults(result, tx);
} catch (e) {
// This is a temporary solution until we improve our internal errors
// We need this to be able to return the transaction hash in the JSON-RPC
// response when the transaction fails
(e as any).transactionHash = bufferToRpcData(tx.hash());

throw e;
}

return bufferToRpcData(tx.hash());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,42 @@ contract Example {
},
topics: {},
};

export const EXAMPLE_REVERT_CONTRACT = {
sourceCode: `
pragma solidity 0.7.3;
contract Foo {
function f(uint x) public {
require(x > 0);
}
}
`,
bytecode: {
linkReferences: {},
object:
"6080604052348015600f57600080fd5b50609d8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063b3de648b14602d575b600080fd5b605660048036036020811015604157600080fd5b81019080803590602001909291905050506058565b005b60008111606457600080fd5b5056fea264697066735822122087ff9e40d7434f96fa4f637d4e462ea949b2b25e73d16ca3d01b1b1c1957b60464736f6c63430007030033",
opcodes:
"PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x9D DUP1 PUSH2 0x1E PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH1 0x28 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xB3DE648B EQ PUSH1 0x2D JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x56 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH1 0x41 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH1 0x58 JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 DUP2 GT PUSH1 0x64 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 DUP8 SELFDESTRUCT SWAP15 BLOCKHASH 0xD7 NUMBER 0x4F SWAP7 STATICCALL 0x4F PUSH4 0x7D4E462E 0xA9 0x49 0xB2 0xB2 0x5E PUSH20 0xD16CA3D01B1B1C1957B60464736F6C6343000703 STOP CALLER ",
sourceMap: "24:70:0:-:0;;;;;;;;;;;;;;;;;;;",
},
abi: [
{
inputs: [
{
internalType: "uint256",
name: "x",
type: "uint256",
},
],
name: "f",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
],
selectors: {
f: "0xb3de648b",
},
topics: {},
};
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const PROVIDERS = [
{
name: "Hardhat Network",
isFork: false,
isJsonRpc: false,
networkId: DEFAULT_NETWORK_ID,
chainId: DEFAULT_CHAIN_ID,
useProvider: (loggerEnabled = true) => {
Expand All @@ -56,6 +57,7 @@ export const PROVIDERS = [
{
name: "JSON-RPC",
isFork: false,
isJsonRpc: true,
networkId: DEFAULT_NETWORK_ID,
chainId: DEFAULT_CHAIN_ID,
useProvider: (loggerEnabled = true) => {
Expand All @@ -68,6 +70,7 @@ export const INTERVAL_MINING_PROVIDERS = [
{
name: "Hardhat Network",
isFork: false,
isJsonRpc: false,
useProvider: (loggerEnabled = true) => {
useProvider(false, loggerEnabled, undefined, {
auto: false,
Expand All @@ -78,6 +81,7 @@ export const INTERVAL_MINING_PROVIDERS = [
{
name: "JSON-RPC",
isFork: false,
isJsonRpc: true,
useProvider: (loggerEnabled = true) => {
useProvider(true, loggerEnabled, undefined, {
auto: false,
Expand All @@ -99,6 +103,7 @@ if (ALCHEMY_URL !== undefined && ALCHEMY_URL !== "") {
PROVIDERS.push({
name: "Alchemy Forked",
isFork: true,
isJsonRpc: false,
networkId: DEFAULT_NETWORK_ID,
chainId: DEFAULT_CHAIN_ID,
useProvider: (loggerEnabled = true) => {
Expand All @@ -109,6 +114,7 @@ if (ALCHEMY_URL !== undefined && ALCHEMY_URL !== "") {
INTERVAL_MINING_PROVIDERS.push({
name: "Alchemy Forked",
isFork: true,
isJsonRpc: false,
useProvider: (loggerEnabled = true) => {
useProvider(
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
EXAMPLE_BLOCKHASH_CONTRACT,
EXAMPLE_CONTRACT,
EXAMPLE_READ_CONTRACT,
EXAMPLE_REVERT_CONTRACT,
EXAMPLE_SETTER_CONTRACT,
} from "../../helpers/contracts";
import { setCWD } from "../../helpers/cwd";
Expand Down Expand Up @@ -71,7 +72,7 @@ const { recoverTypedSignature_v4 } = require("eth-sig-util");
const PRECOMPILES_COUNT = 8;

describe("Eth module", function () {
PROVIDERS.forEach(({ name, useProvider, isFork, chainId }) => {
PROVIDERS.forEach(({ name, useProvider, isFork, isJsonRpc, chainId }) => {
if (isFork) {
this.timeout(50000);
}
Expand Down Expand Up @@ -3491,6 +3492,25 @@ describe("Eth module", function () {

assertReceiptMatchesGethOne(receipt, receiptFromGeth, 1);
});

it("Should return the hash of the failed transaction", async function () {
if (!isJsonRpc || isFork) {
this.skip();
}

try {
// sends a tx with 21000 gas to the 0x1 precompile
await this.provider.send("eth_sendRawTransaction", [
"0xf8618001825208940000000000000000000000000000000000000001808082011aa03e2b434ea8994b24017a30d58870e7387a69523b25f153f0d90411a8af8343d6a00c26d36e92d8a8334193b02982ce0b2ec9afc85ad26eaf8c2993ad07d3495f95",
]);

assert.fail("Tx should have failed");
} catch (e) {
assert.notInclude(e.message, "Tx should have failed");

assert.isDefined(e.data.txHash);
}
});
});

describe("eth_sendTransaction", async function () {
Expand Down Expand Up @@ -3935,6 +3955,58 @@ describe("Eth module", function () {
});
});

describe("return txHash", () => {
it("Should return the hash of an out of gas transaction", async function () {
if (!isJsonRpc || isFork) {
this.skip();
}

try {
await this.provider.send("eth_sendTransaction", [
{
from: DEFAULT_ACCOUNTS_ADDRESSES[0],
to: "0x0000000000000000000000000000000000000001",
gas: numberToRpcQuantity(21000), // Address 1 is a precompile, so this will OOG
gasPrice: numberToRpcQuantity(1),
},
]);

assert.fail("Tx should have failed");
} catch (e) {
assert.notInclude(e.message, "Tx should have failed");

assert.isDefined(e.data.txHash);
}
});

it("Should return the hash of a reverted transaction", async function () {
if (!isJsonRpc || isFork) {
this.skip();
}

try {
const contractAddress = await deployContract(
this.provider,
`0x${EXAMPLE_REVERT_CONTRACT.bytecode.object}`
);

await this.provider.send("eth_sendTransaction", [
{
to: contractAddress,
from: DEFAULT_ACCOUNTS_ADDRESSES[0],
data: `${EXAMPLE_REVERT_CONTRACT.selectors.f}0000000000000000000000000000000000000000000000000000000000000000`,
},
]);

assert.fail("Tx should have failed");
} catch (e) {
assert.notInclude(e.message, "Tx should have failed");

assert.isDefined(e.data.txHash);
}
});
});

// This test checks that an on-chain value can be set to 0
// To do this, we transfer all the balance of the 0x0000...0001 account
// to some random account, and then check that its balance is zero
Expand Down

0 comments on commit 2fbd769

Please sign in to comment.