diff --git a/pages/book/_meta.js b/pages/book/_meta.js index e6586cba..4d05fad9 100644 --- a/pages/book/_meta.js +++ b/pages/book/_meta.js @@ -12,7 +12,7 @@ export default { maps: 'Maps', 'structs-and-messages': 'Structs and Messages', optionals: 'Optionals', - // <- a place for https://github.com/tact-lang/tact-docs/issues/115 + brief info on comments + contracts: 'Contracts', '-- 2': { type: 'separator', title: 'Expressiveness', @@ -61,4 +61,4 @@ export default { href: 'https://twitter.com/tact_language', newWindow: true }, -} \ No newline at end of file +} diff --git a/pages/book/contracts.mdx b/pages/book/contracts.mdx new file mode 100644 index 00000000..02ffb4e7 --- /dev/null +++ b/pages/book/contracts.mdx @@ -0,0 +1,222 @@ +# Contracts + +import { Callout } from 'nextra/components' + +Contracts in Tact are similar to classes in popular object-oriented languages, except that their instances are deployed on the blockchain and they can't be passed around like [Structs and Messages](/book/structs-and-messages). + +## Self-references [#self] + +Contracts and [traits](/book/types#traits) have a built-in [identifier](/book/expressions#identifiers) `self{:tact}`, which is used for referring to their fields (persistent state [variables](#variables) and [constants](#variables)) and methods ([internal functions](#internal-functions)): + +```tact +contract Example { + // persistent state variables + foo: Int; + + init() { + self.foo = 42; // <- referencing variable foo through self. + } +} +``` + +## Structure + +Each contract can contain: + +* [Persistent state variables](#variables) +* [Constructor function `init(){:tact}`](#init-function) +* [Contract constants](#constants) +* [Getter functions](#get-functions) +* [Receiver functions](#receiver-functions) +* [Internal functions](#internal-functions) + +Furthermore, contracts can inherit all the declarations from [traits](/book/types#traits) and override some of their default behaviours. + +### 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. + +```tact +contract Example { + // persistent state variables + val: Int; // Int + val32: Int as uint32; // Int serialized to an 32-bit unsigned + mapVal: map; // Int keys to Int values + optVal: Int?; // Int or null +} +``` + +State variables must have a default value or initialized in [`init(){:tact}`](#init-function) function, that runs on deployment of the contract. + + + + Note, that Tact supports local, non-persistent-state variables too, see: [Variable declaration](/book/statements#let). + + + +### Contract constants [#constants] + +Unlike [variables](#variables), constants cannot change. Their values are calculated in _compile-time_ and cannot change during execution. + +There isn't much difference between constants defined outside of a contract (global constants) and inside the contract (contract constants). Those defined outside can be used by other contracts in your project. + +Constant initializations must be relatively simple and only rely on values known during compilation. If you add two numbers for example, the compiler will calculate the result during build and put the result in your compiled code. + +You can read constants both in [receivers](#receiver-functions) and in [getters](#getter-functions). + +Unlike [contract variables](#variables), **contract constants don't consume space in persistent state**. Their values are stored directly in the code [`Cell{:tact}`](/book/types#primitive-types) of the contract. + +```tact +// global constants are calculared in compile-time and cannot change +const GlobalConst1: Int = 1000 + ton("42") + pow(10, 9); + +contract Example { + // contract constants are also calculated in compile-time and cannot change + const ContractConst1: Int = 2000 + ton("43") pow(10, 9); + + // contract constants can be an easy alternative to enums + const StateUnpaid: Int = 0; + const StatePaid: Int = 1; + const StateDelivered: Int = 2; + const StateDisputed: Int = 3; + + // no need to init constants + init() {} + + get fun sum(): Int { + // access constants from anywhere + return GlobalConst1 + self.ContractConst1 + self.StatePaid; + } +} +``` + +Read more about constants on their dedicated page: [Constants](/book/constants). + +### Constructor function `init()` [#init-function] + +On deployment of the contract, the constructor function `init(){:tact}` is run. + +If a contract has any [persistent state variables](#variables) without default values specified, it must initialize them in this function. + +```tact +contract Example { + // persistent state variables + var1: Int = 0; // initialized with default value 0 + var2: Int; // must be initialized in the init() function + + // constructor function + init() { + self.var2 = 42; + } +} +``` + + + + To obtain initial state of the target contract in [internal functions](#internal-functions), [receivers](#receiver-functions) or [getters](#getter-functions) use [`initOf{:tact}`](/book/expressions#initof) expression. + + + +### Getter functions + +[Getter functions](/book/functions#getter-functions) are **not accessible from other contracts and exported only to off-chain world**. + +Additionally, **getters cannot modify the contract's state variables**, only read their values and use them in expressions. + +```tact +contract HelloWorld { + foo: Int; + + init() { + self.foo = 0; + } + + // getter function with return type Int + get fun foo(): Int { + return self.foo; // can't change self.foo here + } +} +``` + +Read more about them in their dedicated section: [Getter functions](/book/functions#getter-functions) + +### Receiver functions + +[Receiver functions](/book/functions#receiver-functions) in Tact can be one of the following three kinds: + +* [`receive(){:tact}`](/book/receive), which receive internal messages (from other contracts). +* [`bounced(){:tact}`](/book/bounced), which are called when outgoing message from this contract has bounced back. +* [`external(){:tact}`](/book/external), which don't have a sender and can be sent by anyone in the world. + +```tact +message CanBounce { + counter: Int; +} + +contract HelloWorld { + counter: Int; + + init() { + self.counter = 0; + } + + get fun counter(): Int { + return self.counter; + } + + // internal message receiver, which responds to a string message "increment" + receive("increment") { + self.counter += 1; + + // sending the message back to the sender + send(SendParameters{ + to: sender(), + value: 0, + mode: SendRemainingValue + SendIgnoreErrors, + body: CanBounce{counter: self.counter}.toCell() + }); + } + + // bounced message receiver, which is called when the message bounces back to this contract + bounced(src: bounced) { + self.counter = 0; // reset the counter in case message bounced + } + + // external message receiver, which responds to off-chain message "hello, it's me" + external("hello, it's me") { + // can't be replied to as there's no sender! + self.counter = 0; + } +} +``` + +### Internal functions + +These functions behave similarly to private methods in popular object-oriented languages — they're internal to contracts and can be called by prefixing them with a special [identifier `self{:tact}`](#field-access). That's why internal functions can sometimes be referred to as "contract methods". + +Internal functions can access the contract's [persistent state variables](#variables) and [constants](#constants). + +They can only be called from [receivers](#receiver-functions), [getters](#getter-functions) and other internal functions, but not from other contracts or [`init(){:tact}`](#init-function). + +```tact +contract Functions { + val: Int = 0; + init() {} + + // this contract method can only be called from within this contract and access its variables + fun onlyZeros() { + require(self.val == 0, "Only zeros are permitted!"); + } + + // receiver function, which calls the internal function onlyZeros + receive("only zeros") { + self.onlyZeros(); + } +} +``` + + + + Note, that Tact supports other kinds of functions too, see: [Functions](/book/functions). + + diff --git a/pages/book/expressions.mdx b/pages/book/expressions.mdx index 34230808..ba454036 100644 --- a/pages/book/expressions.mdx +++ b/pages/book/expressions.mdx @@ -96,7 +96,7 @@ Read more about working with `null{:tact}` on the dedicated page: [Optionals](/b ## Identifiers -An identifier is a sequence of characters in the code that _identifies_ a [variable](/book/statements#let), [constant](/book/constants), [map](/book/maps) and a [function](/book/functions), as well as a [Struct][s], [Message][m], [contract](/book/types#contract), [trait](/book/types#trait), or their fields and methods. Identifiers are case-sensitive and not quoted. +An identifier is a sequence of characters in the code that _identifies_ a [variable](/book/statements#let), [constant](/book/constants), [map](/book/maps) and a [function](/book/functions), as well as a [Struct][s], [Message][m], [contract](/book/contracts), [trait](/book/types#traits), or their fields and methods. Identifiers are case-sensitive and not quoted. In Tact, identifiers can contain latin lowercase letters (`a-z`), latin uppercase letters (`A-Z`), underscores (`_`) and digits ($\mathrm{0 - 9}$), but may not start with a digit. An identifier differs from a [string](#string-literals) in that a string is data, while an identifier is part of the code. @@ -152,7 +152,7 @@ fun example(): Int { ## Static function call -Anywhere in the function body, a global [static function](/book/functions#global-static-functions) or an internal function of a contract can be called: +Anywhere in the function body, a global [static function](/book/functions#global-static-functions) or an internal function of a [contract](/book/contracts) can be called: ```tact contract ExampleContract { @@ -170,7 +170,7 @@ contract ExampleContract { ## `initOf` -Expression `initOf{:tact}` computes initial state (`StateInit{:tact}`) of a [contract](/book/types#contracts): +Expression `initOf{:tact}` computes initial state (`StateInit{:tact}`) of a [contract](/book/contracts): ```tact // argument values for the init() function of the contract @@ -185,9 +185,9 @@ Where `StateInit{:tact}` is a built-in [Struct][s], that consists of: Field | Type | Description :----- | :----------------- | :---------- -`code` | [`Cell{:tact}`][p] | initial code of the contract (the compiled bytecode) -`data` | [`Cell{:tact}`][p] | initial data of the contract (arguments of `init(){:tact}` function of the contract) +`code` | [`Cell{:tact}`][p] | initial code of the [contract](/book/contracts) (the compiled bytecode) +`data` | [`Cell{:tact}`][p] | initial data of the [contract](/book/contracts) (arguments of `init(){:tact}` function of the contract) [p]: /book/types#primitive-types [s]: /book/structs-and-messages#structs -[m]: /book/structs-and-messages#messages \ No newline at end of file +[m]: /book/structs-and-messages#messages diff --git a/pages/book/integers.mdx b/pages/book/integers.mdx index 79312610..d4e8ef8d 100644 --- a/pages/book/integers.mdx +++ b/pages/book/integers.mdx @@ -50,13 +50,13 @@ Similarly, working with Toncoins requires nine decimal places instead of the two ## 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. +When encoding `Int{:tact}` values to persistent state (fields of [contracts](/book/contracts) and [traits](/book/types#traits)), it's usually better to use smaller representations than $257$-bits to reduce [storage costs](https://docs.ton.org/develop/smart-contracts/fees#storage-fee). 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 + // 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) @@ -136,4 +136,4 @@ Here, `oneByte` is serialized as a [`uint8`](#serialization-types), which occupi Therefore, be **very** careful with numbers and always double-check calculations when using serialization. - \ No newline at end of file + diff --git a/pages/book/types.mdx b/pages/book/types.mdx index 8b588922..06d87a29 100644 --- a/pages/book/types.mdx +++ b/pages/book/types.mdx @@ -26,7 +26,7 @@ Tact supports a number of primitive data types that are tailored for smart contr The primitive type `Bool{:tact}` is the classical boolean type, which 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 (`+{:tact}`) of two boolean values isn't possible. Hovewer, many comparison [operators](/book/statements#operators) are available, such as: +There are no implicit type conversions in Tact, so addition ([`+{:tact}`](/book/operators#binary-add)) of two boolean values isn't possible. Hovewer, many comparison [operators](/book/statements#operators) are available, such as: * `&&{:tact}` for [logical AND](/book/operators#binary-logical-and), * `||{:tact}` for [logical OR](/book/operators#binary-logical-or), @@ -107,56 +107,67 @@ Learn more about them on a dedicated page: [Optionals][optionals]. ### Contracts -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. +[Contracts](/book/contracts) in Tact serve as the main entrypoints of smart contracts of TON blockchain. They hold all [functions](/book/functions), [getters](/book/functions#getter-functions), and [receivers](/book/functions#receiver-functions) of a TON contract, and much more. -Contracts and [traits](#traits) have a built-in [identifier](/book/expressions#identifiers) `self{:tact}`, which is used for referring to their fields (persistent state variables) and methods (internal functions). +Example of a [contract](/book/contracts): ```tact -// Basic example of a counter contract: contract HelloWorld { + // persistent state variable counter: Int; + // constructor function init(), where all the variables are initialized init() { self.counter = 0; } + // internal message receiver, which responds to a string message "increment" receive("increment") { self.counter += 1; } + // getter function with return type Int get fun counter(): Int { return self.counter; } } ``` +Read more about them on the dedicated page: [Contracts](/book/contracts). + ### Traits -Tact doesn't support classical class inheritance, but instead introduces the concept of **traits**. Trait defines functions, receivers, and required fields. The trait is like abstract classes, but it does not define how and where fields must be stored. **All** fields from all traits must be explicitly declared in the contract itself. Traits themselves don't have `init(){:tact}` constructors, so all initial field initialization also must be done in the main contract. +Tact doesn't support classical class inheritance, but instead introduces the concept of _traits_, which can be viewed as abstract contracts (like abstract classes in popular object-oriented languages). They have the same structure as [contracts](#contracts), but can't [initialize persistent state variables](/book/contracts#init-function), while allowing to override some of their behaviors. + +Example of a trait [`Ownable`](/language/libs/ownable#ownable) from [`@stdlib/ownable`](/language/libs/ownable): ```tact trait Ownable { + // persistent state variable, which cannot be initialized in the trait owner: Address; + // internal function fun requireOwner() { nativeThrowUnless(132, context().sender == self.owner); } + // getter function with return type Address get fun owner(): Address { return self.owner; } } ``` -And the contract that uses trait: +And the [contract](#contracts) that uses trait [`Ownable`](/language/libs/ownable#ownable): ```tact contract Treasure with Ownable { - owner: Address; // Field from trait MUST be defined in contract itself + // persistent state variable, which MUST be defined in the contract + owner: Address; - // Here we init the way we need, trait can't specify how you must init owner field + // constructor function init(), where all the variables are initialized init(owner: Address) { self.owner = owner; } } -``` \ No newline at end of file +```