Events
Parse and assert Anchor events from transaction logs
Understanding Anchor Events
Anchor programs can emit events that are logged during transaction execution. These events are Base64-encoded in the transaction logs with the prefix Program data:. The anchor-litesvm crate provides utilities to parse and assert these events.
┌─────────────────────────────────────────────────┐
│ Anchor Event Structure │
├────────────────┬────────────────────────────────┤
│ Discriminator │ Event Data │
│ (8 bytes) │ (serialized fields) │
└────────────────┴────────────────────────────────┘Defining Events
First, define your events in your Anchor program:
use anchor_lang::prelude::*;
#[event]
pub struct TransferEvent {
pub from: Pubkey,
pub to: Pubkey,
pub amount: u64,
pub timestamp: i64,
}
#[event]
pub struct InitializeEvent {
pub authority: Pubkey,
pub name: String,
}Parsing Events
Parse All Events of a Type
use anchor_litesvm::{AnchorLiteSVM, EventHelpers};
use solana_sdk::{native_token::LAMPORTS_PER_SOL, signature::Signer};
#[test]
fn test_parse_events() {
let mut ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));
let user = ctx.create_funded_account(10 * LAMPORTS_PER_SOL);
// Execute instruction that emits events
let ix = ctx.program()
.accounts(Transfer { /* ... */ })
.args(TransferArgs { amount: 1000 })
.instruction()
.unwrap();
let result = ctx.execute_instruction(ix, &[&user]).unwrap();
// Parse all TransferEvent events from the transaction
let events: Vec<TransferEvent> = result.parse_events().unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events[0].amount, 1000);
}Parse Single Event
use anchor_litesvm::{AnchorLiteSVM, EventHelpers};
#[test]
fn test_parse_single_event() {
let mut ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));
// ... execute instruction ...
// Get the first event of the type
let event: TransferEvent = result.parse_event().unwrap();
println!("Transfer: {} -> {} ({})", event.from, event.to, event.amount);
}Asserting Events
Assert Event Was Emitted
use anchor_litesvm::{AnchorLiteSVM, EventHelpers};
#[test]
fn test_assert_event_emitted() {
let mut ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));
let user = ctx.create_funded_account(10 * LAMPORTS_PER_SOL);
let ix = ctx.program()
.accounts(Initialize { /* ... */ })
.args(InitializeArgs { name: "test".to_string() })
.instruction()
.unwrap();
let result = ctx.execute_instruction(ix, &[&user]).unwrap();
// Assert that at least one InitializeEvent was emitted
result.assert_event_emitted::<InitializeEvent>();
}assert_event_emitted will panic if no events of the specified type are found in the transaction logs.
Assert Event Count
use anchor_litesvm::{AnchorLiteSVM, EventHelpers};
#[test]
fn test_assert_event_count() {
let mut ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));
// Execute instruction that emits multiple events
let ix = ctx.program()
.accounts(BatchTransfer { /* ... */ })
.args(BatchTransferArgs { recipients: vec![...], amounts: vec![...] })
.instruction()
.unwrap();
let result = ctx.execute_instruction(ix, &[&user]).unwrap();
// Assert exactly 3 TransferEvent events were emitted
result.assert_event_count::<TransferEvent>(3);
}Check Event Existence
use anchor_litesvm::{AnchorLiteSVM, EventHelpers};
#[test]
fn test_has_event() {
let mut ctx = AnchorLiteSVM::build_with_program(PROGRAM_ID, include_bytes!("../target/deploy/your_program.so"));
// ... execute instruction ...
// Check without panicking
if result.has_event::<TransferEvent>() {
let event: TransferEvent = result.parse_event().unwrap();
println!("Transfer occurred: {}", event.amount);
} else {
println!("No transfer in this transaction");
}
}Manual Event Parsing
For advanced use cases, you can parse event data directly:
use anchor_litesvm::parse_event_data;
#[test]
fn test_manual_parse() {
// If you have the base64-encoded event data
let base64_data = "SGVsbG8gV29ybGQ="; // Example
let event: TransferEvent = parse_event_data(base64_data).unwrap();
}Event Errors
The EventError enum provides detailed error information:
use anchor_litesvm::EventError;
match result.parse_event::<TransferEvent>() {
Ok(event) => println!("Amount: {}", event.amount),
Err(EventError::EventNotFound) => println!("No event found"),
Err(EventError::ParseError(msg)) => println!("Parse error: {}", msg),
Err(EventError::Base64Error) => println!("Invalid Base64 encoding"),
Err(EventError::InvalidFormat) => println!("Malformed event data"),
Err(EventError::AnchorError(msg)) => println!("Anchor error: {}", msg),
}| Error | Description |
|---|---|
EventNotFound | No event of the specified type in logs |
ParseError(String) | Failed to parse event data |
Base64Error | Failed to decode Base64 |
InvalidFormat | Event data has invalid structure |
AnchorError(String) | Anchor deserialization failed |
Complete Example
Here's a comprehensive example demonstrating event handling:
use anchor_litesvm::{AnchorLiteSVM, EventHelpers};
use anchor_lang::prelude::*;
use solana_sdk::{
native_token::LAMPORTS_PER_SOL,
signature::Signer,
pubkey::Pubkey,
};
declare_id!("YourProgram11111111111111111111111111111111");
// Events
#[event]
pub struct VaultCreated {
pub vault: Pubkey,
pub authority: Pubkey,
pub timestamp: i64,
}
#[event]
pub struct Deposit {
pub vault: Pubkey,
pub depositor: Pubkey,
pub amount: u64,
}
#[event]
pub struct Withdrawal {
pub vault: Pubkey,
pub recipient: Pubkey,
pub amount: u64,
}
#[test]
fn test_vault_events() {
let mut ctx = AnchorLiteSVM::build_with_program(
id(),
include_bytes!("../target/deploy/vault.so"),
);
let authority = ctx.create_funded_account(100 * LAMPORTS_PER_SOL);
let depositor = ctx.create_funded_account(50 * LAMPORTS_PER_SOL);
let (vault_pda, _) = Pubkey::find_program_address(
&[b"vault", authority.pubkey().as_ref()],
&id(),
);
// Create vault
let create_ix = ctx.program()
.accounts(CreateVault {
authority: authority.pubkey(),
vault: vault_pda,
system_program: solana_sdk::system_program::id(),
})
.args(instruction::CreateVault {})
.instruction()
.unwrap();
let result = ctx.execute_instruction(create_ix, &[&authority]).unwrap();
// Assert VaultCreated event
result.assert_event_emitted::<VaultCreated>();
let created_event: VaultCreated = result.parse_event().unwrap();
assert_eq!(created_event.vault, vault_pda);
assert_eq!(created_event.authority, authority.pubkey());
// Deposit
let deposit_ix = ctx.program()
.accounts(DepositToVault {
vault: vault_pda,
depositor: depositor.pubkey(),
system_program: solana_sdk::system_program::id(),
})
.args(instruction::Deposit { amount: 10 * LAMPORTS_PER_SOL })
.instruction()
.unwrap();
let result = ctx.execute_instruction(deposit_ix, &[&depositor]).unwrap();
// Assert Deposit event
result.assert_event_emitted::<Deposit>();
let deposit_event: Deposit = result.parse_event().unwrap();
assert_eq!(deposit_event.amount, 10 * LAMPORTS_PER_SOL);
assert_eq!(deposit_event.depositor, depositor.pubkey());
// Multiple deposits
ctx.execute_instruction(deposit_ix.clone(), &[&depositor]).unwrap();
ctx.execute_instruction(deposit_ix.clone(), &[&depositor]).unwrap();
// Withdrawal
let withdraw_ix = ctx.program()
.accounts(WithdrawFromVault {
vault: vault_pda,
authority: authority.pubkey(),
recipient: authority.pubkey(),
})
.args(instruction::Withdraw { amount: 5 * LAMPORTS_PER_SOL })
.instruction()
.unwrap();
let result = ctx.execute_instruction(withdraw_ix, &[&authority]).unwrap();
// Check withdrawal event
assert!(result.has_event::<Withdrawal>());
let withdraw_event: Withdrawal = result.parse_event().unwrap();
assert_eq!(withdraw_event.amount, 5 * LAMPORTS_PER_SOL);
println!("All event tests passed!");
}Events are a great way to verify that your program executed correctly without having to fetch and deserialize accounts. They're especially useful for tracking state changes over time.