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:
The error conditions could arise from runtime errors such as:
“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 Shared Library 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 Shared Library 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 Shared Library. Interrupted occurs when one thread or process interrupts another thread or process.In most modern Object-Oriented (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 Exception is thrown which terminates the current process. Terminating the process amounts to “popping” the 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 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 Operating System (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).
Example of throwing an exception in Java.
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 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. 1)
Example of defining an exception in Java.
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
Ethereum's Solidity is considered the “standard” for creating smart contracts. The language supports multiple mechanisms (language constructs) that allow for raising of exceptions:
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.
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.
Example of using the revert exception mechanism
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
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.
Example of using the require exception mechanism
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
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.
Example of using the assert exception mechanism
pragma solidity ^0.5.11
contract Inventory
{ unit public quantityInStock;
function checkInventory() external view
{ assert ( quantityInStock <= 0 );
} // End checkInventory
} // End contract Inventroy
Occurs when an exception (throw, revert, require, assert, illegal op-code) occurs somewhere within the 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.
In the following example:
Inventory and ShoppingCart defined (lines 3 and 12 respectively). ShoppingCart contract makes a call to the Inventory contract by creating a pointer (line 17) to a new instance of the Inventory contract. ShoppingCart then makes a call to the Inventory the operation (i.e., function) checkInventory (line 18).quantityInStock from its original zero value, an error condition is detected at (line 6).revert statement (line 7). 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. Example of a propagated exceptions.
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
An alternative to having no error recovery during the execution of a subordinate smart contract (i.e., Inventory from inside ShoppingCart), is to:
call mechanism to invoke checkInventory() which returns a pointer to the new Inventory contract. (Line 17).inventoryPointer) to inventoryAddress by casting the pointer to an address (Line 18)checkInventory() method of the Inventory contract (Line 29).hasInventory is true) (Line 20)revert error (Line 22)Inventory as appropriate (Line 23)Alternate of catching an Exception without propagating up the stack.
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
call is a low-level mechanism used to invoke smart contract operations, but it is subject to re-entrance attacks:
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.
checkInventory (Line 19).checkInventory did not raise an exception (Line 20-22).
successFlag is set to true (Line 21). revert with a reason string was called inside Inventory contract (Line 23-26).
successFlag is set to false (Line 24). revert() was used or there was a failing assertion, division by zero, etc. inside Inventory contract (Line 27-30).
successFlag is set to false (Line 28).
Alternate of catching an Exception with a try/catch block.
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
[char][✓ char, 2022-03-22]Review