====== 2.3.4.8.4.1 Exceptions ====== [[dido:public:ra:1.2_views:3_taxonomic:4_data_tax:08_objects:07_opers:start| Return to Operational Data]] ===== Overview ===== [[dido:public:ra:1.2_views:3_taxonomic:4_data_tax:08_objects:07_opers:01_except | Return to Top]] An important aspect of executing any set of instructions is **''What happens if there is an error?''**. The error condition could arise from system errors such as: * **Access denied [[dido:public:ra:xapend:xapend.a_glossary:s:segfault]]** occurs when the user does not have privileges to a resource, such as a file, or when the resources are locked by some program or user. Other examples would be trying to access memory that is outside the current program space or trying to execute a null instruction. * **Device not ready** occurs when an external device is not ready or available. For example, a disk drive that has not been mounted or accessing a monitor that has been disconnected. The resource may have been powered down, disconnected, damaged, moved, deleted, or had a fault. Alternatively, the resource as specified may simply not exist (i.e., typographical errors in its name). * **Low Disk Space** occurs when the disk system is full or nearly full. * **Out of memory** occurs when the system runs out of **Heap** and/or **[[dido:public:ra:xapend:xapend.a_glossary:s:stack_memory]]** memory. The system can not load new programs or functions (i.e., **Stack**) or execute the existing ones (i.e., **Heap** limit). The error conditions could arise from runtime errors such as: * **Dividing by zero** occurs when, during execution, an attempt is made to divide a number by zero. Because these types of errors can not be c=found at compile-time, they occur at runtime. * **Out of Bound** occurs when a program tries to access an element in an array that is out of range of the array. Many languages dimension arrays 0 to +number), some allow negative indexing and others do not. * **Type Mismatch** occurs when an attempt is made to store a value into an array that is not compatible (i.e., trying to store an integer in an array of strings). * **Range Error** occurs when a value does not meet the allowable range of values specified by the code. For example, trying to set a value of 200 into a value described by a percent type that ranges from 0 to 100. * **Number Format Error** occurs when an attempt is made to convert a string to a number, but the string does not match the described format. For Example, trying to convert the String **''"one"''** to a number. The error conditions could arise from syntax or semantic errors. **Note:** strongly typed systems avoid these errors) such as * **''Value must be an Unsigned Integer''** occurs when the basic types of parameters do not match. Usually, this occurs when the code has changed or the correct [[dido:public:ra:xapend:xapend.a_glossary:s:shared_libraries]] can not be found. * **''Too Few Parameters''** occurs when the code tries to call a procedure, function or method that requires more parameters than can be found in the existing libraries. This can occur due to changes in the calling signature or when a [[dido:public:ra:xapend:xapend.a_glossary:s:shared_libraries]] is not found. * **''Class Not Found''** occurs when the code is trying to instantiate a class whose definition is not included in the current program or any of the other [[dido:public:ra:xapend:xapend.a_glossary:s:shared_libraries]]. * **''Interrupted''** occurs when one thread or process interrupts another thread or process. In most modern [[dido:public:ra:xapend:xapend.a_glossary:o:oo]] languages ( i.e., Java, C#, C++, Python, etc.) these **Error Conditions** are processed through the use of **Exceptions**. When an error occurs, regardless of where an [[dido:public:ra:xapend:xapend.a_glossary:e:exception]] is thrown which terminates the current process. Terminating the process amounts to "popping" the **[[dido:public:ra:xapend:xapend.a_glossary:s:stack_memory]]** for the most recent call and (if possible) releasing memory from the **Heap**. This process of unwinding the **Stack** and the **Heap** until the **Exception** is caught by an [[dido:public:ra:xapend:xapend.a_glossary:e:exception_handler]] programmed to handle the particular kind of error thrown. If an **Exception Handler** is encountered that does not handle the error, the exception is rethrown until an **Exception Handler** is found that does handle the error or the program **Stack** is empty (nothing left to pop), which terminates the program and control reverts to the [[dido:public:ra:xapend:xapend.a_glossary:o:os]]. **Exception Handlers** can be quite specific in which **Exceptions** they can handle, or they can be very generic. To catch specific **Exceptions** that can arise from a method, many OO languages allow for a method to declare what known exceptions can be thrown from the method. For example, if a method allocates memory from the **Heap**, it may need to indicate it can throw an **'' Out of Memory''** exception. The following example of a writeMessage routing demonstrates how a method can inform a caller of the potential exceptions it can throw (i.e., **''IOEception''**). public void writeMessage ( char[] message ) throws IOException; To generically catch all **Exceptions** in most OO languages, using a simple **''Catch (Exception e)''** block within the **Exception Handler** is all it takes. It is considered good programming practice to do this at most component, subsystem or system boundaries and to log these conditions for later review. These log entries also usually include a **[[dido:public:ra:xapend:xapend.a_glossary:s:stack_trace]]**, which in simple terms is a list of the method calls that the application was in middle of when an Exception was thrown. (( Stackoverflow, __What is a stack trace, and how can I use it to debug my application errors?__, 2010, Accessed: 6 November 2021, [[https://stackoverflow.com/questions/3988788/what-is-a-stack-trace-and-how-can-i-use-it-to-debug-my-application-errors]] )) public class IncorrectValueExcpetion extends Exception { public IncorrectValueExcpetion ( String errorMessage ) { super(errorMessage); } // End IncorrectFileNameException } // End IncorrectValueExcpetion void myProcedure() throws IncorrectValueExcpetion { throw new IncorrectValueExcpetion ( "Opps! The vaue is incorrect"); } // End myProcedure void tester() { try { myProcedure(); } // End try catch ( IncorrectValueExcpetion incorrectValueExcpetion ) { // Prints what exception has been thrown System.out.println ( incorrectValueExcpetion ); } // End MyException block catch ( Exception exception ) { exception.printStackTrace(); } // End generic catch block } // End tester ===== DIDO Specifics ===== [[dido:public:ra:1.2_views:3_taxonomic:4_data_tax:08_objects:07_opers:01_except | Return to Top]] Ethereum's Solidity is considered the "standard" for creating smart contracts. The language supports multiple mechanisms (language constructs) that allow for raising of exceptions: ==== throw ==== Originally, Solidity had a **''throw''** expression which was analogous to the **''throw''** in other languages such as Ada, C++, C#, Java, etc. However, there is a major difference: in the Solidity **''throw''**, all the state changes made in a smart contract are undone automatically when the **''throw''** occurs, similarly to a database performing a rollback within a transaction. The Solidity keyword **''throw''** has been deprecated in Solidity version 0.4.13 and removed in version 0.5.0. It has been replaced with a **''revert()''** expression with the same semantics as the original **''throw''** expression. However, **''throw''**. It has been reported that the **''revert''** was more readable and semantically also implied the rollback functionality. In addition, the original **''throw''** did not refund any unused gas while the **''revert''** expression does. ==== revert ==== Does not evaluate any condition and does not depend on any state or statement. It is used to generate exceptions, display errors, and revert the function call. This statement contains a string message which indicates the issue related to the information of the exception. Calling a revert statement implies an exception is thrown, the unused gas is returned, and the state reverts to its original state. pragma solidity ^0.5.11 contract Innventory { unit public quantityInStock; function checkInventory() external view { if ( quantityInStock <= 0 ) { revert ( 'quantityInStock must be greater than or equal to 0'); } // End if } // End checkInventory } // End contract Inventroy : **Note:** The reason displayed for the error in the console is quantityInStock must be greater than or equal to 0. ==== require ==== Evaluates a boolean expression representing prerequisites for running the function (i.e., it declares the constraints which should be satisfied before executing the code). The **''require''** accepts a single argument and returns a boolean value after evaluation, it supports another optional argument a custom string message option. If **''false''**, an exception is raised and execution is terminated. The unused gas is returned to the caller and the state is reversed to its original state. pragma solidity ^0.5.11 contract Innventory { unit public quantityInStock; function checkInventory() external view { require ( quantityInStock <= 0 , 'quantityInStock must be greater or equal to than 0'); } // End checkInventory } // End contract Inventroy : **Note:** The reason displayed for the error in the console is quantityInStock must be greater than or equal to 0. ==== assert ==== Evaluates a boolean expression and either the program will continue its execution (**''true''**) or when (**''false''**) throws an exception. Instead of returning the unused gas, the assert statement consumes the entire gas supply and the state is then reversed to the original state. **''Assert''** is used to check the current state and function conditions before the execution of the contract. The **''assert''** should only be used during testing and should check for conditions that should never occur in a finished **Smart Contract**. pragma solidity ^0.5.11 contract Inventory { unit public quantityInStock; function checkInventory() external view { assert ( quantityInStock <= 0 ); } // End checkInventory } // End contract Inventroy : **Note:** The reason displayed for the error in the console is Invalid Opcode. ==== propagated exceptions ==== Occurs when an exception (**''throw''**, **''revert''**, **''require''**, **''assert''**, **''illegal op-code''**) occurs somewhere within the [[dido:public:ra:xapend:xapend.a_glossary:s:stack_memory | call stack]]. The call stack is unwound (i.e., popped) until the exception is caught, or the main program is terminated because of the exception. Within Solidity, this occurs when one smart contract calls another smart contract or library function and an exception occurs. === No Error Recover === In the following example: - There are two contracts, **''Inventory''** and **''ShoppingCart''** defined (lines 3 and 12 respectively). - The **''ShoppingCart''** contract makes a call to the **''Inventory''** contract by creating a pointer (line 17) to a new instance of the **''Inventory''** contract. - The **''ShoppingCart''** then makes a call to the **''Inventory''** the operation (i.e., function) **''checkInventory''** (line 18). - Since we have never changed the **''quantityInStock''** from its original zero value, an error condition is detected at (line 6). - An exception is called using the **''revert''** statement (line 7). - Any changes made to **''Inventory''** are unwound and the stack is popped, returning control to **''ShoppingCart''** with an exception which causes the **''ShoppingCart''** contract to **undo** all the state changes made and terminates the **''ShoppingCart''**. : **Note:** This process is repeated until the stack is empty, or the exception is caught. (See next sections). pragma solidity ^0.5.11; contract Inventory { uint external public quantityInStock; function checkInventory() external view { if ( quantityInStock <= 0 ) { revert ( "quantityInStock must be greater than 0"); } // End if } // End checkInventory } // End Inventroy contract contract ShoppingCart { uint public numberToOrder; function addItemToCart ( uint desiredQuantity ) external { Inventory inventoryPointer = new Inventory(); inventoryPointer.checkInventory(); if ( desiredQuantity < inventoryPointer.quantityInStock() ) { revert ( "Not enough inventory for the order"); } // End if } // End addItemToCart operation } // End ShoppingCart contract === With Error Recover === An alternative to having no error recovery during the execution of a subordinate smart contract (i.e., **''Inventory''** from inside **''ShoppingCart''**), is to: - Use the low-level **''call''** mechanism to invoke **''checkInventory()''** which returns a pointer to the new **''Inventory''** contract. (Line 17). - Save the address of the pointer (i.e., **''inventoryPointer''**) to **''inventoryAddress''** by casting the pointer to an **''address''** (Line 18) - Determine if there is any inventory by calling the **''checkInventory()''** method of the **''Inventory''** contract (Line 29). - If there is inventory (i.e., **''hasInventory''** is true) (Line 20) - Then determine if there is enough inventory to fulfill the request (line 21) - If there is not enough inventory to fill the request, use the **''revert''** error (Line 22) - Else, do something to fulfill the order and decrement the quantity in the **''Inventory''** as appropriate (Line 23) contract ShoppingCart { uint public numberToOrder; function addItemToCart ( uint desiredQuantity ) external { Inventory inventoryPointer = new Inventory(); address inventoryAddress = address(inventoryPointer); (bool hasInventory, ) = inventoryAddress.call ( abi.encodePacked ( "checkInventory()" ) ); if ( hasInventory ) { if ( desiredQuantity < inventoryAddress.quantityInStock ) { revert ( "Not enough inventory for the order"); } // End if // do something here!! } // End if } // end addItemToCart operation } // end ShoppingCart contract : **Note:** The **''call''** is a low-level mechanism used to invoke smart contract operations, but it is subject to re-entrance attacks: : //Computer scientists say that a procedure is re-entrant if its execution can be interrupted in the middle, initiated over (re-entered), and both runs can complete without any errors in execution. In the context of Ethereum smart contracts, re-entrancy can lead to serious vulnerabilities. //(( Martin Derka, __What is a Re-Entrancy Attack?__, Quantstamp, 19 August 2019, Accessed: 30 November 2021, [[https://quantstamp.com/blog/what-is-a-re-entrancy-attack]] )). === With Error Catching === Catching errors is easy and efficient in Solidity after version 0.6.0 with its introduction of **''try/catch''** blocks. The previous **Error Recovery** method might be acceptable when there are one to a few smart contracts executed within a smart contract. However, when there are many subordinate contracts called from within a smart contract, the control flow can become very complex and hard to read (see Lines 19-25 above). The **''try/catch''** blocks allow a section of code to be surrounded with an exception handler that can covert a single subordinate contract call (i.e., **''Inventory''**) or multiple contract invocations l using the same exception handling software. - An attempt is made to call **''checkInventory''** (Line 19). - Block is executed if the call to **''checkInventory''** did not raise an exception (Line 20-22). - The **''successFlag''** is set to **''true''** (Line 21). - Block is executed in case a **''revert''** with a reason string was called inside **''Inventory''** contract (Line 23-26). - The **''successFlag''** is set to **''false''** (Line 24). - Block is executed in case a **''revert()''** was used or there was a failing assertion, **''division by zero''**, etc. inside **''Inventory''** contract (Line 27-30). - The **''successFlag''** is set to **''false''** (Line 28). - There is only one place where the program returns to the caller (Line 31). contract ShoppingCart3 { uint public numberToOrder; function addItemToCart ( uint desiredQuantity ) external returns ( bool success ) { Inventory inventoryPointer = new Inventory(); bool successFlag = false; try inventoryPointer.checkInventory() { // Do something here! successFlag = true; } // End try catch Error ( string memory ) // Reason { // do something here and log the error here succsuccessFlagess = false; } // End Error Block catch ( bytes memory ) //lowLevelData { // do something here and log the error here successFlag = false; } // End generic catch Block return successFlag; } // end addItemToCart operation } // end ShoppingCart contract3 Review /**=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- /* To add a discussion page to this page, comment out the line that says ~~DISCUSSION:off~~ */ ~~DISCUSSION:on|Outstanding Issues~~ ~~DISCUSSION:off~~