Composición del contrato

Dado el modelo Actor de envío de mensajes y consultas síncronas, tenemos todos los componentes en bruto necesarios para permitir la composición arbitraria de contratos tanto con otros contratos como con módulos nativos. Aquí explicaremos cómo encajan los componentes y cómo pueden ampliarse.

Terminologia

Ten en cuenta que los "Contratos" se refieren al código CosmWasm que se carga dinámicamente en la blockchain en una dirección determinada, mientras que los "Módulos Nativos" son módulos del SDK de Cosmos (escritos en Go) que se compilan en el binario de la blockchain.

Apoyamos la composición entre ambos tipos, pero debemos examinar la integración con "Módulos Nativos" más de cerca, ya que su uso puede causar problemas de portabilidad. Para minimizar este problema, proporcionamos algunas abstracciones en torno a los "Módulos".

Mensajes

Tanto init como handle pueden devolver un número arbitrario de objetos CosmosMsg, que serán reexpedidos en la misma transacción (proporcionando así éxito/retroceso atómico con la ejecución del contrato). Existen tres clases de mensajes:

  • Contract: Llamará a una dirección de contrato dada con un mensaje dado (proporcionado en forma serializada).

  • Module interfaces: Son interfaces estandarizadas que puede soportar cualquier cadena para exponer módulos nativos bajo una interfaz portable.

  • Custom: Esto encapsula una extensión dependiente de la cadena a los tipos de mensaje para llamar a módulos nativos personalizados. Idealmente, deberían ser inmutables en la misma cadena a lo largo del tiempo, pero no ofrecen garantías de portabilidad.

Consultas

Los contratos pueden realizar consultas síncronas de sólo lectura al tiempo de ejecución circundante. De forma similar a los Mensajes, tenemos tres tipos fundamentales:

  • Contract: consulta una dirección de contrato dada con un mensaje dado (proporcionado en forma serializada).

  • Interfaces de módulos: Son interfaces estandarizadas que puede soportar cualquier cadena para exponer módulos nativos bajo una interfaz portable.

  • Custom:Encapsula una extensión dependiente de la cadena para consultar módulos nativos personalizados. Idealmente, deberían ser inmutables en la misma cadena a lo largo del tiempo, pero no ofrecen garantías de portabilidad.

Las consultas entre contratos toman la dirección del contrato y un QueryMsg serializado en el formato específico del contrato, y reciben de forma sincrónica un valor de retorno binario serializado en el formato específico del contrato. Depende del contrato llamante entender los formatos apropiados. Para simplificar esto, podemos proporcionar algunas envolturas de tipo seguro específicas del contrato, de la misma manera que proporcionamos un simple método query_balance como una envoltura alrededor de la implementación de la consulta proporcionada por el Trait.

Modulos

Para permitir mejores integraciones con la cadena de bloques nativa, estamos proporcionando un conjunto de interfaces de módulos estandarizados que deberían funcionar de forma coherente en todas las cadenas CosmWasm. El más básico es el módulo Bank, que proporciona acceso a los tokens nativos subyacentes. Esto nos proporciona BankMsg::Send, así como BankQuery::Balance y BankQuery::AllBalances para comprobar saldos y mover tokens.

El segundo módulo estandarizado es Staking, que proporciona algunos mensajes estandarizados para Delegar, Desdelegar, Redelegar y Retirar, así como para consultar Validadores y Delegaciones. Estas interfaces están diseñadas para soportar la mayoría de los sistemas PoS y se pueden utilizar con cualquier sistema PoS, no sólo con el actual módulo staking del SDK de Cosmos.

Este enfoque proporciona un diseño limpio, donde se puede desarrollar un contrato que puede funcionar en dos blockchains diferentes, incluso si ambos están muy personalizados y se ejecutan en diferentes versiones de Cosmos SDK. El inconveniente es que cada interfaz de módulo debe añadirse a todas las capas de la pila, lo que puede causar algún retraso en el soporte de características personalizadas, y no podemos añadir fácilmente soporte para cada módulo personalizado en cada blockchain basada en Cosmos SDK.

Dicho esto, es muy recomendable utilizar la interfaz de módulos cuando sea posible y utilizar tipos personalizados como medida temporal para mantener un ciclo de lanzamiento rápido.

Personalizacion

Muchas cadenas querrán permitir que los contratos se ejecuten con sus módulos Go personalizados, sin pasar por todo el trabajo de intentar convertirlos en una Interfaz de Módulo estandarizada y esperar a una nueva versión de CosmWasm. Para este caso, introducimos la variante Custom tanto en CosmosMsg como en QueryRequest. La idea aquí es que el contrato puede definir el tipo que se incluirá en las variantes personalizadas, y sólo tiene que ser acordado por la aplicación Cosmos SDK (en Golang) en el otro lado. Todo el código entre el contrato y el código blockchain lo trata como bytes JSON opacos.

Consideraciones sobre el diseño

Para producir un diseño sólido, necesitamos cumplir estos requisitos:

Portabilidad

El mismo contrato debería poder ejecutarse en dos blockchains distintas, con diferentes módulos Golang, y diferentes versiones del SDK de Cosmos (o idealmente, incluso basado en diferentes frameworks, por ejemplo, ejecutándose en Substrate). Esto debería ser posible si se evitan los mensajes personalizados y se comprueban las características opcionales que uno utiliza. Las características son actualmente Staking, que asume un sistema PoS, e iterator, que asume que podemos hacer escaneos de prefijos sobre el almacenamiento (es decir, es un Merkle Tree, no un Merkle Trie).

Inmutabilidad

Los contratos son inmutables y codifican los formatos de consulta y mensaje en su bytecode. Si permitiéramos despachar sdk.Msg en el formato nativo (ya sea JSON, Amino, o Protobuf), y el formato de los mensajes nativos cambia, entonces los contratos se romperían. Esto puede significar que un módulo de estacado nunca podría deshacer la delegación de los tokens. Si crees que esto es un problema teórico, ten en cuenta que cada actualización importante del SDK de Cosmos ha producido tales cambios de ruptura y tiene migraciones para ellos, migraciones que no se pueden realizar en contratos inmutables. Por lo tanto, tenemos que asegurarnos de que nuestro diseño proporciona una API inmutable a un tiempo de ejecución potencialmente mutable, que es un criterio de diseño primario al diseñar las interfaces del módulo estándar.

Extensibilidad

Debemos ser capaces de añadir nuevas interfaces a los contratos y blockchains sin necesidad de actualizar ninguna de las capas intermedias. Consideremos el ejemplo en el que estás construyendo una aplicación blockchain personalizada de superd que importa x/wasm de wasmd y quiere desarrollar contratos en ella que llamen a tus módulos personalizados de superd. El escenario ideal es que puedas añadir esos tipos de mensajes a una librería adicional cw-superd que puedas importar en tus contratos y añadir las llamadas de retorno a ellos en código superd Go, sin ningún cambio en los repositorios cosmwasm-std, cosmwasm-vm, go-cosmwasm, o wasmd.

Se proporcionan variantes personalizadas de CosmosMsg y QueryRequest para permitir estos casos.

Usabilidad

El uso de la codificación JSON para los mensajes CosmWasm simplifica la exportación de esquemas JSON para cada contrato con el fin de permitir la autogeneración de códecs del lado del cliente.

Comprobación de compatibilidad

Puede utilizar indicadores de características, expuestos como funciones de exportación wasm, para configurar qué características adicionales requiere el contrato. Esto permite a la cadena anfitriona inspeccionar la compatibilidad antes de permitir una carga. Para ello, hacemos algunas exportaciones "fantasma" como requires_staking(). Esto sólo pasaría una comprobación de compatibilidad si el tiempo de ejecución también expusiera dichas características. Al instanciar x/wasm.NewKeeper(), puedes especificar qué características son compatibles.

Envolturas seguras

Cuando consultamos o llamamos a otros contratos, renunciamos a todas las comprobaciones de tipo que obtenemos con las interfaces de módulos nativos, lo que significa que quien llama necesitaría conocer los detalles. Es útil tener envolturas de "interfaz" que un contrato pueda exportar para que otros contratos puedan llamarlo fácilmente.

Por ejemplo:

pub struct NameService(CanonicalAddr);
impl NameService {
  pub fn query_name(deps: &Extern, name: &str) -> CanonicalAddr { /* .. */ }
  pub fn register(api: &Api, name: &str) -> CosmosMsg { /* .. */ }
}

En lugar de almacenar sólo la CanonicalAddr del otro contrato en nuestra configuración, podríamos almacenar NameService, que es un "newtype" de coste cero sobre la dirección original, con el mismo formato de serialización. Sin embargo, nos proporcionaría algunos ayudantes de tipo seguro para hacer consultas contra el contrato, así como producir CosmosMsg para el registro.

Nótese que estas envolturas de tipo seguro no están vinculadas a una implementación de un contrato, sino a la interfaz del contrato. Así, podríamos crear una pequeña biblioteca con una lista de interfaces estándar/populares (como las especificaciones ERCxxx) representadas con tales "newtypes". Un creador de contratos podría importar una de estas envolturas y luego llamar fácilmente al contrato, independientemente de la implementación, siempre y cuando admita la interfaz adecuada.

Last updated