Move nació en las primeras etapas del proyecto Libra en 2018: los dos fundadores de Mysten (Evan y yo) también fuimos el equipo fundador de Libra. Antes de que decidiéramos crear un nuevo lenguaje, el primer equipo de Libra estudió intensamente los lenguajes y casos de uso de contratos inteligentes existentes para comprender qué querían hacer los desarrolladores y dónde no funcionaban los lenguajes existentes. El problema clave que descubrimos es que los contratos inteligentes tienen que ver con activos y control de acceso, pero los primeros lenguajes de contratos inteligentes carecían de representación de tipo/valor para ambos. Nuestra hipótesis es que si proporcionamos abstracciones de primera clase para estos conceptos clave, podemos mejorar en gran medida la seguridad de los contratos inteligentes y la productividad del programador de contratos inteligentes; tener el vocabulario adecuado para la tarea en cuestión puede marcar la diferencia. A lo largo de los años, muchas personas han contribuido al diseño y la implementación de Move a medida que el lenguaje evolucionó desde una idea clave hasta un lenguaje de contrato inteligente independiente de la plataforma con el audaz objetivo de convertirse en el "JavaScript de web3".

Hoy nos complace anunciar un hito en la integración de Move y Sui. La funcionalidad de Sui Move es completa, está respaldada por herramientas avanzadas y cuenta con documentación y ejemplos extensos, incluidas las siguientes secciones:

Una serie de tutoriales sobre programación utilizando objetos Sui Move. Un documento de desarrollo sobre los conocimientos básicos, patrones de diseño y muestras de Sui Move. Un complemento de mejora de VSCode desarrollado por el equipo de Mysten Move, que admite análisis de código y diagnóstico de errores, e integra. la construcción, prueba y gestión de paquetes de Move, generación de documentación y validador de Move integrado con sui CLI.

¿Qué hace que Move sea único?

Move es un lenguaje integrado multiplataforma. La sintaxis central en sí es muy simple: tiene conceptos comunes como estructuras, números enteros y direcciones, pero no tiene conceptos específicos de blockchain como cuentas y transacciones), tiempo, criptografía, etc. Estas funciones deben ser proporcionadas por la plataforma blockchain integrada con Move. Es importante destacar que estas cadenas de bloques no requieren sus propias bifurcaciones Move: cada plataforma utiliza la misma máquina virtual Move, validador de código de bytes, compilador, validador, administrador de paquetes y CLI, pero al construir código sobre estos componentes principales para agregar funcionalidad específica de la cadena de bloques. . Diem fue la primera cadena de bloques en incorporar Move, y las cadenas de bloques posteriores basadas en Move (incluidas 0L, StarCoin y Aptos) adoptaron en su mayoría un enfoque de estilo Diem. Si bien Diem-style Move tiene excelentes cualidades, tanto la naturaleza autorizada de Diem como ciertos detalles de implementación de la cadena de bloques de Diem (especialmente el modelo de almacenamiento) hacen que algunos casos de uso básicos de contratos inteligentes sean difíciles de implementar. En particular, los diseños originales de Move and Diem son anteriores a la explosión de popularidad de las NFT y tienen algunas peculiaridades que hacen que la implementación de casos de uso relacionados con NFT sea particularmente complicada. En esta publicación, demostraremos los problemas con la incrustación original de Move estilo Diem a través de tres ejemplos de este tipo y describiremos cómo resolvimos este problema en Sui Move. Asumimos algunos conocimientos básicos de Move, pero esperamos que estos puntos clave sean comprensibles para cualquier persona con experiencia en programación.

Experiencia sedosa al crear activos a escala

La capacidad de crear y distribuir activos de forma masiva es fundamental tanto para incorporar como para atraer a los usuarios de web3. Tal vez un transmisor de Twitch quiera distribuir NFT conmemorativos, un creador quiera enviar entradas para un evento especial o un desarrollador de juegos quiera lanzar nuevos elementos a todos los jugadores. Aquí hay un intento (fallido) de escribir código para la acuñación masiva de activos en Move estilo Diem. Este código toma como entrada un vector de direcciones de destinatarios, genera un activo para cada destinatario e intenta transferir el activo.

En un movimiento estilo Diem, el almacenamiento global se escribe por pares (dirección, nombre de tipo), es decir, cada dirección puede almacenar como máximo un activo de un tipo específico. Por lo tanto, move_to(receiverient, CoolAsset {...} intenta mover CoolAsset almacenándolo en la dirección del receptor. Sin embargo, este código no se compila en la línea move_to(receiverient, ...). La pregunta clave es, en En un Move estilo Diem, no puede enviar un valor de tipo CoolAsset a una dirección A, a menos que se envíe una transacción desde una dirección distinta a A, y el propietario de una cuenta creada en A envíe una transacción y elija explícitamente recibir CoolAsset. tipo de objeto. Estas son dos transacciones, ¡solo para recibir un activo! La decisión de hacer esto tiene sentido para Diem, que es un sistema autorizado que necesita limitar cuidadosamente la creación de cuentas y evitar que las cuentas estén limitadas por el sistema de almacenamiento. Sin embargo, en lugar de mantener demasiados activos, para un sistema abierto que quiere utilizar la asignación de activos como un mecanismo de incorporación, o simplemente permitir que los activos fluyan libremente entre los usuarios, como lo hacen en Ethereum y cadenas de bloques similares, es muy limitado. .

El código para implementar la misma función en Sui Move es el siguiente:

El almacenamiento global de Sui Move está codificado por ID de objeto. Cada estructura con pares clave-valor es un "objeto Sui", que debe tener un campo de identificación único a nivel mundial. Sui Move introduce una primitiva de transferencia que se puede usar en cualquier objeto Sui, en lugar de usar la estructura restrictiva move_to. En el fondo, esta primitiva asigna la identificación a un CoolAsset en el almacenamiento global y agrega metadatos para indicar que el valor es propiedad del destinatario. Una propiedad interesante de la versión Sui de mass_mint es que se intercambia con todas las demás transacciones (¡incluidas otras transacciones que llaman a mass_mint!). El tiempo de ejecución de Sui notará esto y enviará transacciones que llamen a esta función a través de la "ruta rápida" de transmisión de consenso bizantino que no requiere consenso. Estas transacciones pueden comprometerse o ejecutarse en paralelo. Esto no requiere ningún esfuerzo por parte del programador (simplemente escriben el código anterior y el tiempo de ejecución maneja el resto). Quizás sutilmente, este no es el caso con la variante Diem de este código; aunque el código anterior funciona, existe. y las llamadas guid::create crearán puntos de discordia con otras transacciones que generan GUID o tocan recursos de la cuenta. En algunos casos, es posible reescribir el código Move estilo Diem para evitar el argumento, pero muchas de las formas convencionales de escribir Move estilo Diem introducen pequeños obstáculos que dificultan la ejecución paralela.

Propiedad y transferencias de activos locales

Ampliemos el código Move estilo Diem con una solución alternativa que realmente se compila y se ejecuta. La forma habitual de hacer esto es el "patrón contenedor": dado que Bob no puede mover el CoolAsset directamente a la dirección de Alice, le pedimos a Alice que "se inscriba" para recibir el CoolAsset, publicando primero un tipo contenedor CoolAssetStore, que contiene un tipo Colección. (mesa). Alice puede hacer esto llamando a la función opt_in. Luego agregamos código que le permite a Bob mover el CoolAsset de su CoolAssetStore al CoolAssetStore de Alice. En este código, agreguemos un detalle adicional: solo permitiremos transferencias de CoolAssets si se crearon hace al menos 30 días. Este tipo de política es importante para los creadores que (por ejemplo) quieren evitar que los especuladores compren o promocionen entradas para eventos para que sea más fácil para los verdaderos fans conseguir esas entradas a un precio razonable.

Este código es válido. ¡Pero esta es una forma bastante complicada de realizar la tarea de transferir activos de Alice a Bob! Echemos un vistazo a otra implementación de Sui Move.

Este código es mucho más corto. Lo clave a tener en cuenta aquí es que cool_transfer es una función de entrada (lo que significa que puede ser llamada directamente por el tiempo de ejecución de Sui a través de una transacción), sin embargo, tiene un parámetro de tipo CoolAsset como entrada. Esta es nuevamente la magia de Sui Runtime. Una transacción incluye un conjunto de ID de objeto sobre los que quiere operar, y cuando Sui Runtime:

Analice el ID en un valor de objeto (no es necesario utilizar las partes lend_global_mut y table_remove en el código estilo Diem anterior). Comprueba si el objeto es propiedad del remitente de la transacción (elimina la necesidad de la sección signer::address_of y el código relacionado anterior). Esta parte es particularmente interesante y la explicaremos pronto: en Sui, la verificación segura de la propiedad del objeto es parte del tiempo de ejecución. Verifique el tipo del valor del objeto de acuerdo con el tipo de parámetro de la función llamada cool_transfer. a cool_transfer parámetros y llamar a la función.

Esto permitió a los programadores de Sui Move omitir la plantilla para la parte de la lógica de "Retiro" y saltar directamente a la parte divertida: verificar la política de vencimiento de 30 días. Asimismo, la parte del "depósito" también se simplifica enormemente con la estructura de transferencias de Sui Move explicada anteriormente. Finalmente, no es necesario introducir un tipo contenedor con una colección interna como CoolAssetStore: el almacén global Sui indexado por ID permite que una dirección almacene cualquier número de valores de un tipo determinado. Otra diferencia a destacar es que cool_transfer estilo Diem tiene 5 formas de cancelar (es decir, falla y cobra al usuario por el combustible sin completar la transferencia), mientras que Sui Move cool_transfer tiene solo 1 forma de cancelar: cuando se viola la política de 30 días. Descargar las comprobaciones de propiedad de objetos al tiempo de ejecución es una gran victoria, no sólo en términos de ergonomía, sino también en términos de seguridad. La implementación de seguridad en el nivel de tiempo de ejecución evita errores al implementar estas comprobaciones en la construcción (¡o olvidarse de ellas por completo!). Finalmente, observe que la firma de función de punto de entrada de Sui Move cool_transfer( active: CoolAsset, ...) nos brinda mucha información sobre lo que hará esta función (es más opaca que las firmas de funciones de estilo Diem). Podemos pensar que esta función solicita permiso para transferir CoolAsset, mientras que otra función f(asset: &mut CoolAsset,...) solicita permiso para escribir (pero no transferir) CoolAsset, y g(asset: &CoolAsset,...) solo está pidiendo permiso de lectura.

Debido a que esta información está disponible directamente en la firma de la función (¡no se requiere ejecución ni análisis estático!), las billeteras y otras herramientas del cliente pueden usarla directamente. En Sui Wallet, estamos trabajando en solicitudes de firma legibles por humanos, aprovechando estas firmas de funciones estructuradas para proporcionar solicitudes de permiso al estilo iOS/Android a los usuarios. La billetera puede decir algo como: "Esta transacción requiere permiso para leer su CoolAsset, escribir en su AssetCollection y transferir su ConcertTicket. ¿Continuar?".

Las solicitudes de firmas legibles por humanos resuelven un vector de ataque a gran escala presente en muchas plataformas existentes, incluidas aquellas que utilizan Move! estilo Diem, donde los usuarios de billeteras deben firmar transacciones a ciegas sin comprender el impacto que pueden tener. Creemos que hacer que la experiencia de la billetera sea menos riesgosa es un paso crítico para promover la adopción generalizada de billeteras de criptomonedas, y diseñamos Sui Move para respaldar este objetivo al habilitar funciones como solicitudes de firmas legibles por humanos.

Agrupar diferentes activos

Finalmente, consideremos un ejemplo de cómo agrupar diferentes tipos de activos. Este es un caso de uso bastante común: es posible que los programadores quieran empaquetar diferentes tipos de NFT en una colección, agrupar artículos para venderlos en un mercado o agregar archivos adjuntos a artículos existentes. Supongamos que tenemos la siguiente situación:

Alice define un objeto de personaje para usar en el juego. Alice quiere apoyar la decoración de su personaje con diferentes tipos de accesorios de terceros que se crean posteriormente. Cualquiera debería poder crear un accesorio, pero el propietario de un personaje debería decidir si desea agregar un accesorio. La transferencia de un personaje debería transferir automáticamente todos sus accesorios.

Esta vez, comencemos con el código de Sui Move. Aprovecharemos otro aspecto de la funcionalidad de propiedad de objetos integrada en el tiempo de ejecución de Sui: un objeto puede ser propiedad de otro objeto. Cada objeto tiene un propietario único, pero un objeto principal puede tener cualquier número de objetos secundarios. La relación de objeto padre/hijo se crea utilizando la función transfer_to_object, que es el objeto asociado de la función de transferencia presentada anteriormente.

En este código, el módulo de rol incluye una función de acceso que permite al propietario del rol agregar un objeto accesorio de cualquier tipo como objeto secundario. Esto permite a Bob y Clarissa crear sus propios tipos de adornos con diferentes propiedades y funcionalidades que Alice no tiene, pero se basa en lo que Alice ya ha hecho. Por ejemplo, la camisa de Bob solo se puede equipar si es el color favorito del personaje, y la espada de Clarissa solo se puede empuñar si el personaje es lo suficientemente poderoso. La siguiente es una demostración práctica de Diem-style Move, pero ninguna de ellas tuvo éxito, es decir, Diem-style Move no puede lograr el escenario anterior.

Se puede ver que los problemas en Diem-style Move son los siguientes

Sólo se admiten colecciones del mismo tipo (como mostró la primera prueba), pero los accesorios no son fundamentalmente un tipo de objeto. La relación entre objetos sólo se puede lograr mediante el "envoltorio" (es decir, almacenar un objeto dentro de otro). la colección de objetos que se pueden empaquetar debe estar predefinida (como en el segundo intento) o agregarse de manera temporal, y no se admite la composición de objetos adicionales (como en la tercera prueba).

Resumir

Sui es la primera plataforma que se desvía significativamente del diseño original de Diem en la forma en que se utiliza Move. Diseñar una combinación que aproveche al máximo Move y las capacidades únicas de la plataforma es tanto un arte como una ciencia, y requiere una comprensión profunda del lenguaje Move y las capacidades de la cadena de bloques subyacente. ¡Estamos muy entusiasmados con el progreso que está logrando Sui Move y los nuevos casos de uso que permitirá! ". [1] Otro argumento a favor de una política Move estilo Diem es que "debe suscribirse antes de poder recibir un tipo de activo específico". Este es un buen mecanismo para prevenir el spam. Sin embargo, pensamos en la prevención del spam. Como perteneciente a la capa de aplicación, en lugar de exigir a los usuarios que envíen transacciones que cuestan dinero real para optar por recibir activos, el spam se puede abordar fácilmente (por ejemplo) a nivel de billetera con políticas enriquecidas definidas por el usuario y filtros de spam automatizados. .