Inmersión en movimiento - lenguaje de programación blockchain Libra de Facebook

A continuación, examinaremos en detalle las características principales del lenguaje Move y cuáles son sus diferencias clave con otro lenguaje ya popular para contratos inteligentes: Solidity (en la plataforma Ethereum). El material se basa en un estudio de un documento técnico en línea disponible de 26 páginas.

Introduccion


Move es un lenguaje de código de bytes ejecutable que se utiliza para ejecutar transacciones de usuario y contratos inteligentes. Presta atención a dos puntos:

  1. Mientras Move es un lenguaje de código de bytes que se puede ejecutar directamente en la máquina virtual Move, Solidity (lenguaje de contrato inteligente en Ethereum) es un lenguaje de nivel superior que primero se compila en código de bytes antes de ejecutarse en EVM (Máquina virtual de Ethereum )
  2. Move puede usarse no solo para la implementación de contratos inteligentes, sino también para transacciones de usuarios (más sobre esto más adelante), mientras que Solidity es un lenguaje solo para contratos inteligentes.

Traducción realizada por el equipo del proyecto INDEX Protocol. Anteriormente tradujimos una gran cantidad de material que describe el proyecto Libra , ahora es el turno de mirar el lenguaje Move un poco más en profundidad. La traducción se realizó en conjunto con coolsiu

Una característica clave de Move es la capacidad de definir tipos de recursos personalizados con semántica de lógica lineal: un recurso nunca puede copiarse o eliminarse implícitamente, solo moverse. Funcionalmente, esto es similar a las capacidades del lenguaje Rust. Los valores en Rust solo se pueden asignar a un nombre a la vez. Asignar un valor a otro nombre lo hace inaccesible bajo el nombre anterior.



Por ejemplo, el siguiente fragmento de código arrojará un error: Uso del valor movido 'x'. Esto se debe a que no hay recolección de basura en Rust. Cuando las variables están fuera de alcance, la memoria a la que se refieren también se libera. En pocas palabras, solo puede haber un "propietario" de los datos. En este ejemplo, x es el propietario original y luego y se convierte en el nuevo propietario. Lea más sobre este comportamiento aquí .

Representación de activos digitales en sistemas abiertos.


Hay dos propiedades de los activos físicos que son difíciles de representar digitalmente:

  • Rareza (escasez, en el original - escasez). Se debe controlar la cantidad de activos (emisión) en el sistema. La duplicación de los activos existentes debe estar prohibida, y la creación de nuevos es una operación privilegiada.
  • Control de acceso . El participante del sistema debe poder proteger los activos con políticas de control de acceso.

Estas dos características, que son naturales para los activos físicos, deben implementarse para los objetos digitales, si queremos considerarlos como activos. Por ejemplo, un metal raro: tiene un déficit natural, y solo usted tiene acceso a él (por ejemplo, sosteniéndolo en sus manos) y puede venderlo o gastarlo.

Para ilustrar cómo llegamos a estas dos propiedades, comencemos con las siguientes oraciones:

Proposición 1: la regla más simple sin escasez y control de acceso




  • G [K]: = n significa actualizar el número accesible por la tecla K en el estado global de la cadena de bloques con el nuevo valor n .
  • La transacción ⟨Alice, 100⟩ significa establecer el saldo de la cuenta de Alice en 100.

La solución anterior tiene varios problemas graves:

  • Alice puede recibir un número ilimitado de monedas simplemente enviando la transacción liceAlice, 100⟩.
  • Las monedas que Alice envía a Bob son inútiles, ya que Bob podría enviarse un número ilimitado de monedas utilizando la misma técnica.

Propuesta No. 2: Tomamos en cuenta el déficit




Ahora estamos monitoreando la situación para que el número de monedas Ka sea ​​al menos n antes de la transacción de transferencia. Sin embargo, aunque esto resuelve el problema del déficit, no hay información sobre quién puede enviar las monedas de Alice (hasta ahora, todos pueden hacerlo, lo principal es no violar la regla de los límites de cantidad).

Proposición 3: combinación de déficit y control de acceso




Resolvemos este problema con el mecanismo de firma digital execute_sig antes de verificar el saldo, lo que significa que Alice usa su clave privada para firmar la transacción y confirmar que posee sus monedas.

Lenguajes de programación blockchain


Los lenguajes de blockchain existentes enfrentan los siguientes problemas (todos se resolvieron en Move (nota: desafortunadamente, el autor del artículo solo apela a Ethereum en sus comparaciones, por lo que debe tomarlos solo en este contexto. Por ejemplo, la mayoría de los siguientes también se resuelven en EOS) )):

Representación indirecta de activos . Un activo se codifica utilizando un entero, pero un valor entero no es lo mismo que un activo. De hecho, ¡no hay ningún tipo o valor que represente bitcoin / ether / <Any Coin>! Esto hace que los programas de escritura que usan activos sean difíciles y propensos a errores. Los patrones como la transferencia de activos a / desde procedimientos o el almacenamiento de activos en estructuras requieren un soporte de lenguaje especial.

El déficit no es expandible . El lenguaje representa solo un activo escaso. Además, los remedios contra el déficit están conectados directamente a la semántica del lenguaje mismo. Un desarrollador, si quiere crear un activo de usuario, debe monitorear cuidadosamente todos los aspectos del recurso él mismo. Estos son solo los problemas de los contratos inteligentes de Ethereum.

Los usuarios emiten sus activos, tokens estándar ERC-20, utilizando números enteros para determinar tanto el costo como la emisión total. Cada vez que se crean nuevos tokens, el código de contrato inteligente debe verificar independientemente el cumplimiento de las normas de emisión. Además, la representación indirecta de los activos conduce, en algunos casos, a errores graves: duplicación, doble gasto o incluso una pérdida completa de los activos.

Falta de control de acceso flexible . La única política de control de acceso actualmente en uso es un esquema de firma que utiliza criptografía asimétrica. Al igual que la protección contra el déficit, las políticas de control de acceso están profundamente arraigadas en la semántica del lenguaje. Pero cómo expandir el lenguaje para permitir que los programadores definan sus propias políticas de control de acceso es a menudo una tarea muy trivial.

Esto también es cierto para Ethereum, donde los contratos inteligentes no tienen soporte criptográfico nativo para el control de acceso. Los desarrolladores deben prescribir manualmente el control de acceso, por ejemplo, utilizando el modificador onlyOwner.

Aunque soy un gran admirador de Ethereum, creo que las propiedades de los activos deben ser compatibles de forma nativa con el lenguaje por razones de seguridad. En particular, la transferencia de Ether a un contrato inteligente implica un despacho dinámico, lo que ha llevado a la aparición de una nueva clase de errores conocidos como vulnerabilidades de reincorporación. El despacho dinámico aquí significa que la lógica de ejecución del código se determinará en tiempo de ejecución (dinámico) y no en tiempo de compilación (estático).

Por lo tanto, en Solidity, cuando el contrato A llama a la función del contrato B, el contrato B puede ejecutar código que no fue proporcionado por el desarrollador del contrato A, lo que puede conducir a vulnerabilidades de reingreso (el contrato A realiza accidentalmente la función del contrato B para retirar dinero antes de la deducción real saldos de cuenta).

Mover los conceptos básicos del diseño del lenguaje


Recursos de primer orden


Hablando a un nivel superior, la interacción entre módulos / recursos / procedimientos en el lenguaje Move es muy similar a las relaciones entre clases / objetos y métodos en los lenguajes OOP.
Los módulos en Move son similares a los contratos inteligentes en otras cadenas de bloques. El módulo declara tipos de recursos y procedimientos que establecen las reglas para crear, destruir y actualizar los recursos declarados. Pero todo esto son solo convenciones (" jerga ") en Move. Un poco más adelante ilustraremos este punto.

Flexibilidad


Move agrega flexibilidad a Libra a través de secuencias de comandos. Cada transacción en Libra incluye un script, que en realidad es el procedimiento de transacción principal. El script puede realizar una acción específica, por ejemplo, pagos de acuerdo con la lista especificada de destinatarios, o reutilizar otros recursos, por ejemplo, llamando a un procedimiento en el que se especifica la lógica general. Esta es la razón por la cual los scripts de transacciones Move ofrecen más flexibilidad. El script puede usar comportamientos repetitivos y únicos, mientras que Ethereum solo puede ejecutar scripts repetitivos (llamando a un método de contrato inteligente llamando a un método). La razón por la que se llama "múltiple" es porque las funciones de un contrato inteligente se pueden ejecutar varias veces. (nota: el momento es muy delicado aquí. Por un lado, los scripts de transacción en forma de pseudocódigo también están en Bitcoin. Por otro lado, según tengo entendido, Move expande este lenguaje, de hecho, al nivel de un lenguaje de contrato inteligente completo ).

Seguridad


El formato ejecutable Move es bytecode, que, por un lado, es un lenguaje de un nivel más alto que el ensamblador, pero un nivel más bajo que el código fuente. El bytecode se verifica en la cadena para la disponibilidad de recursos, tipos y seguridad de la memoria utilizando el verificador de bytecode, y luego el intérprete lo ejecuta. Este enfoque permite que Move proporcione seguridad específica para el código fuente, pero sin el proceso de compilación y la necesidad de agregar un compilador al sistema. Hacer mover un lenguaje de código de bytes es una muy buena solución. No es necesario compilarlo desde la fuente, como es el caso de Solidity, no hay que preocuparse por posibles bloqueos o ataques en la infraestructura del compilador.

Verificabilidad


Nuestro objetivo es realizar comprobaciones lo más fáciles posible, ya que todo esto se encadena (nota: en línea, en el proceso de cada transacción, por lo tanto, cualquier retraso conduce a una desaceleración de toda la red ), sin embargo, el diseño del lenguaje está inicialmente listo para usar y medios fuera de cadena de verificación estática. Aunque esto es más preferible, hasta ahora el desarrollo de herramientas de verificación (como un kit de herramientas separado) se ha pospuesto para el futuro, y ahora solo se admite la verificación dinámica en tiempo de ejecución (en cadena).

Modularidad


Los módulos de movimiento proporcionan abstracción de datos y localizan operaciones críticas en los recursos. La encapsulación provista por el módulo, combinada con la protección provista por el sistema de tipo Move, asegura que las propiedades establecidas para los tipos de módulo no puedan ser violadas por código externo al módulo. Este es un diseño de abstracción bien pensado, lo que significa que los datos dentro del contrato solo se pueden cambiar como parte de la ejecución del contrato, pero no desde el exterior.



Mover revisión


Un ejemplo de script de transacción demuestra que las acciones malintencionadas o imprudentes de un programador fuera de un módulo no pueden violar la seguridad de los recursos del módulo. A continuación, veremos ejemplos de cómo se utilizan los módulos, recursos y procedimientos para programar la cadena de bloques Libra.

Pagos de igual a igual




La cantidad de monedas especificada en la cantidad se transferirá del saldo del remitente al destinatario.
Hay varios puntos nuevos (resaltados en rojo):

  • 0x0 : dirección de la cuenta donde se almacena el módulo
  • Moneda : nombre del módulo
  • Moneda : tipo de recurso
  • El valor de la moneda devuelto por el procedimiento es un valor de recurso cuyo tipo es 0x0.Currency.Coin
  • move () : el valor no se puede volver a usar
  • copy () : el valor se puede usar más tarde

Analizamos el código: en el primer paso, el remitente llama a un procedimiento llamado retiro_desde_ender del módulo almacenado en 0x0 . Moneda . En la segunda etapa, el remitente transfiere los fondos al destinatario, trasladando el valor del recurso de la moneda al procedimiento de depósito del módulo 0x0 . Moneda .

Aquí hay tres ejemplos de errores de código que serán rechazados por las verificaciones:
Duplicación de fondos cambiando la llamada a mover (moneda) a copiar (moneda) . Los recursos solo se pueden mover. Intentar duplicar la cantidad de un recurso (por ejemplo, llamando a copy (coin) en el ejemplo anterior) dará como resultado un error al verificar el código de bytes.

Reutilización de fondos especificando movimiento (moneda) dos veces . Agregar la línea 0x0.Currency.deposit (copy (some_other_payee), move (coin)) para el ejemplo anterior permitirá al remitente "gastar" monedas dos veces, la primera vez con el beneficiario y la segunda con some_other_payee . Este es un comportamiento indeseable, imposible con un activo físico. Afortunadamente, Move rechazará este programa.

Pérdida de fondos debido a una falla en el movimiento (moneda) . Si no mueve el recurso (por ejemplo, al eliminar la línea que contiene mover (moneda) ), se generará un error al verificar el código de bytes. Esto protege a los programadores de Move de la pérdida accidental o maliciosa de fondos.

Módulo de moneda




Cada cuenta puede contener 0 o más módulos (representados como rectángulos) y uno o más valores de recursos (representados como cilindros). Por ejemplo, una cuenta en 0x0 contiene un módulo 0x0.Currency y un valor de recurso de tipo 0x0.Currency.Coin . La cuenta en 0x1 tiene dos recursos y un módulo; La cuenta en 0x2 tiene dos módulos y un valor de recurso.

Algunos puntos:

  • El script de transacción es atómico, ya sea completamente ejecutado o no se ejecuta en absoluto.
  • Un módulo es un código de larga duración disponible a nivel mundial.
  • El estado global está estructurado como una tabla hash, donde la clave será la dirección de la cuenta
  • Las cuentas pueden contener no más de un valor de recurso de este tipo y no más de un módulo con un nombre dado (una cuenta en 0x0 no puede contener un recurso adicional 0x0.Currency.Coin u otro módulo llamado Moneda )
  • La dirección del módulo declarado es parte del tipo ( 0x0.Currency.Coin y 0x1.Currency.Coin son tipos separados que no se pueden usar indistintamente)
  • Los programadores pueden almacenar varias instancias de este tipo de recurso en la cuenta definiendo su recurso personalizado - ( recurso TwoCoins {c1: 0x0.Currency.Coin, c2: 0x0.Currency.Coin} )
  • Puede hacer referencia a un recurso por su nombre sin conflictos, por ejemplo, puede hacer referencia a dos recursos utilizando TwoCoins.c1 y TwoCoins.c2 .

Anuncio de recursos de monedas



Un módulo llamado Moneda y un tipo de recurso llamado Moneda

Algunos puntos:

  • La moneda es una estructura de campo único de tipo u64 (entero sin signo de 64 bits)
  • Solo los procedimientos del módulo Moneda pueden crear o destruir valores de Moneda .
  • Otros módulos y scripts pueden escribir o hacer referencia al campo de valor solo a través de procedimientos abiertos proporcionados por el módulo.

Implementación de depósitos




Este procedimiento toma el recurso Coin como entrada y lo combina con el recurso Coin almacenado en la cuenta del destinatario:

  1. Destruir el recurso de entrada de monedas y registrar su valor.
  2. Obtener un enlace a un recurso Coin único almacenado en la cuenta del destinatario.
  3. Cambiar el valor de la cantidad de monedas por el valor pasado en el parámetro cuando se llama al procedimiento.

Algunos puntos:

  • Desempaquetar, BorrowGlobal : procedimientos integrados
  • Descomprimir <T> es la única forma de eliminar un recurso de tipo T. El procedimiento lleva el recurso a la entrada, lo destruye y devuelve el valor asociado con los campos del recurso.
  • BorrowGlobal <T> acepta la dirección como entrada y devuelve un enlace a una instancia única de T publicada (propiedad) por esta dirección
  • & mut Coin es un enlace al recurso Coin

Implemente retirada_desde_sender




Este procedimiento:

  1. Obtiene un enlace a un recurso Coin único vinculado a la cuenta del remitente
  2. Disminuye el valor del recurso Coin por referencia a la cantidad especificada
  3. Crea y devuelve un nuevo recurso Coin con un saldo actualizado.

Algunos puntos:

  • Cualquier persona puede llamar al depósito , pero retiw_from_sender solo tiene acceso a las monedas de la cuenta de llamadas
  • GetTxnSenderAddress es similar a msg.sender en Solidity
  • RejectUnless es similar a require en Solidity. Si esta verificación falla, la transacción se detiene y todos los cambios se revierten.
  • Pack <T> también es un procedimiento incorporado que crea un nuevo recurso de tipo T.
  • Al igual que Unpack <T> , Pack <T> solo se puede llamar dentro del módulo donde se describe el recurso T

Conclusión


Examinamos las características principales del lenguaje Move, lo comparamos con Ethereum y también nos familiarizamos con la sintaxis básica de los scripts. En conclusión, recomiendo mirar el libro blanco original . Incluye muchos detalles sobre los principios del diseño del lenguaje de programación, así como muchos enlaces útiles.

Source: https://habr.com/ru/post/459990/


All Articles