Sem谩ntica contractual
Este documento pretende aclarar la sem谩ntica de c贸mo un contrato CosmWasm interact煤a con su entorno. Existen dos tipos principales de acciones: las acciones mutantes, que reciben DepsMut y pueden modificar el estado de la blockchain, y las acciones de consulta, que se ejecutan en un 煤nico nodo con acceso de solo lectura a los datos.
Ejecucion
La siguiente secci贸n cubrir谩 c贸mo funciona la llamada a ejecuci贸n, pero es importante tener en cuenta que los mismos principios se aplican a otras acciones de mutaci贸n, como instantiate, migrate, sudo, etc.
Contexto SDK
Antes de examinar CosmWasm, primero deber铆amos explorar la sem谩ntica impuesta por el marco de blockchain con el que se integra: el SDK de Cosmos. Este marco se basa en el motor de consenso BFT de Tendermint. Para entenderlo mejor, examinemos primero c贸mo se procesan las transacciones antes de que lleguen a CosmWasm y despu茅s de que salgan.
En primer lugar, el motor Tendermint busca el consenso de 2/3+ sobre una lista de transacciones que se incluir谩n en el siguiente bloque. Esto se hace sin ejecutar las transacciones. Simplemente son sometidas a un pre-filtro m铆nimo por el m贸dulo SDK de Cosmos para asegurar que son transacciones formateadas v谩lidamente con suficientes tasas de gas y que est谩n firmadas por una cuenta con fondos suficientes para pagar las tasas. Esto significa que muchas transacciones que dan lugar a un error pueden incluirse en un bloque.
Una vez que se consigna un bloque (normalmente cada 5 segundos m谩s o menos), las transacciones se env铆an al SDK de Cosmos de forma secuencial para su ejecuci贸n. Cada transacci贸n devuelve un resultado o devuelve un error, junto con los registros de eventos, que se registran en la secci贸n TxResults del siguiente bloque. El AppHash (o Merkle proof o blockchain state) que se genera tras ejecutar el bloque tambi茅n se incluye en el siguiente bloque.
La BaseApp del SDK de Cosmos gestiona cada transacci贸n en un contexto aislado. En primer lugar, verifica todas las firmas y deduce las tasas de gas. Establece el "contador de gas" para limitar la ejecuci贸n a la cantidad de gas pagada por las tasas. Por 煤ltimo, crea un contexto aislado para ejecutar la transacci贸n. Esto permite al c贸digo leer el estado actual de la cadena una vez finalizada la 煤ltima transacci贸n, pero s贸lo escribe en una cach茅 que puede ser confirmada o revertida en caso de error.
Una transacci贸n puede consistir en m煤ltiples mensajes, y cada uno se ejecuta a su vez bajo el mismo contexto y el mismo l铆mite de gas. Si todos los mensajes tienen 茅xito, el contexto se registrar谩 en el estado subyacente de la cadena de bloques y los resultados de todos los mensajes se almacenar谩n en TxResult. Si un mensaje falla, se omiten todos los mensajes posteriores y se revierten todos los cambios de estado. Esto es crucial para la atomicidad.
Por ejemplo, si Alice y Bob firman una transacci贸n con dos mensajes, como Alice paga a Bob 1.000 ATOM y Bob paga a Alice 50 ETH, y Bob no tiene los fondos en su cuenta, el pago de Alice tambi茅n ser谩 revertido. Esto es similar a como funciona una transacci贸n t铆pica en una base de datos.
El x/wasm es un m贸dulo personalizado del SDK de Cosmos que procesa mensajes espec铆ficos y los utiliza para cargar, instanciar y ejecutar contratos inteligentes. En particular, acepta un MsgExecuteContract debidamente firmado y lo enruta a Keeper.Execute, que carga el contrato inteligente apropiado y llama a ejecutar en 茅l.
Tenga en cuenta que este m茅todo puede devolver 茅xito (con datos y eventos) o un error. En caso de error, se revertir谩 toda la transacci贸n del bloque. Este es el contexto en el que nuestro contrato recibe la llamada de ejecuci贸n.
Ejecuci贸n b谩sica
Al implementar un contrato, proporcionamos el siguiente punto de entrada:
Con DepsMut, esta funci贸n puede leer y escribir en el Almacenamiento de respaldo, as铆 como utilizar la Api para validar direcciones, y Consultar el estado de otros contratos o m贸dulos nativos. Una vez hecho esto, devuelve Ok(Response) o Err(ContractError). Examinemos lo que ocurre a continuaci贸n:
Si devuelve Err, este error se convierte en una representaci贸n de cadena (err.to_string()), y 茅sta se devuelve al m贸dulo SDK. Todos los cambios de estado son revertidos, y x/wasm devuelve este mensaje de error, que generalmente (ver excepci贸n submensaje m谩s abajo) abortar谩 la transacci贸n y devolver谩 este mismo mensaje de error al llamador externo.
Si devuelve Ok, entonces el objeto Response es analizado y procesado. Veamos las partes aqu铆:
En el SDK de Cosmos, una transacci贸n devuelve una serie de eventos al usuario, junto con un "resultado de datos" opcional. Este resultado se incluye en el hash del siguiente bloque para que sea comprobable y pueda proporcionar informaci贸n esencial sobre el estado. Sin embargo, las aplicaciones cliente suelen confiar m谩s en los eventos. El resultado tambi茅n se utiliza habitualmente para pasar informaci贸n entre contratos o m贸dulos en el SDK. Tenga en cuenta que el ResultHash incluye s贸lo el C贸digo (distinto de cero indica un error) y el Resultado (datos) de la transacci贸n. Aunque los eventos y registros son accesibles a trav茅s de consultas, no hay pruebas de cliente ligero disponibles para ellos.
Si el contrato establece datos, 茅stos se devolver谩n en el campo Resultado. Los atributos son una lista de pares {clave, valor}, que se a帽adir谩n a un evento por defecto.
El resultado final aparece as铆 para el cliente:
Env铆o de mensajes
Pasemos ahora al campo de los mensajes. Algunos contratos s贸lo necesitan hablar consigo mismos, como un contrato CW20 que simplemente ajusta sus saldos en las transferencias. Sin embargo, muchos contratos quieren mover tokens (nativos o CW20) o llamar a otros contratos para acciones m谩s complejas. Aqu铆 es donde entran en juego los mensajes. Devolvemos un CosmosMsg, que es una representaci贸n serializable de cualquier llamada externa que pueda hacer un contrato.
Por ejemplo, con la funci贸n stargate activada, tiene este aspecto:
Si un contrato devuelve dos mensajes (M1 y M2), ambos ser谩n parseados y ejecutados en x/wasm con los permisos del contrato (lo que significa que info.sender ser谩 el contrato, no el llamador original). Si vuelven con 茅xito, emitir谩n un nuevo evento con los atributos personalizados, y el campo de datos ser谩 ignorado. Cualquier mensaje que devuelvan tambi茅n ser谩 procesado. Si devuelven un error, la llamada padre devolver谩 un error, retrocediendo as铆 el estado de toda la transacci贸n.
Tenga en cuenta que los mensajes se ejecutan primero en profundidad. Esto significa que si el contrato A devuelve M1 (WasmMsg::Execute) y M2 (BankMsg::Send), y si el contrato B (del WasmMsg::Execute) devuelve N1 y N2 (por ejemplo, StakingMsg y DistributionMsg), los mensajes se ejecutar谩n en el siguiente orden: M1, N1, N2, M2.
Esto puede ser dif铆cil de entender al principio, y puede que te preguntes por qu茅 no puedes simplemente llamar a otro contrato. Sin embargo, CosmWasm hace esto para evitar uno de los agujeros de seguridad m谩s extendidos (y m谩s dif铆ciles de detectar) en los contratos de Ethereum: Reentrancy. CosmWasm hace esto siguiendo el modelo actor, que no anida llamadas a funciones, sino que devuelve mensajes que se ejecutar谩n m谩s tarde. Esto significa que todo el estado que se arrastra entre una llamada y la siguiente ocurre en el almacenamiento y no en la memoria. Para m谩s informaci贸n sobre este dise帽o, consulta el Modelo Actor.
Submensajes
A partir de CosmWasm 0.14 (Abril 2021), a帽adieron otra forma de despachar llamadas desde el contrato, debido a la petici贸n com煤n de poder obtener el resultado de uno de los mensajes que despachaste. Por ejemplo, ahora es posible crear un nuevo contrato con WasmMsg::Instantiate, y luego almacenar la direcci贸n del contrato reci茅n creado en la llamada con submensajes. Tambi茅n aborda un caso de uso similar de capturar resultados de errores, de modo que si ejecuta un mensaje desde, por ejemplo, un contrato cron, puede almacenar el mensaje de error y marcar el mensaje como ejecutado, en lugar de abortar toda la transacci贸n. Tambi茅n permite limitar el uso de gas del submensaje (esto no est谩 pensado para ser utilizado en la mayor铆a de los casos, pero es necesario para, por ejemplo, el contrato cron para protegerlo de un bucle infinito en el submensaje, que podr铆a quemar todo el gas y abortar la transacci贸n).
Esto hace uso de CosmosMsg como se mencion贸 anteriormente, pero lo envuelve dentro de un sobre SubMsg:
驴Cu谩l es la sem谩ntica de la ejecuci贸n de un submensaje? En primer lugar, creamos un contexto de subtransacci贸n alrededor del estado, permiti茅ndole leer el 煤ltimo estado escrito por el llamante, para escribir en otra cach茅. Si se establece gas_limit, se limita a la cantidad de gas que puede utilizar hasta que aborta con OutOfGasError. Este error es capturado y devuelto a la persona que llama como cualquier otro error devuelto de la ejecuci贸n del contrato (a menos que quem贸 todo el l铆mite de gas de la transacci贸n). Lo que es m谩s interesante es lo que ocurre al finalizar.
Si se devuelve con 茅xito, el estado temporal se consigna (en la cach茅 del llamante), y la Respuesta se procesa normalmente (se a帽ade un evento al Gestor de Eventos actual, y se ejecutan los mensajes y submensajes). Una vez que la Respuesta es procesada completamente, puede ser interceptada por el contrato llamante (para ReplyOn::Always y ReplyOn::Success). En caso de error, la llamada secundaria revertir谩 cualquier cambio de estado parcial debido a este mensaje, pero no revertir谩 ning煤n cambio de estado en el contrato de llamada. El error puede ser interceptado por el contrato de llamada (para ReplyOn::Always y ReplyOn::Error). En este caso, el mensaje de error no aborta toda la transacci贸n.
Tratamiento de la respuesta
Para poder utilizar submensajes, el contrato de llamada debe poseer un punto de entrada adicional:
Una vez finalizado el submensaje, el llamante tendr谩 la oportunidad de manejar el resultado. Recibir谩 el id original de la subllamada, que se puede utilizar para cambiar sobre c贸mo procesar el resultado, as铆 como el Resultado de la ejecuci贸n, incluyendo tanto los casos de 茅xito como de error. Tenga en cuenta que incluye todos los eventos devueltos por el submensaje, lo que se aplica a los m贸dulos nativos del SDK como Bank, as铆 como los datos devueltos por el submensaje. Esto, junto con el identificador de llamada original, proporciona todo el contexto necesario para continuar el procesamiento. Si necesita m谩s estado, debe guardar algo de contexto local en el almac茅n (bajo el id) antes de devolver el submensaje en la funci贸n de ejecuci贸n original y cargarlo en la respuesta. CosmWasm proh铆be expl铆citamente pasar informaci贸n a trav茅s de la memoria del contrato, ya que ese es el vector clave para los ataques de reentrada, que representan una gran superficie de seguridad en Ethereum.
La propia llamada de respuesta puede devolver un Err, en cuyo caso se trata como si el llamante hubiera cometido un error, y la transacci贸n se aborta. Sin embargo, si el proceso tiene 茅xito, reply puede devolver una Response normal, que ser谩 procesada como de costumbre, con eventos a帽adidos al EventManager y todos los mensajes y submensajes enviados como se ha descrito anteriormente.
La 煤nica diferencia cr铆tica con reply es que no soltamos datos. Si reply devuelve data: Some(valor) en el objeto Response, CosmWasm sobrescribir谩 el campo de datos devuelto por el invocador. Es decir, si ejecutar devuelve data: Some(b "primera idea") y la respuesta (con toda la informaci贸n extra a la que tiene acceso) devuelve data: Some(b "mejor idea"), entonces esto se devolver谩 a quien llam贸 a execute (ya sea el cliente u otra transacci贸n), igual que si el execute original hubiera devuelto data: Some(b "mejor idea"). Si reply devuelve data: None, no modificar谩 ning煤n estado de datos previamente establecido. Si hay varios submensajes, s贸lo se utiliza el 煤ltimo (todos sobrescriben cualquier valor de datos anterior). En consecuencia, puede utilizar data: Some(b"") para borrar los datos anteriores. Esto se representar谩 como una cadena JSON en lugar de null y se manejar谩 como cualquier otro valor Some.
Orden y retroceso
Los submensajes (y sus respuestas) se ejecutan antes que cualquier mensaje. Tambi茅n siguen las reglas de "primero en profundidad", como en el caso de los mensajes. He aqu铆 un ejemplo sencillo: El contrato A devuelve los submensajes S1 y S2, y el mensaje M1. El submensaje S1 devuelve el mensaje N1. El orden ser谩: S1, N1, reply(S1), S2, reply(S2), M1.
Tenga en cuenta que la ejecuci贸n del submensaje y la respuesta pueden producirse en el contexto de otro submensaje. Por ejemplo, contrato-A--submensaje --> contrato-B--submensaje --> contrato-C. Entonces, el contrato-B puede revertir el estado para el contrato-C y para s铆 mismo devolviendo Err en la respuesta del submensaje, pero no revertir el contrato-A o la transacci贸n completa. S贸lo termina devolviendo Err a la funci贸n de respuesta del contrato-A.
Tenga en cuenta que los errores no se manejan con ReplyOn::Success, lo que significa que, en tal caso, un error se tratar谩 igual que un mensaje normal que devuelve un error. Este diagrama puede ayudar a explicarlo. Imagina que un contrato devuelve dos submensajes: (a) con ReplyOn::Success y (b) con ReplyOn::Error:
Semantica de consulta
Hasta ahora, nos hemos centrado en el objeto Respuesta, que nos permite ejecutar c贸digo en otros contratos a trav茅s del modelo de actor. Esto significa que cada contrato se ejecuta secuencialmente, uno tras otro, y las llamadas anidadas no son posibles. Esto es esencial para evitar la reentrada, que ocurre cuando una llamada a otro contrato cambia de estado mientras una transacci贸n est谩 en progreso.
Sin embargo, hay muchos casos en los que necesitamos acceder a informaci贸n de otros contratos durante el procesamiento, como determinar el saldo bancario de un contrato antes de enviar fondos. Para habilitar esto, CosmWasm ha habilitado el Querier de solo lectura para permitir llamadas sincr贸nicas durante la ejecuci贸n. Al hacerlo de solo lectura (y aplicarlo a nivel de VM), CosmWasm puede evitar la posibilidad de reentrada, ya que la consulta no puede modificar ning煤n estado ni ejecutar nuestro contrato.
Cuando "hacemos una consulta", serializamos una estructura QueryRequest que representa todas las llamadas posibles. Luego, lo pasamos a trav茅s de FFI al tiempo de ejecuci贸n, donde se interpreta en el m贸dulo SDK de x/wasm. Este proceso es extensible con consultas personalizadas espec铆ficas de blockchain, del mismo modo que CosmosMsg acepta resultados personalizados. Adem谩s, tenga en cuenta la capacidad de realizar consultas protobuf "Stargate" sin procesar.
Si bien esto es flexible y necesario para la representaci贸n en varios idiomas, puede ser un poco engorroso de generar y usar cuando solo desea encontrar su saldo bancario. Para ayudar con esto, a menudo usamos QuerierWrapper, que envuelve un Querier y expone muchos m茅todos convenientes que usan QueryRequest y Querier.raw_query bajo el cap贸.
Para obtener una explicaci贸n m谩s detallada del dise帽o del Querier, consulte Consulta del estado del contrato.
Last updated