Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/book/spell-check-custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,7 @@ CallResponse
md
URIs
Const
backtrace
Backtrace
backtracing
Backtracing
197 changes: 166 additions & 31 deletions docs/book/src/basics/error_handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,44 +20,38 @@ if some_error_occurred {
}
```

At runtime, the `panic` expression aborts and reverts the execution of the entire program. At compile time, for each `panic` encountered in code, Sway compiler will generate a unique revert code and create an entry in the ABI JSON `errorCodes` section. The generated `errorCodes` entry will contain the information about source location at which the `panic` occurs, as well as the error message.
At runtime, the `panic` expression aborts and reverts the execution of the entire program. At compile time, for each `panic` encountered in code, Sway compiler will generate a unique revert code and create an entry in the ABI JSON `errorCodes` section. The generated `errorCodes` entry will contain the information about the source location at which the `panic` occurs, as well as the error message.

**This mechanism allows for getting a rich troubleshooting information, without an additional on-chain cost.** The generated bytecode will contain only the revert instruction, and the remaining information, the error message and the error location, are stored off-chain, in the ABI JSON file.
In addition, for each function call that might panic, compiler will generate an entry in the ABI JSON `panickingCalls` section. Those entries will contain source locations of functions whose calls might eventually end up in calling a `panic` somewhere in the call chain.

For example, let's assume that the above code is situated in the module `some_module`, contained within the version `v1.2.3` of the package `some_package`.
**Combined together, these two ABI JSON entries, `errorCodes` and `panickingCalls`, allow for getting a rich troubleshooting information, that contains the error location and a partial backtrace, without any or negligible additional on-chain cost.**

At runtime, the `panic` will result in a compiler generated revert code, e.g., 18446744069414584323. At compile time, an entry similar to this will be added to the ABI JSON `errorCodes` section:
The generated bytecode will contain only the revert instructions, while error messages and error locations will be stored off-chain, in the ABI JSON file, making the `panic`king a zero on-chain cost operation. Panic backtrace comes with only a negligible on-chain cost, that can additionally be opted-in or out, as explained in detail in the chapter [Configuring the Backtrace Content](#configuring-the-backtrace-content).

```json
"errorCodes": {
"18446744069414584323": {
"pos": {
"pkg": "[email protected]",
"file": "some_module.sw",
"line": 13,
"column": 9
},
"logId": null,
"msg": "Some error has occurred."
},
}
```
_Partial_ backtrace means that the _backtrace is limited to up to five function calls_. In practice, deeper call chains are rare. Also, it is possible to choose which functions should be a part of the reported backtrace. This is done by using the `backtrace` build option and the `#[trace]` attribute, as also explained in detail in the chapter [Configuring the Backtrace Content](#configuring-the-backtrace-content).

Rust and TypeScript SDK, as well as `forc test`, recognize revert codes generated from `panic` expressions. E.g., if a Sway unit test fails because of a revert caused by the above `panic` line, the `forc test` will display the following:
Tools like Rust and TypeScript SDKs and `forc test` recognize revert codes generated by `panic` expressions. E.g., if a Sway unit test fails because of a revert caused by the above `panic` line, `forc test` might display an output similar to the following:

```console
test some_test, "path/to/failing/test.sw":42
revert code: ffffffff00000003
test some_test, "/tests.sw":42
revert code: 8100000000000000
├─ panic message: Some error has occurred.
└─ panicked in: [email protected], src/some_module.sw:13:9
├─ panicked: in some_package::some_error_occurred
│ └─ at [email protected], src/some_module.sw:13:9
└─ backtrace: called in some_other_package::some_other_module::some_function
└─ at [email protected], src/some_other_module.sw:106:11
called in my_project::some_test
└─ at my_project, src/tests.sw:48:8
```

What the above panic location and the backtrace are telling us, is that `some_test` has called `some_function` that has called `some_error_occurred` which has panicked with the message "Some error has occurred."

### Error Types

Passing textual error messages directly as a `panic` argument is the most convenient way to provide a helpful error message. It is sufficient for many use-cases. However, often we want:
Passing textual error messages directly as a `panic` argument is the most convenient way to provide a helpful error message. It is sufficient for many use-cases. However, sometimes we want:

- to provide an additional runtime information about the error.
- group a certain family of errors together.
- to provide an additional runtime information about the error,
- or to group a certain family of errors together.

For these use-cases, you can use _error types_. Error types are enums annotated with the `#[error_type]` attribute, whose all variants are attributed with the `#[error(m = "<error message>")]` attributes. Each variant represent a particular error, and the enum itself the family of errors. The convention is to postfix the names of error type enums with `Error`.

Expand All @@ -84,17 +78,158 @@ fn do_something_that_requires_admin_access(admin: Identity) {
if !is_admin(admin) {
panic AccessRightError::NotAnAdmin(admin);
}

// ...
}
```

Assuming we have a failing test for the above function, the test output will show the error message, but also the provided `Identity`. E.g.:
Assuming we have a failing test for the above function, the test output will show the error message, but also the provided `Identity` as the _panic value_. E.g.:

```console
test some_test_for_admin_access, "/test.sw":42
revert code: 8100000000000000
├─ panic message: The provided identity is not an administrator.
├─ panic value: NotAnAdmin(Address(Address(79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0)))
├─ panicked: in auth_package::only_admin
│ └─ at [email protected], src/admin_access.sw:11:9
└─ backtrace: ...
```

### `errorCodes` and `panickingCalls` ABI JSON Entries

To explain how `errorCodes` and `panickingCalls` are used to provide error information and backtrace, let's consider the following example:

- a function `only_admin` is defined in the `auth_package` and it panics if the identity is not an admin.
- `only_admin` is called in various guard functions that check preconditions, defined in the `guards_package`. E.g., `check_access_rights`.
- those `guards_package` functions are used within the `funds_contract`.

At compile time, an entry similar to these will be added to the ABI JSON `errorCodes` and `panickingCalls` sections:

```json
"errorCodes": {
"0": { // Unique ID of a `panic` call.
"pos": { // Location in code, at which the `panic` call occurs.
"pkg": "[email protected]",
"function": "auth_package::admin_access::only_admin",
"file": "src/admin_access.sw",
"line": 13,
"column": 9
},
"logId": "10098701174489624218", // Log ID representing the `AccessRightError` enum
// passed as an argument to `panic`.
"msg": null,
},
// Other error codes for other `panic` calls.
},

"panickingCalls": {
"1": { // Unique ID of a potentially panicking function call.
"pos": { // Location in code, at which the function call that might panic occurs.
"function": "guards_package::preconditions::check_admin", // The caller function, `check_admin`.
"pkg": "[email protected]",
// Position within the `check_admin` where `only_admin` is called.
"file": "src/preconditions.sw",
"line": 4,
"column": 9
},
"function": "auth_package::admin_access::only_admin" // The called function, `only_admin`.
},
// Other panicking calls.
}
```

Those unique error and panicking call IDs are embedded by the compiler into the revert code generated for a particular `panic` call.

In case of a revert, tools like SDKs and `forc test` will extract those IDs from the received revert code and using the information contained in the ABI JSON provide a rich troubleshooting details. In the above example, in case of a failing test, `forc test` might display an output similar to the following:

```console
test some_test_for_admin_access, "path/to/failing/test.sw":42
revert code: ffffffff00000007
test some_test, "/tests.sw":42
revert code: 8280000000000003
├─ panic message: The provided identity is not an administrator.
├─ panic value: NotAnAdmin(Address(Address()))
└─ panicked in: [email protected], src/admin_module.sw:11:9
├─ panic value: NotAnAdmin(Address(Address(79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0)))
├─ panicked: in auth_package::admin_access::only_admin
│ └─ at [email protected], src/admin_access.sw:13:9
└─ backtrace: called in guards_package::preconditions::check_admin
└─ at [email protected], src/preconditions.sw:4:9
called in guards_package::preconditions::check_access_rights
└─ at [email protected], src/preconditions.sw:23:13
called in guards_package::preconditions::check_preconditions
└─ at [email protected], src/preconditions.sw:57:9
called in <Contract as Funds>::transfer_funds
└─ at [email protected], src/main.sw:22:9
```

## Configuring the Backtrace Content

### In Default Builds

Backtracing comes with a minimal on-chain cost, in terms of the bytecode size and gas usage. To additionally allow you to opt-in even for this minimal cost, backtracing is configurable via dedicated `backtrace` build option, with different default values for `debug` and `release` builds.

To explain this build option, let us use the following example. We will have five functions named `first`, `second`, ..., `fifth` that call each other sequentially, and the function `fifth` finally calling a failing `assert_eq` that `panic`s.

In the default `debug` build, the output of a failing `forc test` will look similar to this (package names and code locations are omitted for brevity):

```console
test some_test, "test.sw":42
revert code: 8280000000000003
├─ panic message: The provided `expected` and `actual` values are not equal.
├─ panic value: AssertEq(AssertEq { expected: 42, actual: 43 })
├─ panicked: in std::assert::assert_eq
│ └─ at [email protected], src/assert.sw:80:9
└─ backtrace: called in fifth
└─ at ...
called in fourth
└─ at ...
called in third
└─ at ...
called in second
└─ at ...
called in first
└─ at ...
```

In the default `release` build, the backtrace output will contain only the immediate call of the `assert_eq` that happens in the `fifth`:

```console
...
├─ panicked: in std::assert::assert_eq
│ └─ at [email protected], src/assert.sw:80:9
└─ backtrace: called in fifth
└─ at ...
```

Where this difference in backtrace in `debug` and `release` build is coming from? In other words, how the compiler knows which functions to include into backtrace in different build profiles?

The backtrace can be directly influenced by using the `#[trace]` attribute in your code. Similarly to the `#[inline]` attribute, the `#[trace]` attribute can be used on all functions that have implementations. Same like `#[inline]`, it also comes with two arguments, `always` and `never`.

The `#[trace(always)]` instructs the compiler to include the calls of annotated functions in the backtrace in default `release` builds. This attribute should be used to annotate guard functions, like, e.g., `assert`, `assert_eq`, `require`, and `only_owner`, or methods like `Option::unwrap`. When such functions panic, we are actually interested in the places in code in which a failing call happens. E.g., having `#[trace(always)]` on the `assert` function helps us to see _which actual `assert` call has failed_.

**By default, `release` builds will include in the backtrace only the calls to functions annotated with `#[trace(always)]`.** This minimizes the anyhow low on-chain cost of backtrace calculation only to function calls of those function, giving almost a zero on-chain impact while still providing a valuable troubleshooting information.

By using `#[trace(never)]` you can instruct the compiler _not to include a function in a backtrace_, even not in a default `debug` build, which otherwise includes all the panicking calls into backtrace. This is useful, considering that the backtrace will be limited to five functions only. If an intermediate function call can be easily deducted, it might be worth not having it in the backtrace.

E.g., let's assume that the functions `fourth` and `second` are annotated with `#[trace(never)]`. The default `debug` build would then output the following backtrace:

```console
...
├─ panicked: in std::assert::assert_eq
│ └─ at [email protected], src/assert.sw:80:9
└─ backtrace: called in fifth
└─ at ...
called in third
└─ at ...
called in first
└─ at ...
```

### In Custom Builds

To change this default behavior, use the `backtrace` build option. The possible values for the `backtrace` build option, and their meanings are given in the below table.

| Value | Meaning |
| ----- | ------- |
| all | Backtrace all function calls, even of functions annotated with `#[trace(never)]`. |
| all_except_never | Backtrace all function calls, except those of functions annotated with `#[trace(never)]`. This is the default value for `debug` builds. |
| only_always | Backtrace only calls of functions annotated with `#[trace(always)]`. This is the default value for `release` builds. |
| none | Do not backtrace any function calls. Use this option only if you need to fully remove the on-chain cost of backtracing. Considering how negligible the cost is, this will very likely never be needed. |

To learn more about custom builds, see [The `[build-profile.*]` Section](../forc/manifest_reference.md#the-build-profile-section).
11 changes: 6 additions & 5 deletions docs/book/src/forc/manifest_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The `Forc.toml` (the _manifest_ file) is a compulsory file for each package and

* [`[contract-dependencies]`](#the-contract-dependencies-section) - Defines the contract dependencies.

## The `[project]` section
## The `[project]` Section

An example `Forc.toml` is shown below. Under `[project]` the following fields are optional:

Expand Down Expand Up @@ -145,7 +145,7 @@ This allows for a streamlined developer experience while maintaining clear separ
* [forc-index-ts](https://github.com/FuelLabs/example-forc-plugins/tree/master/forc-index-ts): A TypeScript CLI tool for parsing `Forc.toml` metadata to read contract ABI JSON file.
* [forc-index-rs](https://github.com/FuelLabs/example-forc-plugins/tree/master/forc-index-rs): A Rust CLI tool for parsing `Forc.toml` metadata to read contract ABI JSON file.

## The `[dependencies]` section
## The `[dependencies]` Section

The following fields can be provided with a dependency:

Expand All @@ -159,13 +159,13 @@ The following fields can be provided with a dependency:

Please see [dependencies](./dependencies.md) for details

## The `[network]` section
## The `[network]` Section

For the following fields, a default value is provided so omitting them is allowed:

* `URL` - (default: _<http://127.0.0.1:4000>_)

## The `[build-profile.*]` section
## The `[build-profile.*]` Section

The `[build-profile]` tables provide a way to customize compiler settings such as debug options.

Expand All @@ -180,6 +180,7 @@ The following fields can be provided for a build-profile:
* `time_phases` - Whether to output the time elapsed over each part of the compilation process, defaults to false.
* `include_tests` - Whether or not to include test functions in parsing, type-checking, and code generation. This is set to true by invocations like `forc test`, but defaults to false.
* `error_on_warnings` - Whether to treat errors as warnings, defaults to false.
* `backtrace` - Defines which panicking functions to include in a `panic` backtrace. Possible values are `all`, `all_except_never`, `only_always`, and `none`. Defaults to `all_except_never` and `only_always` in the default `debug` and `release` profiles, respectively. For more information on backtracing see the chapter [Irrecoverable Errors](../basics/error_handling.md#irrecoverable-errors).

There are two default `[build-profile]` available with every manifest file. These are `debug` and `release` profiles. If you want to override these profiles, you can provide them explicitly in the manifest file like the following example:

Expand Down Expand Up @@ -217,7 +218,7 @@ error-on-warnings = false
experimental-private-modules = false
```

## The `[patch]` section
## The `[patch]` Section

The [patch] section of `Forc.toml` can be used to override dependencies with other copies. The example provided below patches `https://github.com/fuellabs/sway` with the `test` branch of the same repo.

Expand Down
11 changes: 11 additions & 0 deletions docs/book/src/reference/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Below is the list of attributes supported by the Sway compiler, ordered alphabet
- [Payable](#payable)
- [Storage](#payable)
- [Test](#test)
- [Tracing](#tracing)

## ABI Name

Expand Down Expand Up @@ -155,3 +156,13 @@ The `#[test]` attribute marks a function to be executed as a test.
The `#[test(should_revert)]` attribute marks a function to be executed as a test that should revert.

More details in [Unit Testing](../testing/unit-testing.md).

## Tracing

The tracing attribute tells the compiler if a function should be included in a backtrace of a revert caused by a `panic` expression call.

The `#[tracing(never)]` signals the compiler not to include the function in a backtrace, unless the `backtrace` build option is set to `all`.

The `#[tracing(always)]` signals the compiler to always include the function in a backtrace, unless the `backtrace` build option is set to `none`.

More details in [Irrecoverable Errors](../basics/error_handling.md#irrecoverable-errors).
Loading