-
Notifications
You must be signed in to change notification settings - Fork 29
Conversation
_posts/2021-04-20-custom-errors.md
Outdated
|
||
[Solidity v0.8.4](https://github.com/ethereum/solidity/releases/tag/v0.8.4) provides a convenient and gas-efficient way to explain to user why an operation failed. They can be defined inside and outside of contracts (including interfaces and libraries). | ||
|
||
The syntax of Errors are similar to that of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rather put this after the example? It could be confusing to mention events here, because only the syntax is similar, not the semantics.
_posts/2021-04-20-custom-errors.md
Outdated
} | ||
``` | ||
|
||
In the above example, `revert Unauthorized();` is equivalent to the following Yul code: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this going a bit too deep?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should rather highlight the cost difference compared to revert("Unauthorized")
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to include something that makes it clear that solidity isn't randomly assigning numbers to errors. Perhaps I can rephrase this slightly better, but still include the Yul code.
I'll also highlight the cost difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's not too complicated, maybe we should really just show how to decode it using ethers.js - in that way the encoding should be clear, shouldn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
People will stop reading when they see "depth" and inline assembly. We can use this, but then it should be put at the very end of the blog post.
Maybe we should add
|
Maybe we could even add a snippet for ethers.js about how to decode the error in the frontend manually? |
5ca8713
to
6d1a46d
Compare
@chriseth Fixed the review comments. I did not include anything about RPC calls. Not sure what I should write about it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of comments. :)
_posts/2021-04-20-custom-errors.md
Outdated
contracts it calls directly. Furthermore, any contract can fake any error by returning data that | ||
matches an error signature, even if the error is not defined anywhere. | ||
|
||
Currently, there is no convenient way to catch errors in Solidity, but this is planned. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have an issue or anything that we can link to or can we make the "this is planned" more detailed or sound a bit nicer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't find any issue. I'll make a new one.
f8b7dbc
to
653e4c0
Compare
_posts/2021-04-20-custom-errors.md
Outdated
|
||
contract VendingMachine { | ||
address payable owner = payable(msg.sender); | ||
error Unauthorized(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should move it outside of the contract so that it stands out more?
_posts/2021-04-20-custom-errors.md
Outdated
together with the [revert | ||
statement](https://docs.soliditylang.org/en/latest/control-structures.html#revert-statement) which | ||
causes all changes in the current call to be reverted and passes the error data back to the caller. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add: Using errors together with require
is not yet supported (see below)
and then further down explain that require(c, "abc")
should be translated into if (!c) revert Abc();
_posts/2021-04-20-custom-errors.md
Outdated
} | ||
``` | ||
|
||
The error data would be encoded identically as the ABI encoding for function calls, i.e., |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use this instead of inline assembly above?
_posts/2021-04-20-custom-errors.md
Outdated
const abi = [{ | ||
"inputs": [{ | ||
"internalType": "uint256", | ||
"name": "available", | ||
"type": "uint256" | ||
}, | ||
{ | ||
"internalType": "uint256", | ||
"name": "required", | ||
"type": "uint256" | ||
} | ||
], | ||
"name": "InsufficientBalance", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
}]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One reason why ethers is far superior to web3.js:
const abi = [{ | |
"inputs": [{ | |
"internalType": "uint256", | |
"name": "available", | |
"type": "uint256" | |
}, | |
{ | |
"internalType": "uint256", | |
"name": "required", | |
"type": "uint256" | |
} | |
], | |
"name": "InsufficientBalance", | |
"outputs": [], | |
"stateMutability": "nonpayable", | |
"type": "function" | |
}]; | |
const abi = ["function InsufficientBalance(uint256, uint256)"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would only return
[
BigNumber { _hex: '0x0100', _isBigNumber: true },
BigNumber { _hex: '0x0100000000', _isBigNumber: true }
]
With the full abi-json, it's nice to see the parameters available
and required
mapped to values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it help if you use function InsufficientBalance(uint256 available, uint256 required)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow. That actually works. I'll change the abi.
@ricmoo I'm wondering how for the support for custom errors is in ethers.js - maybe you can help? |
Heya, just catching up now on this issue. I've been waiting for EIP-838 to add to the Human-Readable ABI. I've been planning to add something similar to (for example): const ABI = [
"event Transfer(address from, address to, uint amount",
"function transfer(address to, uint amount)"
"error InsufficientBalance(address owner, uint balance)"
];
// or something similar I hope Solidity dumps out:, similar to Event
const ABI_JSON = [
{
type: "error",
name: "InsufficientBalance",
inputs: [
{ name: "owner", type: "address" },
{ name: "balance", type: "uint256" },
]
}
]; Then if the contract throws that selector (i.e. the length of the result is congruent to 4 mod 32, with he first 4 bytes matching As an example for a blog on manual performing this, it would be something similar to (pseudocode): if (result.length % 32 == 4 && result.slice(0, 4) === SELECTOR) {
const result = abiDecode([ "address", "uint" ], result.slice(4))
console.log(result[0], result[1]);
} Is this available yet? Or is this something else, and I'm just confused. :) Either way, I'll help out any way I can. |
Thanks for the quick response, @ricmoo! The errors will be very similar to events with type 'error' in the ABI. Can we use |
_posts/2021-04-20-custom-errors.md
Outdated
// BigNumber { _hex: '0x0100000000', _isBigNumber: true }, | ||
// available: BigNumber { _hex: '0x0100', _isBigNumber: true }, | ||
// required: BigNumber { _hex: '0x0100000000', _isBigNumber: true } | ||
// ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ricmoo This is currently how we suggested dealing with decoding errors. Do you have anything to add here?
LOL! Awesome and insane. That solution will work, making ethers think the Error is a function and let it compute the selector as is, but I would add a new type, an Then it would look like: // Pure/view will just work
try {
const balance = amount contract.balance(constants.ZeroAddress);
console.log("BALANCE", balance);
} catch (error) {
console.log(error);
// error: {
//. code: "CALL_EXCEPTION",
// errorSignature: "CannotGetBalanceOfZero(address)",
// errorArgs: [ "0x0000...0000" ]
// }
} For non-constant methods though, the return data isn't really around anywhere. Maybe an example would be something akin to using the staticCall? try {
const check = await contract.staticCall.transfer(other, amount);
console.log(check);
} catch (error) {
console.log(error);
// {
//. errorSignature: "InsufficientBalance(address)"
//. errorArgs: other
// }
} When are you thinking of releasing this? Is the support for EIP-838 ready today? I could start adding first-class support tomorrow. |
@ricmoo we plan to release it today and it would be great to have something that shows how it can be used right now instead of maybe tomorrow :) Thing thing about transactions is really annoying. Is there no way to retrieve the error data? This here - https://gist.github.com/gluk64/fdea559472d957f1138ed93bcbc6f78a - seems to just throw eth_call after a failing transaction to see why it went wrong. Do you think that will work? |
So the code you have will work fine for something today. And I'll start on adding first-class support tomorrow. About using function randomlyFail() {
if (block.timestamp % 2) { revert("So long sucka!"); }
} The It would be nice if transaction receipts included return data for the most recent X blocks, but it obviously starts becoming very expensive quick, and would basically require recent blocks to basically have archive data available. I understand the reasons they don't do it. But it would be nice. :) Maybe when interacting with an archive node we could offer a more automatic injection... |
You obviously mean
;) So passing the transaction's block number as in the example would work (unless there are some other transactions that mess with the state in the same block), but you are mainly saying that it will not work on most rpc endpoints because they need an archive node for anything but the most recent blocks, right? |
Ah yes. I'm not up to date on the latest (or possibly any?) syntax. :) But yes, exactly. A transaction may fail while the eth_call passes. Or possibly worse, the transaction may fail for one reason and the eth_call fails with a different error. Only an archive node can replay the transaction to find out what actually happened. |
_posts/2021-04-21-custom-errors.md
Outdated
|
||
const interface = new ethers.utils.Interface(abi); | ||
const error_data = "0xcf47918100000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000100000000"; | ||
interface.decodeFunctionData( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
console.log(...
?
_posts/2021-04-21-custom-errors.md
Outdated
interface.decodeFunctionData( | ||
interface.functions['InsufficientBalance(uint256,uint256)'], | ||
error_data | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe
interface.decodeFunctionData( | |
interface.functions['InsufficientBalance(uint256,uint256)'], | |
error_data | |
); | |
const decoded = interface.decodeFunctionData( | |
interface.functions['InsufficientBalance(uint256,uint256)'], | |
error_data | |
); | |
console.log(`Insufficient balance for transfer. Needed ${decoded.required}` but only ${decoded.available} available.`) |
(plus formatting of the big numbers)
108ae7d
to
fd4b55f
Compare
@ricmoo now that I have you here: I'm assuming this is an error from ethers.js just seeing the version "5.0.7": Can you either fully implement it or create a quick fix by ignoring types in the ABI you don't know about? This error prevents contracts using errors from being deployed by remix. |
@chriseth Yupp, it's being tracked in issue 1497 to remove the error in a patch and issue 1498 for a minor bump to add support. :) |
No description provided.