For the next feature in our Road to Neo3 series, we’ll be looking at the new exception handling mechanism under development for Neo3’s virtual machine (VM). The addition of exception handling capabilities to Neo smart contracts is intended to make them more robust, behaving more like traditional applications that can recover from unexpected errors.
The Neo Virtual Machine
All contracts deployed to the Neo blockchain are executed within NeoVM by nodes on the network. When a contract is invoked, the inputs associated with the invocation transaction are run through the contract bytecode inside the VM. An interactive guide to how the NeoVM works can be found here.
Every node on Neo will execute the same code with the same VM, so the result is deterministic whether the result of the invocation succeeds or fails. This is good because it means that if the transaction had invalid inputs (like trying to send more assets than an address owns), it won’t go any further and the contract execution will stop.
In some cases, it may be desirable for a contract to be able to handle unexpected situations gracefully and continue their operation. To make this possible, developers need to be able to equip their contracts with the ability to predict such events and react to them with case-specific logic.
This is achieved by implementing exception handling mechanisms, which allow developers to write special instructions to be used in the event of an unexpected error. These could simply log and notify the developer of the particular issue encountered to help with debugging, or they could provide further code for analyzing and processing the invocation.
Initial Try-Catch proposal
The common approach to handling exceptions is a try-catch mechanism, which enables a program to test for exceptions ahead of time rather than randomly encountering one and crashing. The program will run the code to be examined in the try section, then detect and process any problems in the catch section.
The desire to add exception handling capabilities to Neo contracts was first fielded by Erik Zhang, Neo co-founder and core developer, in July, 2018. This led to the submission of an initial proposal for a try-catch mechanism by NeoResearch’s Igor Coelho, suggesting a new TRY opcode that would make a note of the location for the VM to jump to if an exception occurs.
The code being watched can then be executed, but instead of faulting on an error, the VM would jump to the catch location before progressing. This means that code that would usually trigger the VM to enter the FAULT state could instead be handled and processed further.
A simple example of this behaviour was provided by Coelho, demonstrating a divide by zero error. In the current Neo2 VM, these instructions would trigger the FAULT state and end the execution of the contract.
With the proposed TRY opcode to jump locations, the failure could instead be predicted and handled. Before executing the code to be watched, the TRY opcode could provide the VM with information on where to go for further instruction in the event of a failure. In the proposed design, this took the form of a CATCH opcode which can provide the reason for the exception.
Further discussion between Neo developers considered the usefulness of implementing a finally opcode as well, which would contain a code section to be run regardless of whether an exception was caught, and whether it should be possible to allow multiple catch sections.
A complete set of opcodes for implementing try-catch-finally for contracts was provided by Zhang. It should be noted that the conditional throw opcodes THROWIF and THROWIFNOT noted here were later replaced with the ABORT and ASSERT instructions, which was intended to improve contract and wallet security.
In the next article, we’ll examine the Neo3 exception handling process in more detail.