LiteSVM Docs
Additional Cratesanchor-litesvm

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),
}
ErrorDescription
EventNotFoundNo event of the specified type in logs
ParseError(String)Failed to parse event data
Base64ErrorFailed to decode Base64
InvalidFormatEvent 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.