Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

feat: Integer update #97

Merged
merged 7 commits into from
Mar 14, 2024
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
3 changes: 2 additions & 1 deletion pages/book/_meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ export default {
type: 'separator',
},
types: 'Type system overview',
integers: 'Integers',
functions: 'Functions',
statements: 'Statements',
constants: 'Constants',
'defining-types': 'Defining composite types',
'composite-types': 'Composite types',
receive: 'Receive Messages',
bounced: 'Bounced Messages',
external: 'External Messages',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Defining composite types
# Composite types

import { Callout } from 'nextra/components'

Expand Down
139 changes: 139 additions & 0 deletions pages/book/integers.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Integers

import { Callout } from 'nextra/components'

Arithmetic in smart contracts on TON is always done with integers and never with floating-point numbers since the floats are [unpredictable](https://learn.microsoft.com/en-us/cpp/build/why-floating-point-numbers-may-lose-precision). Therefore, the big accent goes on integers and their handling.

The only primitive number type in Tact is `Int{:tact}`, for $257$-bit signed integers.\
It's capable of storing integers between $-2^{256}$ and $2^{256} - 1.$

## Notation

Tact supports various ways of writing primitive values of `Int{:tact}` (integer literals).

Most of the notations allow adding underscores (`_`) in-between digits, except for:
* Representations in strings, as seen in [nano-tons](#nano-tons) case.
* Decimal numbers written with a leading zero $0.$ Their use is generally discouraged, see [below](#decimal).

Additionally, several underscores in a row as in $4\_\_2$, or trailing underscores as in $42\_$ are **not** allowed.

### Decimal

Most common and most used way of representing numbers, using the [decimal numeral system](https://en.wikipedia.org/wiki/Decimal): $123456789.$\
You can use underscores (`_`) to improve readability: $123\_456\_789$ is equal to $123456789.$

<Callout type="warning" emoji="⚠️">
Alternatively, you can prefix the number with one $0$, which prohibits use of underscores and only allows decimal digits: $0123 = 123.$
Note, that using this notation with leading zero is **strongly discouraged** due to possible confusion with octal integer literals in TypeScript, which is often used alongside Tact to develop and test contracts.
</Callout>

### Hexadecimal

Represent numbers using [hexadecimal numeral system](https://en.wikipedia.org/wiki/Hexadecimal), denoted by the $\mathrm{0x}$ prefix: $\mathrm{0xFFFFFFFFF}.$\
Use underscores (`_`) to improve readability: $\mathrm{0xFFF\_FFF\_FFF}$ is equal to $\mathrm{0xFFFFFFFFF}.$

### Octal

Represent numbers using [octal numeral system](https://en.wikipedia.org/wiki/Octal), denoted by the $\mathrm{0o}$ prefix: $\mathrm{0o777777777.}$\
Use underscores (`_`) to improve readability: $\mathrm{0o777\_777\_777}$ is equal to $\mathrm{0o777777777}.$

### Binary

Represent numbers using [binary numeral system](https://en.wikipedia.org/wiki/Binary_number), denoted by the $\mathrm{0b}$ prefix: $\mathrm{0b111111111.}$\
Use underscores (`_`) to improve readability: $\mathrm{0b111\_111\_111}$ is equal to $\mathrm{0b111111111}.$

### Nano-tons

For example, arithmetic with dollars requires two decimal places after the dot — those are used for the cents value. But how would we represent the number \$$1.25$ if we're only able to work with integers? The solution is to work with _cents_ directly. This way, \$$1.25$ becomes $125$ cents. We simply memorize that the two rightmost digits represent the numbers after the decimal point.

Similarly, working with Toncoins requires nine decimal places instead of the two. Therefore, the amount of $1.25$ TON, which can be represented in Tact as [`ton("1.25"){:tact}`](/language/ref/common#ton), is actually the number $1250000000$. We refer to such numbers as _nano-tons_ (or _nanoToncoins_) rather than _cents_.

## Serialization

When encoding `Int{:tact}` values to persistent state (fields of [Contracts](/book/types#contracts) and [Traits](/book/types#traits)), it's usually better to use smaller representations than $257$-bits to reduce storage costs. Usage of such representations is also called "serialization" due to them representing the native [TL-B](https://docs.ton.org/develop/data-formats/tl-b-languagehttps://docs.ton.org/develop/data-formats/tl-b-language) types which TON Blockchain operates on.

The persistent state size is specified in every declaration of a state variable after the `as{:tact}` keyword:

```tact
contract SerializationExample {
// contract persistent state variables
oneByte: Int as int8 = 0; // ranges from -128 to 127 (takes 8 bit = 1 byte)
twoBytes: Int as int16; // ranges from -32,768 to 32,767 (takes 16 bit = 2 bytes)

init() {
// needs to be initialized in the init() because it doesn't have the default value
self.twoBytes = 55*55;
}
}
```

Integer serialization is also available for the fields of [Structs](/book/composite-types#structs) and [Messages](/book/composite-types#structs), as well as in key/value types of [maps](/book/types#maps):

```tact
struct StSerialization {
martin: Int as int8;
}

message MsgSerialization {
seamus: Int as int8;
mcFly: map<Int as int8, Int as int8>;
}
```

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.

### Serialization types

Name | Inclusive range | Space taken
:--------------- | :-------------------------: | :------------------------:
`uint8{:tact}` | $0$ to $2^{8} - 1$ | 8 bit = 1 byte
`uint16{:tact}` | $0$ to $2^{16} - 1$ | 16 bit = 2 bytes
`uint32{:tact}` | $0$ to $2^{32} - 1$ | 32 bit = 4 bytes
`uint64{:tact}` | $0$ to $2^{64} - 1$ | 64 bit = 8 bytes
`uint128{:tact}` | $0$ to $2^{128} - 1$ | 128 bit = 16 bytes
`uint256{:tact}` | $0$ to $2^{256} - 1$ | 256 bit = 32 bytes
`int8{:tact}` | $-2^{7}$ to $2^{7} - 1$ | 8 bit = 1 byte
`int16{:tact}` | $-2^{15}$ to $2^{15} - 1$ | 16 bit = 2 bytes
`int32{:tact}` | $-2^{31}$ to $2^{31} - 1$ | 32 bit = 4 bytes
`int64{:tact}` | $-2^{63}$ to $2^{63} - 1$ | 64 bit = 8 bytes
`int128{:tact}` | $-2^{127}$ to $2^{127} - 1$ | 128 bit = 16 bytes
`int256{:tact}` | $-2^{255}$ to $2^{255} - 1$ | 256 bit = 32 bytes
`int257{:tact}` | $-2^{256}$ to $2^{256} - 1$ | 257 bit = 32 bytes + 1 bit
`coins{:tact}` | $0$ to $2^{120} - 1$ | 120 bit = 15 bytes

<Callout>
Read more on serialization here: [Compatibility with FunC](/book/func##convert-serialization)
</Callout>

## Operations

All runtime calculations with numbers are done at 257-bits, so [overflows](https://en.wikipedia.org/wiki/Integer_overflow) are quite rare. Nevertheless, if any math operation overflows, an exception will be thrown, and the transaction will fail. You could say that Tact's math is safe by default.

Note, that there is no problem with mixing variables of [different state sizes](#serialization) in the same calculation. At runtime they are all the same type no matter what — $257$-bit signed, so overflows won't happen then.

However, this can still lead to **errors** in the [compute phase](https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase) of the transaction. Consider the following example:

```tact
import "@stdlib/deploy";

contract ComputeErrorsOhNo with Deployable {
oneByte: Int as uint8; // persistent state variable, max value is 255

init() {
self.oneByte = 255; // initial value is 255, everything fits
}

receive("lets break it") {
let tmp: Int = self.oneByte * 256; // no runtime overflow
self.oneByte = tmp; // whoops, tmp value is out of the expected range of oneByte
}
}
```

Here, `oneByte` is serialized as a [`uint8`](#serialization-types), which occupies only one byte and ranges from $0$ to $2^{8} - 1$, which is $255$. And when used in runtime calculations no overflow happens and everything is calculated as a $257$-bit signed integers. But the very moment we decide to store the value of `tmp` back into `oneByte` we get an error with the [exit code](https://docs.ton.org/learn/tvm-instructions/tvm-exit-codes) 5, which states the following: `Integer out of the expected range`.

<Callout type="warning" emoji="⚠️">
Therefore, be **very** careful with numbers and always double-check calculations when using serialization.
</Callout>
86 changes: 53 additions & 33 deletions pages/book/types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,58 @@ Every variable, item, and value in Tact programs has a type. They can be:
* Composite types, such as [Structs and Messages](#structs-and-messages)
* or [Contracts](#contracts) and [Traits](#traits)

Also, many of those types [can be made nullable](/book/defining-types#optionals).
Also, many of those types [can be made nullable](/book/composite-types#optionals).

## Primitive types

* Int — all numbers in Tact are 257-bit signed integers, but [smaller representations](/book/integers#serialization) can be used to reduce storage costs.
* Bool — classical boolean with `true` and `false` values.
* Address — standard [smart contract address](https://docs.ton.org/learn/overviews/addresses#address-of-smart-contract) in TON Blockchain.
* Slice, Cell, Builder — low-level primitives of TON VM.
* String — represents text strings in TON VM.
* StringBuilder — helper type that allows you to concatenate strings in a gas-efficient way.
* [`Int{:tact}`](/book/integers) — all numbers in Tact are $257$-bit signed integers, but [smaller representations](/book/integers#serialization) can be used to reduce storage costs.
* [`Bool{:tact}`](#booleans) — classical boolean with `true{:tact}` and `false{:tact}` values.
* `Address{:tact}` — standard [smart contract address](https://docs.ton.org/learn/overviews/addresses#address-of-smart-contract) in TON Blockchain.
* `Slice{:tact}`, `Cell{:tact}`, `Builder{:tact}` — low-level primitives of TON VM.
* `String{:tact}` — represents text strings in TON VM.
* `StringBuilder{:tact}` — helper type that allows you to concatenate strings in a gas-efficient way.

## Booleans

The primitive type `Bool{:tact}` can hold only the two values: `true{:tact}` and `false{:tact}`. It's convenient for boolean and logical operations, as well as for storing flags.

There are no implicit type conversions in Tact, so addition (`+`) of two boolean values isn't possible. Hovewer, many comparison [operators](/book/statements#operators) are available, such as:

* `&&` for logical `AND`,
* `||` for logical `OR`,
* and `!` for logical inversion.

Persisting bools to state is very space-efficient, as they only take 1-bit. Storing 1000 bools in state [costs](https://ton.org/docs/develop/smart-contracts/fees#how-to-calculate-fees) about $0.00072$ TON per year.

## Maps

The type `map<k, v>{:tact}` is used as a way to associate data with corresponding keys.

Possible key types:

* [`Int{:tact}`](/book/integers)
* `Address{:tact}`

Possible value types:

* [`Int{:tact}`](/book/integers)
* [`Bool{:tact}`](#booleans)
* `Cell{:tact}`
* `Address{:tact}`
* [Struct](#structs-and-messages)
* [Message](#structs-and-messages)

```tact
contract HelloWorld {
counters: map<Int, Int>;
}
```

## Structs and Messages

[Struct](/book/defining-types#structs) example:
[Structs][structs] and [Messages][messages] are two main ways of combining multiple [primitive types](#primitive-types) into a composite one.

Example of a [Struct][structs]:

```tact
struct Point {
Expand All @@ -31,46 +69,28 @@ struct Point {
}
```

[Message](/book/defining-types#messages) example:
Example of a [Message][messages]:

```tact
// Custom numeric id of the Message
message(0x11111111) SetValue {
key: Int;
value: Int?; // Optional
value: Int?; // Optional, Int or null
coins: Int as coins; // Serialization into TL-B types
}
```

Learn more about them on a dedicated page about [defining composite types](/book/defining-types).
Learn more about them on a dedicated page about [composite types](/book/composite-types).

## Maps

The type `map<k, v>{:tact}` is used as a way to associate data with corresponding keys.

Possible key types:
* Int
* Address

Possible value types:
* Int
* Bool
* Cell
* Address
* [Struct](/book/defining-types#structs)
* [Message](/book/defining-types#messages)

```tact
contract HelloWorld {
counters: map<Int, Int>;
}
```
[structs]: /book/composite-types#structs
[messages]: /book/composite-types#messages

## Contracts

Contracts are the main entry of a smart contract on the TON blockchain. It holds all functions, getters, and receivers of a TON contract.
Contracts are the main entry of a smart contract on the TON blockchain. It holds all [functions](/book/functions), [getters](/book/functions#getter-functions), and [receivers](/book/functions#receiver-functions) of a TON contract.

```tact
// Basic example of a counter contract:
contract HelloWorld {
counter: Int;

Expand Down
15 changes: 15 additions & 0 deletions scripts/redirects-generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,21 @@ const getRedirects = () => [
subSources: undefined,
destination: '/ecosystem/tools/vscode',
},
{
source: '/book/ints',
subSources: undefined,
destination: '/book/integers',
},
{
source: '/book/numbers',
subSources: undefined,
destination: '/book/integers',
},
{
source: '/book/defining-types',
subSources: undefined,
destination: '/book/composite-types',
},
];

/**
Expand Down
Loading