Skip to content

Commit

Permalink
feat(docs): explain how storage variables get updated (#1311)
Browse files Browse the repository at this point in the history
  • Loading branch information
novusnota authored Jan 21, 2025
1 parent 78092d7 commit 22862fc
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added signatures for map methods, such as `.get()`, `.exists()`, `.set()`, `.replace()`, `.replaceGet()`, `.del()`, `.isEmpty()`, `.deepEquals()`, `.asCell()`: PR [#1352](https://github.com/tact-lang/tact/pull/1352)
- Added a compilation-related page with the description of the compilation report: PR [#1309](https://github.com/tact-lang/tact/pull/1309), PR [#1387](https://github.com/tact-lang/tact/pull/1387)
- Documented `BaseTrait` and methods in stdlib code: PR [#1296](https://github.com/tact-lang/tact/pull/1296)
- Document how storage variables get updated in relation to the `init()` function: PR [#1311](https://github.com/tact-lang/tact/pull/1311)

### Release contributors

Expand Down
62 changes: 59 additions & 3 deletions docs/src/content/docs/book/contracts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ In addition to that, [`tact.config.json`](/book/config) may still be used in [Bl

### Persistent state variables {#variables}

Contracts can define state variables that persist between contract calls. Contracts in TON [pay rent](https://docs.ton.org/develop/smart-contracts/fees#storage-fee) in proportion to the amount of persistent space they consume, so [compact representations via serialization](/book/integers#serialization) are encouraged.
Contracts can define state variables that persist between contract calls, and thus commonly referred to as _storage_ variables. Contracts in TON [pay rent](https://docs.ton.org/develop/smart-contracts/fees#storage-fee) in proportion to the amount of persistent space they consume, so [compact representations via serialization](/book/integers#serialization) are encouraged.

```tact
contract Example {
Expand All @@ -149,11 +149,62 @@ contract Example {
}
```

State variables must have a default value or initialized in [`init(){:tact}`](#init-function) function, that runs on deployment of the contract. The only exception is persistent state variables of type [`map<K, V>{:tact}`](/book/maps) since they are initialized empty by default.
State variables must have a default value or be initialized in the [`init(){:tact}`](#init-function) function that runs once on deployment of the contract. The only exception are persistent state variables of type [`map<K, V>{:tact}`](/book/maps), since they are initialized empty by default.

The default value of state variables is assigned before any values could be assigned in the [`init(){:tact}`](#init-function) function.

```tact
contract Example {
// persistent state variables
var1: Int = 0; // initialized with default value 0
// constructor function
init() {
self.var1 = 42; // overrides the default to 42
}
}
```

As the contract state is updated at the very end of the [compute phase][compute] of the transaction, intermediate assignments of [`Int{:tact}`][int] values that exceed the limits specified by [serialization formats](/book/integers#serialization) won't fail immediately. Instead, such assignments would cause an [exit code 5](/book/exit-codes#5) only after all statements have been executed.

This is to be expected because the integers in the temporary [TVM][tvm] memory, which is used to process the [compute phase][compute], always have $257$ bits and are capable of holding values in the inclusive range from $-2^{256}$ to $2^{256} - 1.$

```tact
contract DeRanged {
// Persistent state variables
var: Int as uint8; // cannot store values outside the 0-255 range
init() {
self.var = -1; // this won't fail immediately
self.var = 500; // and that won't fail right away either
} // only here, at the end of the compute phase,
// would there be an error thrown with an exit code 5: Integer out of range
}
```

In the end, this means that you cannot rely on intermediate out-of-bounds checks, because there are none at [TVM][tvm] runtime, and only the last assignment of each state variable is used to update its persistent state value.

```tact
contract Zero {
// Persistent state variables
var: Int as uint8; // cannot store values outside the 0-255 range
var2: Int as uint4; // cannot store values outside the 0-15 range
init() {
self.var = -1; // this won't fail
self.var = 0; // and this is in the range of `uint8`
self.var2 = -1; // this won't fail
self.var2 = 15; // and this is in the range of `uint4`
} // no errors, and now `self.var` is 0 and `self.var2` is 15
}
```

:::note

Note, that Tact supports local, non-persistent-state variables too, see: [Variable declaration](/book/statements#let).
Tact supports local, non-persistent-state variables too, see: [Variable declaration](/book/statements#let).

:::

Expand Down Expand Up @@ -203,10 +254,12 @@ contract Example {
// persistent state variables
var1: Int = 0; // initialized with default value 0
var2: Int; // must be initialized in the init() function
var3: Int = 7; // initialized with default value 7
// constructor function
init() {
self.var2 = 42;
self.var3 = 32; // overrides the default to 32
}
}
```
Expand Down Expand Up @@ -354,7 +407,10 @@ contract Functions {
:::

[p]: /book/types#primitive-types
[int]: /book/integers
[trait]: /book/types#traits

[compute]: https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase
[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview
[bp]: https://github.com/ton-org/blueprint
[bp-config]: https://github.com/ton-org/blueprint/tree/main?tab=readme-ov-file#configuration
9 changes: 9 additions & 0 deletions docs/src/content/docs/book/integers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ Motivation is very simple:
* Storing $1000$ $257$-bit integers in state [costs](https://docs.ton.org/develop/smart-contracts/fees#how-to-calculate-fees) about $0.184$ TON per year.
* Storing $1000$ $32$-bit integers only costs $0.023$ TON per year by comparison.

:::note

Serialization limits apply only to the contract state between transactions and are **not** imposed on the temporary [TVM][tvm] memory, which operates only on $257$-bit integers.

Attempts to assign out-of-bounds values will result in [exit code 5](/book/exit-codes#5) being thrown at the very end of the [compute phase](https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase): `Integer out of range`.

:::

### Common serialization types

Name | [TL-B][tlb] | Inclusive range | Space taken
Expand Down Expand Up @@ -195,6 +203,7 @@ Here, `oneByte` is serialized as a [`uint8`](#common-serialization-types), which
Therefore, be **very** careful with numbers and always double-check calculations when using serialization.
:::

[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview
[tlb]: https://docs.ton.org/develop/data-formats/tl-b-language
[tlb-builtin]: https://docs.ton.org/develop/data-formats/tl-b-language#built-in-types
[varuint]: https://docs.ton.org/develop/data-formats/msg-tlb#varuinteger-n

0 comments on commit 22862fc

Please sign in to comment.