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.
Ejecutar el ejemplo
Primero, sigue el proceso de Configuración para tener tu entorno de desarrollo configurado, luego clona la etiqueta v21.6.0
del repositorio soroban-examples
:
git clone -b v21.6.0 https://github.com/stellar/soroban-examples
O, salta la configuración del entorno de desarrollo y abre este ejemplo en Gitpod.
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
const COUNTER: Symbol = symbol_short!("COUNTER");
#[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 topics:
// - The "COUNTER" symbol.
// - The "increment" symbol.
// The event data is the count.
env.events()
.publish((COUNTER, symbol_short!("increment")), count);
// Return the count to the caller.
count
}
}
Ref: https://github.com/stellar/soroban-examples/tree/v21.6.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 utilizando la función de publicación de eventos de los entornos.
env.events().publish(topics, data);
Temas de eventos
Los temas se definen convenientemente utilizando una tupla. En el código de ejemplo se utilizan dos temas de tipo Symbol
.
env.events().publish((COUNTER, symbol_short!("increment")), ...);
Los temas no tienen que estar hechos del mismo tipo.
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
.
env.events().publish(..., count);
Publicar
Publicar un evento se realiza llamando a la función publish
y dándole los temas y datos. 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.
env.events().publish((COUNTER, symbol_short!("increment")), count);
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 adelante.
#[test]
fn test() {
let env = Env::default();
let contract_id = env.register_contract(None, IncrementContract);
let client = IncrementContractClient::new(&env, &contract_id);
assert_eq!(client.increment(), 1);
assert_eq!(client.increment(), 2);
assert_eq!(client.increment(), 3);
assert_eq!(
env.events().all(),
vec![
&env,
(
contract_id.clone(),
(symbol_short!("COUNTER"), symbol_short!("increment")).into_val(&env),
1u32.into_val(&env)
),
(
contract_id.clone(),
(symbol_short!("COUNTER"), symbol_short!("increment")).into_val(&env),
2u32.into_val(&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_contract(None, 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 afirma que se publicaron los eventos.
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/wasm32-unknown-unknown/release/soroban_events_contract.wasm
Ejecutar el contrato
Si tienes stellar-cli
instalado, puedes invocar funciones del contrato utilizándolo.
- macOS/Linux
- Windows (PowerShell)
stellar contract invoke \
--wasm target/wasm32-unknown-unknown/release/soroban_events_contract.wasm \
--id 1 \
-- \
increment
stellar contract invoke `
--wasm target/wasm32-unknown-unknown/release/soroban_events_contract.wasm `
--id 1 `
-- `
increment
La siguiente salida debería ocurrir utilizando el código anterior.
1
#0: event: {"ext":"v0","contractId":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],"type":"contract","body":{"v0":{"topics":[{"symbol":[67,79,85,78,84,69,82]},{"symbol":[105,110,99,114,101,109,101,110,116]}],"data":{"u32":1}}}}
Se genera un único evento #0
, que es el evento del contrato que publicó el contrato. El evento contiene los dos temas, cada uno un symbol
(mostrado como bytes), y el objeto de datos que contiene el u32
.