TON Blockchain Сourse #FREEDUROV
737 subscribers
17 photos
73 videos
30 files
127 links
для связи @Aleksey_Leon

#freedurov
Download Telegram
💎 Наиболее популярным примером реализации этой идеи является дизайн токенов в экосистеме TON. Балансы отдельных пользователей распределены по так называемым кошелькам Jetton. 🔥 Что в этом круто, так это то, что операции между двумя кошельками Jeton в одном месте не мешают операциям двух других в каком-либо другом месте, и они могут быть распределены по отдельным цепочкам блоков и не создавать никаких узких мест.
2
2.6 Обзор классических бизнес-задач
📝 Вы также узнаете о:

В каких ситуациях мы могли бы применить наши принципы разработки масштабируемых контрактов?
Media is too big
VIEW IN TELEGRAM
2.6 Обзор классических бизнес-задач
✔️ Так же смотрите видео на YouTube
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21
📝 Теперь вы знаете:

В TON токены реализованы в виде отдельных контрактов. По одному контракту на пользователя плюс отдельный контракт minter, который предоставляет интерфейс для создания новых единиц токена.

📚 Примечания к лекции
Здесь мы рассмотрим три примера, чтобы увидеть, как принципы разработки контрактов применяются к различным приложениям и как платформа TON помогает вам создавать масштабируемые и безопасные приложения.

Жетоны.
В реализациях Ethereum или даже без блокчейна токены были бы реализованы как простая бухгалтерская книга счетов.

У вас есть список, программа управляет этим списком учетных записей, и каждый элемент в списке фактически является адресом участника и его балансом. Это довольно просто, но, к сожалению, не масштабируется в контексте блокчейна, потому что ваш смарт-контракт должен постоянно расти вместе с количеством пользователей и становится более дорогим для взаимодействия с каждым пользователем.

Что такое подход TON?

В TON токены реализованы в виде отдельных контрактов. Один контракт на пользователя плюс отдельный контракт minter, который предоставляет интерфейс для создания новых единиц токена.

Контракты для каждого пользователя называются кошельками Jetton, и задача этих кошельков Jetton заключается в хранении баланса токена для каждого отдельного пользователя. Всякий раз, когда пользователь хочет перевести токен с одной учетной записи на другую 💸 , он сначала отправляет внешнее сообщение на свой кошелек, затем этот кошелек разворачивает это внешнее сообщение и отправляет через внутреннее сообщение на кошелек Jetton этого пользователя со словами "Пожалуйста, отправьте деньги на определенный адрес". Затем Jetton wallet уменьшает их баланс на необходимую сумму и отправляет сообщение своему родственному контракту, который имеет точно такой же код, но другого владельца, и в этом сообщении говорится: "Пожалуйста, увеличьте свой баланс на ту же сумму.

🍞 Здесь используется проверка ДНК, потому что код Jettons одинаков.

Контракт с несколькими подписями.
📖 Концепция контракта с несколькими подписями иллюстрируется в контексте TON, где несколько сторон коллективно санкционируют действия, маркируя запросы, инициированные пользователем.

Вместо прямой обработки сообщений пользователи получают уникальные токены, инкапсулирующие их запросы и голоса. Эти токены, принадлежащие пользователям, упрощают отслеживание и предотвращают нарушение контракта злоумышленниками. Честные пользователи собирают голоса за токены временного запроса, и как только порог достигнут, контракт с несколькими подписями выполняет действие после проверки подлинности запроса. Эта токенизация упрощает процесс и фокусируется на временном состоянии системы, а не на долях или значениях отдельных контрактов.

Платежи по подписке.
👀 Начиная с версии 4 кошелька, кошельки поддерживают плагины, которые позволяют пользователям создавать платежи по подписке.

Это очень мощная функция, и она относительно хорошо масштабируется, потому что пользователь контролирует этот список плагинов. Но мы могли бы придумать лучший способ сделать это.

🔥 Классная идея заключается в том, что вместо перечисления адресов конкретных плагинов вы могли бы перечислить различные реализации кода этих плагинов. И если они используют один и тот же код, для любого количества подключаемых плагинов, которые у вас есть, будет только одна запись.
🤯4
Media is too big
VIEW IN TELEGRAM
3.1 Обзор жизненного цикла разработки смарт-контрактов
✔️ Так же смотрите видео на YouTube
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1🔥1🤯1👀1
3. Жизненный цикл разработки смарт-контрактов

💎 Добро пожаловать в 3-ю главу.
В этой главе в целом мы рассмотрим различные аспекты разработки смарт-контрактов. Мы начнем с разбивки всего цикла разработки смарт-контрактов. Это будет включать в себя создание локального проекта, который может скомпилировать образец смарт-контракта. Кроме того, мы рассмотрим написание функционального кода и обеспечение того, чтобы код нашего контракта функционировал должным образом. Мы углубимся в создание пользовательского сценария развертывания, чтобы получить более глубокое представление о лежащей в его основе логике. Наконец, мы сосредоточимся на повышении гибкости нашего процесса развертывания. Благодаря этим 6 урокам мы приобретем всесторонние знания и навыки в области разработки смарт-контрактов.
👨‍💻1
📝 Вы также узнаете о:

Какие этапы включает цикл разработки смарт-контрактов в TON?

Как называется язык программирования для смарт-контрактов в блокчейне TON?

Существуют ли какие-либо стандартные локальные среды настройки для написания, тестирования и развертывания смарт-контрактов?
📝 Теперь вы знаете:

Мы могли бы представить контракт TON как спутник, который запускается на орбиту Земли, и спутник летает вокруг Земли, взаимодействуя с другими спутниками, он способен принимать информацию с Земли, обрабатывать ее и отправлять некоторые результаты. Но прежде чем мы действительно запустим его в космос, он должен пройти несколько этапов.

На первом этапе мы готовим нашу локальную установку, которая позволит нам внедрять наш спутниковый смарт-контракт на всех последующих этапах.

Фактический смарт-контракт на блокчейне TON хранится и выполняется в виде двоичного кода.

На втором этапе мы описываем команды, которые способен обрабатывать наш смарт-контракт.

На третьем этапе мы просто пишем наш функциональный код.

На четвертом этапе мы тестируем поведение нашего функционального кода локально.

На пятом этапе мы развертываем наш контракт в testnet.

📚Конспекты лекций
Вступление к главе 3
В этой главе мы будем очень практичны и фактически разберем весь цикл разработки смарт-контрактов TON, пройдем каждый его шаг вместе, и конечным результатом будет готовая пользовательская локальная настройка для программирования smartcontract, написанный функциональный код контракта, тесты для нашего контракта фактически развернутый контракт.

Давайте сразу перейдем к делу.

TON smartcontract подобен спутнику.
Лучшая аллегория, которую я смог придумать, чтобы объяснить жизненный цикл смарт-контракта TON, заключается в следующем. Вы можете представить, что смарт-контракт TON - это спутник, который запускается на орбиту Земли.

Спутник летает вокруг Земли, взаимодействует с другими спутниками, способен принимать информацию с Земли, обрабатывать ее и отправлять некоторые результаты. Но прежде чем мы действительно запустим его в космос, он должен пройти несколько этапов.

Есть базовая лаборатория, где он собирается. [Локальная настройка]

Существует определенный документированный протокол, который определяет все возможные команды, которые могут быть обработаны нашим спутником. [Схема TLB]

Каждый из сценариев поведения тестируется, пока спутник еще находится в лаборатории. [Локальные тесты]

После этого он помещается в имитируемую среду, очень похожую на ту, что ожидает его в космосе, это позволяет еще раз протестировать ожидаемое поведение в ответ на предопределенные команды, а также взаимодействие с другими объектами в космосе и на Земле. [Тесты Testnet и onchain после развертывания]

Как мы доставим спутник на орбиту? С помощью ракет. Ракеты выводят спутник на орбиту и оставляют его там, отваливаясь после успешного завершения своей миссии. [Развертывание производства Mainnet]

С этого момента спутник находится сам по себе в Космосе, работая со всем, что у него запрограммировано внутри.

Объясняю, как жизненный цикл разработки смарт-контракта TON соотносится с примером sattelite.

Когда мы пишем смарт-контракт, мы используем аналогичный цикл.

1. Мы готовим нашу локальную настройку, которая позволит нам провести наш спутниковый / смарт-контракт через все этапы, упомянутые выше.

Первой ключевой частью нашей локальной настройки будет компилятор. Фактический смарт-контракт в блокчейне TON хранится и выполняется в виде двоичного кода. Но мы хотим запрограммировать логику с помощью чего-то понятного человеку, поэтому язык, который мы используем для программирования контракта TON, - это FunC.

Путь между FunC и байт-кодом следующий: FunC компилируется в ассемблерный код Fift, который генерирует соответствующий байт-код для виртуальной машины TON (пробел, если мы ссылаемся на наш вспомогательный пример).

Для нас суть ассемблерного кода Fift не очень важна, и мы будем рассматривать его просто как скрытое промежуточное состояние нашего кода smartcontract. Мы передаем это нашему компилятору.

Наш код -> FunC -> Fift -> BOC (байт-код)

Я полагаю, что часть, объясняющая, как хранится и выполняется байт-код, должна быть объяснена в главах 1 и 2 (дерево ячеек и т.д.)
🤯1
На данный момент лучшим и распространенным способом работы с компилятором является использование языка TypeScript. Машинопись не имеет ничего общего с TVM. Думайте об этом как о чем-то, что остается на Земле после запуска спутника.

Компилятор, который мы собираемся использовать, - это @ton-community/func-js. Внутренне этот пакет использует как компилятор FunC, так и интерпретатор Fift, объединенный в единую библиотеку, скомпилированную в WebAssembly (WASM).

2. Мы используем схему TL-B для описания команд, которые способен обрабатывать наш smartcontract

TL-B (язык типов - двоичный) служит для описания системы типов, конструкторов и существующих функций. Вот пример возможного документа TL-B:

альтернативный текст для чтения с экрана

Обычно мы пишем наш документ TL-B, как только некоторые базовые функции готовы, а затем продолжаем обновлять его до запуска контракта. Мы собираемся приблизиться к этому, как только напишем наш первый функциональный код.

3. Самая приятная часть. Мы пишем функциональный код.

FunC - это специфичный для предметной области, подобный C, статически типизированный язык. Вы уже видели некоторый функциональный код в первых двух главах. Однако в этом уроке мы собираемся на самом деле написать некоторую логику с помощью функционального кода.

4. Мы тестируем поведение нашего функционального кода локально.

Затем мы снова используем TypeScript для написания тестовой логики. На этом шаге мы локально моделируем TVM-машину, отправляем данные в смоделированный контракт, анализируем выходные данные и повторяем это до тех пор, пока не получим желаемые результаты. Как только все будет готово к развертыванию - мы пишем машинописный код, который фактически развернет наш смарт-контракт в блокчейне. Это наши ракеты.

5. Мы развертываем наш код в testnet.

Процесс развертывания смарт-контрактов в TON очень интересен. Что делает его таким интересным? Как вы могли видеть из предыдущих уроков - мы можем рассчитать адрес, который будет иметь смарт-контракт, еще до того, как мы развернем его в сети. Чтобы сделать это, все, что нам нужно, это знать начальное состояние данных для контракта и его фактический код. Как только мы узнаем адрес - мы отправляем сообщение с начальным состоянием и кодом на этот адрес. Это просто. Это может быть внутреннее сообщение или внешнее сообщение.

Таким образом, наш сценарий развертывания был бы таким же простым, как вычисление адреса и отправка сообщения с начальным состоянием и кодом на этот адрес.

Возможно, вы заметили, что при отправке сообщения нам приходится тратить реальные деньги, поэтому мы не хотим тратить много реальных денег при развертывании и тестировании нашего контракта в сети.

Вот почему у нас есть копия реальной сети, которую я использовал только в тестовых целях. Вы можете бесплатно получать монеты, которые действительны в этой сети, и мы называем эту сеть testnet. Прежде чем наш контракт будет полностью готов к производству - мы только развернем его в тестовой сети.

6. Запуск. Мы развертываем наш код в mainnet.

Развертывание smartcontract в основной сети практически такое же, как и в тестовой сети, но мы развертываем наш контракт, отправляя сообщения с реальными монетами TON, наши сообщения попадают в основной блокчейн, и наш контракт доступен для всех пользователей TON.

Это очень интересный процесс. Иногда трудно найти ответы, но я призываю вас не сдаваться, и на протяжении всей этой главы я позабочусь о том, чтобы у вас:
Была своя локальная "лаборатория" полного цикла для создания и "запуска" ваших смарт-контрактов
Вы поймете основы кодирования функционального контракта
Вы знаете, где искать ответы, если ваш контракт требует большего, чем мы рассмотрим в примерах
Существуют ли какие-либо стандартные локальные настройки (среды) для написания, тестирования и развертывания смарт-контрактов?
Это хороший вопрос. У TON быстро растущий набор инструментов программирования, и иногда нет смысла создавать свою собственную локальную установку. Стоит упомянуть замечательную программу, которая поддерживается командой TonTech и официально поддерживается TON Foundation - Blueprint.

Вы можете использовать его так же просто, как выполнить локальную команду:

npm create ton@latest
Это создаст для вас новый проект с кодом для всех этапов, описанных выше. Вы можете прочитать больше об этом в документации Bluprint.

В рамках этого курса мы по-прежнему собираемся создать пользовательский, чтобы вы могли глубже понять, как весь этот процесс работает под капотом.

Давайте сделаем это!
1
Media is too big
VIEW IN TELEGRAM
3.2 Настройка рабочего процесса компиляции
✔️ Так же смотрите видео на YouTube
Please open Telegram to view this post
VIEW IN TELEGRAM
2🔥1
📚Конспекты лекции
Цель этого урока - настроить локальный проект, способный скомпилировать образец смарт-контракта. Мы пока не собираемся писать никакой функционал. Мы просто готовим нашу лабораторию. Позже эта часть может быть повторно использована во всех ваших проектах.

Прежде всего, пожалуйста, убедитесь, что у вас есть три вещи:

Современная версия Node.js (версия 16.15.0 или более поздняя) Инструкции по установке можно найти здесь Запустите команду node -v в терминале, чтобы подтвердить вашу установку
Менеджер пакетов - вероятно, он уже установлен вместе с Node.js, но, пожалуйста, убедитесь, что он у вас есть. В этом уроке мы собираемся использовать Yarn, но вы можете использовать один на ваш выбор (например, npm)
Неплохая ИДЕЯ с поддержкой FunC и TypeScript, мы рекомендуем использовать Visual Studio Code с установленным плагином FunC. В случае, если вы используете IntelliJ, вот ссылка на соответствующий плагин FunC
Как только будут выполнены вышеупомянутые зависимости - мы готовы начать!
Настройка проекта.
Давайте сначала создадим папку для нашего проекта

mkdir my_first_contract && cd my_first_contract

Давайте запустим файл package.json с помощью менеджера пакетов. Я собираюсь использовать yarn, но если вам удобнее использовать npm - вы вольны выбирать.

yarn init

Вам будет предложено ввести ряд параметров, но не стесняйтесь просто нажимать кнопку ввода в каждом приглашении. После этого в каталоге нашего проекта должен быть файл package.json со следующим содержимым по умолчанию:

{
"name": "my_first_contract",
"version": "1.0.0",
"main": "index.js", //we don't really need this line, feel free to remove it
"license": "MIT"
}


Теперь давайте установим следующие 4 библиотеки:

Это библиотеки, связанные с TypeScript. В этом курсе мы не будем вдаваться в подробности того, как работает сам TypeScript.

yarn add typescript ts-node @types/node @swc/core --dev

Давайте создадим файл tsconfig.json в корне нашего проекта и поместим туда следующую конфигурацию:
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"ts-node": {
"transpileOnly": true,
"transpiler": "ts-node/transpilers/swc"
}
}


Еще три лаборатории, которые нам понадобятся, на самом деле связаны между собой, это:

ton-core - библиотека ядра, которая реализует низкоуровневые примитивы блокчейна.
ton-crypto - Криптографические примитивы для создания приложений для блокчейна TON.
@ton-community/func-js - компилятор функций TON.

yarn add ton-core ton-crypto @ton-community/func-js --dev

Пример FunC кода
Теперь мы собираемся создать файл с образцом минимального функционального кода, а затем написать скрипт, который бы его скомпилировал.

Давайте создадим папку contracts и один функциональный файл (main.fc) в ней:

mkdir contracts && cd contracts && touch main.fc

Откройте файл main.fc для редактирования и вставьте следующий пример функционального кода:

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {

}


Вы уже знаете, что смарт-контракт способен получать два вида сообщений, пожалуйста, обратитесь к главе 1, если вы не уверены, что это за сообщения.

Этого простого кода нам будет достаточно, чтобы написать скрипт компилятора. Давайте попробуем!
Пишем наш скрипт компиляции
Создаем папку scripts в корне нашего проекта и новый файл compile.ts в папке scripts:

mkdir scripts && cd scripts && touch compile.ts

У нас есть файл compile.ts, но давайте сделаем так, чтобы его было удобно запускать, как только мы закончим с его кодом. Давайте создадим ярлык скрипта в файле package.json. Просто добавьте следующий ключ scripts:

{
//...your previous package.json contents
"scripts": {
"compile": "ts-node ./scripts/compile.ts"
}
}


Теперь откройте файл scripts/compile.ts в вашем редакторе и давайте начнем писать наш скрипт компиляции. Мы собираемся добавлять код шаг за шагом вместе с пояснениями, так что к концу этого раздела у вас будет полный код для скрипта компиляции.

Прежде всего, мы импортируем:

fs для работы с файлами
process для управления процессом выполнения скрипта
Конструктор Cell (байт-код нашего контракта будет сохранен как Cell)
compileFunc - фактическая функция компиляции

import * as fs from "fs";
import process from "process";
import { Cell } from "ton-core";
import { compileFunc } from "@ton-community/func-js";


У нас будет некоторый асинхронный код, поэтому давайте создадим асинхронную функцию, которая будет запускать его внутри:

import * as fs from "fs";
import process from "process";
import { Cell } from "ton-core";
import { compileFunc } from "@ton-community/func-js";

async function compileScript() {

}

compileScript();


Теперь давайте передадим имя файла нашего образца контракта в функцию compile Func и завершим скрипт, если статус результата - ошибка:

import * as fs from "fs";
import process from "process";
import { Cell } from "ton-core";
import { compileFunc } from "@ton-community/func-js";

async function compileScript() {

const compileResult = await compileFunc({
targets: ["./contracts/main.fc"],
sources: (x) => fs.readFileSync(x).toString("utf8"),
});

if (compileResult.status === "error") {
process.exit(1);
}

}
compileScript();



Классно! Технически, вы уже могли бы перейти в корень нашего проекта и запустить команду yarn compile. Но если вы это сделаете - вы не сможете увидеть результаты компиляции. Давайте сохраним результаты нашей компиляции в папке build в корне нашего проекта (убедитесь, что вы сначала создали ее).

Результатом компиляции будет строка BOC (Body of Cell) base64, но если мы хотим в дальнейшем работать с этим скомпилированным контрактом локально (при написании тестов), нам нужно создать ячейку, которая будет содержать этот BOC, и сохранить ее для последующего использования.

У конструктора ячейки есть метод .fromBoc, который ожидает получения буфера, поэтому мы предоставим ему буфер из строки BOC base64. Как только мы создадим ячейку из BOC - мы хотим создать шестнадцатеричное представление этой ячейки и сохранить его в файле JSON. Вот как выглядит команда построения и преобразования ячейки:

Cell.fromBoc(Buffer.from(compileResult.codeBoc, "base64"))[0].toBoc() .toString("hex")

А теперь давайте поместим это в наш скрипт, а также используем fs.writeFileSync для сохранения результатов в наш JSON-файл:

import * as fs from "fs";
import process from "process";
import { Cell } from "ton-core";
import { compileFunc } from "@ton-community/func-js";

async function compileScript() {

const compileResult = await compileFunc({
targets: ["./contracts/main.fc"],
sources: (x) => fs.readFileSync(x).toString("utf8"),
});

if (compileResult.status === "error") {
process.exit(1);
}

const hexArtifact = `build/main.compiled.json`;

fs.writeFileSync(
hexArtifact,
JSON.stringify({
hex: Cell.fromBoc(Buffer.from(compileResult.codeBoc, "base64"))[0]
.toBoc()
.toString("hex"),
})
);
}
compileScript();


Этот код готов к запуску, но давайте внесем некоторые окончательные правки, чтобы сделать наш скрипт информативным во время его выполнения. Мы собираемся добавлять красивые консольные журналы на каждом шаге.

Вот окончательный код вместе с консольными журналами:
import * as fs from "fs";
import process from "process";
import { Cell } from "ton-core";
import { compileFunc } from "@ton-community/func-js";

async function compileScript() {
console.log(
"================================================================="
);
console.log(
"Compile script is running, let's find some FunC code to compile..."
);

const compileResult = await compileFunc({
targets: ["./contracts/main.fc"],
sources: (x) => fs.readFileSync(x).toString("utf8"),
});

if (compileResult.status === "error") {
console.log(" - OH NO! Compilation Errors! The compiler output was:");
console.log(`\n${compileResult.message}`);
process.exit(1);
}

console.log(" - Compilation successful!");

const hexArtifact = `build/main.compiled.json`;

fs.writeFileSync(
hexArtifact,
JSON.stringify({
hex: Cell.fromBoc(Buffer.from(compileResult.codeBoc, "base64"))[0]
.toBoc()
.toString("hex"),
})
);

console.log(" - Compiled code saved to " + hexArtifact);
}
compileScript();


Это наш скрипт компиляции, который мы будем запускать каждый раз, когда будем вносить какие-либо изменения в наш код. Позже, когда у нас будет некоторая тестируемая функциональная логика и сценарии развертывания - мы сможем автоматизировать запуск этого скрипта перед любыми тестами или развертыванием.

Наша лаборатория "satellite assembly" оснащается все больше и больше!

Но давайте еще раз запустим yarn compile и посмотрим, каковы результаты нашей компиляции. Для этого давайте откроем наш основной файл.compiled.fc.

Что мы там видим:

{"hex":"b5ee9c72410102010012000114ff00f4a413f4bcf2c80b010006d35f03fbffbf07"}

Звучит безумно, но это наш контракт. На следующем уроке мы собираемся написать нашу первую функциональную логику и скомпилировать ее с помощью только что написанного сценария.
🔥1
Media is too big
VIEW IN TELEGRAM
3.3 Написание простого функционального контракта
✔️ Так же смотрите видео на YouTube
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👨‍💻1
📚Конспекты лекции
На предыдущем уроке мы создали скрипт компиляции, и теперь пришло время приступить к написанию функционального кода. Знаете, что круто? Теперь мы готовы проверить, работает ли наш функциональный код на самом деле. Как только мы напишем какой-нибудь навороченный код - мы просто запустим yarn compile и убедимся, что наш код может работать на TVM.

Смарт-контракт в этом уроке будет очень-очень простым, но нам этого будет достаточно, чтобы ознакомиться с базовыми функциями sintax и структурой.
Параметры, которые мы получаем с помощью внутреннего обработчика сообщений recv_internal
Давайте разберемся с кодом. В предыдущем уроке мы уже создали файл contracts/main.fc. Давайте откроем его и посмотрим, что у нас там есть:

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {

}


Что у нас уже есть, так это функция, которая обрабатывает входящие сообщения. Как вы уже знаете из главы 1, любые транзакции в TON называются messages.

Итак, что нам приносит сообщение? Что мы уже можем видеть, так это три параметра, которые передаются в функцию recv_internal:

msg_value - этот параметр сообщает нам, сколько тонн монет (или граммов) получено с этим сообщением
in_msg - это полное сообщение, которое мы получили, со всей информацией о том, кто его отправил и т.д. Мы видим, что у него есть тип Cell. Что это значит? Тело сообщения хранится в виде ячейки в TVM, поэтому для нашего сообщения выделена целая ячейка со всеми его данными.
in_msg_body - это фактическая "читаемая" часть сообщения, которое мы получили. Он имеет тип slice, поскольку он является частью ячейки, он указывает "адрес", с какой части ячейки мы должны начать чтение, если мы хотим прочитать этот параметр slice.
TODO: Подробнее о содержимом ячейки in_msg.

Как вы можете видеть, и msg_value, и in_msg_body являются производными от in_msg, но для удобства использования мы получаем их в качестве параметров в функцию receive_internal.

Спецификаторы функций
Я уверен, что вы заметили слово impure сразу после параметров, переданных в функцию. Это один из 3 возможных спецификаторов функций:

impure
inline/inline_ref
method_id

Один, несколько или ни один из них не может быть помещен в объявление функции, но в настоящее время они должны быть представлены в правильном порядке. Например, не разрешается помещать impure после inline.

На данный момент нас интересует только спецификатор impure, но мы собираемся рассмотреть остальные, как только они начнут появляться в нашем коде.

impure спецификатор означает, что функция может иметь некоторые побочные эффекты, которые нельзя игнорировать. Например, мы должны указать нечистый спецификатор, если функция может изменять хранилище контрактов, отправлять сообщения или генерировать исключение, когда некоторые данные недействительны, и функция предназначена для проверки этих данных.

Если значение impure не указано и результат вызова функции не используется, то компилятор FunC может удалить и удалит этот вызов функции.

Importing stdlib.fc

Чтобы манипулировать данными и писать другую логику в нашем контракте, нам нужно сделать еще одну важную вещь. Нам нужно импортировать стандартную библиотеку FunC. В настоящее время эта библиотека является просто оболочкой для наиболее распространенного ассемблера команд TVM, которые не являются встроенными. Описание каждой команды TVM, используемой в библиотеке, можно найти в документации.

Чтобы импортировать stdlib.fc, давайте создадим папку imports внутри нашей папки contracts. Затем создайте файл stdlib.fc и заполните его содержимым официальной библиотеки stdlib.fc, которую вы можете получить здесь.

Теперь, в самом начале нашего main.fc нам нужно вставить фактический импорт:

#include "imports/stdlib.fc";

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {

}


Отлично, теперь мы готовы идти!
Разбор in_msg
Давайте, наконец, узнаем, что мы можем сделать с параметрами, переданными нашей функции recv_internal.

Всякий раз, когда мы хотим обработать внутреннее сообщение, прежде чем мы даже начнем читать содержательную часть in_msg_body, нам нужно сначала понять, какого рода внутреннее сообщение мы получили. Случаи могут быть разными. Например, мы могли бы получить это сообщение, потому что наш контракт ранее отправил кому-то какое-то сообщение, и принимающая сторона не смогла его принять, поэтому оно "отскочило" обратно. Иногда мы не хотим обрабатывать сообщения такого рода. Мы собираемся поговорить о таких сценариях позже в этом курсе.

В каждом сообщении, которое мы получаем, есть флаги. Флаги - это, по сути, 4-разрядное целое число, где каждый бит ... (TODO: Elaborate on structure of flags. int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool)

Эти флаги на самом деле сообщат нам ценную информацию о сообщении, такую как "Это сообщение было отскочено получателем, и теперь оно фактически возвращается обратно".

Итак, первое, что должен сделать наш контракт при получении внутреннего сообщения - проанализировать его. Давайте проанализируем in_msg:

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
slice cs = in_msg.begin_parse();
int flags = cs~load_uint(4);
}


Давайте разберем, что именно происходит в этом коде:

Вам нужно будет запомнить эту концепцию, этот фрагмент - это "address", указатель. Итак, когда мы анализируем - мы анализируем, начиная с некоторого места. В этом случае функция begin_parse() сообщает нам, с чего мы должны начать синтаксический анализ, она дает нам указатель на самый первый бит ячейки in_msg.

Затем мы анализируем 4-битное целое число, вызывая load_uint(4), и присваиваем результат переменной int flags.

Как только мы собираемся вызвать еще несколько ~load_{*} для переменной cs, мы фактически продолжим синтаксический анализ с того места, где завершился предыдущий ~load_{*}.

В случае, если мы попытаемся проанализировать что-то, чего на самом деле не существует в ячейке - наш контракт завершится с кодом 9. Вы можете прочитать больше о стандартных ошибках кода здесь

В ячейке in_msg есть еще кое-какая ценная информация, а именно - адрес отправителя, поэтому давайте продолжим разбор:

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
slice cs = in_msg.begin_parse();
int flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();
}


Когда мы хотим создать переменную, хранящую адрес, мы всегда используем тип slice, поэтому мы сохраняем pointer только оттуда, откуда память должна считывать адрес, как только это потребуется.

Что мы собираемся делать с переменными, которые у нас уже есть? Позвольте мне сначала познакомить вас с еще двумя возможностями смарт-контракта:

Наш смарт-контракт имеет постоянное хранилище (называемое хранилищем c4)
Наш смарт-контракт может иметь метод получения, который позволит любому человеку из-за пределов мира получить некоторые данные из нашего контракта.
Используя эти две новые возможности, мы можем сделать следующее. Мы можем сохранить адрес отправителя в нашем хранилище и создать метод получения, который будет возвращаться при его вызове.

Другими словами, наш получатель всегда возвращал бы адрес контракта, который отправил сообщение нашему контракту последним.
Использование постоянного хранилища
Чтобы сохранить те же данные в нашем постоянном хранилище c4, мы собираемся использовать необычную стандартную функцию set_data. Эта функция принимает и сохраняет ячейку.

Если мы хотим сохранить больше данных, чем помещается в ячейку, мы можем легко записать "ссылку" на другую ячейку внутри первой. Такая ссылка называется ref. Мы можем записать до 4 refs ссылок в ячейку.

Давайте обновим наш код функцией set_data и узнаем, как мы передаем в нее ячейку.

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
slice cs = in_msg.begin_parse();
int flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();

set_data(begin_cell().end_cell());
}


Чтобы передать ячейку в set_data, нам нужно сначала создать ее. Это легко делается с помощью двух функций begin_cell() и end_cell().

На данный момент мы передаем пустую ячейку, технически это нормально, но мы хотим записать адрес отправителя сообщения в хранилище, поэтому мы должны обновить им нашу ячейку:

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
slice cs = in_msg.begin_parse();
int flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();

set_data(begin_cell().store_slice(sender_address).end_cell());
}


Для этой цели мы используем method .store_slice().

Вуаля, у нас есть смарт-контракт, который способен записывать адрес отправителя в постоянное хранилище! Каждый раз, когда наш контракт будет получать внутреннее сообщение, он будет заменять ячейку, хранящуюся в c4, новой ячейкой, в которой будет новый адрес отправителя. Все очень просто.
👍1
Используя методы получения
Как вы помните, мы не хотели просто сохранять адрес отправителя. Мы хотели, чтобы любой мог прочитать последний адрес отправителя. Чтобы получить доступ к таким данным из-за пределов TVM, в нашем контракте должна быть специальная функция.

Недавно мы говорили о спецификаторах функций. Чтобы сделать наши данные доступными за пределами TVM, мы собираемся создать функцию и использовать указанный method_id. Если у функции задан этот спецификатор - тогда ее можно вызвать в lite-клиенте orton-explorer как get-метод по ее имени.

Давайте создадим один:

slice get_the_latest_sender() method_id {

}


Функции-получатели размещаются вне функции recv_internal

Как вы можете видеть, мы определяем, какое время должно быть возвращено этой функцией, имя функции и спецификатор method_id.

Теперь давайте напишем логику считывания данных из постоянного хранилища и возврата их значения. Для этого мы используем стандартную функцию FunC get_data:

slice get_the_latest_sender() method_id {
slice ds = get_data().begin_parse();
return ds~load_msg_addr();
}


Как вы можете видеть, мы снова используем begin_parse(), чтобы получить указатель, из которого мы собираемся проанализировать ячейку, хранящуюся в хранилище c4.

Чтобы загрузить сохраненный адрес, мы используем ~load_msg_addr для загрузки адреса.