diff --git a/CHANGELOG.md b/CHANGELOG.md index 7312641c81a..70bc76b8e03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -666,7 +666,8 @@ Released with 1.0.0-beta.37 code base. ### Fixed - Improved the error propagation in `web3-providers-http` package to effectively propagate useful error infomation about failed HTTP connections (#5955) - - Fix error: "n.data.substring is not a function", that is raised when there is a revert and `web.eth.handleRevert = true` (#6000) + - Fixed "Uncaught TypeError" calling a contract function that revert using MetaMask (#4454) and related "n.data.substring is not a function", that is raised when there is a revert and `web.eth.handleRevert = true` (#6000) + ### Changed - `transaction.type` is now formatted to a hex string before being send to provider (#5979) diff --git a/packages/web3-core-method/src/index.js b/packages/web3-core-method/src/index.js index 7747460b2cf..54ff4b7128d 100644 --- a/packages/web3-core-method/src/index.js +++ b/packages/web3-core-method/src/index.js @@ -653,7 +653,15 @@ Method.prototype.buildCall = function () { if (!err && method.isRevertReasonString(result)){ reasonData = result.substring(10); } else if (err && err.data){ - reasonData = (err.data.data || err.data).substring(10); + // workaround embedded error details got from some providers like MetaMask + if (typeof err.data === 'object') { + // Ganache has no `originalError` sub-object unlike others + var originalError = err.data.originalError ?? err.data; + reasonData = originalError.data.substring(10); + } + else { + reasonData = err.data.substring(10); + } } if (reasonData){ diff --git a/test/eth.call.revert.js b/test/eth.call.revert.js new file mode 100644 index 00000000000..75bf8eb89c5 --- /dev/null +++ b/test/eth.call.revert.js @@ -0,0 +1,98 @@ +var chai = require('chai'); +var assert = chai.assert; +var FakeIpcProvider = require('./helpers/FakeIpcProvider'); +var Web3 = require('../packages/web3'); + +describe('call revert', function () { + var provider; + var web3; + + beforeEach(function () { + provider = new FakeIpcProvider(); + web3 = new Web3(provider); + web3.eth.handleRevert = true; + }); + + it('Errors with revert reason string through MetaMask', async function() { + provider.injectRawError({ + "code": -32603, + "message": "execution reverted: DeadlineExpired", + "data": { + "originalError": { + "code": 3, + "data": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000f446561646c696e65457870697265640000000000000000000000000000000000", + "message": "execution reverted: DeadlineExpired" + } + } + }); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual( + payload.params, + [{ + to: '0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b', + data: '0x23455654', + gas: '0xb', + gasPrice: '0xb' + }, 'latest'] + ); + }); + + var options = { + to: '0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b', + data: '0x23455654', + gas: 11, + gasPrice: 11 + }; + + try { + await web3.eth.call(options, 'latest'); + assert.fail('call should have failed!'); + } catch (error) { + assert.equal(error.reason, 'DeadlineExpired'); + } + }); + + it('Errors with revert reason string from Ganache through MetaMask', async function() { + provider.injectRawError({ + "code": -32603, + "message": "Internal JSON-RPC error.", + "data": { + "message": "VM Exception while processing transaction: revert ImproperState", + "stack": "CallError: VM Exception while processing transaction: revert ImproperState\n at Blockchain.simulateTransaction (C:\\Users\\nicos\\AppData\\Roaming\\npm\\node_modules\\ganache\\dist\\node\\1.js:2:82786)", + "code": -32000, + "name": "CallError", + "data": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d496d70726f706572537461746500000000000000000000000000000000000000" + } + }); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual( + payload.params, + [{ + to: '0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b', + data: '0x23455654', + gas: '0xb', + gasPrice: '0xb' + }, 'latest'] + ); + }); + + var options = { + to: '0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b', + data: '0x23455654', + gas: 11, + gasPrice: 11 + }; + + try { + await web3.eth.call(options, 'latest'); + assert.fail('call should have failed!'); + } catch (error) { + assert.equal(error.reason, 'ImproperState'); + } + }); + +}); diff --git a/test/helpers/FakeIpcProvider.js b/test/helpers/FakeIpcProvider.js index 6f52cb6c667..c877027127a 100644 --- a/test/helpers/FakeIpcProvider.js +++ b/test/helpers/FakeIpcProvider.js @@ -142,6 +142,11 @@ FakeIpcProvider.prototype.injectError = function (error) { this.error.push(errorStub); }; +// to simulate strange behavior of MetaMask +FakeIpcProvider.prototype.injectRawError = function (error) { + this.error.push(error); +}; + FakeIpcProvider.prototype.injectValidation = function (callback) { this.validation.push(callback); };