Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates #22

Merged
merged 1 commit into from
Oct 29, 2023
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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
2. The terminal output is located in `src/routes/(examples)/+layout.svelte` file.
3. Run `npm run tact-build` to compile the Tact code you just added.

### About the Examples Order

1. Check the order in the `src/routes/(examples)/examples.json` file.
2. The `id` determines the sequence of the examples in the app.

### Running the project

Once you've run `npm install` start a development server:
Expand Down
9 changes: 8 additions & 1 deletion src/routes/(examples)/01-a-simple-counter/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ State variables should be initialized in `init()` that runs on deployment of the

## Receiving messages

This contract can receive *messages* from users. Unlike getters that are just read-only, messages can do write operations and change the contract's persistent state. Incoming messages are processed in `receive()` methods as transactions and cost gas for the sender.
This contract can receive **_messages_** from users.

Unlike getters that are just read-only, messages can do write operations and change the contract's persistent state. Incoming messages are processed in `receive()` methods as transactions and cost gas for the sender.

After deploying the contract, send the `increment` message by pressing the <span class="mdButton grape">Send increment</span> button in order to increase the counter value by one. Afterwards, call the getter `value()` to see that the value indeed changed.

<div style="padding-left: 1em; margin: 1em 0; position: relative;">
<div style="position: absolute; top: 0; bottom: 0%; left: 0; width: 3px; background-color: green;"></div>
<strong>Info</strong>: We will learn more in details about "getter" functions in the next example.
</div>
14 changes: 9 additions & 5 deletions src/routes/(examples)/01-the-deployable-trait/content.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
# The Deployable Trait

Tact doesn't support classical class inheritance, but contracts can implement *traits*. One of the commonly used traits is `Deployable`. It implements a simple receiver for the `Deploy` message which helps deploy contracts in a standard way.
Tact doesn't support classical class inheritance, but contracts can implement **_traits_**.

All contracts are deployed by sending them a message. This can be any message, but best practice is to designate the special `Deploy` message for this purpose.
One commonly used trait is `Deployable`, which implements a simple receiver for the `Deploy` message. This helps deploy contracts in a standardized manner.

This message has a single field, `queryId`, which is provided by the deployer (normally zero). If the deploy succeeds, the contract will reply with the message `DeployOk` and echo the same `queryId` in the response.
All contracts are deployed by sending them a message. While any message can be used for this purpose, best practice is to use the special `Deploy` message.

This message has a single field, `queryId`, provided by the deployer (usually set to zero). If the deployment succeeds, the contract will reply with a `DeployOk` message and echo the same `queryId` in the response.

---

If you're using Tact's [auto-generated](https://docs.tact-lang.org/tools/typescript#tact-contract-in-typescript) TypeScript classes to deploy, sending the deploy message should look like:

```ts
const msg = { $$type: "Deploy", queryId: 0n };
await contract.send(sender, { value: toNano(1) }, msg);
await contract.send(sender, { value: toNano(1) }, msg);
```

You can see the implementation of the trait [here](https://github.com/tact-lang/tact/blob/main/stdlib/libs/deploy.tact). Notice that the file *deploy.tact* needs to be imported from the standard library using the `import` keyword.
You can see the implementation of the trait [here](https://github.com/tact-lang/tact/blob/main/stdlib/libs/deploy.tact). Notice that the file **_deploy.tact_** needs to be imported from the standard library using the `import` keyword.
8 changes: 4 additions & 4 deletions src/routes/(examples)/02-addresses/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

`Address` is another primitive data type. It represents standard addresses on the TON blockchain. Every smart contract on TON is identifiable by its address. Think of this as a unique id.

TON is divided into multiple chains called *workchains*. This allows to balance the load more effectively. One of the internal fields of the address is the workchain id:
TON is divided into multiple chains called _workchains_. This allows to balance the load more effectively. One of the internal fields of the address is the workchain id:

* `0` - The standard workchain, for regular users. Your contracts will be here.
- `0` - The standard workchain, for regular users. Your contracts will be here.

* `-1` - The masterchain, usually for validators. Gas on this chain is significantly more expensive, but you'll probably never use it.
- `-1` - The masterchain, usually for validators. Gas on this chain is significantly more expensive, but you'll probably never use it.

There are multiple ways on TON to [represent](https://docs.ton.org/learn/overviews/addresses#bounceable-vs-non-bounceable-addresses) the same address. Notice in the contract that the bouncable and non-bouncable representations of the same address actually generate the exact same value. Inside the contract, it doesn't matter which representation you use.

## State costs

Most addresses take 264-bit to store (8-bit for the workchain id and 256-bit for the account id). This means that storing 1000 addresses [costs](https://ton.org/docs/develop/smart-contracts/fees#how-to-calculate-fees) about 0.189 TON per year.
Most addresses take 264-bit to store (8-bit for the workchain id and 256-bit for the account id). **This means that storing 1000 addresses [costs](https://ton.org/docs/develop/smart-contracts/fees#how-to-calculate-fees) about 0.189 TON per year.**
2 changes: 1 addition & 1 deletion src/routes/(examples)/02-bools/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ The only supported operations with booleans are `&&` `||` `!` - if you try to ad

## State costs

Persisting bools to state is very space-efficient, 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.
Persisting bools to state is very space-efficient, 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.**
8 changes: 4 additions & 4 deletions src/routes/(examples)/02-constants/content.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Constants

Unlike variables, constants cannot change. Their values are calculated in *compile-time* and cannot change during execution.
Unlike variables, constants cannot change. Their values are calculated in _compile-time_ and cannot change during execution.

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* and in *getters*.
You can read constants both in **_receivers_** and in **_getters_**.

Unlike contract variables, constants don't consume space in persistent state. Their values are stored directly in the code cell.
Unlike contract variables, **constants don't consume space in persistent state. Their values are stored directly in the code cell.**

There isn't much difference between constants defined outside of a contract and inside the contract. Those defined outside can be used by other contracts in your project.
There isn't much difference between constants defined outside of a contract and inside the contract. Those defined outside can be used by other contracts in your project.
16 changes: 10 additions & 6 deletions src/routes/(examples)/02-integer-ops/content.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Integer Operations

Since all runtime calculations with integers are done at 257-bit, overflows are quite rare. An overflow can happen if the result of a math operation is too big to fit. For example, multiplying 2^256 by 2^256 will not fit within 257-bit.
Since all runtime calculations with integers are done at 257-bit, overflows are quite rare. An overflow can happen if the result of a math operation is too big to fit.

Nevertheless, if any math operation overflows, an exception will be thrown and the transaction will fail. You can say that Tact's math is safe by default.
**For example, multiplying 2^256 by 2^256 will not fit within 257-bit.**

There's no problem with mixing variables of different state sizes in the same calculation. In runtime, they are all the same type - always 257-bit signed. This is the largest supported integer type, so they all fit.
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.

## Decimal point with integers
There is no problem with mixing variables of different state sizes in the same calculation. At runtime, they are all the same type—**always 257-bit signed**. This is the largest supported integer type, so they all fit.

Arithmetics with dollars, for example, requires 2 decimal places. How can we represent the number `1.25` if we can only work with integers? The answer is to work with *cents*. So `1.25` becomes `125`. We just remember that the two lowest digits are coming after the decimal point.
## Decimal Point with Integers

In the same way, working with TON coins has 9 decimal places instead of 2. So the amount 1.25 TON which can be coded in Tact as `ton("1.25")` is actually the number `1250000000` - we call these *nano-tons* instead of cents.
Arithmetic with dollars, for example, requires two decimal places. How can we represent the number `1.25` if we are only able to work with integers? The solution is to work with _cents_. In this way, `1.25` becomes `125`. We simply remember that the two rightmost digits represent the numbers after the decimal point.

Similarly, working with TON coins requires nine decimal places instead of two. Therefore, the amount of 1.25 TON, which can be represented in Tact as `ton("1.25")`, is actually the number `1250000000`.

**We refer to these as _nano-tons_ rather than cents.**
5 changes: 3 additions & 2 deletions src/routes/(examples)/02-integers/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ Tact supports a number of primitive data types that are tailored for smart contr

`Int` is the primary number type. Math in smart contracts is always done with integers and never with floating points since floats are [unpredictable](https://learn.microsoft.com/en-us/cpp/build/why-floating-point-numbers-may-lose-precision).

The runtime type `Int` is *always* 257-bit signed, so all runtime calculations are done at 257-bit. This should be large enough for pretty much anything you need as it's large enough to hold the number of atoms in the universe.
The runtime type `Int` is _always_ 257-bit signed, so all runtime calculations are done at 257-bit. This should be large enough for pretty much anything you need as it's large enough to hold the number of atoms in the universe.

Persistent state variables can be initialized inline or inside `init()`. If you forget to initialize a state variable, the code will not compile.

## State costs

When encoding `Int` to persistent state, we will usually use smaller representations than 257-bit to reduce storage cost. The persistent state size is specified in every declaration of a state variable after the `as` keyword.

Storing 1000 257-bit integers in state [costs](https://ton.org/docs/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.
- Storing 1000 257-bit integers in state [costs](https://ton.org/docs/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.
6 changes: 3 additions & 3 deletions src/routes/(examples)/02-variables/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ The most important variables are those that are persisted in state and retain th

Persisting data in state costs gas. The contract must pay rent periodically from its balance. State storage is expensive, about [4 TON per MB per year](https://ton.org/docs/develop/smart-contracts/fees#how-to-calculate-fees). If the contract runs out of balance, the data will be deleted. If you need to store large amounts of data, like images, a service like [TON Storage](https://ton.org/docs/participate/ton-storage/storage-faq) would be more suitable.

Persistent state variables can only change in *receivers* by sending messages as transactions. Sending these transactions will cost gas to users.
Persistent state variables can only change in _receivers_ by sending messages as transactions. **Sending these transactions will cost gas to users.**

Executing *getters* is read-only, they can access all variables, but cannot change state variables. They are free to execute and don't cost any gas.
Executing _getters_ is read-only, they can access all variables, but cannot change state variables. They are free to execute and don't cost any gas.

Local variables like `localVar1` are temporary. They're not persisted to state. You can define them in any function and they will only exist in run-time during the execution of the function. You can change their value in *getters* too.
Local variables like `localVar1` are temporary. They're not persisted to state. You can define them in any function and they will only exist in run-time during the execution of the function. You can change their value in _getters_ too.
15 changes: 12 additions & 3 deletions src/routes/(examples)/03-getters/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Getters are special contract functions that allow users to query information from the contract.

Contract methods starting with the prefix `get fun` are all getters. You can define as many getters are you want. Each getter must also specify its return type - `counter()` for example returns an `Int`.
Contract methods starting with the prefix `get fun` are all getters. You can define as many getters are you want. Each getter must also specify its return type - `counter()` for example returns an `Int`.

Calling getters is free and does not cost gas. The call is executed by a full node and doesn't go through consensus with all the validators nor is added to a new block.

Expand All @@ -12,6 +12,15 @@ If we were to omit the `get` keyword from the function declaration of a getter,

## Getters between contracts

A contract cannot execute a getter of another contract. Getters are only executable by end-users off-chain. Since contracts are running on-chain, they do not have access to each other's getters.
**A contract cannot execute a getter of another contract.**

So, if you can't call a getter, how can two contracts communicate? The only way for contracts to communicate on-chain is by sending messages to each other. Messages are handled in *receivers*.
Getters are only executable by end-users off-chain. Since contracts are running on-chain, they do not have access to each other's getters.

So, if you can't call a getter, how can two contracts communicate?

The only way for contracts to communicate on-chain is by sending messages to each other. Messages are handled in _receivers_.

<div style="padding-left: 1em; margin: 1em 0; position: relative;">
<div style="position: absolute; top: 0; bottom: 0%; left: 0; width: 3px; background-color: green;"></div>
<strong>Info</strong>: TON Blockchain is an asynchronous blockchain, which means that smart contracts can interact with each other only by sending messages.
</div>
22 changes: 12 additions & 10 deletions src/routes/(examples)/03-messages-between-contracts/content.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
# Messages Between Contracts

Different contracts can only communicate with each other by sending each other messages. This example shows two separate contracts that work together:
Different contracts can communicate with each other only by sending messages. This example showcases two separate contracts working in tandem:

* `Counter` - Our simple counter that can only increment by 1.
- `Counter` - A simple counter that can increment only by 1.
- `BulkAdder` - This contract instructs `Counter` to increment multiple times.

* `BulkAdder` - This contract will tell `Counter` to increment multiple times.
Click the <span class="mdButton blue">Deploy</span> button to deploy both contracts. To make the counter reach 5, send the `Reach` message to BulkAdder by clicking the <span class="mdButton grape">Send Reach{5}</span> button.

Press the <span class="mdButton blue">Deploy</span> button to deploy both. Then, to make the counter reach 5, send BulkAdder the `Reach` message by pressing the <span class="mdButton grape">Send Reach{5}</span> button.
Observe the number of messages exchanged between the two contracts. Each message is processed as a _separate_ transaction. Also note that BulkAdder cannot call a _getter_ on Counter; it must send a `query` message instead.

Notice how many messages are sent back and forth between the two contracts as a result. Each of these messages is processed as a *separate* transaction! Also notice that BulkAdder can't call a *getter* on Counter, it must send the `query` message instead.
## Who's Paying for Gas

## Who's paying for gas
**By default, the original sender is responsible for covering the gas costs of the entire cascade of messages they initiate.** This is funded by the original TON coin value sent with the first `Reach` message.

The default behavior is that the original sender will pay for the entire cascade of messages that they triggered. This is funded from the original TON coin value sent on the first `Reach` message.
Internally, this is managed by each message handler forwarding the remaining excess TON coin value to the next message it sends.

Under the hood, this works by each message handler sending the remaining excess TON coin value it received on the next message it sends out.

**Challenge:** modify the code to refund the original sender any unused excess gas.
<div style="padding-left: 1em; margin: 1em 0; position: relative;">
<div style="position: absolute; top: 0; bottom: 0%; left: 0; width: 3px; background-color: Purple;"></div>
<strong>Challenge</strong>: Try to modify the code to refund the original sender any unused excess gas.
</div>
9 changes: 7 additions & 2 deletions src/routes/(examples)/03-receive-coins/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ Every incoming message normally carries some TON coin value sent by the sender.

You can query the contract balance with `myBalance()` - note that the value is in nano-tons (like cents, just with 9 decimals). The balance already contains the incoming message value.

<div style="padding-left: 1em; margin: 1em 0; position: relative;">
<div style="position: absolute; top: 0; bottom: 0%; left: 0; width: 3px; background-color: green;"></div>
<strong>Info</strong>: More detail about myBalance() can be found here: <a href="https://docs.tact-lang.org/language/ref/common#mybalance">myBalance()</a>
</div>

## Refunding senders

If the transaction reverts, unused excess value will be sent back to sender on the *bounced* message.
If the transaction reverts, unused excess value will be sent back to sender on the _bounced_ message.

You can also refund the excess if the transaction succeeds by sending it back using `self.reply()` in a response message. This is the best way to guarantee senders are only paying for the exact gas that their message consumed.
You can also refund the excess if the transaction succeeds by sending it back using `self.reply()` in a response message. This is the best way to guarantee senders are only paying for the exact gas that their message consumed.
Loading