Skip to content
Draft
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
1 change: 1 addition & 0 deletions docs/reference/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
- [Structs](./documentation/misc/advanced-concepts/structs.md)
- [Enums](./documentation/misc/advanced-concepts/enums.md)
- [Compiler Intrinsics](./documentation/misc/advanced-concepts/compiler-intrinsics.md)
- - [Compiler Intrinsics](./documentation/misc/advanced-concepts/trivial-encoding.md)
- [Known Issues](./documentation/misc/workarounds/index.md)
- [General](./documentation/misc/workarounds/general.md)
- [Missing Features](./documentation/misc/workarounds/missing-features.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Trivially Encodable & Decodable Types

When a contract calls another contract, all arguments are **encoded** just before the call is actually executed,
and the callee **decodes** these arguments right before the target method starts.
This adds a small but non‑negligible gas cost, from hundreds to thousands of gas depending on the complexity of the arguments.

The Sway compiler mitigates this overhead for a subset of types that can be **trivially encoded** and/or **trivially decoded** –
that is, types that their *runtime representation*, how the type bytes are laid out inside the VM, is *identical* to their *encoded representation*,
how their bytes are laid out in the encoded buffer.

For such types the compiler can skip the encoding/decoding process entirely, saving gas and simplifying the generated code.

> **Trivial encoding** – encoding is replaced with a simple "transmute".
> **Trivial decoding** – encoding is replaced with a simple "transmute".

The compiler can skip each individually, but the whole gain comes only when both are skipped together.

## Checking Triviality

Each struct that should be treated as trivially encodable/decodable can be annotated with the `#[trivial]` attribute:

```sway
#[trivial(encode = "require", decode = "require")]
pub struct SomeArgument {
a: bool,
b: SomeEnum,
}
```

- `encode = "require"` – the compiler will check if the type is trivially encodable; if not, the build fails.
- `decode = "require"` – similarly for decoding.

Possible values are:

- required: compiler will check and error if the check fails;
- optional: compiler will only warn non-compliances;
- any: nothing will be checked.

This attributed can be used directly on types, but also on entry points such as "main" function for scripts and predicates; and contract methods for contracts.

## Which Types Are Trivial?

| Type | Trivially Encodable | Trivially Decodable | Notes |
|------|---------------------|---------------------|-------|
| `bool` | ✅ | ❌ | `bool` encodes to a single byte (`0` or `1`), but decoding must validate that the byte is a legal value. |
| `u8`, `u64`, `u256`, `b256` | ✅ | ✅ | |
| `u16`, `u32` | ❌ | ❌ | Their runtime representation is actually a `u64` |
| Structs | ✅ If all their members are trivial | ✅ If all their member are trivial | Recursively evaluated. |
| Enums | ✅ If all variants are trivial | ❌ | Enums have an `u64` discriminant that cannot be trivially decodable. |
| Arrays | ✅ If the item type is trivial | ✅ if the item type is trivial |
| String Arrays | ✅ See * | ✅ See * | |
| Vec, Dictionary, String, etc. | ❌ | ❌ | Data Structures are never trivial |

* Only when the feature "str_array_no_padding" is turned on. When the feature toggle is off, only string arrays that its length is multiple of 8.

### Why `bool` and `enum` are not trivially decodable

Probably the most surprising non trivial base data type is `bool`. Mainly because `bool` is obviously trivially encodable. But there is no guarantee
that buffer does not have a value like `2`, that being "transmuted" into a bool would be allow its runtime representation to be `2`, which is **undefined behaviour**.

The same limitation applies to enums. Enums are implemented as "tagged unios" which means that their runtime representation has a discriminant value as `u64`. There

Check warning on line 61 in docs/reference/src/documentation/misc/advanced-concepts/trivial-encoding.md

View workflow job for this annotation

GitHub Actions / find-typos

"unios" should be "unions".
is no guarantee that the buffer would have a valid value for its discriminant.

---

## 3. Work‑arounds for Non‑trivial Types

If you need to expose a `bool` or an enum as a public argument, you can either:

1. **Manual validation** – expose a raw `u64` (or `u8`) and check its value in the callee.
```sway
#[trivial(encode = "require", decode = "require")]
pub struct Flag(u8); // manually validate that value <= 1
```

2. **Custom wrappers** – Sway ships with `TrivialBool` and `TrivialEnum<T>` that enforce the bounds at compile time.

```sway
use sway::primitive::TrivialBool;
use sway::primitive::TrivialEnum;

#[trivial(encode = "require", decode = "require")]
pub struct SomeArgument {
a: TrivialBool,
b: TrivialEnum<SomeEnum>,
}
```

These wrappers automatically provide the guard checks and still let the compiler treat them as trivial.
Their usage is veryy similar to `Option<bool>`.

```sway
let a: bool = some_argument.a.unwrap();
let b: SomeEnum = some_argument.b.unwrap();
```
6 changes: 6 additions & 0 deletions sway-ast/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ pub const ABI_NAME_NAME_ARG_NAME: &str = "name";
pub const EVENT_ATTRIBUTE_NAME: &str = "event";
pub const INDEXED_ATTRIBUTE_NAME: &str = "indexed";

// Require Attributes
pub const REQUIRE_ATTRIBUTE_NAME: &str = "require";
pub const REQUIRE_ARG_NAME_TRIVIALLY_ENCODABLE: &str = "trivially_encodable";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trivially encodable check defined but never enforced

Medium Severity

REQUIRE_ARG_NAME_TRIVIALLY_ENCODABLE is defined and registered as a valid argument for the #[require] attribute, but the checking logic in compile_entry_function only checks for REQUIRE_ARG_NAME_TRIVIALLY_DECODABLE. A user writing #[require(trivially_encodable = "true")] gets no compiler error and no validation — the attribute is silently accepted and ignored, giving a false sense of safety.

Additional Locations (1)
Fix in Cursor Fix in Web

pub const REQUIRE_ARG_NAME_TRIVIALLY_DECODABLE: &str = "trivially_decodable";

pub const KNOWN_ATTRIBUTE_NAMES: &[&str] = &[
STORAGE_ATTRIBUTE_NAME,
DOC_COMMENT_ATTRIBUTE_NAME,
Expand All @@ -78,6 +83,7 @@ pub const KNOWN_ATTRIBUTE_NAMES: &[&str] = &[
ABI_NAME_ATTRIBUTE_NAME,
EVENT_ATTRIBUTE_NAME,
INDEXED_ATTRIBUTE_NAME,
REQUIRE_ATTRIBUTE_NAME,
];

/// An attribute declaration. Attribute declaration
Expand Down
3 changes: 3 additions & 0 deletions sway-ast/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub enum Intrinsic {
Alloc, // __alloc<T>(size: u64) -> raw_ptr
RuntimeMemoryId, // __runtime_mem_id::<T>() -> u64
EncodingMemoryId, // __encoding_mem_id::<T>() -> u64
EnumDiscriminantCount, // __enum_discriminant_count::<T>() -> u64
}

impl fmt::Display for Intrinsic {
Expand Down Expand Up @@ -100,6 +101,7 @@ impl fmt::Display for Intrinsic {
Intrinsic::Alloc => "alloc",
Intrinsic::RuntimeMemoryId => "runtime_mem_id",
Intrinsic::EncodingMemoryId => "encoding_mem_id",
Intrinsic::EnumDiscriminantCount => "enum_discriminant_count",
};
write!(f, "{s}")
}
Expand Down Expand Up @@ -155,6 +157,7 @@ impl Intrinsic {
"__alloc" => Alloc,
"__runtime_mem_id" => RuntimeMemoryId,
"__encoding_mem_id" => EncodingMemoryId,
"__enum_discriminant_count" => EnumDiscriminantCount,
_ => return None,
})
}
Expand Down
2 changes: 2 additions & 0 deletions sway-core/src/ir_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ pub fn compile_program<'a>(
logged_types,
messages_types,
declarations,
decls_to_check,
..
} = program;

Expand Down Expand Up @@ -384,6 +385,7 @@ pub fn compile_program<'a>(
&mut panicking_fn_cache,
&test_fns,
&mut compiled_fn_cache,
decls_to_check,
),
ty::TyProgramKind::Predicate { entry_function, .. } => compile::compile_predicate(
engines,
Expand Down
Loading
Loading