Skip to content

Commit 7547ea3

Browse files
Refactoring steps(draft)
1 parent 86fe71a commit 7547ea3

File tree

2 files changed

+81
-126
lines changed

2 files changed

+81
-126
lines changed

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

Lines changed: 66 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ What we specifically are interested in is the source address of message, by obta
3333
<Tabs groupId="language">
3434
<TabItem value="FunC" label="FunC">
3535
```func
36+
;; This is NOT a part of the project, just an example.
3637
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
3738
;; Parse the sender address from in_msg_full
3839
slice cs = in_msg_full.begin_parse();
@@ -59,6 +60,7 @@ What we specifically are interested in is the source address of message, by obta
5960
</TabItem>
6061
<TabItem value="Tolk" label="Tolk">
6162
```tolk
63+
// This is NOT a part of the project, just an example.
6264
fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice) {
6365
// Parse the sender address from in_msg_full
6466
var cs: slice = msgFull.beginParse();
@@ -89,6 +91,7 @@ Another common pattern in TON contracts is to include a **32-bit operation code*
8991
<Tabs groupId="language">
9092
<TabItem value="FunC" label="FunC">
9193
```func
94+
;; This is NOT a part of the project, just an example!
9295
const int op::increment = 1;
9396
const int op::decrement = 2;
9497
@@ -118,6 +121,7 @@ const int op::decrement = 2;
118121
</TabItem>
119122
<TabItem value="Tolk" label="Tolk">
120123
```tolk
124+
//This is NOT a part of the project, just an example!
121125
const op::increment : int = 1;
122126
const op::decrement : int = 2;
123127
@@ -149,23 +153,72 @@ fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: sli
149153

150154
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.
151155

152-
### Implementation in Tact
156+
## Implementation in Tact
153157

154158
Tact is a high-level programming language for the TON Blockchain, focused on efficiency and simplicity. It is designed to be easy to learn and use while being well-suited for smart contract development. Tact is a statically typed language with a simple syntax and a powerful type system.
155159

156160
:::info
157161
For more details, refer to the [Tact documentation](https://docs.tact-lang.org/#start) and [Tact By Example](https://tact-by-example.org/00-hello-world).
158162
:::
159163

160-
#### Creating a Tact contract
164+
Let's create and modify our smart-contract folowing standard steps decsribed in previous [Blueprint SDK overview](/v3/guidelines/quick-start/developing-smart-contracts/blueprint-sdk-overview) section.
165+
166+
### Step1: Creating and modifying Tact contract
161167

162168
First, let's create Tact contract.
163169

164170
```bash
165171
npx blueprint create CounterInternal --type tact-counter
166172
```
167173

168-
At the top of the generated contract file, you may see a [message](https://docs.tact-lang.org/book/structs-and-messages/) definition:
174+
Resulted project structure should look like this:
175+
176+
<Tabs groupId="language">
177+
<TabItem value="FunC" label="FunC">
178+
```ls title="Project structure"
179+
Example/
180+
├─ contracts/ # Smart contract source code
181+
│ ├─ counter_internal.tact # CounterInternal contract in Tact language
182+
│ ├─ hello_world.fc # HelloWorld contract in FunC language
183+
├─ scripts/ # Deployment and interaction scripts
184+
│ ├─ deployCounterInternal.ts # Script to deploy contract
185+
│ ├─ deployHelloWorld.ts # Script to deploy contract
186+
│ ├─ incrementCounterInternal.ts # Script to increment counter
187+
│ └─ incrementHelloWorld.ts # Script to increment counter
188+
├─ tests/ # Test suite for contracts
189+
│ ├─ CounterInternal.spec.ts # Tests for CounterInternal contract
190+
│ └─ HelloWorld.spec.ts # Tests for HelloWorld contract
191+
└─ wrappers/ # Wrappers for contract interaction
192+
├─ CounterInternal.compile.ts # Compilation configuration
193+
├─ CounterInternal.ts # Wrapper for CounterInternal contract
194+
├─ HelloWorld.compile.ts # Compilation configuration
195+
└─ HelloWorld.ts # Wrapper for HelloWorld contract
196+
```
197+
</TabItem>
198+
<TabItem value="Tolk" label="Tolk">
199+
```ls title="Project structure"
200+
Example/
201+
├─ contracts/ # Smart contract source code
202+
│ ├─ counter_internal.tact # CounterInternal contract in Tact language
203+
│ ├─ hello_world.tolk # HelloWorld contract in FunC language
204+
├─ scripts/ # Deployment and interaction scripts
205+
│ ├─ deployCounterInternal.ts # Script to deploy contract
206+
│ ├─ deployHelloWorld.ts # Script to deploy contract
207+
│ ├─ incrementCounterInternal.ts # Script to increment counter
208+
│ └─ incrementHelloWorld.ts # Script to increment counter
209+
├─ tests/ # Test suite for contracts
210+
│ ├─ CounterInternal.spec.ts # Tests for CounterInternal contract
211+
│ └─ HelloWorld.spec.ts # Tests for HelloWorld contract
212+
└─ wrappers/ # Wrappers for contract interaction
213+
├─ CounterInternal.compile.ts # Compilation configuration
214+
├─ CounterInternal.ts # Wrapper for CounterInternal contract
215+
├─ HelloWorld.compile.ts # Compilation configuration
216+
└─ HelloWorld.ts # Wrapper for HelloWorld contract
217+
```
218+
</TabItem>
219+
</Tabs>
220+
221+
At the top of the generated contract file: `counter_internal.tolk`, you may see a [message](https://docs.tact-lang.org/book/structs-and-messages/) definition:
169222

170223
```tact
171224
message Add {
@@ -308,7 +361,7 @@ Note, that the `owner` getter is automatically defined via the `Ownable` trait.
308361

309362
#### Complete contract
310363

311-
```tact
364+
```tact title="CounterInternal.tact"
312365
import "@stdlib/deploy";
313366
import "@stdlib/ownable";
314367
@@ -354,132 +407,34 @@ contract CounterInternal with Deployable, Ownable {
354407
}
355408
```
356409

357-
#### Using contract wrappers
410+
### Step2: Using contract wrappers
358411

359412
[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:
360413

361414
```typescript ./wrappers/CounterInternal.ts
362415
export * from '../build/CounterInternal/tact_CounterInternal';
363416
```
364417

418+
### Step3: Updating tests
419+
420+
TODO: add separate test
365421

366422
---
367423

368424
## External Messages
369425

370-
`External messages` are your main way of toggling smart contract logic from outside the blockchain. Usually, there is no need for implementation of them in smart contracts because in most cases you don't want external entry points to be accessible to anyone except you. If this is all functionality that you want from external section - standard way is to delegate this responsibility to separate actor - `wallet` which is practically the main reason they were designed for.
371-
372-
### Wallets
373-
374-
When we sent coins using wallet app in [getting started](/v3/guidelines/quick-start/getting-started#step-3-exploring-the-blockchain) section what `wallet app` actually performs is sending `external message` to your `wallet` smart contract which performs sending message to destination smart-contract address that you wrote in send menu. While most wallet apps during creation of wallet deploy most modern versions of wallet smart contracts - `v5`, providing more complex functionality, let's examine `recv_external` section of more basic one - `v3`:
375-
376-
```func
377-
() recv_external(slice in_msg) impure {
378-
var signature = in_msg~load_bits(512);
379-
var cs = in_msg;
380-
var (subwallet_id, msg_seqno) = (cs~load_uint(32), cs~load_uint(32));
381-
var ds = get_data().begin_parse();
382-
var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256));
383-
ds.end_parse();
384-
throw_unless(33, msg_seqno == stored_seqno);
385-
throw_unless(34, subwallet_id == stored_subwallet);
386-
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));
387-
accept_message();
388-
cs~touch();
389-
while (cs.slice_refs()) {
390-
var mode = cs~load_uint(8);
391-
send_raw_message(cs~load_ref(), mode);
392-
}
393-
set_data(begin_cell()
394-
.store_uint(stored_seqno + 1, 32)
395-
.store_uint(stored_subwallet, 32)
396-
.store_uint(public_key, 256)
397-
.end_cell());
398-
}
399-
```
400-
426+
`External messages` are your only way of toggling smart contract logic from outside the blockchain. Usually, there is no need for implementation of them in smart contracts because in most cases you don't want external entry points to be accessible to anyone except you. If this is all functionality that you want from external section - standard way is to delegate this responsibility to separate actor - [wallet](v3/documentation/smart-contracts/contracts-specs/wallet-contracts#basic-wallets) which is practically the main reason they were designed for.
401427

402-
First thing to mention - is `signature` in message body and stored `public_key`. This refers to standard mechanism of asymmetric cryptography: during deployment process you create **private and public key pair**, store the second one in initial contract storage and then during sending external message through client **sign** it with **private key** attaching calculated `signature` to message body. Smart contract on its side checks if signature matches `public_key` and accepts external message if it is so.
428+
Developing external endpoint includes several standard [approuches](/v3/documentation/smart-contracts/message-management/external-messages) and [security measures](/v3/guidelines/smart-contracts/security/overview) that might be overwhelming at this point. Therefore in this guide we will realize incrementing previously added to `hello_world` contract `ctxCounterExt` and add send message to out `Tact` contract.
403429

404-
Standard signature system for TON smart-contracts is `Ed25519` which is directly provided by `TVM` instruction `check_signature()`, but you can always implement another preferred algorithm by yourself.
405-
406-
:::tip
407-
When you entered **magic 24 secret words** (i.e. **mnemonic phrase**) during [wallet creation](/v3/documentation/data-formats/tlb/tl-b-language) in your app what is practically performed is concatenation of those words into one string and hashing it to create your **private key**. So remember not to show them to anyone.
430+
:::danger
431+
This implementation is **unsafe** and may lead to loosing your contract funds. Don't deploy it to `mainnet`, especially with high smart-contract balance.
408432
:::
409433

410-
Second thing is `seqno` (sequential number) as you can see this is practically just a counter that increments each time wallet smart-contract receives external message, but why do we need one?
411-
The reason behind that lies in blockchain nature: since all transactions are visible to anyone, potential malefactor could repeatedly send already signed transaction to your smart contract.
412-
413-
> **Simpliest scenario:** you transfer some amount of funds to receiver, receiver examines transaction and sends it to your contract repeatedly until you run out of funds and receiver gains almost all of them.
414-
415-
Third thing is `subwallet_id` that just checks equality to the stored one, we will discuss its meaning a little bit later in internal messages section.
434+
## Implementation
416435

417-
### Implementation
418436

419-
At this point reasons behind changes that we made to our counter in previous storage and get methods [section](/v3/guidelines/quick-start/developing-smart-contracts/storage-and-get-methods#smart-contract-storage-operations) should start to be more clear! We already prepared our storage to contain `seqno`, `public_key` and `ctx_id` which will serve same task as `subwallet_id` so let's adapt wallet's `recv_external` function to our project:
420-
421-
<Tabs groupId="language">
422-
<TabItem value="FunC" label="FunC">
423-
```func
424-
() recv_external(slice in_msg) impure {
425-
;; retrives validating data from message body
426-
var signature = in_msg~load_bits(512);
427-
var cs = in_msg;
428-
var (ctx_id, msg_seqno) = (cs~load_uint(32), cs~load_uint(32));
429-
430-
;; retrieves stored data for validation checks
431-
var (stored_id, stored_seqno, public_key) = load_data();
432-
;; replay protection mechanism through seqno chack and incrementing
433-
throw_unless(33, msg_seqno == stored_seqno);
434-
;; id field for multiply addresess with same private_key
435-
throw_unless(34, ctx_id == stored_id);
436-
;; ed25519 signature check
437-
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));
438-
;; accepting message after all checks
439-
accept_message();
440-
;; optimization technique
441-
;; putting message body to stack head
442-
cs~touch();
443-
;; sending serialized on client side messages
444-
while (cs.slice_refs()) {
445-
var mode = cs~load_uint(8);
446-
send_raw_message(cs~load_ref(), mode);
447-
}
448-
save_data(stored_id, stored_seqno + 1, public_key);
449-
}
450-
```
451-
</TabItem>
452-
<TabItem value="Tolk" label="Tolk">
453-
```tolk
454-
fun acceptExternalMessage(): void
455-
asm "ACCEPT";
456-
457-
fun onExternalMessage(inMsg: slice) {
458-
var signature = inMsg.loadBits(512);
459-
var cs = inMsg;
460-
var (ctx_id, msg_seqno) = (cs.loadUint(32), cs.loadUint(32));
461-
462-
// retrieves stored data for validation checks
463-
var (stored_id, stored_seqno, public_key) = loadData();
464-
// replay protection mechanism through seqno chack and incrementing
465-
assert(msg_seqno == stored_seqno, 33);
466-
// id field for multiply addresess with same private_key
467-
assert(ctx_id == stored_id, 34);
468-
// ed25519 signature check
469-
assert(isSignatureValid(sliceHash(inMsg), signature, public_key), 35);
470-
// accepting message after all checks
471-
acceptExternalMessage();
472-
// sending serialized on client side messages
473-
while (!cs.isEndOfSliceRefs()) {
474-
var mode = cs.loadUint(8);
475-
sendRawMessage(cs.loadRef(), mode);
476-
}
477-
saveData(stored_id, stored_seqno + 1, public_key);
478-
}
479-
```
480437

481-
</TabItem>
482-
</Tabs>
483438

484439
And add wrapper method to call it through our wrapper class:
485440

docs/v3/guidelines/quick-start/developing-smart-contracts/storage-and-get-methods.mdx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ interface Cell {
4242

4343
## Implementation
4444

45-
Let's try to modify our smart-contract folowing standard steps decsribed in previous [Blueprint SDK overview](/v3/guidelines/quick-start/developing-smart-contracts/storage-and-get-methods) section.
45+
Let's try to modify our smart-contract folowing standard steps decsribed in previous [Blueprint SDK overview](/v3/guidelines/quick-start/developing-smart-contracts/blueprint-sdk-overview) section.
4646

4747
### Step1: Edit smart-contract code
4848

4949
In case it's inconvenient to always serialize and deserialize storage cell, there is a pretty standard practice to define two wrapper methods that provide corresponding logic. If you didn't change smart-contract code it should contain following lines:
5050

5151
<Tabs groupId="language">
5252
<TabItem value="FunC" label="FunC">
53-
```func hello_world.fc
53+
```func title="hello_world.fc"
5454
global int ctx_id;
5555
global int ctx_counter;
5656
@@ -76,7 +76,7 @@ global int ctx_counter;
7676
```
7777
</TabItem>
7878
<TabItem value="Tolk" label="Tolk">
79-
```tolk
79+
```tolk title="hello_world.tolk"
8080
global ctxID: int;
8181
global ctxCounter: int;
8282
@@ -110,7 +110,7 @@ Result of our modifications should look like this:
110110

111111
<Tabs groupId="language">
112112
<TabItem value="FunC" label="FunC">
113-
```func
113+
```func title="hello_world.fc"
114114
;; load_data retrieves variables from TVM storage cell
115115
(int, int, int) load_data() {
116116
var ds = get_data().begin_parse();
@@ -140,7 +140,7 @@ Result of our modifications should look like this:
140140
```
141141
</TabItem>
142142
<TabItem value="Tolk" label="Tolk">
143-
```tolk
143+
```tolk title="hello_world.tolk"
144144
// load_data retrieves variables from TVM storage cell
145145
// impure because of writting into global variables
146146
fun loadData(): (int, int, int) {
@@ -175,13 +175,13 @@ Don't forget to delete global variables `ctx_id`, `ctx_counter` and modify usage
175175

176176
<Tabs groupId="language">
177177
<TabItem value="FunC" label="FunC">
178-
```func
178+
```func title="hello_world.fc"
179179
var (ctx_id, ctxCounter, ctxCounterExt) = load_data();
180180
save_data(ctx_id, ctxCounter, ctxCounterExt);
181181
```
182182
</TabItem>
183183
<TabItem value="Tolk" label="Tolk">
184-
```tolk
184+
```tolk title="hello_world.tolk"
185185
var (ctx_id, ctxCounter, ctxCounterExt) = load_data();
186186
save_data(ctx_id, ctxCounter, ctxCounterExt);
187187
```
@@ -194,15 +194,15 @@ The primary use of get methods is reading our storage data from outside the bloc
194194

195195
<Tabs groupId="language">
196196
<TabItem value="FunC" label="FunC">
197-
```func
197+
```func title="hello_world.fc"
198198
(int, int) get_counters() method_id {
199199
var (_, ctxCounter, ctxCounterExt) = load_data();
200200
return (ctxCounter, ctxCounterExt);
201201
}
202202
```
203203
</TabItem>
204204
<TabItem value="Tolk" label="Tolk">
205-
```tolk
205+
```tolk title="hello_world.tolk"
206206
get get_counters(): (int, int) {
207207
var (_, ctxCounter, ctxCounterExt) = load_data();
208208
return (ctxCounter, ctxCounterExt);
@@ -219,13 +219,13 @@ npm run build
219219

220220
And that's it! In practice all get methods follow this simple flow and don't require anything more. Note that you can omit values returned from functions using '_' syntax.
221221

222-
## Step2: Updating wrapper
222+
### Step2: Updating wrapper
223223

224224
Now lets update our wrapper class corresponding to new storage layout and new `get method`.
225225

226226
First, let's modify `helloWorldConfigToCell` function and `HelloWorldConfig` type to properly initialize our storage during deployment:
227227

228-
```typescript
228+
```typescript title="HelloWorld.ts"
229229
export type HelloWorldConfig = {
230230
id: number;
231231
ctxCounter: number;
@@ -242,7 +242,7 @@ export function helloWorldConfigToCell(config: HelloWorldConfig): Cell {
242242
```
243243
Second, add a method to perform a request for the newly created get method:
244244

245-
```typescript
245+
```typescript title="HelloWorld.ts"
246246
async getSeqnoPKey(provider: ContractProvider) {
247247
const result = await provider.get('get_counters', []);
248248
const counter = result.stack.readNumber();
@@ -252,13 +252,13 @@ async getSeqnoPKey(provider: ContractProvider) {
252252
}
253253
```
254254

255-
## Step3: Updating tests
255+
### Step3: Updating tests
256256

257257
And finally, let's write a simple test, checking that the deployment process initializes smart contract storage and correctly retrieves its data by get methods.
258258

259259
First, let's update the `before each` section and particularly the `openContract` logic with the following one:
260260

261-
```typescript
261+
```typescript title="HelloWorld.spec.ts"
262262
helloWorld = blockchain.openContract(
263263
HelloWorld.createFromConfig(
264264
{
@@ -273,7 +273,7 @@ helloWorld = blockchain.openContract(
273273

274274
And add new test case for get methods:
275275

276-
```typescript
276+
```typescript title="HelloWorld.spec.ts"
277277
it('should correctly initialize and return the initial data', async () => {
278278
// Define the expected initial values (same as in beforeEach)
279279
const expectedConfig = {

0 commit comments

Comments
 (0)