Skip to content

Commit d7150c9

Browse files
Feedback common(draft)
1 parent 14d9158 commit d7150c9

File tree

5 files changed

+140
-129
lines changed

5 files changed

+140
-129
lines changed

docs/v3/guidelines/quick-start/developing-smart-contracts/blueprint-sdk-overview.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Example/
3030
```
3131
</TabItem>
3232
<TabItem value="Tolk" label="Tolk">
33-
```
33+
```ls title="Project structure"
3434
Example/
3535
├── contracts/ # Folder containing smart contracts code
3636
│ └── hello_world.tolk # Main contract file
@@ -52,11 +52,11 @@ This folder contains your smart contract source code written in one of the avail
5252

5353
### `/wrappers`
5454

55-
While `@ton/ton SDK` provides us interfaces of serializing and sending messages for standard smart-contracts such as `wallets`, if we develop our own smart-contract that will deserialize received messages by its own custom protocol we need to provide some wrapper object that will serialize messages sent to smart-contract, deserialize responses from `get method`s and serialize contract itself for deployment.
55+
To interact with your smart-contract off-chain you need to serialize and desirialize messages sended to it. `Wrapper` classes developed to mirror your smart-contract implementation making it simple to use it's functionality.
5656

5757
### `/tests`
5858

59-
This directory contains test files for your smart contracts. Testing contracts directly in TON network is not the best option because it requires some amount of time and may lead to losing funds. This testing playground tool allow you to execute multiple smart-contracts and even send messages between them in your "local network". Tests are crucial for ensuring your smart contracts behave as expected before deployment to the network.
59+
This directory contains test files for your smart contracts. Testing contracts directly in TON network is not the best option because it requires some amount of time and may lead to losing funds. This testing playground tool allow you to execute multiple smart-contracts and even send messages between them in your **"local network"**. Tests are crucial for ensuring your smart contracts behave as expected before deployment to the network.
6060

6161
### `/scripts`
6262

docs/v3/guidelines/quick-start/developing-smart-contracts/deploying-to-network.mdx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ In this part of the guide we will proceed to deployment of previously developed
66

77
## Address and initial state
88

9-
We already know that [address](/v3/documentation/smart-contracts/addresses/) is unique identifier of `smart-contract`, i.e `actor`, `account` in network which is used to send transactions and verify their sender upon receive but we still didn't discussed how it's created. The common formula of smart-contract address looks like that:
9+
We already know that [address](/v3/documentation/smart-contracts/addresses/) is unique identifier of `smart-contract` in network which is used to send transactions and verify their sender upon receive, but we still didn't discussed how it's created. The common formula of smart-contract address looks like that:
1010

1111
***address=hash(state_init(code, data))***
1212

1313
Address of smart-contract is a hash of aggregated initial code and data of smart-contracts upon deployment. This simple mechanism have few important consequences:
1414

1515
### You already know the address
1616

17-
In TON any address that didn't accept any transaction and, as a consequnce, dont have any data is considered in `nonexzist` state, nethertheless when we created a wallet using wallet app in [Getting started](/v3/guidelines/quick-start/getting-started) section we still was able to get address of our **future** wallet smart-contract from wallet app before it's deployment and examine it in explorer.
17+
In TON any address that don't have any data is considered in `nonexistent` state, nevertheless when we created a wallet using wallet app in [Getting started](/v3/guidelines/quick-start/getting-started) section we still was able to get address of our **future** wallet smart-contract from wallet app before it's deployment and examine it in explorer.
1818

19-
The reason behind that is because creating your private and public key pair through **mnemonic phrase**, where second key is part of initial data of smart-contract makes `state_init` of our contract fully determent:
19+
The reason behind that is because creating your **private** and **public** key pair through **mnemonic phrase**, where second key is part of initial data of smart-contract makes `state_init` of our contract fully determent:
2020
- **code** is one of the standard wallet implementation, like `v5r1`.
2121
- **data** is `public_key` with other default initialized fields.
2222

@@ -40,7 +40,7 @@ init(id: Int, owner: Address) {
4040
}
4141
```
4242

43-
If we remove `id` field from its initial storage we can ensure that **only one** `CounterInternal` smart-cotnract could exzist for a particular owner, moreover, if we consider owner as wallet smart-contract, by knowing its public_key and version we could calculate wallet address and, as a consequnce, address of its `CounterInternal` contract.
43+
If we remove `id` field from its initial storage we can ensure that **only one** `CounterInternal` smart-cotnract could exzist for a particular owner.
4444

4545
:::info Tokens
4646
This mechanism plays cruicial role in [Jetton Processing](v3/guidelines/dapps/asset-processing/jettons), each non-native(jetton) token requires it's own `Jetton Wallet` for a particular owner and therefore provides a calculatable address from it, creating a **star scheme** with basic wallet in center.
@@ -50,23 +50,19 @@ This mechanism plays cruicial role in [Jetton Processing](v3/guidelines/dapps/as
5050

5151
Now, when our smart-contracts is fully tested, we are ready to deploy them into the TON. In `Blueprint SDK` this process is the same for both `mainnet` and `testnet` and any of the presented languages in guide: `FunC`, `Tact`, `Tolk`. Deploy scripts relays on the same wrappers that you have used in testing scripts:
5252

53-
```typescript
53+
```typescript title="/scripts/deployHelloWorld"
5454
import { toNano } from '@ton/core';
5555
import { HelloWorld } from '../wrappers/HelloWorld';
5656
import { CounterInternal } from '../wrappers/CounterInternal';
5757
import { compile, NetworkProvider } from '@ton/blueprint';
58-
import { mnemonicToWalletKey } from '@ton/crypto';
5958

6059
export async function run(provider: NetworkProvider) {
61-
const mnemonic = "".split(' '); // Insert your mnemonic
62-
const { publicKey, secretKey } = await mnemonicToWalletKey(mnemonic);
63-
6460
const helloWorld = provider.open(
6561
HelloWorld.createFromConfig(
6662
{
6763
id: Math.floor(Math.random() * 10000),
68-
seqno: 0,
69-
publicKey: publicKey
64+
ctxCounter: 0,
65+
ctxCounterExt: 0n,
7066
},
7167
await compile('HelloWorld')
7268
)
@@ -99,6 +95,7 @@ export async function run(provider: NetworkProvider) {
9995
console.log('ID', await helloWorld.getID());
10096
console.log('ID', (await counterInternal.getId()).toString());
10197
}
98+
10299
```
103100

104101
You can run those scripts by entering following command:

docs/v3/guidelines/quick-start/developing-smart-contracts/processing-messages.mdx

Lines changed: 85 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import TabItem from '@theme/TabItem';
55

66
> **Summary:** In previous steps we modified our smart-contract interaction with `storage, `get methods` and learned basic smart-contract development flow.
77
8-
Now that we have learned basic examples of modifying smart-contract code and using development tools, we are ready to move on to the main functionality of smart contracts - sending and receiving messages. In TON messages not only used for sending currency, but also as data-exchange mechanism between smart-contracts.
8+
Now we are ready to move on to the main functionality of smart contracts - **sending and receiving messages**. In TON messages not only used for sending currency, but also as data-exchange mechanism between smart-contracts.
99

1010

1111
:::tip
@@ -18,81 +18,14 @@ If you are stuck on some of the examples you can find original template project
1818

1919
Before we proceed to implementation let's briefly describe main ways and patterns that we can use to process internal messages.
2020

21-
### Operations
22-
23-
Common pattern in TON contracts is to include a **32-bit operation code** in message bodies which tells your contract what action to perform:
24-
25-
<Tabs groupId="language">
26-
<TabItem value="FunC" label="FunC">
27-
```func
28-
;; This is NOT a part of the project, just an example!
29-
const int op::increment = 1;
30-
const int op::decrement = 2;
31-
32-
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
33-
;; Step 1: Check if the message is empty
34-
if (in_msg_body.slice_empty?()) {
35-
return; ;; Nothing to do with empty messages
36-
}
37-
38-
;; Step 2: Extract the operation code
39-
int op = in_msg_body~load_uint(32);
40-
41-
;; Step 3-7: Handle the requested operation
42-
if (op == op::increment) {
43-
increment(); ;;call to specific operation handler
44-
return;
45-
} else if (op == op::decrement) {
46-
decrement();
47-
;; Just accept the money
48-
return;
49-
}
50-
51-
;; Unknown operation
52-
throw(0xffff);
53-
}
54-
```
55-
</TabItem>
56-
<TabItem value="Tolk" label="Tolk">
57-
```tolk
58-
//This is NOT a part of the project, just an example!
59-
const op::increment : int = 1;
60-
const op::decrement : int = 2;
61-
62-
fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice) {
63-
// Step 1: Check if the message is empty
64-
if (slice.isEndOfSlice()) {
65-
return; // Nothing to do with empty messages
66-
}
67-
68-
// Step 2: Extract the operation code
69-
var op = in_msg_body~load_uint(32);
70-
71-
// Step 3-7: Handle the requested operation
72-
if (op == op::increment) {
73-
increment(); //call to specific operation handler
74-
return;
75-
} else if (op == op::decrement) {
76-
decrement();
77-
// Just accept the money
78-
return;
79-
}
80-
81-
// Unknown operation
82-
throw(0xffff);
83-
}
84-
```
85-
</TabItem>
86-
</Tabs>
87-
8821
### Actors and roles
8922

9023
Since TON implements [actor](/v3/concepts/dive-into-ton/ton-blockchain/blockchain-of-blockchains/#single-actor) model it's natural to think about smart-contracts relations in terms of `roles`, determining who can access smart-contract functionality or not. The most common examples of roles are:
9124

9225
- `anyone`: any contract that don't have distinct role.
9326
- `owner`: contract that has exclusive access to some crucial parts of functionality.
9427

95-
Let's examine `recv_internal` function signature to understdand how we could use that:
28+
Let's examine `recv_internal` function signature to understand how we could use that:
9629

9730
<Tabs groupId="language">
9831
<TabItem value="FunC" label="FunC">
@@ -175,6 +108,75 @@ fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: sli
175108
</TabItem>
176109
</Tabs>
177110

111+
112+
### Operations
113+
114+
Common pattern in TON contracts is to include a **32-bit operation code** in message bodies which tells your contract what action to perform:
115+
116+
<Tabs groupId="language">
117+
<TabItem value="FunC" label="FunC">
118+
```func
119+
;; This is NOT a part of the project, just an example!
120+
const int op::increment = 1;
121+
const int op::decrement = 2;
122+
123+
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
124+
;; Step 1: Check if the message is empty
125+
if (in_msg_body.slice_empty?()) {
126+
return; ;; Nothing to do with empty messages
127+
}
128+
129+
;; Step 2: Extract the operation code
130+
int op = in_msg_body~load_uint(32);
131+
132+
;; Step 3-7: Handle the requested operation
133+
if (op == op::increment) {
134+
increment(); ;;call to specific operation handler
135+
return;
136+
} else if (op == op::decrement) {
137+
decrement();
138+
;; Just accept the money
139+
return;
140+
}
141+
142+
;; Unknown operation
143+
throw(0xffff);
144+
}
145+
```
146+
</TabItem>
147+
<TabItem value="Tolk" label="Tolk">
148+
```tolk
149+
//This is NOT a part of the project, just an example!
150+
const op::increment : int = 1;
151+
const op::decrement : int = 2;
152+
153+
fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice) {
154+
// Step 1: Check if the message is empty
155+
if (slice.isEndOfSlice()) {
156+
return; // Nothing to do with empty messages
157+
}
158+
159+
// Step 2: Extract the operation code
160+
var op = in_msg_body~load_uint(32);
161+
162+
// Step 3-7: Handle the requested operation
163+
if (op == op::increment) {
164+
increment(); //call to specific operation handler
165+
return;
166+
} else if (op == op::decrement) {
167+
decrement();
168+
// Just accept the money
169+
return;
170+
}
171+
172+
// Unknown operation
173+
throw(0xffff);
174+
}
175+
```
176+
</TabItem>
177+
</Tabs>
178+
179+
178180
By combining both of these patterns you can achieve a comprehensive description of your smart-contract's systems ensuring secure interaction between them and unleash full potential of TON actors model.
179181

180182
## Implementation in Tact
@@ -244,7 +246,7 @@ Example/
244246

245247
At the top of the generated contract file: `counter_internal.tact`, you may see a [message](https://docs.tact-lang.org/book/structs-and-messages/) definition:
246248

247-
```tact title="counter_internal.tact"
249+
```tact title="/contracts/counter_internal.tact"
248250
message Add {
249251
queryId: Int as uint64;
250252
amount: Int as uint32;
@@ -253,7 +255,7 @@ message Add {
253255

254256
A message is a basic structure for communication between contracts. Tact automatically serializes and deserializes messages into cells. To ensure that opcodes will be the same during message structure changes, it may be added like below:
255257

256-
```tact title="counter_internal.tact"
258+
```tact title="/contracts/counter_internal.tact"
257259
message(0x7e8764ef) Add {
258260
queryId: Int as uint64;
259261
amount: Int as uint32;
@@ -293,7 +295,7 @@ counter: Int as uint32;
293295

294296
To ensure that only the contract owner can interact with specific functions, add an `owner` field:
295297

296-
```tact title="counter_internal.tact"
298+
```tact title="/contracts/counter_internal.tact"
297299
id: Int as uint32;
298300
counter: Int as uint32;
299301
owner: Address;
@@ -305,7 +307,7 @@ These fields are serialized similarly to structures and stored in the contract's
305307

306308
If you compile the contract at this stage, you will encounter the error: `Field "owner" is not set`. This is because the contract needs to initialize its fields upon deployment. Define an [`init()`](https://docs.tact-lang.org/book/contracts/#init-function) function to do this:
307309

308-
```tact title="counter_internal.tact"
310+
```tact title="/contracts/counter_internal.tact"
309311
init(id: Int, owner: Address) {
310312
self.id = id;
311313
self.counter = 0;
@@ -317,7 +319,7 @@ init(id: Int, owner: Address) {
317319

318320
To accept messages from other contracts, use a [receiver](https://docs.tact-lang.org/book/functions/#receiver-functions) function. Receiver functions automatically match the message's opcode and invoke the corresponding function:
319321

320-
```tact title="counter_internal.tact"
322+
```tact title="/contracts/counter_internal.tact"
321323
receive(msg: Add) {
322324
self.counter += msg.amount;
323325
self.notify("Cashback".asComment());
@@ -328,7 +330,7 @@ receive(msg: Add) {
328330

329331
To ensure that only the contract owner can send messages, use the `require` function:
330332

331-
```tact title="counter_internal.tact"
333+
```tact title="/contracts/counter_internal.tact"
332334
receive(msg: Add) {
333335
// `sender()` function is used to retrieve message sender address
334336
require(sender() == self.owner, "Not owner!");
@@ -343,7 +345,7 @@ receive(msg: Add) {
343345

344346
Tact also provides handy ways to share same logic through [traits](https://docs.tact-lang.org/book/types/#traits). Use the `Ownable` trait, which provides built-in ownership checks:
345347

346-
```tact title="counter_internal.tact"
348+
```tact title="/contracts/counter_internal.tact"
347349
// Import library to use trait
348350
import "@stdlib/ownable";
349351
@@ -370,7 +372,7 @@ Tact supports [getter functions](https://docs.tact-lang.org/book/functions/#gett
370372
Get function cannot be called from another contract.
371373
:::
372374

373-
```tact title="counter_internal.tact"
375+
```tact title="/contracts/counter_internal.tact"
374376
get fun counter(): Int {
375377
return self.counter;
376378
}
@@ -380,7 +382,7 @@ Note, that the `owner` getter is automatically defined via the `Ownable` trait.
380382

381383
#### Complete contract
382384

383-
```tact title="counter_internal.tact"
385+
```tact title="/contracts/counter_internal.tact"
384386
import "@stdlib/deploy";
385387
import "@stdlib/ownable";
386388
@@ -436,15 +438,15 @@ npm blueprint build
436438

437439
[Wrappers](https://docs.tact-lang.org/book/compile/#wrap-ts) facilitate contract interaction from TypeScript. Unlike in FunC or Tolk, they are generated automatically during the build process:
438440

439-
```typescript ./wrappers/CounterInternal.ts
441+
```typescript title="/wrappers/CounterInternal.ts"
440442
export * from '../build/CounterInternal/tact_CounterInternal';
441443
```
442444

443445
### Step 3: Updating tests
444446

445447
Now let's ensure that our smart-contract failes if non-owner tries to increment smart-contract by adding this test:
446448

447-
```typescript title="tests/CounterInternal.spec.ts"
449+
```typescript title="/tests/CounterInternal.spec.ts"
448450
import { Blockchain, SandboxContract, TreasuryContract} from '@ton/sandbox';
449451
import { Cell, toNano } from '@ton/core';
450452
import { CounterInternal } from '../wrappers/CounterInternal';
@@ -540,7 +542,7 @@ Let's modify our smart-contract to recieve external messages and send increase c
540542

541543
Add `recv_external` function to `HelloWorld` smart-contract:
542544

543-
```func title="HelloWorld.fc"
545+
```func title="/contracts/HelloWorld.fc"
544546
() recv_external(slice in_msg) impure {
545547
accept_message();
546548
@@ -578,7 +580,7 @@ npm blueprint build
578580

579581
And add wrapper method to call it through our wrapper class for sending external message:
580582

581-
```typescript
583+
```typescript title="/wrappers/HelloWorld.ts"
582584
async sendExternalIncrease(
583585
provider: ContractProvider,
584586
opts: {
@@ -603,7 +605,7 @@ async sendExternalIncrease(
603605

604606
Update test to esnure that `HelloWorld` contract recieved internal message, sended internal message to `CounterInternal` contract and both updated their counters:
605607

606-
```typescript title="HelloWorld.spec.ts"
608+
```typescript title="/tests/HelloWorld.spec.ts"
607609
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
608610
import { Cell, toNano} from '@ton/core';
609611
import { HelloWorld } from '../wrappers/HelloWorld';
@@ -693,4 +695,3 @@ Verify that all examples is correct by running test script:
693695
npm blueprint test
694696
```
695697

696-
Congratulations! You have successfully created a **multi-contract** system and learned how to handle **internal** and **external** messages. This example illustrates the typical flow of any message chain: sending an `external message`, triggering the `internal messages` flow based on your system model, and so on. Now that our contracts have been fully tested, we are ready to deploy them and interact with them on-chain.

0 commit comments

Comments
 (0)