Telegram Open Network (TON): Инструкция по компиляции и созданию простого смарт-контракта

Данная статья является результатом перевода официальной документации – https://github.com/ton-blockchain/docs/blob/master/docs/howto/step-by-step.md

Адрес смарт-контрактов

Адреса смарт-контрактов в сети TON состоят из двух частей:

  • идентификатор рабочей цепочки (32-битное целое число со знаком) и
  • адрес внутри рабочей цепочки (64-512 бит в зависимости от рабочей цепочки).

В настоящее время в сети блокчейн TON работает только мастерчейн (workchain_id=-1), а иногда и базовый воркчейн (workchain_id=0). Оба они имеют 256-битные адреса, поэтому в дальнейшем мы предполагаем, что workchain_id равен либо 0, либо -1, и что адрес внутри рабочей цепочки точно 256-битный.

При указанных выше условиях адрес смарт-контракта может быть представлен в следующих формах:

  • «Необработанный»: <десятичный workchain_id>: <64 шестнадцатеричных цифры с адресом>
  • «Удобный для пользователя», который получается путем генерации из необработанного:
    • один байт тега (0x11 для “bounceable” адресов, 0x51 для “non-bounceable” адресов, не допускающих возврат; также добавляется +0x80, если адрес не должен приниматься программой запущеной в продакшен сети)
    • один байт, содержащий 8-битное целое число со знаком с workchain_id (0x00 для базовой рабочей цепочки, 0xff для основной цепочки)
    • 32 байта, содержащие 256 бит адреса смарт-контракта внутри рабочей цепочки (обратный порядок байтов)
    • 2 байта, содержащие CRC16-CCITT предыдущих 34 байтов«Удобный для пользователя», который получается путем предварительного создания:

В случае с вариантом «Удобный для пользователя» – полученные таким образом 36 байт затем кодируются с использованием base64 (т.е. с цифрами, прописными и строчными латинскими буквами, ‘/’ и ‘+’) или base64url (с ‘_’ и ‘-‘ вместо ‘ /’ и ‘+’), что дает 48 печатных символов без пробелов.

Пример:

«test giver» (специальный смарт-контракт, находящийся в мастерчейне тестовой сети, который дает до 20 тестовых монет любому, кто попросит) имеет адрес:

# в «необработанном» виде (обратите внимание, что вместо «a»...«f» можно использовать заглавные латинские буквы «A»...«F»):
-1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260

# и в "Удобный для пользователя" виде (для отображения клиентам):
kf/8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny (base64)
kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny (base64url)

Обратите внимание, что обе формы (base64 и base64url) допустимы и должны быть приняты.

Между прочим, другие двоичные данные, связанные с блокчейном TON, имеют аналогичные «armored» представления base64, отличающиеся своими первыми байтами.

Например, вездесущие 256-битные открытые ключи Ed25519 представлены сначала созданием 36-байтовой последовательности следующим образом:

  • один байт тега 0x3E, означающий, что это открытый ключ
  • один байт тега 0xE6, что означает, что это открытый ключ Ed25519
  • 32 байта, содержащие стандартное двоичное представление открытого ключа Ed25519.
  • 2 байта, содержащие представление CRC16-CCITT с обратным порядком байтов предыдущих 34 байтов.

Полученная 36-байтовая последовательность преобразуется в 48-символьную строку base64 или base64url стандартным способом.

Например, Ed25519 открытый ключ E39ECDA0A7B0C60A7107EC43967829DBE8BC356A49B9DFC6186B3EAC74B5477D (обычно представляемый последовательностью из 32 байтов 0xE3, 0x9E, ..., 0x7D) имеет следующее «armored» представление:

Pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2.

Проверка состояния смарт-контракта

Проверить состояние смарт-контрактов с помощью клиента TON Lite очень просто.

Для примера смарт-контракта, описанного выше, вы должны запустить Lite Client и ввести следующие команды:

> last
...
> getaccount -1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260
или
> getaccount kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny

В итоге, вы увидите что-то вроде этого:

got account state for -1 : FCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260 with respect to blocks (-1,8000000000000000,2075):BFE876CE2085274FEDAF1BD80F3ACE50F42B5A027DF230AD66DCED1F09FB39A7:522C027A721FABCB32574E3A809ABFBEE6A71DE929C1FA2B1CD0DDECF3056505
account state is (account
  addr:(addr_std
    anycast:nothing workchain_id:-1 address:xFCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260)
  storage_stat:(storage_info
    used:(storage_used
      cells:(var_uint len:1 value:3)
      bits:(var_uint len:2 value:707)
      public_cells:(var_uint len:0 value:0)) last_paid:1568899526
    due_payment:nothing)
  storage:(account_storage last_trans_lt:2310000003
    balance:(currencies
      grams:(nanograms
        amount:(var_uint len:6 value:9998859500889))
      other:(extra_currencies
        dict:hme_empty))
    state:(account_active
      (
        split_depth:nothing
        special:nothing
        code:(just
          value:(raw@^Cell 
            x{}
             x{FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F260D31F01ED44D0D31FD166BAF2A1F8000120D74A8E11D307D459821804A817C80073FB0201FB00DED1A4C8CB1FC9ED54}
            ))
        data:(just
          value:(raw@^Cell 
            x{}
             x{00009A15}
            ))
        library:hme_empty))))
x{CFFFCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB2923226020680B0C2EC1C0E300000000226BF360D8246029DFF56534_}
 x{FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F260D31F01ED44D0D31FD166BAF2A1F8000120D74A8E11D307D459821804A817C80073FB0201FB00DED1A4C8CB1FC9ED54}
 x{00000003}
last transaction lt = 2310000001 hash = 73F89C6F8910F598AD84504A777E5945C798AC8C847FF861C090109665EAC6BA

Первая строка got account state… for… показывает адрес учетной записи и идентификатор блока мастерчейн, относительно которого был запрошен статус учетной записи. Обратите внимание, что даже если состояние учетной записи изменится в последующем блоке, команда getaccount xxx будет возвращать тот же результат до тех пор, пока эталонный блок не будет обновлен командой last до более нового значения. Таким образом можно изучить состояние всех счетов и получить согласованные результаты.

Строка account state is (account … печатает в отформатированном виде десериализованное представление состояния учетной записи. Это десериализация учетной записи типа данных TL-B, используемая для представления состояний учетной записи в блокчейне TON, как описано в документации TON Blockchain. (Вы можете найти схему TL-B, используемую для десериализации, в исходном файле crypto/block/block.tlb; обратите внимание, что если схема устарела, десериализация возможно больше не используется.)

Наконец, последние несколько строк, начинающиеся с x{CFF… (“raw dump“), содержат ту же информацию, которая отображается в виде дерева ячеек. В этом случае у нас есть одна корневая ячейка, содержащая биты данных CFF…34_ (подчеркивание означает, что последняя двоичная единица и все последующие двоичные нули должны быть удалены, поэтому шестнадцатеричное 4_ соответствует двоичному 0), и две ячейки, которые являются его дочерними элементами (отображаются с отступом в один пробел).

Мы видим, что x{FF0020DD20…} — это код этого смарт-контракта. Если мы обратимся к Приложению A документации по виртуальной машине TON, мы сможем даже разобрать этот код: FF00 — это SETCP 0, 20 — это DUP, DD — это IFNOTRET, 20 — это DUP и так далее. (Кстати, вы можете найти исходный код этого смарт-контракта в исходном файле crypto/block/new-testgiver.fif)

Мы также видим, что x{00009A15} (фактическое значение, которое вы видите, может отличаться) является постоянными данными этого смарт-контракта. На самом деле это 32-битное целое число без знака, используемое смарт-контрактом в качестве счетчика операций, выполненных до сих пор. Обратите внимание, что это значение имеет обратный порядок байтов (т. е. 3 закодировано как x{00000003}, а не как x{03000000}), как и все целые числа в блокчейне TON. В этом случае счетчик равен 0x9A15 = 39445.

Текущий баланс смарт-контракта легко увидеть в красиво напечатанной части вывода. В этом случае мы видим … balance:(currencies:(grams:(nanograms:(… value:1000000000000000…)))), который является балансом счета в (тестовых) нанотоннах (миллион проверьте монеты TON в этом примере; фактическое количество, которое вы видите, может быть меньше). Если вы изучите схему TL-B, представленную в crypto/block/scheme.tlb, вы сможете найти это число (10^15) в двоичной форме с обратным порядком байтов и в части необработанного дампа (оно расположено рядом с конец битов данных корневой ячейки).

Компиляция нового смарт-контракта

Перед загрузкой нового смарт-контракта в блокчейн TON вам необходимо определить его код и данные и сохранить их в сериализованной форме в файл (называемый «bag-of-cells» или BOC-файл, обычно с суффиксом .boc).

Рассмотрим случай простого смарт-контракта кошелька, который хранит в своих постоянных данных 32-битный счетчик операций и 256-битный открытый ключ его владельца Ed25519.

Очевидно, вам понадобятся некоторые инструменты для разработки смарт-контрактов, а именно компилятор смарт-контрактов TON. По сути, компилятор смарт-контракта TON — это программа, которая считывает исходный код смарт-контракта на специализированном языке программирования высокого уровня и создает из этого источника файл .boc.

Одним из таких инструментов является интерпретатор Fift, который включен в этот дистрибутив и может помочь в создании простых смарт-контрактов. Смарт-контракты большего размера следует разрабатывать с использованием более сложных инструментов (таких как компилятор FunC, включенный в этот дистрибутив, который создает файлы ассемблера Fift из исходных файлов FunC; вы можете найти некоторые исходники смарт-контрактов FunC в каталоге crypto/smartcont). Тем не менее, Fift достаточно для демонстрационных целей.

Рассмотрим файл new-wallet.fif (обычно расположенный как crypto/smartcont/new-wallet.fif относительно исходного каталога), содержащий исходный код простого смарт-контракта кошелька:

#!/usr/bin/env fift -s
"TonUtil.fif" include
"Asm.fif" include

{ ."usage: " @' $0 type ." <workchain-id> [<filename-base>]" cr
  ."Creates a new wallet in specified workchain, with private key saved to or loaded from <filename-base>.pk" cr
  ."('new-wallet.pk' by default)" cr 1 halt
} : usage
$# 1- -2 and ' usage if

$1 parse-workchain-id =: wc    // set workchain id from command line argument
def? $2 { @' $2 } { "new-wallet" } cond constant file-base

."Creating new wallet in workchain " wc . cr

// Create new simple wallet
<{ SETCP0 DUP IFNOTRET // return if recv_internal
   DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
     DROP c4 PUSHCTR CTOS 32 PLDU  // cnt
   }>
   INC 32 THROWIF  // fail unless recv_external
   512 INT LDSLICEX DUP 32 PLDU   // sign cs cnt
   c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS  // sign cs cnt cnt' pubk
   s1 s2 XCPU            // sign cs cnt pubk cnt' cnt
   EQUAL 33 THROWIFNOT   // ( seqno mismatch? )
   s2 PUSH HASHSU        // sign cs cnt pubk hash
   s0 s4 s4 XC2PU        // pubk cs cnt hash sign pubk
   CHKSIGNU              // pubk cs cnt ?
   34 THROWIFNOT         // signature mismatch
   ACCEPT
   SWAP 32 LDU NIP 
   DUP SREFS IF:<{
     // 3 INT 35 LSHIFT# 3 INT RAWRESERVE    // reserve all but 103 coins from the balance
     8 LDU LDREF         // pubk cnt mode msg cs
     s0 s2 XCHG SENDRAWMSG  // pubk cnt cs ; ( message sent )
   }>
   ENDS
   INC NEWC 32 STU 256 STU ENDC c4 POPCTR
}>c // >libref
// code
<b 0 32 u, 
   file-base +".pk" load-generate-keypair
   constant wallet_pk
   B, 
b> // data
null // no libraries
// Libs{ x{ABACABADABACABA} drop x{AAAA} s>c public_lib x{1234} x{5678} |_ s>c public_lib }Libs
<b b{0011} s, 3 roll ref, rot ref, swap dict, b>  // create StateInit
dup ."StateInit: " <s csr. cr
dup hash wc swap 2dup 2constant wallet_addr
."new wallet address = " 2dup .addr cr
2dup file-base +".addr" save-address-verbose
."Non-bounceable address (for init): " 2dup 7 .Addr cr
."Bounceable address (for later access): " 6 .Addr cr
<b 0 32 u, b>
dup ."signing message: " <s csr. cr
dup hash wallet_pk ed25519_sign_uint rot
<b b{1000100} s, wallet_addr addr, b{000010} s, swap <s s, b{0} s, swap B, swap <s s, b>
dup ."External message for initialization is " <s csr. cr
2 boc+>B dup Bx. cr
file-base +"-query.boc" tuck B>file
."(Saved wallet creating query to file " type .")" cr

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

Теперь, при условии, что вы скомпилировали двоичный файл Fift (обычно расположенный как crypto/fift по отношению к каталогу сборки), вы можете запустить:

$ crypto/fift -I<source-directory>/crypto/fift/lib -s <source-directory>/crypto/smartcont/new-wallet.fif 0 my_wallet_name

где:

  • 0 — это workchain, содержащий новый кошелек (0 = basechain, -1 = masterchain),
  • my_wallet_name — любой идентификатор, который вы хотите связать с этим кошельком. Адрес нового кошелька будет сохранен в файле my_wallet_name.addr, его вновь сгенерированный приватный ключ будет сохранен в my_wallet_name.pk (если этот файл еще не существует; вместо этого ключ будет загружен из этого файла), а внешний сообщение будет сохранено в my_wallet_name-query.boc. Если вы не укажете имя своего кошелька (my_wallet_name в примере выше), используется имя по умолчанию new-wallet.

Вы можете выбрать для переменной среды FIFTPATH ​​указать значение <source-directory>/crypto/fift/lib:<source-directory>/crypto/smartcont каталога, которое содержит файлы библиотек Fift.fif и Asm.fif, а также примеры простейших смарт-контрактов; тогда вы можете не испольовать аргумент -I для интерпретатора Fift. Если вы устанавливаете crypto/fift в каталог, который включенный в ваш PATH (например, /usr/bin/fift), вы можете просто вызвать: $ fift -s new-wallet.fif 0 my_wallet_name вместо указания полных путей поиска в командной строке.

Если все заработало, вы увидите что-то вроде следующего:

Creating new wallet in workchain 0 
Saved new private key to file my_wallet_name.pk
StateInit: x{34_}
 x{FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
 x{00000000C59DC52962CC568AC5E72735EABB025C5BDF457D029AEEA6C2FFA5EB2A945446}

new wallet address = 0:2ee9b4fd4f077c9b223280c35763df9edab0b41ac20d36f4009677df95c3afe2 
(Saving address to file my_wallet_name.addr)
Non-bounceable address (for init): 0QAu6bT9Twd8myIygMNXY9-e2rC0GsINNvQAlnfflcOv4uVb
Bounceable address (for later access): kQAu6bT9Twd8myIygMNXY9-e2rC0GsINNvQAlnfflcOv4rie
signing message: x{00000000}

External message for initialization is x{88005DD369FA9E0EF93644650186AEC7BF3DB5616835841A6DE8012CEFBF2B875FC41190260D403E40B2EE8BEB2855D0F4447679D9B9519BE64BE421166ABA2C66BEAAAF4EBAF8E162886430243216DDA10FCE68C07B6D7DDAA3E372478D711E3E1041C00000001_}
 x{FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
 x{00000000C59DC52962CC568AC5E72735EABB025C5BDF457D029AEEA6C2FFA5EB2A945446}

B5EE9C724104030100000000E50002CF88005DD369FA9E0EF93644650186AEC7BF3DB5616835841A6DE8012CEFBF2B875FC41190260D403E40B2EE8BEB2855D0F4447679D9B9519BE64BE421166ABA2C66BEAAAF4EBAF8E162886430243216DDA10FCE68C07B6D7DDAA3E372478D711E3E1041C000000010010200A2FF0020DD2082014C97BA9730ED44D0D70B1FE0A4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54004800000000C59DC52962CC568AC5E72735EABB025C5BDF457D029AEEA6C2FFA5EB2A945446BCF59C17
(Saved wallet creating query to file my_wallet_name-query.boc)

В двух словах, – ассемблер Fift (подключаемый в коде как "Asm.fif" include) используется для компиляции исходного кода смарт-контракта (содержащегося в строках <{SETCP0 … c4 POPCTR }>) во внутреннее представление. Также создаются исходные данные смарт-контракта (строками <b 0 32 u, … b>), содержащие 32-битный порядковый номер (равный нулю) и 256-битный открытый ключ из вновь сгенерированной Ed25519 пары ключей. Соответствующий закрытый ключ сохраняется в файле my_wallet_name.pk, если он еще не существует (если вы дважды запустите этот код в одном и том же каталоге, закрытый ключ будет загружен из этого файла).

Код и данные для нового смарт-контракта объединяются в структуру StateInit (в следующих строках), вычисляется и выводится адрес нового смарт-контракта (равный хешу этой структуры StateInit), а затем создается внешнее сообщение (external message) с адресом назначения, равный адресу нового смарт-контракта. Это external message содержит как правильный StateInit для нового смарт-контракта, так и нетривиальную полезную нагрузку (подписанную правильным закрытым ключом).

Наконец, external message сериализуется в пакет ячеек (представленный B5EE…BE63) и сохраняется в файл my_wallet_name-query.boc. По сути, этот файл представляет собой ваш скомпилированный смарт-контракт со всей дополнительной информацией, необходимой для его загрузки в блокчейн TON.

Перевод средств на новый смарт-контракт

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

> sendfile new-wallet-query.boc
или
> sendfile my_wallet_name-query.boc

К сожалению, это не сработает, потому что смарт-контракты должны иметь положительный баланс, чтобы иметь возможность оплачивать хранение и обработку своих данных в блокчейне. Поэтому сначала вам нужно перевести часть средств на новый адрес смарт-контракта, отображаемый при его генерации как -1:60c0…c0d0 (в необработанном виде) и 0f9..EKD (в удобном для пользователя виде).

В реальном сценарии вы либо перевели бы несколько монет TON из своего уже существующего кошелька, либо попросили бы сделать это друга, либо купили бы несколько монет TON на бирже криптовалют, указав 0f9…EKD в качестве учетной записи для перевода новых монет TON.

В тестовой сети у вас есть еще один вариант: вы можете попросить «test giver» дать вам несколько тестовых монет TON (до 20).

Объясняю, как это сделать.

Использование смарт-контракта “test giver”

Вам нужно знать адрес смарт-контракта «test giver». Предположим, что это -1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260 или, что то же самое kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny, как показано в одном из предыдущих примеров.

Вы проверяете состояние этого смарт-контракта в Lite Client, набрав:

> last
> getaccount kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny

как объяснено выше, – единственное число, которое вам нужно знать из возвращаемых данных, – это 32-битный порядковый номер, хранящийся в данных смарт-контракта (в приведенном выше примере это 0x9A15, но у вас он будет свой). Более простой способ получить текущее значение этого порядкового номера — ввести:

> last
> runmethod kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny seqno

выдает правильное значение 39445 = 0x9A15:

got account state for -1 : FCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260 with respect to blocks (-1,8000000000000000,2240):18E6DA7707191E76C71EABBC5277650666B7E2CFA2AEF2CE607EAFE8657A3820:4EFA2540C5D1E4A1BA2B529EE0B65415DF46BFFBD27A8EB74C4C0E17770D03B1
creating VM
starting VM to run method `seqno` (85143) of smart contract -1:FCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260
...
arguments:  [ 85143 ] 
result:  [ 39445 ]

Затем вы создаете внешнее сообщение (external message) для отправителя теста с просьбой отправить другое сообщение на ваш (неинициализированный) смарт-контракт, содержащий указанное количество тестовых монет TON. Существует специальный скрипт Fift для генерации этого внешнего сообщения, расположенный по адресу crypto/smartcont/testgiver.fif:

#!/usr/bin/env fift -s
"TonUtil.fif" include

{ ."usage: " @' $0 type ." <dest-addr> <seqno> <amount> [<savefile>]" cr
  ."Creates a request to TestGiver and saves it into <savefile>.boc" cr
  ."('testgiver-query.boc' by default)" cr 1 halt
} : usage

$# 3 - -2 and ' usage if

// "testgiver.addr" load-address 
Masterchain 0xfcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260
2constant giver_addr
 ."Test giver address = " giver_addr 2dup .addr cr 6 .Addr cr

$1 true parse-load-address =: bounce 2=: dest_addr
$2 parse-int =: seqno
$3 $>GR =: amount
def? $4 { @' $4 } { "testgiver-query" } cond constant savefile

."Requesting " amount .GR ."to account "
dest_addr 2dup bounce 7 + .Addr ." = " .addr
."seqno=0x" seqno x. ."bounce=" bounce . cr

// create a message (NB: 01b00.., b = bounce)
<b b{01} s, bounce 1 i, b{000100} s, dest_addr addr, 
   amount Gram, 0 9 64 32 + + 1+ 1+ u, "GIFT" $, b>
<b seqno 32 u, 1 8 u, swap ref, b>
dup ."enveloping message: " <s csr. cr
<b b{1000100} s, giver_addr addr, 0 Gram, b{00} s,
   swap <s s, b>
dup ."resulting external message: " <s csr. cr
2 boc+>B dup Bx. cr
savefile +".boc" tuck B>file
."(Saved to file " type .")" cr

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

$ crypto/fift -I<include-path> -s <path-to-testgiver-fif> <dest-addr> <testgiver-seqno> <coins-amount> [<savefile>]

# Для примера:
$ crypto/fift -I<source-directory>/crypto/fift/lib:<source-directory>/crypto/smartcont -s testgiver.fif 0QAu6bT9Twd8myIygMNXY9-e2rC0GsINNvQAlnfflcOv4uVb 0x9A15 6.666 wallet-query
# или
$ fift -s testgiver.fif 0QAu6bT9Twd8myIygMNXY9-e2rC0GsINNvQAlnfflcOv4uVb 0x9A15 6.666 wallet-query

при условии, что вы установили переменную среды FIFTPATH ​​в <source-directory>/crypto/fift/lib:<source-directory>/crypto/smartcont и установили двоичный файл fift как /usr/bin/fift (или указав какое-то другое расположение согласно вашему PATH).

У вновь созданного сообщения для нового смарт-контракта должен быть сброшен бит возврата, иначе перевод будет «bounced» (отправлен) его отправителю. По этой причине мы передали адрес 0QAu6bT9Twd8myIygMNXY9-e2rC0GsINNvQAlnfflcOv4uVb нашего нового смарт-контракта кошелька.

Этот код Fift создает внутреннее сообщение от смарт-контракта тестового поставщика на адрес нашего нового смарт-контракта, содержащего 6,666 тестовых монет TON (вы можете ввести здесь любую другую сумму, приблизительно до 20 монет TON). Затем это сообщение конвертируется во внешнее сообщение, адресованное тестирующему; это внешнее сообщение также должно содержать правильный порядковый номер тестера. Когда поставщик тестов получает такое внешнее сообщение, он проверяет, совпадает ли порядковый номер с номером, хранящимся в его постоянных данных, и, если это так, отправляет встроенное внутреннее сообщение с требуемым количеством тестовых монет TON по назначению (наш смарт-контракт в таком случае).

Внешнее сообщение сериализуется и сохраняется в файл wallet-query.boc. На экране отобразится что-то вроде этого:

Test giver address = -1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260 
kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny
Requesting GR$6.666 to account 0QAu6bT9Twd8myIygMNXY9-e2rC0GsINNvQAlnfflcOv4uVb = 0:2ee9b4fd4f077c9b223280c35763df9edab0b41ac20d36f4009677df95c3afe2 seqno=0x9a15 bounce=0 
enveloping message: x{00009A1501}
 x{42001774DA7EA783BE4D91194061ABB1EFCF6D585A0D61069B7A004B3BEFCAE1D7F1280C6A98B4000000000000000000000000000047494654}

resulting external message: x{89FF02ACEEB6F264BCBAC5CE85B372D8616CA2B4B9A5E3EC98BB496327807E0E1C1A000004D0A80C_}
 x{42001774DA7EA783BE4D91194061ABB1EFCF6D585A0D61069B7A004B3BEFCAE1D7F1280C6A98B4000000000000000000000000000047494654}

B5EE9C7241040201000000006600014F89FF02ACEEB6F264BCBAC5CE85B372D8616CA2B4B9A5E3EC98BB496327807E0E1C1A000004D0A80C01007242001774DA7EA783BE4D91194061ABB1EFCF6D585A0D61069B7A004B3BEFCAE1D7F1280C6A98B4000000000000000000000000000047494654AFC17FA4
(Saved to file wallet-query.boc)

Загрузка внешнего сообщения (external message) в смарт-контракт “test giver”

Теперь мы можем вызвать Lite Client, проверить состояние генератора тестов (если порядковый номер изменился, наше внешнее сообщение не будет выполнено), а затем набрать:

> sendfile wallet-query.boc

# в итоге мы увидим что-то вроде этого:
# ... external message status is 1

это означает, что внешнее сообщение было доставлено в “collator pool”. После этого один из сортировщиков может включить это внешнее сообщение в блок, создав транзакцию для смарт-контракта “test giver” для обработки этого external message.

Мы можем проверить, изменилось ли состояние дающего тест:

> last
> getaccount kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny


# (Если вы забудете ввести последний текст, вы, скорее всего, увидите неизменное состояние смарт-контракта поставщика тестов.) 
# Результатом будет:

got account state for -1 : FCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260 with respect to blocks (-1,8000000000000000,2240):18E6DA7707191E76C71EABBC5277650666B7E2CFA2AEF2CE607EAFE8657A3820:4EFA2540C5D1E4A1BA2B529EE0B65415DF46BFFBD27A8EB74C4C0E17770D03B1
account state is (account
  addr:(addr_std
    anycast:nothing workchain_id:-1 address:xFCB91A3A3816D0F7B8C2C76108B8A9BC5A6B7A55BD79F8AB101C52DB29232260)
  storage_stat:(storage_info
    used:(storage_used
      cells:(var_uint len:1 value:3)
      bits:(var_uint len:2 value:707)
      public_cells:(var_uint len:0 value:0)) last_paid:0
    due_payment:nothing)
  storage:(account_storage last_trans_lt:10697000003
    balance:(currencies
      grams:(nanograms
        amount:(var_uint len:7 value:999993280210000))
      other:(extra_currencies
        dict:hme_empty))
    state:(account_active
      (
        split_depth:nothing
        special:nothing
        code:(just
          value:(raw@^Cell 
            x{}
             x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
            ))
        data:(just
          value:(raw@^Cell 
            x{}
             x{00009A16}
            ))
        library:hme_empty))))
x{CFF8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D2068086C00000000000000009F65D110DC0E35F450FA914134_}
 x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
 x{00000001}

Как можно заметить, – порядковый номер, хранящийся в персистентных данных, изменился (в нашем примере на 0x9A16 = 39446), а поле last_trans_lt (логическое время последней транзакции этой учетной записи) было увеличено.

Теперь мы можем проверить состояние нашего нового смарт-контракта:

> getaccount 0QAu6bT9Twd8myIygMNXY9-e2rC0GsINNvQAlnfflcOv4uVb
# или
> getaccount 0:2ee9b4fd4f077c9b223280c35763df9edab0b41ac20d36f4009677df95c3afe2


# Результатом будет:

<code class="notranslate">got account state for 0:2EE9B4FD4F077C9B223280C35763DF9EDAB0B41AC20D36F4009677DF95C3AFE2 with respect to blocks (-1,8000000000000000,16481):890F4D549428B2929F5D5E0C5719FBCDA60B308BA4B907797C9E846E644ADF26:22387176928F7BCEF654411CA820D858D57A10BBF1A0E153E1F77DE2EFB2A3FB and (-1,8000000000000000,16481):890F4D549428B2929F5D5E0C5719FBCDA60B308BA4B907797C9E846E644ADF26:22387176928F7BCEF654411CA820D858D57A10BBF1A0E153E1F77DE2EFB2A3FB
account state is (account
  addr:(addr_std
    anycast:nothing workchain_id:0 address:x2EE9B4FD4F077C9B223280C35763DF9EDAB0B41AC20D36F4009677DF95C3AFE2)
  storage_stat:(storage_info
    used:(storage_used
      cells:(var_uint len:1 value:1)
      bits:(var_uint len:1 value:111)
      public_cells:(var_uint len:0 value:0)) last_paid:1553210152
    due_payment:nothing)
  storage:(account_storage last_trans_lt:16413000004
    balance:(currencies
      grams:(nanograms
        amount:(var_uint len:5 value:6666000000))
      other:(extra_currencies
        dict:hme_empty))
    state:account_uninit))
x{CFF60C04141C6A7B96D68615E7A91D265AD0F3A9A922E9AE9C901D4FA83F5D3C0D02025BC2E4A0D9400000000F492A0511406354C5A004_}

Наш новый смарт-контракт имеет некоторый положительный баланс (6,666 тестовых монет TON), но не имеет кода или данных (отражаемых состоянием: account_uninit).

Загрузка кода и данных для нового смарт-контракта

Теперь вы наконец-то можете загрузить внешнее сообщение с StateInit нового смарт-контракта, содержащее его код и данные:

> sendfile my_wallet_name-query.boc
... external message status is 1
> last
...
> getaccount 0QAu6bT9Twd8myIygMNXY9-e2rC0GsINNvQAlnfflcOv4uVb
...
got account state for 0:2EE9B4FD4F077C9B223280C35763DF9EDAB0B41AC20D36F4009677DF95C3AFE2 with respect to blocks (-1,8000000000000000,16709):D223B25D8D68401B4AA19893C00221CF9AB6B4E5BFECC75FD6048C27E001E0E2:4C184191CE996CF6F91F59CAD9B99B2FD5F3AA6F55B0B6135069AB432264358E and (-1,8000000000000000,16709):D223B25D8D68401B4AA19893C00221CF9AB6B4E5BFECC75FD6048C27E001E0E2:4C184191CE996CF6F91F59CAD9B99B2FD5F3AA6F55B0B6135069AB432264358E
account state is (account
  addr:(addr_std
    anycast:nothing workchain_id:0 address:x2EE9B4FD4F077C9B223280C35763DF9EDAB0B41AC20D36F4009677DF95C3AFE2)
  storage_stat:(storage_info
    used:(storage_used
      cells:(var_uint len:1 value:3)
      bits:(var_uint len:2 value:963)
      public_cells:(var_uint len:0 value:0)) last_paid:1553210725
    due_payment:nothing)
  storage:(account_storage last_trans_lt:16625000002
    balance:(currencies
      grams:(nanograms
        amount:(var_uint len:5 value:5983177000))
      other:(extra_currencies
        dict:hme_empty))
    state:(account_active
      (
        split_depth:nothing
        special:nothing
        code:(just
          value:(raw@^Cell 
            x{}
             x{FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54}
            ))
        data:(just
          value:(raw@^Cell 
            x{}
             x{00000001F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3}
            ))
        library:hme_empty))))
x{CFF60C04141C6A7B96D68615E7A91D265AD0F3A9A922E9AE9C901D4FA83F5D3C0D020680F0C2E4A0EB280000000F7BB57909405928024A134_}
 x{FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54}
 x{00000001F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3}

Вы увидите, что смарт-контракт был инициализирован с использованием кода и данных из StateInit внешнего сообщения, а его баланс немного уменьшился из-за платы за обработку. Теперь он запущен и работает, и вы можете активировать его, сгенерировав новые внешние сообщения и загрузив их в блокчейн TON с помощью Lite Client команды sendfile.

Использование смарт-контракта простого кошелька

На самом деле, смарт-контракт простого кошелька, использованный в этом примере, можно использовать для перевода тестовых монет TON на любые другие счета. В этом отношении он похож на рассмотренный выше смарт-контракт “test giver”, с той  лишь разницей, что он обрабатывает только внешние сообщения, подписанные правильным закрытым ключом (его владельца). В нашем случае это приватный ключ, сохраненный в файл my_wallet_name.pk при компиляции смарт-контракта (см. выше).

Пример того, как вы можете использовать этот смарт-контракт, приведен в образце файла crypto/smartcont/wallet.fif:

#!/usr/bin/env fift -s
"TonUtil.fif" include

{ ."usage: " @' $0 type ." <filename-base> <dest-addr> <seqno> <amount> [-B <body-boc>] [<savefile>]" cr
  ."Creates a request to simple wallet created by new-wallet.fif, with private key loaded from file <filename-base>.pk "
  ."and address from <filename-base>.addr, and saves it into <savefile>.boc ('wallet-query.boc' by default)" cr 1 halt
} : usage
$# dup 4 < swap 5 > or ' usage if
def? $6 { @' $5 "-B" $= { @' $6 =: body-boc-file [forget] $6 def? $7 { @' $7 =: $5 [forget] $7 } { [forget] $5 } cond
  @' $# 2- =: $# } if } if

true constant bounce

$1 =: file-base
$2 bounce parse-load-address =: bounce 2=: dest_addr
$3 parse-int =: seqno
$4 $>GR =: amount
def? $5 { @' $5 } { "wallet-query" } cond constant savefile

file-base +".addr" load-address
2dup 2constant wallet_addr
."Source wallet address = " 2dup .addr cr 6 .Addr cr
file-base +".pk" load-keypair nip constant wallet_pk

def? body-boc-file { @' body-boc-file file>B B>boc } { <b "TEST" $, b> } cond
constant body-cell

."Transferring " amount .GR ."to account "
dest_addr 2dup bounce 7 + .Addr ." = " .addr 
."seqno=0x" seqno x. ."bounce=" bounce . cr
."Body of transfer message is " body-cell <s csr. cr
  
// create a message
<b b{01} s, bounce 1 i, b{000100} s, dest_addr addr, amount Gram, 0 9 64 32 + + 1+ u, 
  body-cell <s 2dup s-fits? not rot over 1 i, -rot { drop body-cell ref, } { s, } cond
b>
<b seqno 32 u, 1 8 u, swap ref, b>
dup ."signing message: " <s csr. cr
dup hash wallet_pk ed25519_sign_uint
<b b{1000100} s, wallet_addr addr, 0 Gram, b{00} s,
   swap B, swap <s s, b>
dup ."resulting external message: " <s csr. cr
2 boc+>B dup Bx. cr
savefile +".boc" tuck B>file
."(Saved to file " type .")" cr

Вы можете вызвать этот скрипт следующим образом:

$ fift -I<source-directory>/crypto/fift/lib:<source-directory>/crypto/smartcont -s wallet.fif <your-wallet-id> <destination-addr> <your-wallet-seqno> <coins-amount>

# или еще проще в случае, если вы правильно настроили PATH и FIFTPATH:
$ fift -s wallet.fif <your-wallet-id> <destination-addr> <your-wallet-seqno> <coins-amount>

# для примера:
$ fift -s wallet.fif my_wallet_name kf8Ty2EqAKfAksff0upF1gOptUWRukyI9x5wfgCbh58Pss9j 1 .666

Здесь my_wallet_name — идентификатор вашего кошелька, использовавшегося ранее с new-wallet.fif; адрес и закрытый ключ вашего тестового кошелька будут загружены из файлов my_wallet_name.addr и my_wallet_name.pk в текущем каталоге.

Когда вы запускаете этот код (вызывая интерпретатор Fift), вы создаете внешнее сообщение указав место назначения, равный адресу смарт-контракта вашего кошелька, содержащее правильную подпись Ed25519, порядковый номер и обернутое внутреннее сообщение от смарт-контракта вашего кошелька в смарт-контракт, указанный в dest_addr, с произвольным присоединенным значением и произвольной полезной нагрузкой. Когда ваш смарт-контракт получает и обрабатывает это внешнее сообщение, он сначала проверяет подпись и порядковый номер. Если они верны, он принимает внешнее сообщение, отправляет встроенное внутреннее сообщение от себя в предполагаемое место назначения и увеличивает порядковый номер в своих постоянных данных (это простая мера для предотвращения повторных атак, в случае, если этот пример смарт-контракта кошелька код в конечном итоге используется в реальном приложении кошелька).

Конечно, настоящее приложение кошелька TON Blockchain скроет все промежуточные шаги, описанные выше. Сначала он сообщит пользователю адрес нового смарт-контракта, попросив его перевести некоторые средства на указанный адрес (отображаемый в удобной для пользователя форме без возможности возврата) из другого кошелька или криптовалютной биржи, а затем предоставит простой интерфейс для отображения текущего баланса и перевода средств на любые другие адреса по желанию пользователя. (Цель этого документа — объяснить, как создавать новые нетривиальные смарт-контракты и экспериментировать с тестовой сетью блокчейна TON, а не объяснять, как можно использовать Lite Client вместо более удобного приложения-кошелька).

Последнее замечание: в приведенных выше примерах смарт-контракты использовались в базовой workchain (workchain=0). Точно так же они будут работать в masterchain (workchain=-1), если передать идентификатор рабочей цепочки -1 вместо 0 в качестве первого аргумента в new-wallet.fif. Разница лишь в том, что плата за обработку и хранение в базовой workchain в 100-1000 раз ниже, чем в masterchain. Некоторые смарт-контракты (например, смарт-контракт о выборах валидатора) принимают переводы только от смарт-контрактов мастерчейн, поэтому вам понадобится кошелек в мастерчейне, если вы хотите делать ставки от имени своего собственного валидатора и участвовать в выборах.

Рейтинг
( 2 оценки, среднее 5 из 5 )
Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: