Eventos en Solidity: Informa al mundo exterior


Cuando desarrollas smart contracts, una de las necesidades más frecuentes es poder registrar y comunicar las acciones importantes que ocurren durante su ejecución. Ya sea para notificar a una aplicación externa sobre una transferencia, documentar cambios en el estado del contrato o facilitar la auditoría de sus operaciones, es fundamental contar con un mecanismo que permita rastrear lo que sucede en la blockchain de forma eficiente.

¿Qué son los eventos?

Los eventos son una forma de registrar información en la blockchain. Cuando un contrato emite un evento, los datos se guardan en los logs de la transacción, haciendo que estén accesibles pero sin ocupar espacio en el almacenamiento del contrato (¡un ahorro importante de gas!). Estos logs no son accesibles por otros smart contracts, pero sí por aplicaciones externas utilizando bibliotecas como ethers o web3.js (en el caso de usar JavaScript).

En términos simples, los eventos son la forma en que los contratos “hablan” con el mundo exterior.

Declaración de un evento

Declarar un evento en Solidity es tan sencillo como definir una función. Aquí tienes un ejemplo básico:

contract EjemploEventos {
 // Declarar el evento
 event Transfer(address indexed from, address indexed to, uint256 amount);

 function transferir(address _to, uint256 _amount) public {
  // Emitir el evento
  emit Transfer(msg.sender, _to, _amount);
 }
}

¿Qué ocurre aquí?

  • event Transfer: Define el evento que será “emitido”. Piensa en él como una declaración pública de algo que ocurrió.
  • indexed: Permite filtrar los eventos en las aplicaciones externas. En este caso, puedes buscar todos los eventos de transferencias realizadas por un usuario específico.
  • emit: Es la palabra clave que utilizamos para disparar el evento.

Parámetros: indexed vs no indexed

Cuando declaras un evento, puedes incluir parámetros que almacenen datos relacionados con la acción que ocurrió. Estos parámetros se dividen en dos categorías: indexed y no indexed.

¿Qué son los parámetros indexed?

Los parámetros marcados como indexed permiten filtrar eventos en función de un valor específico cuando los lees desde fuera de la blockchain.

Estos valores se almacenan en la estructura de índices de logs, lo que permite buscarlos de manera eficiente. Sin embargo, tienen un costo de gas ligeramente más alto.

¿Qué son los parámetros no indexed?

Los parámetros no indexed no pueden usarse para filtrar eventos. Aun así, se almacenan en los logs y se pueden leer posteriormente, pero requieren procesar toda la información del log en lugar de realizar búsquedas directas.

Ejemplo: indexed vs no indexed

Aquí tienes un ejemplo que muestra cómo combinar ambos tipos:

pragma solidity ^0.8.0;

contract EjemploIndexed {
 // Declaración del evento
 event Transfer(
  address indexed from, // Indexado: puedes filtrar por esta dirección
  address indexed to, // Indexado: puedes filtrar por esta dirección
  uint256 amount // No indexado: no puedes filtrar, pero puedes leer el dato
 );

 function transferir(address _to, uint256 _amount) public {
  emit Transfer(msg.sender, _to, _amount); // Emitir el evento
 }
}

Cuando este evento se emite, los datos se almacenan de la siguiente manera:

  • Datos indexados (from, to): Estos valores se guardan en la estructura de índice del log, permitiendo búsquedas rápidas y específicas.
  • Datos no indexados (amount): Este valor se almacena como un dato adicional en el log. Para obtenerlo, necesitas leer y procesar todo el log.

Límites de los indexed

  • Solo puedes tener hasta 3 parámetros indexed por evento, debido a las restricciones de los logs en la blockchain.
  • Los parámetros indexed ocupan más espacio de gas al ser emitidos, por lo que debes usarlos con moderación.

Cómo escuchar eventos

Las bibliotecas como ethers.js o web3.js son tus mejores aliadas para interactuar con los eventos. Aquí tienes un ejemplo básico en ethers.js:

const contract = new ethers.Contract(contractAddress, abi, provider);

// Escuchar un evento específico
contract.on("Transfer", (from, to, amount) => {
    console.log(` ${from} -> ${to} - ${amount.toString()}`);
});

//Si usas los typechain que genera hardhat lo correcto sería:
contract.on(contract.filters.Transfer, (from, to, amount) => {
    console.log(` ${from} -> ${to} - ${amount.toString()}`);
});

Gracias a los parámetros indexed, también puedes filtrar eventos por valores específicos. Por ejemplo:

// Escuchar eventos emitidos por una dirección específica
contract.on(contract.filters.Transfer(account), (from, to, amount) => {
    console.log(`Transferencia desde ${from} a ${to} por ${amount}`);
});

Si from no estuviera marcado como indexed, este tipo de filtrado no sería posible.

⚠️

La versión actual de typechain tiene un bug. Esto provoca que en lugar de devolver los argumentos del evento devuelva otro objeto.

contract.on(contract.filters.Transfer(account), (payload: any) => {
 const args = payload.args as TransferEvent.OutputObject
 console.log(` ${args.from} -> ${args.to} - ${args.amount}`)
});

¿Por qué usar eventos?

  1. Notificaciones en tiempo real: Los eventos permiten que las aplicaciones sean notificadas cuando algo ocurre en un contrato.
  2. Ahorro de gas: Al almacenar datos en logs en lugar del almacenamiento del contrato, puedes reducir significativamente el coste en gas.
  3. Auditabilidad: Los eventos registran un historial en la blockchain, resulta ideal para auditar las acciones realizadas por un contrato.

Limitaciones de los eventos

Aunque los eventos son muy útiles, tienen algunas limitaciones importantes a considerar:

  1. Datos complejos o anidados: Aunque los eventos pueden aceptar parámetros como structs, estos datos se serializan en una lista de valores simples al almacenarse en los logs. Esto significa que, es posible emitir estructuras complejas estos no se conservan en su forma original, y será necesario reconstruirlas manualmente fuera de la blockchain. Además, incluir estructuras de datos grandes o dinámicas (como arrays muy largos) puede aumentar los costos de gas.

  2. Uso exclusivo para aplicaciones externas: Los eventos son diseñados para ser consumidos por sistemas externos, como aplicaciones descentralizadaso herramientas de monitoreo. Los smart contracts no pueden acceder a los logs de los eventos, por lo que no pueden usarse para transferir datos entre contratos.

Buenas prácticas

  • Usa indexed sabiamente: Cada evento puede tener hasta 3 parámetros indexados. Úsalos en los campos que más probablemente necesitarás filtrar.

  • No abuses de ellos: Aunque son más baratos que el almacenamiento, emitir demasiados eventos puede hacer que las transacciones sean innecesariamente costosas.

  • Sé explícito en los nombres y parámetros: Los nombres de los eventos y sus parámetros deben describir exactamente lo que ocurre para evitar confusiones.

  • Evita estructuras de datos complejas: Para mantener los costos de gas bajos y facilitar el uso de los eventos, emite únicamente los datos esenciales.

Conclusión

Los eventos en Solidity son esenciales para desarrollar aplicaciones con blockchain, ya que permiten conectar smart contracts con el mundo exterior y registrar acciones. Al comprender cómo declararlos, emitirlos y usarlos para interactuar con sistemas externos, puedes diseñar contratos más dinámicos y auditables.