UNPKG

eth-revert-reason

Version:

Get the revert reason from an Ethereum transaction hash

280 lines (274 loc) 15 kB
const getRevertReason = require('../') const ethers = require('ethers') describe('getRevertReason', () => { const TX_HASH = { FAILED_AUTHEREUM_TX: { MAINNET: '0x6ea1798a2d0d21db18d6e45ca00f230160b05f172f6022aa138a0b605831d740', KOVAN: '0x371670e8e8dae59844f21845a6d9a96b1724a6f9e38842184e918b059fd143c7', GOERLI: '0x33d83df611f67f1cc609e3e6c44a7b3c95c38c0213199f08c9f4877a2a4b7baf', ROPSTEN: '0x80c344509e59b91c7b1e4ce5ec61204ad81ff4ee6198c59e0ab476513d7b7ea7', RINKEBY: '0xccd466d1fb2b5798ff751a760e132e22ae5f3672315d244d91e8a19b205c60b0' }, // The failure message is 'Failed test' FAILED_RANDOM_TX: { MAINNET: '0xf212cc42d0eded75041225d71da6c3a8348bdb9102f2b73434b480419d31d69a', KOVAN: '0xf683310b52501194b0e9dacb089bcfd00071ba71099682b1230e6c73e5d3b215', GOERLI: '0x640d2e0d1f4cff9b6e273458216451efb0dc08ebc13c30f6c88d48be7b35872a', ROPSTEN: '0xa41c14a059a8012e86733242ecff470683e524258d475426818303ab0ff5945c', RINKEBY: '0x2429ac3f86da6522d500a3a0d15d0a6e9a1c27d3189cf18f5628b5b8c60be8e9' }, NO_REVERT_MSG: { MAINNET: '0x95ac5a6a1752ccac9647eb21ef8614ca2d3e40a5dbb99914adf87690fb1e6ccf', KOVAN: '0x7df41e36899e0f4329afbc56985197751981ff245413503e57be375089b028f9', GOERLI: '0xa80d6653300142800d1bc2b675aec5eee3297575c987495d081b7f88b5fd31d6', ROPSTEN: '0x9a46afe833dfe42afc039e4ff4d3b248aa74652a468838e0d3e93c10ed7a96ef', RINKEBY: '0xad9cf1f3c332e03eff9badc668f7b673ca6a8adb012e672a18796080caf5e770' }, OUT_OF_GAS: { MAINNET: '0x5871434b2e89ac62d643cc42a6c44ba75e16f229dbbdba3994e8202df5f1102f', KOVAN: '0xbfcb5416557910fac32346c868a4ff8d254a7b744eb5a7a6089d0e0a96f1781b', GOERLI: '0xe478a5a661821ee839d0cb7db51d4d9a8df9e9a5e1f5e28ef8eb7d3cc6c570c4', ROPSTEN: '0x2439fa82407f070e4e0e4aad353780fc98fd3a7f406c527c2b3d2af405ac2243', RINKEBY: '0xaf9c2cebfcf15bbf6421ed1c1cb0d3b463c29ab0e7704fa0cc2feb1a53152671' }, BAD_INSTRUCTION: { MAINNET: '0xb84a441996ba3cffc82b17c5dd282b02feeb63826846931a8d16211fbece45bf', KOVAN: '0x3a7019d68dc220df3e402ebb45de7fe9fd9983b20d78fe94b9f0f89ec327db99', GOERLI: '0x6e9a8114b7acd8ec73411b5ed246f974be575cf849348239c1433466d5e21c2d', ROPSTEN: '0x2cb358e26fd80c267b5a2142eb2e877440a6920d52bc2ba8287efdb87ed9fb89', RINKEBY: '0xf5f98e8e2032e07b1695c9b80df51ffae8b91157740c2e130162914e486983a6' }, SUCCESSFUL_TX: { MAINNET: '0x02b8f8a00a0c0e9dcf60ddebd37ea305483fb30fd61233a505b73036408cae75', KOVAN: '0xdbfef20645354e200a00a7e1a815722d832e9071f727f9cd0158f4906384e97f', GOERLI: '0x5e5dfb4d08ebcfbc3de543879d2ea2183bab5503bc2a68f8b2bbc622a901b2b4', ROPSTEN: '0x31590a132bc0b858ad69be3d409d3de625ebab98b48ad07db97edf5d0cfce786', RINKEBY: '0x2a215ad51888b09c585c4244d4ed5e9d9bccb773d400497881ba387b8bd7cc61' }, // The error message is: "Tried to read `uint64` from a `CBOR.Value` with majorType != 0" // NOTE: This error message also tests the odd-length revert messages SPECIAL_CHARACTERS: { MAINNET: '0xceee8c5fea071d03008e0e29fe65d1387a1c9871f11bc20870b1b74ef44d1bd7', KOVAN: '0x22186d2d5da40b4cbadd1097b35471afa64c2f0eca8be79d8ee7ea354765bfbe', GOERLI: '0x0d2835694314658d752631611357a2e08029045bcfbabf2a38be5ff6a4a21f59', ROPSTEN: '0x6d8f63fa85c66f8ba65afea6c0b57a8bad9963be55712a64fe52f08a35f03958', RINKEBY: '0xfe0f6540e9b5dc15d31b131196ee4857b2c183166f8fcdc636bd3549aa8b5717' } } // REVERT_REASONS const REVERT_REASON = { // NOTE: The real reason why this transaction failed is 'BA: Insufficient gas (token) for refund', but // since the address has made another transaction since then, the `call` from this new context will say // the auth key is invalid, since the signature is technically invalid for this state. FAILED_AUTHEREUM_TX: 'LKMTA: Login key is expired', FAILED_RANDOM_TX: 'Failed test', UNABLE_TO_DECODE: 'Unable to decode revert reason', PARTY_TRACE_NOT_AVAILABLE: 'Please use a provider that exposes the Parity trace methods to decode the revert reason', SUCCESSFUL_TX: '', FAILURE_WITH_NO_REVERT_REASON: '', OUT_OF_GAS: '', BAD_INSTRUCTION: '', SPECIAL_CHARACTERS: 'Tried to read `uint64` from a `CBOR.Value` with majorType != 0', INVALID_TX_HASH: 'Invalid transaction hash', NOT_VALID_NETWORK: 'Not a valid network', FUTURE_BLOCK_NUMBER: 'You cannot use a blocknumber that has not yet happened.', ARCHIVE_NODE_REQUIRED: 'You cannot use a blocknumber that is older than 128 blocks. Please use a provider that uses a full archival node.', INSUFFICIENT_FUNDS: 'BA: Insufficient gas (ETH) for refund' } describe('Happy Path', () => { describe('mainnet', () => { const _network = 'mainnet' test('authereum transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.MAINNET, _network)).toEqual(REVERT_REASON.FAILED_AUTHEREUM_TX) }) test('random transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_RANDOM_TX.MAINNET, _network)).toEqual(REVERT_REASON.FAILED_RANDOM_TX) }) test('failure with no revert reason', async () => { expect(await getRevertReason(TX_HASH.NO_REVERT_MSG.MAINNET, _network)).toEqual(REVERT_REASON.FAILURE_WITH_NO_REVERT_REASON) }) }) describe('kovan', () => { const _network = 'kovan' test('authereum transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.KOVAN, _network)).toEqual(REVERT_REASON.FAILED_AUTHEREUM_TX) }) test('random transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_RANDOM_TX.KOVAN, _network)).toEqual(REVERT_REASON.FAILED_RANDOM_TX) }) test('failure with no revert reason', async () => { expect(await getRevertReason(TX_HASH.NO_REVERT_MSG.KOVAN, _network)).toEqual(REVERT_REASON.FAILURE_WITH_NO_REVERT_REASON) }) }) describe('goerli', () => { const _network = 'goerli' test('authereum transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.GOERLI, _network)).toEqual(REVERT_REASON.FAILED_AUTHEREUM_TX) }) test('random transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_RANDOM_TX.GOERLI, _network)).toEqual(REVERT_REASON.FAILED_RANDOM_TX) }) test('failure with no revert reason', async () => { expect(await getRevertReason(TX_HASH.NO_REVERT_MSG.GOERLI, _network)).toEqual(REVERT_REASON.FAILURE_WITH_NO_REVERT_REASON) }) }) describe('ropsten', () => { const _network = 'ropsten' test('authereum transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.ROPSTEN, _network)).toEqual(REVERT_REASON.FAILED_AUTHEREUM_TX) }) test('random transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_RANDOM_TX.ROPSTEN, _network)).toEqual(REVERT_REASON.FAILED_RANDOM_TX) }) test('failure with no revert reason', async () => { expect(await getRevertReason(TX_HASH.NO_REVERT_MSG.ROPSTEN, _network)).toEqual(REVERT_REASON.FAILURE_WITH_NO_REVERT_REASON) }) }) describe('rinkeby', () => { const _network = 'rinkeby' test('authereum transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.RINKEBY, _network)).toEqual(REVERT_REASON.FAILED_AUTHEREUM_TX) }) test('random transaction', async () => { expect(await getRevertReason(TX_HASH.FAILED_RANDOM_TX.RINKEBY, _network)).toEqual(REVERT_REASON.FAILED_RANDOM_TX) }) test('failure with no revert reason', async () => { expect(await getRevertReason(TX_HASH.NO_REVERT_MSG.RINKEBY, _network)).toEqual(REVERT_REASON.FAILURE_WITH_NO_REVERT_REASON) }) }) }) describe('Non-Happy Path', () => { describe('mainnet', () => { const _network = 'mainnet' test('successful transaction', async () => { expect(await getRevertReason(TX_HASH.SUCCESSFUL_TX.MAINNET, _network)).toEqual(REVERT_REASON.SUCCESSFUL_TX) }) test('out of gas', async () => { expect(await getRevertReason(TX_HASH.OUT_OF_GAS.MAINNET, _network)).toEqual(REVERT_REASON.OUT_OF_GAS) }) test('bad instruction', async () => { expect(await getRevertReason(TX_HASH.BAD_INSTRUCTION.MAINNET, _network)).toEqual(REVERT_REASON.BAD_INSTRUCTION) }) test('special characters', async () => { expect(await getRevertReason(TX_HASH.SPECIAL_CHARACTERS.MAINNET, _network)).toEqual(REVERT_REASON.SPECIAL_CHARACTERS) }) }) describe('kovan', () => { const _network = 'kovan' test('successful transaction', async () => { expect(await getRevertReason(TX_HASH.SUCCESSFUL_TX.KOVAN, _network)).toEqual(REVERT_REASON.SUCCESSFUL_TX) }) test('out of gas', async () => { expect(await getRevertReason(TX_HASH.OUT_OF_GAS.KOVAN, _network)).toEqual(REVERT_REASON.OUT_OF_GAS) }) test('bad instruction', async () => { expect(await getRevertReason(TX_HASH.BAD_INSTRUCTION.KOVAN, _network)).toEqual(REVERT_REASON.BAD_INSTRUCTION) }) test('special characters', async () => { expect(await getRevertReason(TX_HASH.SPECIAL_CHARACTERS.KOVAN, _network)).toEqual(REVERT_REASON.SPECIAL_CHARACTERS) }) }) describe('goerli', () => { const _network = 'goerli' test('successful transaction', async () => { expect(await getRevertReason(TX_HASH.SUCCESSFUL_TX.GOERLI, _network)).toEqual(REVERT_REASON.SUCCESSFUL_TX) }) test('out of gas', async () => { expect(await getRevertReason(TX_HASH.OUT_OF_GAS.GOERLI, _network)).toEqual(REVERT_REASON.OUT_OF_GAS) }) test('bad instruction', async () => { expect(await getRevertReason(TX_HASH.BAD_INSTRUCTION.GOERLI, _network)).toEqual(REVERT_REASON.BAD_INSTRUCTION) }) test('special characters', async () => { expect(await getRevertReason(TX_HASH.SPECIAL_CHARACTERS.GOERLI, _network)).toEqual(REVERT_REASON.SPECIAL_CHARACTERS) }) }) describe('ropsten', () => { const _network = 'ropsten' test('successful transaction', async () => { expect(await getRevertReason(TX_HASH.SUCCESSFUL_TX.ROPSTEN, _network)).toEqual(REVERT_REASON.SUCCESSFUL_TX) }) test('out of gas', async () => { expect(await getRevertReason(TX_HASH.OUT_OF_GAS.ROPSTEN, _network)).toEqual(REVERT_REASON.OUT_OF_GAS) }) test('bad instruction', async () => { expect(await getRevertReason(TX_HASH.BAD_INSTRUCTION.ROPSTEN, _network)).toEqual(REVERT_REASON.BAD_INSTRUCTION) }) test('special characters', async () => { expect(await getRevertReason(TX_HASH.SPECIAL_CHARACTERS.ROPSTEN, _network)).toEqual(REVERT_REASON.SPECIAL_CHARACTERS) }) }) describe('rinkeby', () => { const _network = 'rinkeby' test('successful transaction', async () => { expect(await getRevertReason(TX_HASH.SUCCESSFUL_TX.RINKEBY, _network)).toEqual(REVERT_REASON.SUCCESSFUL_TX) }) test('out of gas', async () => { expect(await getRevertReason(TX_HASH.OUT_OF_GAS.RINKEBY, _network)).toEqual(REVERT_REASON.OUT_OF_GAS) }) test('bad instruction', async () => { expect(await getRevertReason(TX_HASH.BAD_INSTRUCTION.RINKEBY, _network)).toEqual(REVERT_REASON.BAD_INSTRUCTION) }) test('special characters', async () => { expect(await getRevertReason(TX_HASH.SPECIAL_CHARACTERS.RINKEBY, _network)).toEqual(REVERT_REASON.SPECIAL_CHARACTERS) }) }) }) describe('other tests', () => { test('invalid txHash - invalid length', async () => { const _txHash = '0x123' const _network = 'mainnet' await expect(getRevertReason(_txHash, _network)).rejects.toThrow(new Error(REVERT_REASON.INVALID_TX_HASH)) }) test('invalid txHash - invalid characters', async () => { const _txHash = '0xzzz1798a2d0d21db18d6e45ca00f230160b05f172f6022aa138a0b605831d740' const _network = 'mainnet' await expect(getRevertReason(_txHash, _network)).rejects.toThrow(new Error(REVERT_REASON.INVALID_TX_HASH)) }) test('invalid txHash - no 0x prefix', async () => { const _txHash = 'aa6ea1798a2d0d21db18d6e45ca00f230160b05f172f6022aa138a0b605831d740' const _network = 'mainnet' await expect(getRevertReason(_txHash, _network)).rejects.toThrow(new Error(REVERT_REASON.INVALID_TX_HASH)) }) test('abnormal txHash - all upper case', async () => { const _txHash = '0xF212CC42D0EDED75041225D71DA6C3A8348BDB9102F2B73434B480419D31D69A' const _network = 'mainnet' expect(await getRevertReason(_txHash, _network)).toEqual(REVERT_REASON.FAILED_RANDOM_TX) }) test('unknown network', async () => { const _network = 'test' await expect(getRevertReason(TX_HASH.SUCCESSFUL_TX.KOVAN, _network)).rejects.toThrow(new Error(REVERT_REASON.NOT_VALID_NETWORK)) }) test('upercase network', async () => { const _network = 'MaInNeT' expect(await getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.MAINNET, _network)).toEqual(REVERT_REASON.FAILED_AUTHEREUM_TX) }) test('upercase network', async () => { const _network = 'MaInNeT' expect(await getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.MAINNET, _network)).toEqual(REVERT_REASON.FAILED_AUTHEREUM_TX) }) test('future block number', async () => { const _network = 'mainnet' const _blockNumber = 999999999999 await expect(getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.MAINNET, _network, _blockNumber)).rejects.toThrow(new Error(REVERT_REASON.FUTURE_BLOCK_NUMBER)) }) test('early block number with no archive node', async () => { const _network = 'mainnet' const _blockNumber = 123 await expect(getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.MAINNET, _network, _blockNumber)).rejects.toThrow(new Error(REVERT_REASON.ARCHIVE_NODE_REQUIRED)) }) test('get revert reason from the context of an early block', async () => { const _network = 'mainnet' // Revert reason from 'latest' context should be REVERT_REASON.FAILED_AUTHEREUM_TX expect(await getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.MAINNET, _network)).toEqual(REVERT_REASON.FAILED_AUTHEREUM_TX) // Revert reason from block 9892243 context should be REVERT_REASON.INSUFFICIENT_FUNDS const _blockNumber = 9892243 const _provider = getAlchemyProvider(_network) expect(await getRevertReason(TX_HASH.FAILED_AUTHEREUM_TX.MAINNET, _network, _blockNumber, _provider)).toEqual(REVERT_REASON.INSUFFICIENT_FUNDS) }) }) }) function getAlchemyProvider(network) { const rpcUrl = `https://eth-${network}.alchemyapi.io/v2/demo` return new ethers.providers.JsonRpcProvider(rpcUrl) }