Lecciones aprendidas sobre la implementación de contratos inteligentes, Parte 2


Este es el segundo artículo de nuestra serie sobre la integración de canales de pago en Telegram Open Network. En la primera parte, presentamos la red, detallamos nuestra experiencia en el concurso y explicamos cómo funcionan los contratos inteligentes síncronos y asíncronos. Como la próxima adición a la serie, este artículo detalla cómo construimos un canal de pago sincrónico en la red durante el concurso de TON en septiembre. Aquí, hablaremos solo sobre Fift (lenguaje de programación de propósito general de TON) y FunC (lenguaje de programación de TON para escribir contratos inteligentes).

El libro blanco de TON proporciona información más detallada sobre los canales de pago, pero los explicaremos brevemente nuevamente.

Relacionado: Detrás de las escenas de TON: Lecciones aprendidas sobre la implementación de contratos inteligentes, Parte 1

Un canal de pago sincrónico permite enviar transacciones entre dos usuarios fuera de la cadena utilizando activos en la cadena. En nuestro caso – GRAMOS. Es imposible que una de las partes engañe a la otra fuera de la cadena, y las transacciones se realizan mucho más rápido que la ejecución de transacciones de blockchain de capa uno, ya que solo se utilizan dispositivos de usuario para completarlas sin tener que escribir en la cadena de bloques. Hay dos operaciones básicas: depositar y retirar. La retirada es la más difícil de implementar.

Para realizar un retiro correcto, los usuarios deben proporcionar la información más reciente sobre el estado de su canal. El estado consta de los pasos y las firmas digitales de cada participante, lo que significa que no es posible proporcionar un estado correcto con datos que no hayan sido aprobados por ambas partes.

Para implementar un contrato inteligente, debe escribir un script de implementación en Fift y compilarlo en un .boc (bolsa de celdas) archivo. Hacer esto crea múltiples celdas que se vincularán entre sí. Luego, los GRAM deben enviarse a la dirección que se recibió durante la ejecución del script de implementación. Una vez que los GRAM estén en la dirección, envíe el .boc

archivo a la red y se implementará el contrato.

Para realizar una llamada a la función, escriba un script que enviará un mensaje externo al contrato inteligente implementado.

Básicamente, cualquier cosa en TON es una celda con algunas referencias. Una bolsa de celdas es una estructura de datos que fue diseñada por el equipo de Telegram. Es un modelo de actor. Más detalles están en TON whitepaper: "Todo es una bolsa de células". Está creando una celda que interactuará con otra celda cuando se implemente.

Cada canal de pago entre pares es un único contrato inteligente. Echemos un vistazo a los segmentos de un contrato inteligente.

Relacionado: Qué esperar de la red abierta de Telegram: la perspectiva de un desarrollador

Parte de implementación

Se utiliza un script Fift serializado para implementar un contrato. Se guarda en un .boc archivo y enviado a la red a través de TON Cli, el cliente ligero de la red.

La última celda de la pila es el resultado de ejecutar el script Fift anterior.

Los segmentos habituales de un script de implementación de Fift incluyen (pero no se limitan a):

  1. Código del contrato inteligente como una sola celda (generalmente escrito en FunC, luego compilado en el código de Fift ASM e incluido en el principal .fif archivo usando path-to-compiled-asm.fif)
  2. Almacenamiento inicial del contrato inteligente (ver abajo).
  3. Nueva dirección de contrato inteligente (el hash del estado inicial del contrato inteligente que también incluye la celda del código del contrato inteligente y la celda de almacenamiento inicial).
  4. Argumentos de la primera llamada de la recv_external función (la cantidad de argumentos y tipo depende del contrato).
  5. Una celda de mensaje externo para la inicialización, que se serializará en bytes y se empaquetará en .boc archivo, que consta de todos los datos de los puntos 1 a 4 y algunos adicionales que aún carecen de documentación.

Cuando el .boc Cuando se compila, se debe enviar una cantidad específica de GRAM a la dirección del contrato inteligente. El archivo .boc debe enviarse a la red para inicializar el contrato inteligente. La cantidad de GRAM depende del tamaño y el volumen de los cálculos de la celda de mensaje externo del contrato inteligente implementado (no solo el código del mismo). El precio del gas × gas se toma del saldo del contrato inteligente desplegado. Este monto es el mínimo necesario para pagar el gas durante el despliegue.

Una representación del almacenamiento:

  1. seqno 32 bits
  2. estado del contrato 4 bits
  3. first_user_pubkey. La clave pública de la primera parte 256 bits
  4. second_user_pubkey. La clave pública de la segunda parte 256 bits
  5. time_to_send. Hora de enviar después del primer estado real que se envía 32 bits (válido hasta 2038)
  6. depositSum. La suma depositada de dos participantes hasta 121 bits
  7. estado_num 64 bits. La cantidad actual de estados que ocurrieron

Una celda contiene hasta 1023 bits y cuatro referencias a otras celdas. Pudimos ajustar todo el almacenamiento en una celda sin una sola referencia. Nuestro almacenamiento puede ocupar un máximo de 765 bits.

Todos los estados de contrato inteligentes

0x0 – Estado de implementación

0x1 – Canal abierto y listo para depositar

0x2 – Depósito por usuario 1

0x3 – Depósito por usuario 2

0x4 – El depósito está bloqueado. Es posible proporcionar un estado al contrato inteligente

0x5 – El usuario 1 ha proporcionado el estado

0x6 – El usuario 2 ha proporcionado el estado

0x7 – El canal está cerrado.

Depositar

La función de depósito recibe un mensaje de una billetera simple (transferencia) con una carga útil adicional del cuerpo.

Depositando GRAM en el canal:

  1. El usuario genera una carga útil adicional del cuerpo que incluye un mensaje (por ejemplo, 1 bit) y su firma en un mensaje separado. .fif archivo.
  2. La carga útil del cuerpo se compila en un .boc archivo.
  3. La carga útil del cuerpo se carga desde esto .boc en un archivo .fif como referencia de "transferencia" de la celda del cuerpo (el .fif es responsable de transferir GRAM de la billetera).
  4. los recv_external La función se llama con argumentos (el monto del depósito y la dirección de destino del canal) cuando se compila .fif El archivo se envía a la red.
  5. los send_raw_message Se ejecuta la función. Los GRAM depositados y la carga útil adicional del cuerpo se envían a una dirección de destino de contrato inteligente del canal P2P.
  6. los recv_internal Se llama a la función del contrato inteligente del canal P2P. Los GRAM se reciben por contratos de canal.

Se puede llamar a la función de depósito si el estado del contrato inteligente del canal P2P es 0x1 o 0x2 o 0x3.

Código FunC que verifica el estado:

Solo los propietarios de las claves públicas (escritas en el almacenamiento inicial) pueden realizar un depósito. El contrato inteligente verifica la firma de cada mensaje interno que se recibirá a través del recv_internal función. Si el mensaje está firmado por uno de los propietarios de la clave pública, el estado del contrato cambia a 0x2 o 0x3 (0x2 si es la clave pública 1 y 0x3 si es clave pública 2). Si todos los usuarios han realizado un depósito, el estado del contrato cambia a 0x4 en la misma llamada de función.

El código FunC responsable de cambiar el estado del contrato:

Reembolso

Los fondos se pueden devolver si una contraparte no ha realizado un depósito a tiempo.

Para hacer eso, un usuario debe proporcionar su dirección y firma a través de un mensaje externo. Los fondos serán reembolsados ​​si la firma proporcionada pertenece a la clave pública 1 o la clave pública 2 (personas que hicieron un depósito) y el estado del contrato es 0x2 o 0x3.

Código FunC responsable de verificar la solicitud de reembolso:

Retirada

Cada persona debe proporcionar un estado de salida, la firma de este estado y la firma del mensaje del cuerpo.

Detalles del estado:

  1. Dirección de contrato inteligente (para excluir la posibilidad de ingresar el estado correcto del canal P2P anterior con los mismos participantes).
  2. El saldo final del primer participante.
  3. El saldo final del segundo participante.
  4. Número de estado

La firma del mensaje del cuerpo se almacena en el segmento principal, el estado se almacena en una referencia separada y las firmas de estado se almacenan como referencias a una referencia de "firmas" para evitar el desbordamiento de la celda.

Pasos de retirada:

  1. Verifique la firma del mensaje del cuerpo y determine el participante.

  1. Verifique que sea el turno del participante o que hayan pasado 24 horas desde el último estado ingresado. Escriba el turno del participante actual (0x5 o 0x6) al estado del contrato.

Un ejemplo de una firma correcta del mensaje del cuerpo para el propietario de first_user_pubkey:

Luego debemos verificar que la dirección del contrato inteligente escrita en el estado sea la dirección del contrato real:

A continuación, debemos verificar las firmas bajo el estado:

Después de eso, hay dos afirmaciones:

  1. El monto depositado del almacenamiento debe ser igual a la suma de los saldos totales de los participantes.
  2. El nuevo número de estado ingresado debe ser mayor o igual que el anterior.

En caso de new_state_num> state_num necesitamos almacenar new_state_num con lo nuevo time_to_send igual a ahora () + 86401 (24 horas desde la hora actual), y también escriba el estado real del contrato (0x5 si el primer participante realizó una llamada, de lo contrario 0x6)

En otro caso, si new_state_num == state_num necesitamos poner dos referencias adicionales a la referencia de "firmas" con las direcciones de cada participante y las firmas debajo de sus direcciones.

Si las firmas son correctas, los GRAM se retiran de una dirección y se colocan en la dirección del propietario.

Cada vez que se realiza una llamada exitosa, necesitamos almacenar todos los datos de almacenamiento, incluso si no cambian.

Problemas sin resolver

La suposición es que el primer usuario implementó el contrato y los participantes acordaron comisiones. El acuerdo sobre comisiones en nuestro caso está llegando fuera de cadena.

Todavía no hemos descubierto cómo calcular la comisión total, teniendo en cuenta el hecho de que los jugadores pueden escribir un estado irrelevante y registrar estados reales después de eso. Tenga en cuenta que debemos pagar las tarifas del contrato inteligente del canal P2P cada vez que llamamos con éxito recv_internal o recv_external funciones

Como se mencionó anteriormente, necesitamos agregar una cierta cantidad de GRAM a una dirección de contrato inteligente futura no rebotable para poder inicializarla.

El último día de la competencia, los desarrolladores de TON hecho un compromiso con el stdlib.fc biblioteca con una nueva función que permite obtener el saldo real del contrato inteligente.

¡Las sugerencias para posibles soluciones a este problema son bienvenidas!

Conclusión

FunC y Fift permiten a cualquier desarrollador acceder al mundo de bajo nivel de ingeniería de software, abriendo nuevas oportunidades y características para los desarrolladores de blockchain que ya se han acostumbrado a Ethereum o cualquier otra plataforma de contrato inteligente. Es importante que TON sea una blockchain fragmentada, por lo que implementar contratos inteligentes es más desafiante. Por ejemplo, los contratos de Ethereum se ejecutan sincrónicamente y no requieren situaciones de manejo como esperar una respuesta de otro contrato.

La forma asincrónica de comunicación de contrato inteligente es la única opción para hacerlo escalable, y TON tiene estas opciones. Nuestra solución terminó siendo más difícil de implementar que Solidity, pero siempre hay una compensación. Definitivamente es posible construir un contrato inteligente avanzado en TON, y la forma en que el equipo de TON lo manejó es muy impresionante. Esperamos ver más bibliotecas y herramientas que ayudarán a implementar y construir contratos FunC.

Disfrutamos muchísimo de todas las tareas y deseamos haber tenido más tiempo para implementarlas. Sin embargo, ganamos dos premios en el Concurso TON: primer lugar para el mejor canal de pago sincrónico, así como el tercer lugar para el mejor canal de pago asincrónico.

Compartiremos nuestros propios comentarios personales en la tercera parte.

Los puntos de vista, pensamientos y opiniones expresados ​​aquí son solo de los autores y no necesariamente reflejan o representan los puntos de vista y opiniones de Cointelegraph.

Este artículo fue escrito por Nick Kozlov y Kirill Kuznetsov.

Nick Kozlov es el CTO y cofundador de Button Wallet, un desarrollador e investigador de software, así como uno de los ganadores del concurso TON.

Kirill Kuznetsov es el cofundador de Button Wallet, así como uno de los ganadores del concurso TON.

LO MÁS LEÍDO
Heaven32: