Cómo escribir y publicar un contrato inteligente en Telegram Open Network (TON)

Cómo escribir y publicar un contrato inteligente en TON

¿De qué trata este artículo?

En este artículo, compartiré mi experiencia al participar en el primer (de dos) concurso de Telegram sobre blockchain, donde no obtuve un premio y decidí documentar mi experiencia para que no se pierda en el olvido y, posiblemente, ayude a alguien más.

Dado que no quería escribir código abstracto, sino hacer algo funcional, para el artículo escribí un contrato inteligente de lotería instantánea y un sitio que muestra los datos del contrato inteligente directamente desde TON sin utilizar almacenamiento intermedio.

El artículo será útil para aquellos que desean crear su primer contrato inteligente en TON, pero no saben por dónde empezar.

A través del ejemplo de la lotería, abarcaré desde la instalación del entorno hasta la publicación del contrato inteligente, la interacción con él y crearé un sitio para obtener y publicar datos.

Sobre mi participación en el concurso

En octubre del año pasado, Telegram anunció un concurso sobre blockchain con nuevos lenguajes Fift y FunC. Se podía elegir escribir cualquiera de los cinco contratos inteligentes propuestos. Consideré que sería interesante involucrarme en algo inusual, aprender el lenguaje y hacer algo, aunque en el futuro no tuviera que escribir más. Además, el tema siempre está en boca de todos.

Cabe mencionar que no tenía experiencia en el desarrollo de contratos inteligentes.

Planeaba participar hasta el final y luego escribir un artículo de revisión, pero fallé desde el principio. Yo escribí una billetera con múltiples firmas en FunC y funcionaba en general. Me basé en un contrato inteligente en Solidity.

En ese momento, consideré que esto era suficiente para obtener al menos algún premio. Al final, alrededor de 40 de 60 participantes se convirtieron en premiados y yo no estaba entre ellos. En general, no es algo terrible, pero había algo que me preocupaba. En el momento del anuncio de resultados, no había revisión con pruebas para mi contrato, pregunté a los participantes en el chat si había alguien más que no lo tuviera, pero no había ninguno.

Al parecer, al notar mis mensajes, dos días después los jueces publicaron un comentario y no entendí si habían pasado por alto mi contrato inteligente durante la evaluación o simplemente consideraron que era tan malo que no necesitaba comentarios. Hice una pregunta en la página, pero no obtuve respuesta. Aunque quién juzgó no es un secreto, consideré innecesario enviar mensajes personales.

Se ha dedicado mucho tiempo a comprenderlo, por lo que se decidió escribir un artículo. Dado que la información aún es limitada, el artículo ayudará a ahorrar tiempo a todos los interesados.

Concepto del funcionamiento de los contratos inteligentes en TON

Antes de escribir algo, es necesario aclarar desde qué perspectiva abordar este tema. Por lo tanto, ahora explicaré de qué partes se compone el sistema. Más bien, qué partes es necesario conocer para escribir al menos algún contrato funcional.

Nos centraremos en la redacción de un contrato inteligente y en el trabajo con TON Virtual Machine (TVM), Fift y FunC, por lo que el artículo se asemeja más a una descripción del desarrollo de un programa convencional. No nos detendremos en cómo funciona la plataforma en sí.

En general, sobre cómo funciona TVM y el lenguaje Fift hay buena documentación oficial. Durante mi participación en el concurso y ahora mientras escribo el contrato actual, la he consultado con frecuencia.

El lenguaje principal en el que se escriben los contratos inteligentes es FunC. Actualmente no hay documentación sobre él, por lo que para escribir algo es necesario estudiar ejemplos de contratos inteligentes del repositorio oficial y la implementación del propio lenguaje allí, además de poder revisar ejemplos de contratos inteligentes de los dos concursos pasados. Los enlaces estarán al final del artículo.

Supongamos que ya hemos escrito un contrato inteligente en FunC, después de esto compilaremos el código en ensamblador Fift.

El contrato inteligente compilado queda listo para ser publicado. Para ello es necesario escribir una función en Fift, que recibirá como entrada el código del contrato inteligente y algunos parámetros adicionales, y como salida se generará un archivo con la extensión .boc (que significa "bag of cells"), y, dependiendo de cómo lo escribamos, una clave privada y una dirección, que se generan en función del código del contrato inteligente. A la dirección del contrato inteligente, que aún no ha sido publicado, ya se pueden enviar gramos.

Para publicar el contrato inteligente en TON, el archivo obtenido necesita ser enviado a la blockchain con la ayuda de un cliente ligero (de lo que se habla más adelante). Pero antes de publicar, es necesario transferir gramos a la dirección generada, de lo contrario, el contrato inteligente no será publicado. Después de la publicación, se podrá interactuar con el contrato inteligente, enviándole mensajes desde afuera (por ejemplo, a través de un cliente ligero) o desde adentro (por ejemplo, un contrato inteligente envía un mensaje a otro dentro de TON). .boc el archivo deberá enviarse a la blockchain mediante un lite client (sobre lo cual se hablará más adelante). Pero antes de publicarlo, es necesario transferir gramos a la dirección generada, de lo contrario, el smart contract no será publicado. Una vez publicado, se podrá interactuar con el smart contract, enviándole mensajes desde el exterior (por ejemplo, a través del lite client) o desde el interior (por ejemplo, un smart contract le envía un mensaje a otro dentro de TON).

Una vez que entendimos cómo se publica el código, el resto se vuelve más sencillo. Más o menos sabemos qué queremos escribir y cómo funcionará nuestro programa. Y mientras escribimos, buscamos cómo ya se ha implementado en los contratos inteligentes existentes, o miramos el código de implementación. Fift y FunC en el repositorio oficial, o consultamos la documentación oficial.

A menudo buscaba palabras clave en el chat de Telegram donde se reunieron todos los participantes del concurso, incluidos empleados de Telegram. Durante el concurso, todos se reunieron ahí y comenzaron a discutir Fift y FunC. El enlace está al final del artículo.

Es hora de pasar de la teoría a la práctica.

Preparación del entorno para trabajar con TON

Todo lo que se describe en este artículo lo hice en MacOS y verifiqué en una Ubuntu 18.04 LTS limpia en Docker.

Lo primero que necesitamos es descargar e instalar lite-client con el cual se pueden enviar solicitudes a TON.

Las instrucciones en el sitio web oficial describen el proceso de instalación de manera bastante detallada y clara, omitiendo algunos detalles. Aquí seguimos las instrucciones mientras instalamos las dependencias que faltan. No quise compilar cada proyecto yo mismo e instalé desde el repositorio oficial de Ubuntu (en MacOS usé brew).

apt -y install git
apt -y install wget
apt -y install cmake
apt -y install g++
apt -y install zlib1g-dev
apt -y install libssl-dev 

Una vez que todas las dependencias han sido instaladas, se puede instalar lite-client, Fift, FunC.

Primero clonamos el repositorio de TON junto con las dependencias. Para mayor comodidad, haremos todo en la carpeta ~\/TON.

cd ~\/TON
git clone https:\/\/github.com\/ton-blockchain\/ton.git
cd .\/ton
git submodule update --init --recursive

El repositorio también almacena implementaciones de Fift y FunC.

Ahora estamos listos para compilar el proyecto. El código del repositorio ha sido clonado en la carpeta ~\/TON\/ton. Hay ~\/TON creamos una carpeta build y compilamos el proyecto en ella.

mkdir ~\/TON\/build
cd ~\/TON\/build
cmake ..\/ton

Como vamos a escribir un contrato inteligente, no solo necesitamos lite-client, sino también Fift con FunC, así que compilamos todo. No es un proceso rápido, así que esperamos.

cmake --build . --target lite-client
cmake --build . --target fift
cmake --build . --target func

A continuación, descargamos el archivo de configuración en el que se encuentran los datos sobre el nodo a que lite-client se conectará.

wget https://test.ton.org/ton-lite-client-test1.config.json

Hacemos nuestras primeras solicitudes a TON

Ahora ejecutemos lite-client.

cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json

Si la compilación fue exitosa, al iniciar verás el registro de conexión del cliente ligero al nodo.

[ 1][t 2][1582054822.963129282][lite-client.h:201][!testnode]   conn ready
[ 2][t 2][1582054823.085654020][lite-client.cpp:277][!testnode] la versión del servidor es 1.1, capacidades 7
[ 3][t 2][1582054823.085725069][lite-client.cpp:286][!testnode] la hora del servidor es 1582054823 (delta 0)
...

Se puede ejecutar el comando help y ver qué comandos están disponibles.

help

Enumeremos los comandos que utilizaremos en este artículo.

lista de comandos disponibles:
last    Obtiene el último bloque y la información de estado del servidor
sendfile  Carga un mensaje serializado desde  y lo envía al servidor
getaccount  []  Carga el estado más reciente de la cuenta especificada;  está en el formato [:]
runmethod  []  ...   Ejecuta el método GET  de la cuenta  con los parámetros especificados

last obtiene el último bloque creado del servidor.

sendfile  envía un archivo con un mensaje al TON, es con este comando que se publica el contrato inteligente y se envían solicitudes a él.

getaccount  carga el estado actual del contrato inteligente en la dirección especificada.

runmethod  []    ejecuta los métodos get del contrato inteligente. 

Ahora estamos listos para escribir el contrato mismo.

Implementación

La idea

Como ya mencioné, el contrato inteligente que estamos escribiendo es una lotería.

Sin embargo, esta no es una lotería en la que debes comprar un boleto y esperar una hora, día o mes, sino instantánea, en la que el usuario transfiere a la dirección del contrato N gramos, y obtiene de vuelta instantáneamente 2 * N gramos o pierde. Haremos que la probabilidad de ganar sea de aproximadamente el 40%. Si no hay suficientes gramos para el pago, consideraremos la transacción como un depósito.

Además, es importante que las apuestas se puedan ver en tiempo real y de manera conveniente, para que el usuario pueda entender de inmediato si ganó o perdió. Por lo tanto, necesitamos hacer un sitio web que muestre las apuestas y los resultados directamente desde TON.

Escritura de contratos inteligentes

Para mayor comodidad, he creado resaltado de código para FunC; el plugin se puede encontrar e instalar en la búsqueda de Visual Studio Code. Si alguien desea agregar algo, he puesto el plugin en acceso público. También se había creado previamente un plugin para trabajar con Fift, que también se puede encontrar e instalar en VSC.

Primero, crearemos un repositorio donde vamos a commitear los resultados intermedios.

Para facilitarnos la vida, escribiremos el contrato inteligente y lo probaremos localmente hasta que esté listo. Solo después de eso lo publicaremos en TON.

El contrato inteligente tiene dos métodos externos a los que se puede acceder. Primero, recv_external() esta función se ejecuta cuando se realiza una solicitud al contrato desde el mundo exterior, es decir, no desde TON, por ejemplo, cuando formamos un mensaje y lo enviamos a través del cliente ligero. Segundo, recv_internal() esto es cuando un contrato dentro de TON se refiere al nuestro. En ambos casos, se pueden pasar parámetros a la función.

Comencemos con un ejemplo simple que funcionará si se publica, pero no tiene ninguna carga funcional.

() recv_internal(slice in_msg) impuro {
    ;; TODO: implementación 
}

() recv_external(slice in_msg) impuro {
    ;; TODO: implementación  
}

Aquí hay que aclarar qué es slice. Todos los datos almacenados en TON Blockchain son una colección celda TVM o simplemente celda, en esta celda se pueden almacenar hasta 1023 bits de datos y hasta 4 enlaces a otras celdas.

slice de celda TVM o slice es parte de una existente celda que se utiliza para su análisis, más adelante estará claro. Lo principal para nosotros es que podemos pasar slice y dependiendo del tipo de mensaje, procesar los datos en recv_external() o recv_internal().

impuro — es una palabra clave que indica que la función cambia los datos del contrato inteligente.

Guardemos el código del contrato en lottery-code.fc y compilemos.

~\/TON\/build\/crypto\/func -APSR -o lottery-compiled.fif ~\/TON\/ton\/crypto\/smartcont\/stdlib.fc .\/lottery-code.fc 

Se pueden ver los valores de los flags con el comando

~\/TON\/build\/crypto\/func -help

Obtenemos un código ensamblador Fift compilado en lottery-compiled.fif:

// lottery-compiled.fif

"Asm.fif" include
// automatically generated from `/Users/rajymbekkapisev/TON/ton/crypto/smartcont/stdlib.fc` `./lottery-code.fc` 
PROGRAM{
  DECLPROC recv_internal
  DECLPROC recv_external
  recv_internal PROC:<{
    //  in_msg
    DROP    // 
  }>
  recv_external PROC:<{
    //  in_msg
    DROP    // 
  }>
}END>c

Se puede ejecutar localmente, para ello preparemos el entorno.

Notemos que en la primera línea se incluye Asm.fif, este es un código escrito en Fift para el ensamblador Fift.

Dado que queremos ejecutar y probar el contrato inteligente localmente, crearemos un archivo lottery-test-suite.fif y copiaremos allí el código compilado, reemplazando la última línea que graba el código del contrato inteligente en la constante code, para luego poder pasarlo a la máquina virtual:

"TonUtil.fif" include
"Asm.fif" include

PROGRAM{
  DECLPROC recv_internal
  DECLPROC recv_external
  recv_internal PROC:<{
    \/\/  in_msg
    DROP    \/\/ 
  }>
  recv_external PROC:<{
    \/\/  in_msg
    DROP    \/\/ 
  }>
}END>s constant code

Hasta ahora parece claro, ahora agreguemos al mismo archivo el código que utilizaremos para ejecutar TVM.

0 tuple 0x076ef1ea , \/\/ magic
0 , 0 , \/\/ acciones msg_sents
1570998536 , \/\/ unix_time
1 , 1 , 3 , \/\/ block_lt, trans_lt, rand_seed
0 tuple 100000000000000 , dictnew , , \/\/ saldo restante
0 , dictnew , \/\/ contract_address, global_config
1 tuple \/\/ envolver a otro tuple
constant c7

0 constant recv_internal \/\/ para ejecutar recv_internal() 
-1 constant recv_external \/\/ para invocar recv_external()

En c7 grabamos el contexto, es decir, los datos con los que se ejecutará TVM (o el estado de la red). También durante el concurso, uno de los desarrolladores mostró cómo se crea c7 y lo copié. En este artículo, es posible que necesitemos cambiar rand_seed Dado que depende de la generación de un número aleatorio y no se cambia, siempre devolverá el mismo número.

recv_internal y recv_external Las constantes con el valor 0 y -1 se encargan de invocar las funciones correspondientes en el contrato inteligente.

Ahora estamos listos para crear nuestra primera prueba para nuestro contrato inteligente vacío. Por claridad, por ahora agregaremos todas las pruebas en este mismo archivo. lottery-test-suite.fif.

Creamos una variable almacenamiento y la asignamos a un vacío celda, este será el almacenamiento del contrato inteligente.

message este es el mensaje que pasaremos al contrato inteligente desde el exterior. También lo dejaremos vacío por ahora.

almacenamiento de variables 
<b b> ¡almacenamiento de ! 

mensaje de variable 
<b b> ¡mensaje ! 

Después de haber preparado las constantes y variables, iniciamos la TVM con el comando runvmctx y pasamos los parámetros creados como entrada.

message @ 
recv_external 
code 
storage @ 
c7 
runvmctx 

Al final tendremos este tipo de código intermedio en Fift.

Ahora podemos ejecutar el código obtenido.

export FIFTPATH=~\/TON\/ton\/crypto\/fift\/lib \/\/ ejecutamos una vez para conveniencia 
~\/TON\/build\/crypto\/fift -s lottery-test-suite.fif 

El programa debe ejecutarse sin errores y en la salida veremos el registro de ejecución:

execute SETCP 0
execute DICTPUSHCONST 19 (xC_,1)
execute DICTIGETJMPZ
execute DROP
execute implicit RET
[ 3][t 0][1582281699.325381279][vm.cpp:479]     steps: 5 gas: used=304, max=9223372036854775807, limit=9223372036854775807, credit=0

Excelente, hemos escrito la primera versión funcional del contrato inteligente.

Ahora necesitamos agregar funcionalidad. Primero abordaremos los mensajes que vienen del mundo exterior en recv_external()

El desarrollador puede elegir el formato del mensaje que el contrato puede aceptar.

Pero generalmente,

  • en primer lugar, queremos proteger nuestro contrato del mundo exterior y hacer que solo el propietario del contrato pueda enviar mensajes externos a él.
  • En segundo lugar, cuando enviamos un mensaje válido a TON queremos que esto ocurra exactamente una vez y al reenviar el mismo mensaje, el contrato inteligente lo rechace.

Por lo tanto, casi en cada contrato se abordan estos dos problemas, dado que nuestro contrato acepta mensajes externos, también debemos ocuparnos de esto.

Haremos esto en orden inverso. Primero resolveremos el problema de la repetición, si el contrato ya ha recibido y procesado ese mensaje, no lo ejecutará una segunda vez. Y luego resolveremos el problema de que solo un círculo específico de personas pueda enviar mensajes al contrato inteligente.

Hay diferentes maneras de resolver el problema de los mensajes duplicados. Haremos lo siguiente. En el contrato inteligente, inicializaremos un contador de mensajes recibidos con un valor inicial de 0. En cada mensaje al contrato inteligente, añadiremos el valor actual del contador. Si el valor del contador en el mensaje no coincide con el valor en el contrato inteligente, no lo procesaremos; si coincide, lo procesaremos y aumentaremos el contador en el contrato inteligente en 1.

Regresamos a lottery-test-suite.fif y añadimos una segunda prueba. Enviaremos un número incorrecto; el código debería lanzar una excepción. Por ejemplo, supongamos que en los datos del contrato se almacena 166, pero enviamos 165.

<b 166 32 u, b> almacenamiento !
<b 165 32 u, b> mensaje !

mensaje @ 
recv_external 
código 
almacenamiento @ 
c7 
runvmctx

drogar 
exit_code ! 
."Código de salida " exit_code @ . cr 
exit_code @ 33 - abortar"Prueba #2 No superada"

Iniciaremos.

 ~\/TON\/build\/crypto\/fift -s lottery-test-suite.fif 

Y veremos que la prueba se ejecuta con un error.

[ 1][t 0][1582283084.210902214][words.cpp:3046] lottery-test-suite.fif:67: abort": Test #2 No aprobado
[ 1][t 0][1582283084.210941076][fift-main.cpp:196]      Error al interpretar el archivo `lottery-test-suite.fif`: error al interpretar el archivo incluido `lottery-test-suite.fif`: lottery-test-suite.fif:67: abort": Test #2 No aprobado

En esta etapa lottery-test-suite.fif debería verse como en el enlace.

Ahora añadamos la lógica del contador en el contrato inteligente en lottery-code.fc.

() recv_internal(slice in_msg) impura {
    ;; TODO: implementación 
}

() recv_external(slice in_msg) impura {
    if (slice_empty?(in_msg)) {
        return (); 
    }
    int msg_seqno = in_msg~load_uint(32);
    var ds = begin_parse(get_data());
    int stored_seqno = ds~load_uint(32);
    throw_unless(33, msg_seqno == stored_seqno);
}

En slice in_msg contiene el mensaje que estamos enviando.

Lo primero que hacemos es verificar si hay datos en el mensaje; si no, simplemente salimos.

Luego analizamos el mensaje. in_msg~load_uint(32) carga el número 165, de 32 bits unsigned int del mensaje recibido.

Luego, cargamos 32 bits de la almacenamiento del contrato inteligente. Verificamos que el número cargado coincide con el enviado; si no, se lanza una excepción. En nuestro caso, como enviamos un número que no coincide, debería lanzarse una excepción.

Ahora compilamos.

~\/TON\/build\/crypto\/func -APSR -o lottery-compiled.fif ~\/TON\/ton\/crypto\/smartcont\/stdlib.fc .\/lottery-code.fc 

Copiamos el código resultante en lottery-test-suite.fif, sin olvidar reemplazar la última línea.

Verificamos que la prueba pasa:

~\/TON\/build\/crypto\/fift -s lottery-test-suite.fif

Aquí hay un caso se puede ver el commit correspondiente con los resultados actuales.

Notemos que copiar constantemente el código compilado del contrato inteligente en el archivo de pruebas no es cómodo; por lo tanto, escribiremos un script que escriba el código en una constante por nosotros, y simplemente conectaremos el código compilado en nuestras pruebas usando "include".

En la carpeta del proyecto, crearemos un archivo build.sh con el siguiente contenido.

#!/bin/bash

~/TON/build/crypto/func -SPA -R -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc

Lo haremos ejecutable.

chmod +x .\/build.sh

Ahora, solo tenemos que ejecutar nuestro script para compilar el contrato. Pero además, necesitamos guardarlo en una constante. codePor lo tanto, crearemos un nuevo archivo. lotter-compiled-for-test.fif, que incluiremos en el archivo. lottery-test-suite.fif.

Añadiremos al script sh el código que simplemente duplicará el archivo compilado en lotter-compiled-for-test.fif y cambiará la última línea.

# copy and change for test 
cp lottery-compiled.fif lottery-compiled-for-test.fif
sed '$d' lottery-compiled-for-test.fif > test.fif
rm lottery-compiled-for-test.fif
mv test.fif lottery-compiled-for-test.fif
echo -n "}END>s constant code" >> lottery-compiled-for-test.fif

Ahora, para verificar, ejecutemos el script resultante y se generará un archivo. lottery-compiled-for-test.fif, que incluiremos en nuestro lottery-test-suite.fif

En lottery-test-suite.fif eliminamos el código del contrato y añadimos la línea "lottery-compiled-for-test.fif" include.

Ejecutamos las pruebas para verificar que pasan.

~\/TON\/build\/crypto\/fift -s lottery-test-suite.fif

Perfecto, ahora, para automatizar la ejecución de las pruebas, creamos un archivo. test.sh, que primero ejecutará build.sh, y luego lanzará las pruebas.

touch test.sh
chmod +x test.sh

Dentro escribimos

.\/build.sh 

echo "nCompilación completadan"

export FIFTPATH=~\/TON\/ton\/crypto\/fift\/lib
~\/TON\/build\/crypto\/fift -s lottery-test-suite.fif

Haremos test.sh y ejecutaremos para asegurarnos de que las pruebas funcionan.

chmod +x .\/test.sh
.\/test.sh

Verificamos que el contrato se compila y que las pruebas se ejecutan.

Perfecto, ahora al ejecutar test.sh la compilación y la ejecución de las pruebas ocurrirán de inmediato. Aquí está el enlace a commit.

Bien, antes de continuar, vamos a hacer una cosa más para mayor comodidad.

Crearemos una carpeta build donde almacenaremos el contrato compilado y su clon guardado en una constante. lottery-compiled.fif, lottery-compiled-for-test.fif. También crearemos una carpeta test donde se almacenarán los archivos de prueba lottery-test-suite.fif y potencialmente otros archivos auxiliares. Enlace a los cambios correspondientes..

Continuaremos con el desarrollo del contrato inteligente.

A continuación, debe haber una prueba que verifique que el mensaje se recibe y que el contador se actualiza en el almacenamiento cuando enviamos un número correcto. Pero haremos eso más tarde.

Ahora, pensemos en qué estructura de datos y qué datos necesitamos guardar en el contrato inteligente.

Describiré todo lo que estamos almacenando.

`seqno` número entero positivo de 32 bits contador. 

`pubkey` número entero positivo de 256 bits clave pública, con la cual verificaremos la firma del mensaje enviado desde el exterior, como se explicará a continuación. 

`order_seqno` número entero positivo de 32 bits que almacena el contador de apuestas. 

`number_of_wins` número entero positivo de 32 bits que almacena la cantidad de victorias. 

`incoming_amount` tipo de dato Gram (los primeros 4 bits responden a la longitud), almacena la cantidad total de gramos que han sido enviados al contrato. 

`outgoing_amount` cantidad total de gramos que ha sido enviado a los ganadores. 

`owner_wc` número del workchain, número entero de 32 bits (en algunos lugares se menciona que es de 8 bits). Actualmente hay solo dos: -1 y 0. 

`owner_account_id` número entero positivo de 256 bits, dirección del contrato en el workchain actual. 

`orders` variable tipo diccionario que almacena las últimas veinte apuestas. 

A continuación, necesitamos escribir dos funciones. La primera la llamaremos pack_state(), que empaquetará los datos para su posterior almacenamiento en la memoria del contrato inteligente. La segunda, la llamaremos unpack_state() leerá y devolverá los datos de la memoria.

_ pack_state(int seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) inline_ref {
    return begin_cell()
            .store_uint(seqno, 32)
            .store_uint(pubkey, 256)
            .store_uint(order_seqno, 32)
            .store_uint(number_of_wins, 32)
            .store_grams(incoming_amount)
            .store_grams(outgoing_amount)
            .store_int(owner_wc, 32)
            .store_uint(owner_account_id, 256)
            .store_dict(orders)
            .end_cell();
}

_ unpack_state() inline_ref {
    var ds = begin_parse(get_data());
    var unpacked = (ds~load_uint(32), ds~load_uint(256), ds~load_uint(32), ds~load_uint(32), ds~load_grams(), ds~load_grams(), ds~load_int(32), ds~load_uint(256), ds~load_dict());
    ds.end_parse();
    return unpacked;
}

Agregamos estas dos funciones al principio del contrato inteligente. Obtendremos este tipo de un resultado intermedio.

Para guardar los datos, será necesario llamar a la función integrada set_data() y esta escribirá los datos en pack_state() la memoria del contrato inteligente.

cell packed_state = pack_state(arg_1, .., arg_n); 
set_data(packed_state);

Ahora que tenemos funciones convenientes para grabar y leer datos, podemos avanzar.

Necesitamos verificar que el mensaje entrante desde el exterior esté firmado por el propietario del contrato (o por otro usuario que tenga acceso a la clave privada).

Cuando publicamos un contrato inteligente, podemos inicializarlo con los datos que deseamos en el almacenamiento, que se guardarán para uso futuro. Registraremos allí la clave pública para que se pueda verificar que la firma del mensaje entrante fue realizada con la clave privada correspondiente.

Antes de continuar, crearemos una clave privada y la registraremos en test/keys/owner.pk. Para ello, ejecutaremos Fift en modo interactivo y realizaremos cuatro comandos.

`newkeypair` genera la clave pública y privada y las registra en la pila. 

`drop` elimina el elemento superior de la pila (en este caso, la clave pública) 

`.s` simplemente muestra qué hay actualmente en la pila 

`"owner.pk" B>file` registra la clave privada en un archivo llamado `owner.pk`. 

`bye` finaliza la sesión con Fift. 

Crearemos una carpeta keys dentro de la carpeta test y allí registraremos la clave privada.

mkdir test/keys
cd test/keys
~\/TON\/build\/crypto\/fift -i 
newkeypair
 ok
.s 
BYTES:128DB222CEB6CF5722021C3F21D4DF391CE6D5F70C874097E28D06FCE9FD6917 BYTES:DD0A81AAF5C07AAAA0C7772BB274E494E93BB0123AA1B29ECE7D42AE45184128 
drop 
 ok
"owner.pk" B>file
 ok
bye

Vemos en la carpeta actual el archivo owner.pk.

Eliminamos la clave pública de la pila, y cuando lo necesitemos podemos obtenerla de la privada.

Ahora necesitamos escribir la verificación de la firma. Comencemos con la prueba. Primero, leemos la clave privada del archivo usando la función file>B y la registramos en la variable owner_private_key, luego, con la función priv>pub convertimos la clave privada en pública y registramos el resultado en owner_public_key.

variable owner_private_key
variable owner_public_key 

"./keys/owner.pk" file>B owner_private_key !
owner_private_key @ priv>pub owner_public_key !

Ambas claves nos serán necesarias.

Inicializamos el almacenamiento del contrato inteligente con datos arbitrarios en la misma secuencia que en la función pack_state()y lo registraremos en la variable almacenamiento.

variable owner_private_key
variable owner_public_key 
variable orders
variable owner_wc
variable owner_account_id

".\/keys\/owner.pk" archivo&gt;B owner_private_key !
owner_private_key @ priv&gt;pub owner_public_key !
dictnew orders !
0 owner_wc !
0 owner_account_id !

<b 0 32 u, owner_public_key @ b, gram, owner_wc i, owner_account_id 256 orders dict, b> almacenamiento !

Luego, elaboraremos un mensaje firmado, que solo contendrá la firma y el valor del contador.

Primero creamos los datos que queremos enviar, luego los firmamos con la clave privada y finalmente formamos el mensaje firmado.

mensaje_variable_a_firmar
mensaje_variable_a_enviar
firma_variable
<b 0 32 u, b> mensaje_a_firmar !
mensaje_a_firmar @ propietario_de_clave_privada @ firma_ed25519_sign_uint !
<b signature @ b, 0 32 u, b> &lt;s !  

En consecuencia, el mensaje que enviaremos al contrato inteligente está registrado en la variable message_to_send, sobre las funciones hashu, ed25519_sign_uint se puede leer en la documentación de Fift.

Y para ejecutar la prueba, llamamos nuevamente.

message_to_send @ 
recv_external 
code 
storage @
c7
runvmctx

Así es como el archivo con las pruebas debe verse así en esta etapa.

Ejecutaremos la prueba y fallará, así que modificaremos el contrato inteligente para que pueda recibir mensajes de dicho formato y verificar la firma.

Primero, leemos 512 bits de la firma del mensaje y lo almacenamos en una variable, luego leemos 32 bits de la variable del contador.

Dado que tenemos una función para leer datos del almacenamiento del contrato inteligente, la utilizaremos.

A continuación, verificamos el contador enviado con el almacenamiento y validamos la firma. Si algo no coincide, lanzamos una excepción con el código correspondiente.

var signature = in_msg~load_bits(512);
var message = in_msg;
int msg_seqno = message~load_uint(32);
(int stored_seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) = unpack_state();
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, check_signature(slice_hash(in_msg), signature, pubkey));

Compromiso correspondiente aquí.

Ejecutamos las pruebas y vemos que la segunda prueba falla. Por dos razones, falta de bits en el mensaje y falta de bits en el almacenamiento, por lo que el código falla al analizar. Necesitamos añadir la firma del mensaje que estamos enviando y copiar el almacenamiento de la última prueba.

En la segunda prueba, añadimos la firma del mensaje y cambiamos el almacenamiento del contrato inteligente. Así es como el archivo de pruebas se ve actualmente.

Vamos a escribir la cuarta prueba, en la que enviaremos un mensaje firmado con la clave privada de otro. Crearemos otra clave privada y la guardaremos en un archivo not-owner.pk. Usaremos esta clave privada para firmar el mensaje. Ejecutamos las pruebas y nos aseguramos de que todas las pruebas pasan. Compromiso hasta el momento.

Ahora, por fin, podemos pasar a la implementación de la lógica del contrato inteligente.
En recv_external() Aceptaremos dos tipos de mensajes.

Dado que nuestro contrato acumulará las pérdidas de los jugadores, ese dinero debe ser transferido al creador de la lotería. La dirección del monedero del creador de la lotería se registra en el almacenamiento al crear el contrato.

Por si acaso, necesitamos la posibilidad de cambiar la dirección a la que se envían las gramas de los perdedores. También debemos tener la posibilidad de enviar gramas desde la lotería a la dirección del propietario.

Comencemos con el primero. Primero escribiremos una prueba que verifique que después de enviar un mensaje, el contrato inteligente ha guardado la nueva dirección en el almacenamiento. Notemos que en el mensaje, además del contador y de la nueva dirección, también pasamos acción un número entero no negativo de 7 bits, y dependiendo de ello, decidiremos cómo procesar el mensaje en el contrato inteligente.

<b 0 32 u, 1 @ 7 new_owner_wc i, new_owner_account_id 256 b> mensaje_a_firmar !

En la prueba se puede ver cómo ocurre la deserialización del almacenamiento del contrato inteligente. almacenamiento En Fift. La deserialización de variables está descrita en la documentación de Fift.

Enlace al commit con la adición de la prueba.

Ejecutamos la prueba y nos aseguramos de que falle. Ahora agreguemos la lógica para cambiar la dirección del propietario de la lotería.

En el contrato inteligente, continuamos analizando message, leemos en acción. Recordemos que tendremos dos acción: cambio de dirección y envío de gramos.

Luego leemos la nueva dirección del propietario del contrato y la guardamos en el almacén.
Ejecutamos las pruebas y vemos que la tercera prueba falla. Falla porque el contrato ahora analiza 7 bits adicionales del mensaje, que faltan en la prueba. Agreguemos al mensaje un acción. Ejecutaremos las pruebas y veremos que todas pasan. Aquí commit de cambios. Excelente.

Ahora escribiremos la lógica para enviar la cantidad especificada de gramos a la dirección que se guardó anteriormente.

Primero escribiremos una prueba. Escribiremos dos pruebas: una cuando no hay suficiente saldo, y otra cuando todo debería pasar con éxito. Las pruebas se pueden ver en este commit.

Ahora escribiremos el código. Primero escribiremos dos métodos auxiliares. El primer método get para conocer el saldo actual del contrato inteligente.

int balance() inline_ref method_id {
    return get_balance().pair_first();
}

Y el segundo para enviar gramos a otro contrato inteligente. Este método lo copié completamente de otro contrato inteligente.

() send_grams(int wc, int addr, int grams) impure {
    ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
    cell msg = begin_cell()
    ;;  .store_uint(0, 1) ;; 0 <= indicador de formato int_msg_info$0 
    ;;  .store_uint(1, 1) ;; 1 <= ihr deshabilitado
    ;;  .store_uint(1, 1) ;; 1 <= rebote = verdadero
    ;;  .store_uint(0, 1) ;; 0 <= rebotado = falso
    ;;  .store_uint(4, 5)  ;; 00100 <= flags de dirección, cualquier caso = falso, workchain de 8 bits
        .store_uint (196, 9)
        .store_int(wc, 8)
        .store_uint(addr, 256)
        .store_grams(grams)
        .store_uint(0, 107) ;; 106 ceros +  0 como un indicador de que no hay celda con los datos.
        .end_cell(); 
    send_raw_message(msg, 3); ;; modo, 2 para ignorar errores, 1 para que el remitente pague tarifas, 64 para devolver el valor del mensaje entrante
}

Agregaremos estos dos métodos al contrato inteligente y escribiremos la lógica. Primero analizamos la cantidad de gramos del mensaje. Luego verificamos el saldo, si no es suficiente lanzamos una excepción. Si todo está bien, enviamos los gramos a la dirección guardada y actualizamos el contador.

int amount_to_send = message~load_grams();
throw_if(36, amount_to_send + 500000000 > balance());
accept_message();
send_grams(owner_wc, owner_account_id, amount_to_send);
set_data(pack_state(stored_seqno + 1, pubkey, order_seqno, number_of_wins, incoming_amount, outgoing_amount, owner_wc, owner_account_id, orders));

Así es como así se ve el contrato inteligente en este momento. Ejecutemos las pruebas y asegurémonos de que pasen.

Por cierto, se cobra una comisión por cada mensaje procesado por el contrato inteligente. Para que el contrato inteligente ejecute la solicitud del mensaje, después de las verificaciones básicas, hay que llamar a accept_message().

Ahora nos ocuparemos de los mensajes internos. De hecho, solo vamos a recibir gramos y enviar de vuelta al jugador el doble de la cantidad en caso de ganar, y un tercio al propietario en caso de perder.

Primero escribiremos una prueba sencilla. Para esto necesitaremos una dirección de prueba del contrato inteligente desde la cual supuestamente enviamos gramos al contrato inteligente.

La dirección del contrato inteligente consiste en dos números, un número entero de 32 bits que representa el workchain y un número entero no negativo único de 256 bits que es el número de cuenta en ese workchain. Por ejemplo, -1 y 12345, esta dirección la guardaremos en un archivo.

He copiado la función para guardar la dirección de TonUtil.fif.

// ( wc addr fname -- )  Save address to file in 36-byte format
{ -rot 256 u>B swap 32 i>B B+ swap B>file } : save-address

Vamos a analizar cómo funciona la función, esto nos dará una comprensión de cómo funciona Fift. Ejecutamos Fift en modo interactivo.

~\/TON\/build\/crypto\/fift -i 

Primero colocamos en la pila -1, 12345 y el nombre del futuro archivo "sender.addr":

-1 12345 "sender.addr" 

El siguiente paso es ejecutar la función -rot, que mueve la pila de tal manera que el número único del contrato inteligente queda en la parte superior de la pila:

"sender.addr" -1 12345

256 u>B convierte el entero no negativo de 256 bits a bytes.

"sender.addr" -1 BYTES:0000000000000000000000000000000000000000000000000000000000003039

swap cambia de lugar los dos elementos superiores de la pila.

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 -1

32 i>B convierte el entero de 32 bits a bytes.

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 BYTES:FFFFFFFF

B+ concatena las dos secuencias de bytes.

 "sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF

De nuevo swap.

BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF "sender.addr" 

Y finalmente, se ejecuta la escritura de bytes en el archivo B>file. Después de esto, nuestra pila estará vacía. Detenemos Fift. En la carpeta actual se ha creado un archivo sender.addr. Vamos a mover el archivo a la carpeta creada test\/addresses\/.

Escribiremos una prueba sencilla que enviará gramos al contrato inteligente. Aquí está el commit.

Ahora nos ocuparemos de la lógica de la lotería.

Lo primero que hacemos es verificar el mensaje bounced o no, si bounced, lo ignoramos. bounced significa que el contrato devolverá gramos si ocurre algún error. No devolveremos gramos si surge algún error.

Verificamos el balance; si es menor que medio gramo, simplemente aceptamos el mensaje e ignoramos.

A continuación, analizamos la dirección del contrato inteligente desde el que llegó el mensaje.

Leemos los datos del almacenamiento y luego eliminamos las apuestas antiguas del historial si hay más de veinte. Para mayor comodidad, he escrito tres funciones adicionales. pack_order(), unpack_order(), remove_old_orders().

Luego, verificamos si no hay suficiente saldo para el pago; si es así, consideramos que no es una apuesta, sino un depósito y lo guardamos en orders.

Ahora, finalmente, la esencia del contrato inteligente.

Primero, si el jugador pierde, lo guardamos en el historial de apuestas y si la cantidad supera 3 gramos, enviamos 1/3 al propietario del contrato inteligente.

Si el jugador gana, enviamos el monto duplicado a la dirección del jugador y luego guardamos la información de la apuesta en el historial.

() recv_internal(int order_amount, cell in_msg_cell, slice in_msg) impure {
    var cs = in_msg_cell.begin_parse();
    int flags = cs~load_uint(4);  ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
    if (flags & 1) { ;; ignorar rebotado
        return ();
    }
    if (order_amount < 500000000) { ;; solo recibir gramos sin cambiar el estado 
        return ();
    }
    slice src_addr_slice = cs~load_msg_addr();
    (int src_wc, int src_addr) = parse_std_addr(src_addr_slice);
    (int stored_seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) = unpack_state();
    orders = remove_old_orders(orders, order_seqno);
    if (balance() = 4) {
        builder order = pack_order(order_seqno, 3, now(), order_amount, src_wc, src_addr);
        orders~udict_set_builder(32, order_seqno, order);
        set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins, incoming_amount + order_amount, outgoing_amount, owner_wc, owner_account_id, orders));
        if (order_amount > 3000000000) {
            send_grams(owner_wc, owner_account_id, order_amount / 3);
        }
        return ();
    }
    send_grams(src_wc, src_addr, 2 * order_amount);
    builder order = pack_order(order_seqno, 2, now(), order_amount, src_wc, src_addr);
    orders~udict_set_builder(32, order_seqno, order);
    set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins + 1, incoming_amount, outgoing_amount + 2 * order_amount, owner_wc, owner_account_id, orders));
}

Eso es todo. El commit correspondiente.

Ahora queda lo simple, haremos métodos GET para que desde el mundo exterior se pueda obtener información sobre el estado del contrato (prácticamente, leer los datos desde el almacenamiento del contrato inteligente).

Agregaremos métodos GET. De cómo obtener información sobre el contrato inteligente escribiremos a continuación.

También olvidé agregar el código que manejará la primera solicitud que se realiza al publicar un contrato inteligente. El commit correspondiente. Y también arreglé el error de enviar 1/3 de la suma a la cuenta del propietario.

Luego, queda publicar el contrato inteligente. Crearemos una carpeta requests.

Tomé como base el código de publicación simple-wallet-code.fc que se pueden encontrar en el repositorio oficial.

Entre las cosas a tener en cuenta, formamos el almacenamiento del contrato inteligente y el mensaje de entrada. Después, se genera la dirección del contrato inteligente, es decir, la dirección es conocida incluso antes de la publicación en TON. Luego, se deben enviar algunos gramos a esta dirección y solo después de esto se debe enviar el archivo con el contrato inteligente en sí, ya que la red cobra una comisión por el almacenamiento del contrato inteligente y las operaciones en él (los validadores que almacenan y ejecutan los contratos inteligentes). Puedes ver el código aquí.

Luego ejecutamos el código de publicación y obtenemos lottery-query.boc el archivo y la dirección del contrato inteligente.

~\/TON\/build\/crypto\/fift -s requests\/new-lottery.fif 0

No olvides guardar los archivos generados: lottery-query.boc, lottery.addr, lottery.pk.

Entre otras cosas, en los registros de ejecución veremos la dirección del contrato inteligente.

new wallet address = 0:044910149dbeaf8eadbb2b28722e7d6a2dc6e264ec2f1d9bebd6fb209079bc2a 
(Saving address to file lottery.addr)
Non-bounceable address (for init): 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd
Bounceable address (for later access): kQAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KpFY

Por curiosidad, haremos una solicitud a TON

$ .\/lite-client\/lite-client -C ton-lite-client-test1.config.json 
getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

Y veremos que la cuenta con esa dirección está vacía.

el estado de la cuenta está vacío

Enviamos a la dirección 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 Gramos y después de unos segundos ejecutamos el mismo comando. Para enviar gramos utilizo la billetera oficial, y se pueden pedir gramos de prueba a alguien del chat, del cual hablaré al final del artículo.

> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

Ve que en la red ha aparecido un contrato inteligente no inicializado (state:account_uninit) con esa dirección y un saldo de 1 000 000 000 nanogramas.

el estado de la cuenta es (cuenta
  dir:(addr_std
    anycast:nada workchain_id:0 dirección:x044910149DBEAF8EADBB2B28722E7D6A2DC6E264EC2F1D9BEBD6FB209079BC2A)
  storage_stat:(storage_info
    usado:(storage_used
      celdas:(var_uint len:1 value:1)
      bits:(var_uint len:1 value:103)
      celdas_públicas:(var_uint len:0 value:0)) último_pago:1583257959
    pago_vencido:nada)
  almacenamiento:(account_storage last_trans_lt:3825478000002
    saldo:(currencies
      gramos:(nanograms
        cantidad:(var_uint len:4 value:2000000000))
      otro:(extra_currencies
        dict:hme_empty))
    estado:cuenta_no_inicializada))
x{C00044910149DBEAF8EADBB2B28722E7D6A2DC6E264EC2F1D9BEBD6FB209079BC2A20259C2F2F4CB3800000DEAC10776091DCD650004_}
la última transacción lt = 3825478000001 hash = B043616AE016682699477FFF01E6E903878CDFD6846042BA1BFC64775E7AC6C4
el saldo de la cuenta es 2000000000ng

Ahora publicaremos el contrato inteligente. Ejecutaremos el cliente ligero y procederemos.

> sendfile lottery-query.boc
[ 1][t 2][1583008371.631410122][lite-client.cpp:966][!testnode] enviando consulta desde el archivo lottery-query.boc
[ 3][t 1][1583008371.828550100][lite-client.cpp:976][!query]    el estado del mensaje externo es 1 

Verifiquemos que el contrato ha sido publicado.

> last
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

Entre otras cosas, recibiremos.

  almacenamiento:(account_storage last_trans_lt:3825499000002
    saldo:(currencies
      gramos:(nanograms
        cantidad:(var_uint len:4 value:1987150999))
      otro:(extra_currencies
        dict:hme_empty))
    estado:(cuenta_activa

Vemos que cuenta_activa.

El commit correspondiente con cambios aquí.

Ahora crearemos solicitudes para interactuar con el contrato inteligente.

Más bien, dejaremos el primero para cambiar la dirección como un trabajo independiente, y el segundo para enviar gramos a la dirección del propietario lo haremos. De hecho, tenemos que hacer lo mismo que en la prueba de envío de gramos.

Este es el mensaje que enviaremos al contrato inteligente, donde msg_seqno 165, acción 2 y 9.5 gramos para enviar.

<b 165 32 u, 2 7 9500000000 gram, b>

No olvidemos firmar el mensaje con la clave privada lottery.pk, que fue generada anteriormente al crear el contrato inteligente. Aquí está el commit correspondiente.

Obtenemos información del contrato inteligente mediante métodos get

Ahora veamos cómo ejecutar los métodos get del contrato inteligente.

Iniciamos lite-client y ejecutamos los métodos get que hemos escrito.

$ .\/lite-client\/lite-client -C ton-lite-client-test1.config.json
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd balance
argumentos:  [ 104128 ] 
resultado:  [ 64633878952 ] 
...

En resultado contiene el valor que devuelve la función balance() de nuestro contrato inteligente.
Haremos lo mismo para varios métodos.

> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_seqno
...
argumentos:  [ 77871 ] 
resultado:  [ 1 ] 

Solicitaremos el historial de apuestas.

> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_orders
...
argumentos:  [ 67442 ] 
resultado:  [ ([0 1 1583258284 10000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [1 3 1583258347 4000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [2 1 1583259901 50000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308]) ] 

Usaremos lite-client y métodos GET para mostrar información sobre el contrato inteligente en el sitio web.

Mostramos los datos del contrato inteligente en el sitio web

He escrito un sitio web simple en Python para mostrar los datos del contrato inteligente de una manera conveniente. No entraré en detalles sobre esto y publicaré el sitio. con un solo commit.

Las solicitudes a TON se realizan desde Python usando lite-client. Para mayor comodidad, el sitio se empaqueta en Docker y se publica en Google Cloud. Enlace al sitio.

Probamos

Ahora intentaremos enviar gramos para recargar desde la billetera. Enviemos 40 gramos. Y haremos un par de apuestas para ver. Vemos que el sitio muestra el historial de apuestas, el porcentaje de ganancia actual y otra información útil.

Vemos, que ganamos la primera, perdimos la segunda.

Póscrito

El artículo resultó ser mucho más largo de lo que esperaba, tal vez podría haber sido más corto, o tal vez es justo para una persona que no sabe nada sobre TON y quiere escribir y publicar un contrato inteligente no tan simple con la posibilidad de interactuar con él. Tal vez algunas cosas se podrían haber explicado de manera más sencilla.

Es posible que algunos aspectos de la implementación se podrían haber hecho de manera más eficiente y elegante, pero eso habría requerido aún más tiempo para preparar el artículo. También es posible que me haya equivocado o que no entendiera algo, por lo que si haces algo serio, deberías basarte en la documentación oficial o en el repositorio oficial con el código de TON.

Cabe señalar que dado que TON está en una fase activa de desarrollo, pueden ocurrir cambios que rompan alguno de los pasos en este artículo (lo que ya sucedió mientras escribía, ya lo corregí), pero el enfoque general difícilmente cambiará.

No hablaré sobre el futuro de TON. Tal vez la plataforma se convierta en algo más y deberíamos dedicar tiempo a estudiarla y ocupar un nicho con nuestros productos desde ahora.

También está Libra de Facebook, que tiene una audiencia potencial de usuarios mayor que la de TON. No sé casi nada sobre Libra, pero por lo que he visto en el foro, la actividad allí es mucho mayor que en la comunidad de TON. Aunque los desarrolladores y la comunidad de TON se parecen más a un underground, lo cual también es genial.

Enlaces

  1. Documentación oficial sobre TON: https://test.ton.org
  2. Repositorio oficial de TON: https://github.com/ton-blockchain/ton
  3. Billetera oficial para diferentes plataformas: https://wallet.ton.org
  4. Repositorio del contrato inteligente de este artículo: https://github.com/raiym/astonished
  5. Enlace al sitio del contrato inteligente: https://ton-lottery.appspot.com
  6. Repositorio de la extensión para Visual Studio Code para FunC: https://github.com/raiym/func-visual-studio-plugin
  7. Chat sobre TON en Telegram, que ayudó mucho a entender en la etapa inicial. No creo que esté mal decir que allí están todos los que han escrito algo para TON. También se puede pedir prueba de gramos. https://t.me/tondev_ru
  8. Otro chat sobre TON en el que encontré información útil: https://t.me/TONgramDev
  9. Primera etapa del concurso: https://contest.com/blockchain
  10. Segunda etapa del concurso: https://contest.com/blockchain-2

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster