Open wbt opened 1 year ago
Hm, I'm confused why there was data attached if the transaction was rejected by the user. I would expect that to hit either line 58 or line 61, so you wouldn't get this message. In order to hit line 86 (which is obviously what it's hitting), there has to be data attached. What is this data and why...?
In a recent repro, after a tx was rejected the following was shown as originating from Metamask's inpage.js:1
(i.e. before Truffle tries to decode it):
{
code: 4001,
message: "MetaMask Tx Signature: User denied transaction signature."
}
Approximately 0.2 seconds later, the error was output to console by application code (i.e. just after Truffle's decoding attempt), with a few more keys (modifications made to stack
value only):
{
code: 4001,
hijackedStack: "{\n \"code\": 4001,\n \"message\": \"MetaMask Tx Signature: User denied transaction signature.\",\n \"stack\": \"Error: MetaMask Tx Signature: User denied transaction signature.\\n at new n (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:12:23059)\\n at new t.EthereumProviderError (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:12:23574)\\n at o (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:12:25909)\\n at Object.userRejectedRequest (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:12:27140)\\n at g.<anonymous> (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:1:6733)\\n at Object.l (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-3.js:10:210791)\\n at u (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:3:1144)\\n at a.emit (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:3:1680)\\n at g._setTransactionStatus (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:1:53365)\\n at g.setTxStatusRejected (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:1:51552)\\n at B.cancelTransaction (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:1:23301)\\n at a.<anonymous> (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-4.js:10:104664)\\n at b (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:1:96305)\\n at a.emit (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:1:99300)\\n at A (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111283)\\n at E (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111098)\\n at _.push (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111912)\\n at e.exports._write (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:45435)\\n at _ (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:124328)\\n at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:127512\\n at y.write (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:127539)\\n at e.exports.m (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:116483)\\n at b (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:1:96305)\\n at a.emit (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:1:99300)\\n at A (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111283)\\n at E (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111098)\\n at _.push (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111912)\\n at e.exports._onMessage (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-3.js:10:216138)\\n at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-3.js:10:215985\"\n}\n at new n (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:12:23059)\n at new t.EthereumProviderError (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:12:23574)\n at o (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:12:25909)\n at Object.userRejectedRequest (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:12:27140)\n at g.<anonymous> (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:1:6733)\n at Object.l (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-3.js:10:210791)\n at u (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:3:1144)\n at a.emit (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:3:1680)\n at g._setTransactionStatus (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:1:53365)\n at g.setTxStatusRejected (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:1:51552)\n at B.cancelTransaction (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:1:23301)\n at a.<anonymous> (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-4.js:10:104664)\n at b (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:1:96305)\n at a.emit (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:1:99300)\n at A (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111283)\n at E (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111098)\n at _.push (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111912)\n at e.exports._write (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:45435)\n at _ (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:124328)\n at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:127512\n at y.write (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:127539)\n at e.exports.m (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:116483)\n at b (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:1:96305)\n at a.emit (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-1.js:1:99300)\n at A (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111283)\n at E (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111098)\n at _.push (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:111912)\n at e.exports._onMessage (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-3.js:10:216138)\n at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-3.js:10:215985",
message: MetaMask Tx Signature: User denied transaction signature. -- Reason given: Custom error (could not decode).",
reason: "Custom error (could not decode)",
stack: "{\n at https://server/js/bundles/myBundle.js:2:1234567\n at new Promise (<anonymous>)\n at Object.contractCallWrapper (https://server/js/bundles/myBundle.js:2:1237654)\n at Object.callingContext (https://server/js/bundles/myBundle.js:2:1234675)\n at https://server/js/bundles/myBundle.js:2:1234576"
}
The hijackedStack
references look to all be in MetaMask (v10.24.2), and the stack
references are in application code.
My first guess is that the bug is somewhere in the _extract
function, probably one of the first three lines, but maybe in its calling context.
I'm not seeing result
or error
keys in either of these error objects.
OK, I think I know what's going on. The thing we weren't considering here is that Truffle Contract, in order to get the revert message, replays the transaction as a call; that's what's hitting _extract
, etc. I forgot about that step.
But, in this case, replaying the transaction as a call will succeed! And if that successful call has a return value... you'll hit "could not decode". Oops.
Indeed, I'm able to reproduce this without using MetaMask by just sending a transaction that has a return value and giving it too little gas. Notably, when I do this for a transaction with no return value, the error doesn't occur.
Hm, that's troublesome. Will have to think about what to do here...
I guess a quick fix might be, check the length of the response and see if it's 4 mod 32...? I guess I'll go do that, that seems like probably the best solution to me.
Well, OK, that might not quite be enough -- that'll fix the immediate problem, but if the replay as a call itself fails, and has a revert message, then you could still get a revert message inappropriately attached. So checking for code 4001 might also be a good idea? Possibly certain other codes? That seems to me like it's probably better handled separately, though.
OK, I've put up #5903, which should fix the bulk of the problem. We can look into a more comprehensive solution later, I figure.
Thanks! I'd recommend at least checking for code 4001 as that's a pretty common issue.
Issue
When a user uses a Truffle contract object to trigger a transaction in a browser (tested using MetaMask), and the user rejects the transaction, the error coming back has members
likely due to this line.
It also has
code: 4001
which could be used to figure out that this is a user rejecting the transaction.That seems like a common enough issue that it should probably be handled well. It might also lead to different decisions about what to do in the UI.
Steps to Reproduce
Use a Truffle contract method to trigger a transaction in a browser with MetaMask, and reject the transaction. Have code that listens for and displays errors, in the console and/or on-screen.
Expected Behavior
A notice that the user rejected the transaction.
Actual Results
A notice that the issue was a custom error Truffle couldn't decode. The numeric code is present and can be caught/decoded in the application code, so this is not a major blocker, but it is a common enough use case that "could not decode" suggests Truffle immaturity as a tool for smart contract interactions.
Environment