Skip to main content
Tolk supports exceptions and handling them with try catch. If an exception is not caught, the program terminates with errCode. In TVM, exceptions are numbers. There is no Error class or exception hierarchy, only exit codes.

Error codes

A simple pattern is to define a constant for each exception that a contract may produce:
const ERR_LOW_BALANCE = 200
const ERR_SIGNATURE_MISMATCH = 201
Yet, this pattern should not be used in production environments, as it does not scale. Instead, use an enum — they are easier to maintain and reference, as enumerations come with exhaustive checks from the compiler:
enum ErrCode {
    LowBalance = 200,
    SignatureMismatch,    // implicitly 201
}
Use these constants in throw and related statements described on this page.
Use error codes between 64 and 2048Lower values are reserved by TVM. Larger ones are more gas-expensive.

throw statement

To throw an exception unconditionally:
throw ERR_CODE;
Non-constant expressions such as throw someVariable are supported but not recommended. It works, but the compiler cannot determine possible error codes and therefore cannot provide a correct ABI for external callers. An exception can carry an argument:
throw (ERR_CODE, errArg);
The argument is available in catch and must be a TVM primitive, not a tuple nor a tensor.

assert statement

An assert is a shorthand for “throw if a condition is not satisfied”. It is commonly used when parsing user input:
assert (msg.seqno == storage.seqno) throw E_INVALID_SEQNO;
assert (msg.validUntil > blockchain.now()) throw E_EXPIRED;
  • The long form assert (condition) throw ERR_CODE is preferred.
  • The short form assert (condition, ERR_CODE) exists but is not recommended.
The condition must be either a boolean or an integer, where non-zero integers are treated as true and all other integer values as false. An assert statement is equivalent to:
if (!condition) {
    throw ERR_CODE;
}

TVM implicit throws

During contract execution, TVM may throw runtime exceptions. For example:
  • slice.loadInt(8) will fail when the slice is empty.
  • builder.storeRef(cell) will fail when the builder has 4 references already.
  • tuple.push(value) will fail when the tuple has 255 elements already.
  • etc.
Additionally, an out-of-gas exception may occur at any point, and it cannot be caught using a catch block. It can only be prevented by writing exhaustive testing suites.

try catch statement

The catch block is used to handle most runtime errors within the try block, except for out-of-gas exceptions:
try {
    // ...
} catch (errCode) {
    // errCode is `int`
}
  • Use the short form catch { ... } when errCode is not needed.
  • Use the long form catch (errCode, arg) when the exception may carry an argument produced by throw (errCode, arg). If the exception was thrown as throw errCode, the argument is null.
try {
    throw (ERR_LOW_BALANCE, 555);
} catch (errCode, arg) {
    val data = arg as int;    // 555
}
Any error inside a try block reverts all changes made within. TVM restores local variables and control registers to the state they had before entering try.However, gas counters and TVM codepage settings are not rolled back. Gas is always spent.