diff --git a/docs/060-breaking-changes.rst b/docs/060-breaking-changes.rst index effa966f9..c1c4e8f83 100644 --- a/docs/060-breaking-changes.rst +++ b/docs/060-breaking-changes.rst @@ -50,6 +50,12 @@ This section gives detailed instructions on how to update prior code for every b * Change ``uint length = array.push(value)`` to ``array.push(value);``. The new length can be accessed via ``array.length``. +New Features +============ + + * The :ref:`try/catch statement ` allows you to react on failed external calls. + + Deprecated Elements =================== diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 75056dbe6..4d744e430 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -15,6 +15,10 @@ Most of the control structures known from curly-braces languages are available i There is: ``if``, ``else``, ``while``, ``do``, ``for``, ``break``, ``continue``, ``return``, with the usual semantics known from C or JavaScript. +Solidity also supports exception handling in the form of ``try``/``catch``-statements, +but only for :ref:`external function calls ` and +contract creation calls. + Parentheses can *not* be omitted for conditionals, but curly brances can be omitted around single-statement bodies. @@ -383,7 +387,7 @@ of an exception instead of "bubbling up". .. warning:: The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the account called is non-existent, as part of the design of EVM. Existence must be checked prior to calling if needed. -It is not yet possible to catch exceptions with Solidity. +Exceptions can be caught with the ``try``/``catch`` statement. ``assert`` and ``require`` -------------------------- @@ -488,3 +492,94 @@ In the above example, ``revert("Not enough Ether provided.");`` returns the foll .. note:: There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which was deprecated in version 0.4.13 and removed in version 0.5.0. + + +.. _try-catch: + +``try``/``catch`` +----------------- + +A failure in an external call can be caught using a try/catch statement, as follows: + +:: + + pragma solidity >=0.5.0 <0.7.0; + + interface DataFeed { function getData(address token) external returns (uint value); } + + contract FeedConsumer { + DataFeed feed; + uint errorCount; + function rate(address token) public returns (uint value, bool success) { + // Permanently disable the mechanism if there are + // more than 10 errors. + require(errorCount < 10); + try feed.getData(token) returns (uint v) { + return (v, true); + } catch Error(string memory /*reason*/) { + // This is executed in case + // revert was called inside getData + // and a reason string was provided. + errorCount++; + return (0, false); + } catch (bytes memory /*lowLevelData*/) { + // This is executed in case revert() was used + // or there was a failing assertion, division + // by zero, etc. inside getData. + errorCount++; + return (0, false); + } + } + } + +The ``try`` keyword has to be followed by an expression representing an external function call +or a contract creation (``new ContractName()``). +Errors inside the expression are not caught (for example if it is a complex expression +that also involves internal function calls), only a revert happening inside the external +call itself. The ``returns`` part (which is optional) that follows declares return variables +matching the types returned by the external call. In case there was no error, +these variables are assigned and the contract's execution continues inside the +first success block. If the end of the success block is reached, execution continues after the ``catch`` blocks. + +Currently, Solidity supports different kinds of catch blocks depending on the +type of error. If the error was caused by ``revert("reasonString")`` or +``require(false, "reasonString")`` (or an internal error that causes such an +exception), then the catch clause +of the type ``catch Error(string memory reason)`` will be executed. + +It is planned to support other types of error data in the future. +The string ``Error`` is currently parsed as is and is not treated as an identifier. + +The clause ``catch (bytes memory lowLevelData)`` is executed if the error signature +does not match any other clause, there was an error during decoding of the error +message, if there was a failing assertion in the external +call (for example due to a division by zero or a failing ``assert()``) or +if no error data was provided with the exception. +The declared variable provides access to the low-level error data in that case. + +If you are not interested in the error data, you can just use +``catch { ... }`` (even as the only catch clause). + +In order to catch all error cases, you have to have at least the clause +``catch { ...}`` or the clause ``catch (bytes memory lowLevelData) { ... }``. + +The variables declared in the ``returns`` and the ``catch`` clause are only +in scope in the block that follows. + +.. note:: + + If an error happens during the decoding of the return data + inside a try/catch-statement, this causes an exception in the currently + executing contract and because of that, it is not caught in the catch clause. + If there is an error during decoding of ``catch Error(string memory reason)`` + and there is a low-level catch clause, this error is caught there. + +.. note:: + + If execution reaches a catch-block, then the state-changing effects of + the external call have been reverted. If execution reaches + the success block, the effects were not reverted. + If the effects have been reverted, then execution either continues + in a catch block or the execution of the try/catch statement itself + reverts (for example due to decoding failures as noted above or + due to not providing a low-level catch clause). \ No newline at end of file diff --git a/docs/grammar.txt b/docs/grammar.txt index 4433b4a43..a04ec0705 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -62,12 +62,14 @@ StorageLocation = 'memory' | 'storage' | 'calldata' StateMutability = 'pure' | 'view' | 'payable' Block = '{' Statement* '}' -Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement | +Statement = IfStatement | TryStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement | ( DoWhileStatement | PlaceholderStatement | Continue | Break | Return | Throw | EmitStatement | SimpleStatement ) ';' ExpressionStatement = Expression IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? +TryStatement = 'try' Expression ( 'returns' ParameterList )? Block CatchClause+ +CatchClause = 'catch' Identifier? ParameterList Block WhileStatement = 'while' '(' Expression ')' Statement PlaceholderStatement = '_' SimpleStatement = VariableDefinition | ExpressionStatement