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_signer::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.svm.create_funded_account(10_000_000_000).unwrap();// Execute instruction that emits eventslet 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 transactionlet 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 typelet 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.svm.create_funded_account(10_000_000_000).unwrap();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 emittedresult.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 eventslet 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 emittedresult.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 panickingif 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 datalet base64_data = "SGVsbG8gV29ybGQ="; // Examplelet 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, TestHelpers};use anchor_lang::{prelude::*, system_program};use solana_signer::Signer;// declare_program! generates client types AND re-exports event structsanchor_lang::declare_program!(vault);#[test]fn test_vault_events() {let mut ctx = AnchorLiteSVM::build_with_program(VAULT_PROGRAM_ID,include_bytes!("../target/deploy/vault.so"),);let authority = ctx.svm.create_funded_account(100_000_000_000).unwrap();let depositor = ctx.svm.create_funded_account(50_000_000_000).unwrap();let vault_pda = ctx.svm.get_pda(&[b"vault", authority.pubkey().as_ref()],&VAULT_PROGRAM_ID,);// Create vaultlet create_ix = ctx.program().accounts(vault::client::accounts::CreateVault {authority: authority.pubkey(),vault: vault_pda,system_program: system_program::ID,}).args(vault::client::args::CreateVault {}).instruction().unwrap();let result = ctx.execute_instruction(create_ix, &[&authority]).unwrap();// Assert VaultCreated eventresult.assert_event_emitted::<vault::VaultCreated>();let created_event: vault::VaultCreated = result.parse_event().unwrap();assert_eq!(created_event.vault, vault_pda);assert_eq!(created_event.authority, authority.pubkey());// Depositlet deposit_ix = ctx.program().accounts(vault::client::accounts::DepositToVault {vault: vault_pda,depositor: depositor.pubkey(),system_program: system_program::ID,}).args(vault::client::args::Deposit { amount: 10_000_000_000 }).instruction().unwrap();let result = ctx.execute_instruction(deposit_ix, &[&depositor]).unwrap();// Assert Deposit eventresult.assert_event_emitted::<vault::Deposit>();let deposit_event: vault::Deposit = result.parse_event().unwrap();assert_eq!(deposit_event.amount, 10_000_000_000);assert_eq!(deposit_event.depositor, depositor.pubkey());// Withdrawallet withdraw_ix = ctx.program().accounts(vault::client::accounts::WithdrawFromVault {vault: vault_pda,authority: authority.pubkey(),recipient: authority.pubkey(),}).args(vault::client::args::Withdraw { amount: 5_000_000_000 }).instruction().unwrap();let result = ctx.execute_instruction(withdraw_ix, &[&authority]).unwrap();// Check withdrawal eventassert!(result.has_event::<vault::Withdrawal>());let withdraw_event: vault::Withdrawal = result.parse_event().unwrap();assert_eq!(withdraw_event.amount, 5_000_000_000);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.
Is this page helpful?