Saltar al contenido principal

Eventos

El ejemplo de eventos demuestra cómo publicar eventos desde un contrato. Este ejemplo es una extensión del ejemplo de almacenamiento de datos.

Whisk Cambios

Con el lanzamiento de Whisk, Protocolo 23, la sintaxis para publicar eventos de smart contracts ha cambiado. Para proporcionar la información más actualizada, este ejemplo ha sido actualizado para incluir los nuevos patrones. Encuentra información más detallada en la documentación del SDK de Rust.

Abrir en Codespaces

Abrir en Codeanywhere

Ejecutar el ejemplo

Primero sigue el proceso de Configuración para configurar tu entorno de desarrollo, luego clona la etiqueta v23.0.0 del repositorio soroban-examples:

git clone -b v23.0.0 https://github.com/stellar/soroban-examples

O bien, puedes evitar la configuración del entorno de desarrollo y abrir este ejemplo en GitHub Codespaces o en Code Anywhere.

Para ejecutar las pruebas del ejemplo, navega al directorio events, y usa cargo test.

cd events
cargo test

Deberías ver la salida:

running 1 test
test test::test ... ok

Código

events/src/lib.rs
const COUNTER: Symbol = symbol_short!("COUNTER");

// Define two static topics for the event: "COUNTER" and "increment".
// Also set the data format to "single-value", which means that the event data
// payload will contain a single value not nested into any data structure.
#[contractevent(topics = ["COUNTER", "increment"], data_format = "single-value")]
struct IncrementEvent {
count: u32,
}

#[contract]
pub struct IncrementContract;

#[contractimpl]
impl IncrementContract {
/// Increment increments an internal counter, and returns the value.
pub fn increment(env: Env) -> u32 {
// Get the current count.
let mut count: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0); // If no value set, assume 0.

// Increment the count.
count += 1;

// Save the count.
env.storage().instance().set(&COUNTER, &count);

// Publish an event about the increment occuring.
// The event has two static topics ("COUNTER", "increment") and actual
// count as the data payload.
IncrementEvent { count }.publish(&env);

// Return the count to the caller.
count
}
}

Referencia: https://github.com/stellar/soroban-examples/tree/v23.0.0/events

Cómo funciona

Este contrato de ejemplo extiende el ejemplo de incremento publicando un evento cada vez que se incrementa el contador.

Los eventos del contrato permiten a los contratos emitir información sobre lo que su contrato está haciendo. Los contratos pueden publicar eventos creando una struct definida y publicándola en los entornos de smart contracts.

Primero, debe definirse la estructura #[contractevent]

#[contractevent(topics = ["COUNTER", "increment"], data_format = "single-value")]
struct IncrementEvent {
count: u32,
}

Luego, dentro de la función del contrato, podemos crear y publicar el evento con los datos relevantes.

IncrementEvent { count }.publish(&env);

Temas de eventos

Los temas pueden definirse de forma estática o dinámica. En el código de ejemplo se usan dos temas estáticos, que serán del tipo Symbol: COUNTER e increment.

#[contractevent(topics = ["COUNTER", "increment"], ...)]
consejo

Los temas no tienen que estar hechos del mismo tipo.

Los temas también pueden definirse dinámicamente, dentro de la estructura. En este caso, el nombre en snake_case de la estructura será el primer tema. Por ejemplo, el siguiente evento tendrá dos temas: el Symbol "increment", seguido por una Address.

#[contractevent]
pub struct Increment {
#[topic]
addr: Address,
count: u32,
}

Datos del evento

Un evento también contiene un objeto de datos de cualquier valor o tipo, incluyendo tipos definidos por contratos utilizando #[contracttype]. En el ejemplo, los datos son el conteo u32. En el ejemplo, los datos son el conteo u32. El data_format = "single-value" indica que el evento publique solo los datos, sin estructura adicional alrededor.

#[contractevent(..., data_format = "single-value")]

Por defecto, los datos del evento cumplirán con la estructura definida en la struct. El data_format también puede especificarse como vec o single-value. Nuevamente, consulta la [documentación del Rust SDK] para más detalles.

Publicar

Publicar un evento se realiza llamando a la función publish sobre la estructura del evento creada. La función no retorna nada si tiene éxito, y causa pánico en caso de falla. Las razones de fallo pueden incluir entradas mal formadas (por ejemplo, el conteo de temas excede el límite) y agotar el presupuesto de recursos (TBD). Una vez publicado exitosamente, el nuevo evento estará disponible para aplicaciones que consuman los eventos.

IncrementEvent { count }.publish(&env);
advertencia

Los eventos publicados se descartan si una invocación de contrato falla debido a un pánico, agotamiento del presupuesto, o cuando el contrato retorna un error.

Pruebas

Abre el archivo events/src/test.rs para seguir el ejemplo.

events/src/test.rs
#[test]
fn test() {
let env = Env::default();
let contract_id = env.register(IncrementContract, ());
let client = IncrementContractClient::new(&env, &contract_id);

assert_eq!(client.increment(), 1);
assert_eq!(
env.events().all(),
vec![
&env,
(
contract_id.clone(),
(symbol_short!("COUNTER"), symbol_short!("increment")).into_val(&env),
1u32.into_val(&env)
),
]
);
assert_eq!(client.increment(), 2);
assert_eq!(
env.events().all(),
vec![
&env,
(
contract_id.clone(),
(symbol_short!("COUNTER"), symbol_short!("increment")).into_val(&env),
2u32.into_val(&env)
),
]
);
assert_eq!(client.increment(), 3);
assert_eq!(
env.events().all(),
vec![
&env,
(
contract_id,
(symbol_short!("COUNTER"), symbol_short!("increment")).into_val(&env),
3u32.into_val(&env)
),
]
);
}

En cualquier prueba, lo primero que siempre se requiere es un Env, que es el entorno de Soroban en el que el contrato se ejecutará.

let env = Env::default();

El contrato se registra con el entorno utilizando el tipo de contrato.

let contract_id = env.register(IncrementContract, ());

Todas las funciones públicas dentro de un bloque impl que está anotado con el atributo #[contractimpl] tienen una función correspondiente generada en un tipo de cliente generado. El tipo de cliente se llamará igual que el tipo de contrato con Client añadido. Por ejemplo, en nuestro contrato el tipo de contrato es IncrementContract, y el cliente se llama IncrementContractClient.

let client = IncrementContractClient::new(&env, &contract_id);

El ejemplo invoca el contrato varias veces.

assert_eq!(client.increment(), 1);

El ejemplo verifica que el evento fue publicado.

assert_eq!(
env.events().all(),
vec![
&env,
(
contract_id.clone(),
(symbol_short!("COUNTER"), symbol_short!("increment")).into_val(&env),
1u32.into_val(&env)
),
]
);

Construir el contrato

Para construir el contrato, usa el comando stellar contract build.

stellar contract build

Un archivo .wasm debería generarse en el directorio target:

target/wasm32v1-none/release/soroban_events_contract.wasm

Ejecutar el contrato

Si tienes stellar-cli instalado, puedes invocar funciones del contrato utilizándolo.

stellar contract invoke \
--wasm target/wasm32v1-none/release/soroban_events_contract.wasm \
--id 1 \
-- \
increment

La siguiente salida debería ocurrir utilizando el código anterior.

📅 CAAA... - Success - Event: [{"symbol":"COUNTER"},{"symbol":"increment"}] = {"u32":1}
1

Se muestra un solo evento, que es el evento del contrato que el contrato publicó. El evento contiene los dos temas, cada uno un Symbol, y el objeto de datos que contiene el u32.