-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Try/catch for external calls #6927
Comments
#1505 is related to this, but I wanted to create a new issue altogether to discuss the proposal without interfering with the discussion in the previous one, which is not restricted to a single external call. Note however that @SilentCicero's comment there illustrates the scenario I described above. |
Thoughts on removing the
This would be more in line with other traditional programming languages. |
@maraoz going for full-featured exception support (e.g. including If we indeed restrict This is the main reason why @chriseth suggested to have a single expression covered by |
I really dislike the
Java has a similar syntax for try-with-resources:
Its semantics are completely different from the ones proposed here, so I think it would also be confusing. Isn't the root of this confusion reusing |
I would prefer something like
I've also considered this, and am not against it, but have no good suggestions. Since |
My concern is using try/catch for this use case could be confusing for developers coming from other languages. Is it possible to wrap an external call and append a success bool and error data to the return types so that an if/else pattern could be used? (T ret_value, bool success, bytes error_value) = ext_call(doStuff())
if (success) {
// external call successful do something based on ret_value
} else {
// external call unsuccessful do something based on error_value
} |
Yes, but then you lose the niceties of having |
Which explains why appended success/error used with if/else is not a runner unfortunately. 😄 Synonyms for Same goes for external (ext_call)
then (T ret_value) {
// block to be executed if ext_call succeeds, i.e. regular Solidity behaviour
}
catch (bytes error_value) {
// block to be executed if ext_call fails, as opposed to reverting the current transaction
} I am more reassured seeing the Java try-with-resources. The key will be education on its use. |
I'd love to see this implemented. I talked about try catch in my eth new york talk and wrote a post with an example on how to do it with current syntax just a couple of days back https://blog.polymath.network/try-catch-in-solidity-handling-the-revert-exception-f53718f76047 Although what this proposal proposes can already be done fairly easily, I still really want this to be implemented because this will bring in the static checks done by the compiler with it and make it slightly easier to do. Now back to the proposal, do you think the syntax of |
Your blogpost reminded me of this issue and prompted me opening this feature request 😂
I guess we could have the compiler transform an internal call to an external one automatically when inside |
Yeah, that's what I was thinking. It makes things a bit cleaner but brings in a bit of ambiguity. I'd be happy to see this implemented in either way. |
Alternatively we can use try (external_call) as (T ret_value) {
// statements in case of success
} catch (bytes error_value) {
// statements in case of revert
} The |
Ohhh, I really like this! That usage of |
I like
or even use
|
I love @frangio's proposal!! |
Just fiddling a little:
|
|
(sorry for the spam) |
@chriseth I like the second and third options you proposed, the first one might trick people into thinking Would implementing this be feasible for an external contributor? If so, could you give some pointers regarding what would need to be done? I'm thinking the whole parsing of the new syntax might be a bit too much, but perhaps people could help on the implementation, tests, etc. once that's done. |
I definitely like this to be in rather sooner than later. We would of course be very happy to have this done externally! The following components would need changes:
|
Since
|
We stumbled across some problematic edge cases that require some discussion: What happens if the return values of Related, but maybe a little different: What happens if error message decoding fails? |
Let's assume contract alpha calls contract bravo using try-catch. If I am not mistaken, this problem can only arise when contract bravo completes its execution properly but the contract alpha is unable to decode the data returned by bravo. I think this try-catch is meant to catch reverts that happen in the contract bravo. Since this error is in contract alpha, a normal revert should happen. |
@maxsam4 thanks for your comment! Yes, this is a striking argument against catching decoding errors for success return data: If control-flow ends up in any of the catch clauses, then you assume that the external call reverted and especially that any state-changes it did reverted. So it looks like we have no choice there. The only question left for debate thus seems to be how to handle decoding errors in the failure data. Should we continue in the catch-all-catch-clause or should we just revert? |
Reverting sounds like the way to go, I would only go to the What happens if only one of the two |
My current plan is as follows:
|
@chriseth Can this be closed? |
Implemented in #7328 |
Hey @chriseth @nventuro In our case, target contracts are a user input, and also it is critical for the code to be confident this external call can not revert. Is there a way to receive a return value from the target and have a confidence this "line" will not revert? I would say, something like 'try to decode'? Code that would do the job for us currently would look somewhat like this:
|
Thanks for your feedback, @forshtat ! Can you provide some code that uses try/catch and explain how you would like it to behave? "Code that would do the job for us" - I don't see how this is different from the behaviour of try/catch. |
@chriseth is it possible to use |
|
Abstract
Solidity wraps the
call
opcode in a higher-level concept, that of external function calls. An external function call does more than a simplecall
, including:call
failureThis is a proposal to break down these steps so that reverting is optional, while keeping all other current semantics and behaviour.
Motivation
There are multiple scenarios in which it is necessary to not revert the whole transaction if one of the inner transactions failed, including batch transfers of tokens and most meta-tx solutions. In these cases, the calling contract should handle the failed call and continue its own execution.
The only way to do this currently is by using
call
directly, and performing a test on thesuccess
boolean value returned by it. However, keeping this consistent with other external function calls requires a deep understanding of how these are performed, and is not trivial to get right.try/catch
is a well-known pattern that should immediately convey the intent behind the construct to a developer familiar with other languages that support it (including JavaScript, Python, Java, C++, C#).Since we're not attempting to create a full-fledged exception handling mechanism, a syntax that clearly indicates a single external call will be covered by the
try
block is desirable.Specification
@chriseth and I discussed a couple alternatives on the solidity-dev gitter channel. The final proposal is as follows:
ext_call
is an external call expression (i.e. a call to anexternal
function on a contract type), that (optionally) returns a value of typeT
Expected behaviour is equivalent to a Solidity function call, except that all conditions that may cause a revert (including no destination code, failed transaction, failure at decoding the return value) cause the execution flow to jump to the
catch
block. A missingthen
orcatch
block simply means said block will not be executed.A couple notes about the proposed syntax:
ret_value
anderror_value
are scoped to the blocks where they are valid and initializedthen
, but it does have the benefit of not being a common variable or function name, and is highly related to execution flowthen
andcatch
can be removed, leaving simplytry (ext_call)
, an expression with no trailing semicolon. We may want to makecatch
mandatoryBackwards Compatibility
Both
try
andcatch
are reserved keywords as of v0.5.9.then
is not, nor could I identify any other reserved keyword (e.g.success
) that we could use in its place.Since dropping the
then
keyword would lead to very weird syntax, we should add a new one in the next breaking change release (see #6040).The text was updated successfully, but these errors were encountered: