From 0be172ec2d0b24e236d4131e8db46d93c98fe5f9 Mon Sep 17 00:00:00 2001 From: Roberts Pumpurs <33699735+roberts-pumpurs@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:48:09 +0200 Subject: [PATCH] feat: library for stateful Solana event parsing (#538) --- solana/Cargo.lock | 14 +- solana/Cargo.toml | 1 + .../Cargo.toml | 2 +- .../src/gateway.rs | 63 +--- solana/crates/gateway-event-stack/Cargo.toml | 21 ++ solana/crates/gateway-event-stack/src/lib.rs | 350 ++++++++++++++++++ .../tests/integration/approve_message.rs | 25 +- .../tests/integration/rotate_signers.rs | 23 +- .../tests/evm-e2e/from_solana_to_evm.rs | 12 +- .../tests/module/send_to_gateway.rs | 16 +- .../tests/module/validate_message.rs | 15 +- 11 files changed, 454 insertions(+), 88 deletions(-) create mode 100644 solana/crates/gateway-event-stack/Cargo.toml create mode 100644 solana/crates/gateway-event-stack/src/lib.rs diff --git a/solana/Cargo.lock b/solana/Cargo.lock index cbf1e2e..55b1868 100644 --- a/solana/Cargo.lock +++ b/solana/Cargo.lock @@ -774,9 +774,9 @@ dependencies = [ "axelar-executable", "axelar-solana-encoding", "axelar-solana-gateway", - "base64 0.21.7", "bincode", "ed25519-dalek 2.1.1", + "gateway-event-stack", "libsecp256k1", "rand 0.7.3", "rand 0.8.5", @@ -2903,6 +2903,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gateway-event-stack" +version = "0.1.0" +dependencies = [ + "axelar-solana-gateway", + "base64 0.21.7", + "pretty_assertions", + "solana-sdk", + "test-log", + "tracing", +] + [[package]] name = "generic-array" version = "0.14.7" diff --git a/solana/Cargo.toml b/solana/Cargo.toml index 6622e2d..56614cc 100644 --- a/solana/Cargo.toml +++ b/solana/Cargo.toml @@ -136,6 +136,7 @@ gateway = { path = "programs/gateway", package = "gmp-gateway" } axelar-solana-gateway = { path = "programs/axelar-solana-gateway" } # helper crates +gateway-event-stack = { path = "crates/gateway-event-stack" } axelar-solana-encoding = { path = "crates/axelar-solana-encoding" } axelar-executable = { path = "crates/axelar-executable" } axelar-executable-old = { path = "helpers/axelar-executable-old" } diff --git a/solana/crates/axelar-solana-gateway-test-fixtures/Cargo.toml b/solana/crates/axelar-solana-gateway-test-fixtures/Cargo.toml index 740a701..fe6227b 100644 --- a/solana/crates/axelar-solana-gateway-test-fixtures/Cargo.toml +++ b/solana/crates/axelar-solana-gateway-test-fixtures/Cargo.toml @@ -17,7 +17,7 @@ ed25519-dalek = { workspace = true, features = ["rand_core", "digest"] } libsecp256k1.workspace = true rand.workspace = true libsecp-rand.workspace = true -base64.workspace = true +gateway-event-stack.workspace = true [lints] workspace = true diff --git a/solana/crates/axelar-solana-gateway-test-fixtures/src/gateway.rs b/solana/crates/axelar-solana-gateway-test-fixtures/src/gateway.rs index 325b92e..7a2efe6 100644 --- a/solana/crates/axelar-solana-gateway-test-fixtures/src/gateway.rs +++ b/solana/crates/axelar-solana-gateway-test-fixtures/src/gateway.rs @@ -20,8 +20,7 @@ use axelar_solana_gateway::state::GatewayConfig; use axelar_solana_gateway::{ bytemuck, get_gateway_root_config_pda, get_incoming_message_pda, get_verifier_set_tracker_pda, }; -use base64::engine::general_purpose; -use base64::Engine; +pub use gateway_event_stack::{MatchContext, ProgramInvocationState}; use rand::Rng as _; use solana_program::pubkey::Pubkey; use solana_program_test::{BanksTransactionResultWithMetadata, ProgramTest}; @@ -501,59 +500,13 @@ impl SolanaAxelarIntegration { #[must_use] pub fn get_gateway_events( tx: &solana_program_test::BanksTransactionResultWithMetadata, -) -> Vec { - tx.metadata - .as_ref() - .unwrap() - .log_messages - .iter() - .map(|log| { - log.trim() - .trim_start_matches("Program data:") - .split_whitespace() - .filter_map(decode_base64) - }) - .filter_map(|mut raw_log| { - use axelar_solana_gateway::event_prefixes::*; - let disc = raw_log.next()?.try_into().ok()?; - match &disc { - CALL_CONTRACT => { - let event = - axelar_solana_gateway::processor::CallContractEvent::new(raw_log).ok()?; - Some(GatewayEvent::CallContract(event)) - } - MESSAGE_APPROVED => { - let event = - axelar_solana_gateway::processor::MessageEvent::new(raw_log).ok()?; - Some(GatewayEvent::MessageApproved(event)) - } - MESSAGE_EXECUTED => { - let event = - axelar_solana_gateway::processor::MessageEvent::new(raw_log).ok()?; - Some(GatewayEvent::MessageExecuted(event)) - } - OPERATORSHIP_TRANSFERRED => { - let event = - axelar_solana_gateway::processor::OperatorshipTransferredEvent::new( - raw_log, - ) - .ok()?; - Some(GatewayEvent::OperatorshipTransferred(event)) - } - SIGNERS_ROTATED => { - let event = - axelar_solana_gateway::processor::VerifierSetRotated::new(raw_log).ok()?; - Some(GatewayEvent::VerifierSetRotated(event)) - } - _ => None, - } - }) - .collect::>() -} - -#[inline] -fn decode_base64(input: &str) -> Option> { - general_purpose::STANDARD.decode(input).ok() +) -> Vec> { + let match_context = MatchContext::new(&axelar_solana_gateway::ID.to_string()); + gateway_event_stack::build_program_event_stack( + &match_context, + tx.metadata.as_ref().unwrap().log_messages.as_slice(), + gateway_event_stack::parse_gateway_logs, + ) } /// Create a new verifier set diff --git a/solana/crates/gateway-event-stack/Cargo.toml b/solana/crates/gateway-event-stack/Cargo.toml new file mode 100644 index 0000000..c1ceeff --- /dev/null +++ b/solana/crates/gateway-event-stack/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "gateway-event-stack" +version.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +edition.workspace = true + +[dependencies] +axelar-solana-gateway.workspace = true +tracing.workspace = true +solana-sdk.workspace = true +base64.workspace = true + +[dev-dependencies] +pretty_assertions.workspace = true +test-log.workspace = true + +[lints] +workspace = true diff --git a/solana/crates/gateway-event-stack/src/lib.rs b/solana/crates/gateway-event-stack/src/lib.rs new file mode 100644 index 0000000..03629de --- /dev/null +++ b/solana/crates/gateway-event-stack/src/lib.rs @@ -0,0 +1,350 @@ +//! Parse Solana events from transaction data + +use axelar_solana_gateway::processor::{EventParseError, GatewayEvent}; +use base64::{engine::general_purpose, Engine}; + +/// Represents the state of a program invocation along with associated events. +#[derive(Debug, PartialEq, Eq)] +pub enum ProgramInvocationState { + /// The program invocation is currently in progress, holding a list of events and their indexes. + InProgress(Vec<(usize, T)>), + /// The program invocation has succeeded, holding a list of events and their indexes. + Succeeded(Vec<(usize, T)>), + /// The program invocation has failed, holding a list of events and their indexes. + Failed(Vec<(usize, T)>), +} + +#[allow(clippy::struct_field_names)] +/// Context for matching specific log prefixes in program invocations. +pub struct MatchContext { + /// The log prefix indicating the start of the target program. + expected_start: String, + /// The log prefix indicating the successful completion of the target program. + expected_success: String, + /// The log prefix indicating the failure of the target program. + expected_failure: String, +} + +impl MatchContext { + /// Creates a new `MatchContext` for the given program ID. + /// + /// # Arguments + /// + /// * `program_id` - The ID of the program to match in the logs. + #[must_use] + pub fn new(program_id: &str) -> Self { + Self { + expected_start: format!("Program {program_id} invoke"), + expected_success: format!("Program {program_id} success"), + expected_failure: format!("Program {program_id} failed"), + } + } +} + +/// Builds a stack of program invocation states from logs by parsing events. +/// +/// # Arguments +/// +/// * `ctx` - The `MatchContext` containing expected log prefixes. +/// * `logs` - A slice of logs to parse. +/// * `transformer` - A function that transforms a log entry into an event and updates the program stack. +/// +/// # Returns +/// +/// A vector of `ProgramInvocationState` representing parsed program invocations and their events. +/// +/// # Type Parameters +/// +/// * `T` - The type of log entries, convertible to a string slice. +/// * `K` - The type of events. +/// * `Err` - The error type returned by the transformer function. +/// * `F` - The transformer function that will transform logs into even structures +pub fn build_program_event_stack( + ctx: &MatchContext, + logs: &[T], + transformer: F, +) -> Vec> +where + T: AsRef, + F: Fn(&mut [ProgramInvocationState], &T, usize) -> Result<(), Err>, +{ + let logs = logs.iter().enumerate(); + let mut program_stack: Vec> = Vec::new(); + + for (idx, log) in logs { + tracing::trace!(log = ?log.as_ref(), "incoming log from Solana"); + if log.as_ref().starts_with(ctx.expected_start.as_str()) { + // Start a new program invocation + program_stack.push(ProgramInvocationState::InProgress(Vec::new())); + } else if log.as_ref().starts_with(ctx.expected_success.as_str()) { + handle_success_log(&mut program_stack); + } else if log.as_ref().starts_with(ctx.expected_failure.as_str()) { + handle_failure_log(&mut program_stack); + } else { + // Process logs if inside a program invocation + #[allow(clippy::let_underscore_must_use, clippy::let_underscore_untyped)] + let _ = transformer(&mut program_stack, log, idx); + } + } + program_stack +} + +#[inline] +/// Decodes a Base64-encoded string into bytes. +#[must_use] +pub fn decode_base64(input: &str) -> Option> { + general_purpose::STANDARD.decode(input).ok() +} + +/// Parses gateway logs and extracts events. +/// +/// # Arguments +/// +/// * `program_stack` - A mutable slice of program invocation states to update. +/// * `log` - The log entry to parse. +/// * `idx` - The index of the log entry in the logs slice. +/// +/// # Returns +/// +/// `Ok(())` if parsing is successful, or an `EventParseError` if parsing fails. +/// +/// # Errors +/// +/// - if the discrimintant for the event is not present +/// - if the event was detected via the discriminant but the data does not match +pub fn parse_gateway_logs( + program_stack: &mut [ProgramInvocationState], + log: &T, + idx: usize, +) -> Result<(), EventParseError> +where + T: AsRef, +{ + use axelar_solana_gateway::event_prefixes::*; + let Some(&mut ProgramInvocationState::InProgress(ref mut events)) = program_stack.last_mut() + else { + return Ok(()); + }; + + let mut logs = log + .as_ref() + .trim() + .trim_start_matches("Program data:") + .split_whitespace() + .filter_map(decode_base64); + let disc = logs + .next() + .ok_or(EventParseError::MissingData("discriminant"))? + .try_into() + .map_err(|err: Vec| EventParseError::InvalidLength { + field: "discriminant", + expected: 32, + actual: err.len(), + })?; + let gateway_event = match &disc { + CALL_CONTRACT => { + let event = axelar_solana_gateway::processor::CallContractEvent::new(logs)?; + GatewayEvent::CallContract(event) + } + MESSAGE_APPROVED => { + let event = axelar_solana_gateway::processor::MessageEvent::new(logs)?; + GatewayEvent::MessageApproved(event) + } + MESSAGE_EXECUTED => { + let event = axelar_solana_gateway::processor::MessageEvent::new(logs)?; + GatewayEvent::MessageExecuted(event) + } + OPERATORSHIP_TRANSFERRED => { + let event = axelar_solana_gateway::processor::OperatorshipTransferredEvent::new(logs)?; + GatewayEvent::OperatorshipTransferred(event) + } + SIGNERS_ROTATED => { + let event = axelar_solana_gateway::processor::VerifierSetRotated::new(logs)?; + GatewayEvent::VerifierSetRotated(event) + } + _ => return Ok(()), + }; + + events.push((idx, gateway_event)); + Ok(()) +} + +/// Handles a failure log by marking the current program invocation as failed. +fn handle_failure_log(program_stack: &mut Vec>) { + let Some(state) = program_stack.pop() else { + tracing::warn!("Program failure without matching invocation"); + return; + }; + + match state { + ProgramInvocationState::InProgress(events) => { + program_stack.push(ProgramInvocationState::Failed(events)); + } + ProgramInvocationState::Succeeded(_) | ProgramInvocationState::Failed(_) => { + tracing::warn!("Unexpected state when marking program failure"); + } + } +} + +/// Handles a success log by marking the current program invocation as succeeded. +fn handle_success_log(program_stack: &mut Vec>) { + let Some(state) = program_stack.pop() else { + tracing::warn!("Program success without matching invocation"); + return; + }; + match state { + ProgramInvocationState::InProgress(events) => { + program_stack.push(ProgramInvocationState::Succeeded(events)); + } + ProgramInvocationState::Succeeded(_) | ProgramInvocationState::Failed(_) => { + tracing::warn!("Unexpected state when marking program success"); + } + } +} + +#[cfg(test)] +mod tests { + use core::str::FromStr; + + use axelar_solana_gateway::processor::CallContractEvent; + use pretty_assertions::assert_eq; + use solana_sdk::pubkey::Pubkey; + use test_log::test; + + use super::*; + + static GATEWAY_EXAMPLE_ID: &str = "gtwEpzTprUX7TJLx1hFXNeqCXJMsoxYQhQaEbnuDcj1"; + + // Include the test_call_data function + fn fixture_call_data() -> (&'static str, GatewayEvent) { + // this is a `CallContract` extract form other unittests + let base64_data = "Y2FsbCBjb250cmFjdF9fXw== 6NGe5cm7PkXHz/g8V2VdRg0nU0l7R48x8lll4s0Clz0= xtlu5J3pLn7c4BhqnNSrP1wDZK/pQOJVCYbk6sroJhY= ZXRoZXJldW0= MHgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2YzIwNjAzYzdiODc2NjgyYzEyMTczYmRlZjlhMWRjYTUyOGYxNGZk 8J+QqvCfkKrwn5Cq8J+Qqg=="; + // Simple `CallContract` fixture + let event = GatewayEvent::CallContract(CallContractEvent { + sender_key: Pubkey::from_str("GfpyaXoJrd9XHHRehAPCGETie3wpM8xDxscAUoC12Cxt").unwrap(), + destination_chain: "ethereum".to_owned(), + destination_contract_address: + "0x0000000000000000000000006c20603c7b876682c12173bdef9a1dca528f14fd".to_owned(), + payload: vec![ + 240, 159, 144, 170, 240, 159, 144, 170, 240, 159, 144, 170, 240, 159, 144, 170, + ], + payload_hash: [ + 198, 217, 110, 228, 157, 233, 46, 126, 220, 224, 24, 106, 156, 212, 171, 63, 92, 3, + 100, 175, 233, 64, 226, 85, 9, 134, 228, 234, 202, 232, 38, 22, + ], + }); + (base64_data, event) + } + + fn fixture_match_context() -> MatchContext { + MatchContext::new(GATEWAY_EXAMPLE_ID) + } + + #[test] + fn test_simple_event() { + // Use the test_call_data fixture + let (base64_data, event) = fixture_call_data(); + + // Sample logs with multiple gateway calls, some succeed and some fail + let logs = vec![ + format!("Program {GATEWAY_EXAMPLE_ID} invoke [1]"), // Invocation 1 starts + "Program log: Instruction: Call Contract".to_owned(), + format!("Program data: {}", base64_data), + format!("Program {GATEWAY_EXAMPLE_ID} success"), // Invocation 1 succeeds + ]; + + let result = build_program_event_stack(&fixture_match_context(), &logs, parse_gateway_logs); + + // Expected result: two successful invocations with their events, one failed invocation + let expected = vec![ProgramInvocationState::Succeeded(vec![(2, event)])]; + + assert_eq!(result, expected); + } + + #[test] + fn test_multiple_gateway_calls_some_succeed_some_fail() { + // Use the test_call_data fixture + let (base64_data, event) = fixture_call_data(); + + // Sample logs with multiple gateway calls, some succeed and some fail + let logs = vec![ + format!("Program {GATEWAY_EXAMPLE_ID} invoke [1]"), // Invocation 1 starts + "Program log: Instruction: Call Contract".to_owned(), + format!("Program data: {}", base64_data), + format!("Program {GATEWAY_EXAMPLE_ID} success"), // Invocation 1 succeeds + format!("Program {GATEWAY_EXAMPLE_ID} invoke [2]"), // Invocation 2 starts + "Program log: Instruction: Call Contract".to_owned(), + format!("Program data: {}", base64_data), + format!("Program {GATEWAY_EXAMPLE_ID} failed"), // Invocation 2 fails + format!("Program {GATEWAY_EXAMPLE_ID} invoke [3]"), // Invocation 3 starts + "Program log: Instruction: Call Contract".to_owned(), + format!("Program data: {}", base64_data), + format!("Program {GATEWAY_EXAMPLE_ID} success"), // Invocation 3 succeeds + ]; + + let result = build_program_event_stack(&fixture_match_context(), &logs, parse_gateway_logs); + + // Expected result: two successful invocations with their events, one failed invocation + let expected = vec![ + ProgramInvocationState::Succeeded(vec![(2, event.clone())]), + ProgramInvocationState::Failed(vec![(6, event.clone())]), + ProgramInvocationState::Succeeded(vec![(10, event)]), + ]; + + assert_eq!(result, expected); + } + + #[test] + fn test_no_gateway_calls() { + // Logs with no gateway calls + let logs = vec![ + "Program some_other_program invoke [1]".to_owned(), + "Program log: Instruction: Do something".to_owned(), + "Program some_other_program success".to_owned(), + ]; + + let result = build_program_event_stack(&fixture_match_context(), &logs, parse_gateway_logs); + + // Expected result: empty stack + let expected = Vec::new(); + + assert_eq!(result, expected); + } + + #[test] + fn test_gateway_call_with_no_events() { + // Gateway call that succeeds but has no events + let logs = vec![ + format!("Program {GATEWAY_EXAMPLE_ID} invoke [1]"), + "Program log: Instruction: Do something".to_owned(), + format!("Program {GATEWAY_EXAMPLE_ID} success"), + ]; + + let result = build_program_event_stack(&fixture_match_context(), &logs, parse_gateway_logs); + + // Expected result: one successful invocation with no events + let expected = vec![ProgramInvocationState::Succeeded(vec![])]; + + assert_eq!(result, expected); + } + + #[test] + fn test_gateway_call_failure_with_events() { + // Use the test_call_data fixture + let (base64_data, event) = fixture_call_data(); + + // Gateway call that fails but has events (events should be discarded) + let logs = vec![ + format!("Program {GATEWAY_EXAMPLE_ID} invoke [1]"), + format!("Program data: {}", base64_data), + format!("Program {GATEWAY_EXAMPLE_ID} failed"), + ]; + + let result = build_program_event_stack(&fixture_match_context(), &logs, parse_gateway_logs); + + // Expected result: one failed invocation + let expected = vec![ProgramInvocationState::Failed(vec![(1, event)])]; + + assert_eq!(result, expected); + } +} diff --git a/solana/programs/axelar-solana-gateway/tests/integration/approve_message.rs b/solana/programs/axelar-solana-gateway/tests/integration/approve_message.rs index ca730e8..2da756a 100644 --- a/solana/programs/axelar-solana-gateway/tests/integration/approve_message.rs +++ b/solana/programs/axelar-solana-gateway/tests/integration/approve_message.rs @@ -12,7 +12,7 @@ use axelar_solana_gateway::state::incoming_message::{ command_id, IncomingMessage, IncomingMessageWrapper, }; use axelar_solana_gateway_test_fixtures::gateway::{ - get_gateway_events, make_messages, make_verifier_set, + get_gateway_events, make_messages, make_verifier_set, ProgramInvocationState, }; use axelar_solana_gateway_test_fixtures::SolanaAxelarIntegration; use itertools::Itertools; @@ -61,7 +61,7 @@ async fn successfully_approves_messages() { incoming_message_pda_bump, ) .unwrap(); - let tx_result = metadata.send_tx(&[ix]).await.unwrap(); + let tx = metadata.send_tx(&[ix]).await.unwrap(); // Assert event let expected_event = axelar_solana_gateway::processor::MessageEvent { @@ -73,11 +73,14 @@ async fn successfully_approves_messages() { payload_hash: message.payload_hash, destination_chain: message.destination_chain, }; - let emitted_event = get_gateway_events(&tx_result).pop().unwrap(); - let GatewayEvent::MessageApproved(emitted_event) = emitted_event else { - panic!("unexpected event"); + let emitted_events = get_gateway_events(&tx).pop().unwrap(); + let ProgramInvocationState::Succeeded(vec_events) = emitted_events else { + panic!("unexpected event") }; - assert_eq!(emitted_event, expected_event); + let [(_, GatewayEvent::MessageApproved(emitted_event))] = vec_events.as_slice() else { + panic!("unexpected event") + }; + assert_eq!(emitted_event, &expected_event); // Assert PDA state for message approval let account = metadata.incoming_message(incoming_message_pda).await; @@ -136,7 +139,7 @@ async fn successfully_idempotent_approvals_across_batches() { for message_info in merkle_messages_batch_two { dbg!(&message_info); let hash = message_info.leaf.message.hash::(); - let tx_result = metadata + let tx = metadata .approve_message( execute_data_batch_two.payload_merkle_root, message_info.clone(), @@ -146,9 +149,11 @@ async fn successfully_idempotent_approvals_across_batches() { .unwrap(); message_counter += 1; - if let Some(GatewayEvent::MessageApproved(_emitted_event)) = - get_gateway_events(&tx_result).pop() - { + let emitted_events = get_gateway_events(&tx).pop().unwrap(); + let ProgramInvocationState::Succeeded(vec_events) = emitted_events else { + panic!("unexpected event") + }; + if let [(_, GatewayEvent::MessageApproved(_emitted_event))] = vec_events.as_slice() { events_counter += 1; }; diff --git a/solana/programs/axelar-solana-gateway/tests/integration/rotate_signers.rs b/solana/programs/axelar-solana-gateway/tests/integration/rotate_signers.rs index dc30112..d61ff59 100644 --- a/solana/programs/axelar-solana-gateway/tests/integration/rotate_signers.rs +++ b/solana/programs/axelar-solana-gateway/tests/integration/rotate_signers.rs @@ -8,6 +8,7 @@ use axelar_solana_gateway::processor::{GatewayEvent, VerifierSetRotated}; use axelar_solana_gateway::state::verifier_set_tracker::VerifierSetTracker; use axelar_solana_gateway_test_fixtures::gateway::{ get_gateway_events, make_messages, make_verifier_set, random_bytes, random_message, + ProgramInvocationState, }; use axelar_solana_gateway_test_fixtures::SolanaAxelarIntegration; use solana_program_test::tokio; @@ -59,15 +60,18 @@ async fn successfully_rotates_signers() { let new_epoch: U256 = 2u128.into(); // - expected events - let emitted_event = get_gateway_events(&tx_result).pop().unwrap(); - let GatewayEvent::VerifierSetRotated(emitted_event) = emitted_event else { - panic!("unexpected event"); + let emitted_events = get_gateway_events(&tx_result).pop().unwrap(); + let ProgramInvocationState::Succeeded(vec_events) = emitted_events else { + panic!("unexpected event") + }; + let [(_, GatewayEvent::VerifierSetRotated(emitted_event))] = vec_events.as_slice() else { + panic!("unexpected event") }; let expected_event = VerifierSetRotated { epoch: new_epoch, verifier_set_hash: new_verifier_set_hash, }; - assert_eq!(emitted_event, expected_event); + assert_eq!(emitted_event, &expected_event); // - signers have been updated let root_pda_data = metadata.gateway_confg(metadata.gateway_root_pda).await; @@ -258,15 +262,18 @@ async fn succeed_if_verifier_set_signed_by_old_verifier_set_and_submitted_by_the // Assert assert!(tx.result.is_ok()); let new_epoch: U256 = 3_u128.into(); - let emitted_event = get_gateway_events(&tx).pop().unwrap(); - let GatewayEvent::VerifierSetRotated(emitted_event) = emitted_event else { - panic!("unexpected event"); + let emitted_events = get_gateway_events(&tx).pop().unwrap(); + let ProgramInvocationState::Succeeded(vec_events) = emitted_events else { + panic!("unexpected event") + }; + let [(_, GatewayEvent::VerifierSetRotated(emitted_event))] = vec_events.as_slice() else { + panic!("unexpected event") }; let expected_event = VerifierSetRotated { epoch: new_epoch, verifier_set_hash: new_verifier_set_hash, }; - assert_eq!(emitted_event, expected_event); + assert_eq!(emitted_event, &expected_event); // - signers have been updated let root_pda_data = metadata.gateway_confg(metadata.gateway_root_pda).await; diff --git a/solana/programs/axelar-solana-memo-program/tests/evm-e2e/from_solana_to_evm.rs b/solana/programs/axelar-solana-memo-program/tests/evm-e2e/from_solana_to_evm.rs index 72795bc..4801f10 100644 --- a/solana/programs/axelar-solana-memo-program/tests/evm-e2e/from_solana_to_evm.rs +++ b/solana/programs/axelar-solana-memo-program/tests/evm-e2e/from_solana_to_evm.rs @@ -1,7 +1,10 @@ use std::str::FromStr; use axelar_solana_gateway::processor::{CallContractEvent, GatewayEvent}; -use axelar_solana_gateway_test_fixtures::{base::TestFixture, gateway::get_gateway_events}; +use axelar_solana_gateway_test_fixtures::{ + base::TestFixture, + gateway::{get_gateway_events, ProgramInvocationState}, +}; use axelar_solana_memo_program::get_counter_pda; use axelar_solana_memo_program::instruction::call_gateway_with_memo; use ethers_core::utils::hex::ToHex; @@ -187,8 +190,11 @@ async fn call_solana_gateway( let event = get_gateway_events(&tx).into_iter().next().unwrap(); - let GatewayEvent::CallContract(event) = event else { + let ProgramInvocationState::Succeeded(vec_events) = event else { panic!("unexpected event") }; - event + let [(_, GatewayEvent::CallContract(event))] = vec_events.as_slice() else { + panic!("unexpected event") + }; + event.clone() } diff --git a/solana/programs/axelar-solana-memo-program/tests/module/send_to_gateway.rs b/solana/programs/axelar-solana-memo-program/tests/module/send_to_gateway.rs index 3f767e0..d0c4ac3 100644 --- a/solana/programs/axelar-solana-memo-program/tests/module/send_to_gateway.rs +++ b/solana/programs/axelar-solana-memo-program/tests/module/send_to_gateway.rs @@ -1,5 +1,5 @@ use axelar_solana_gateway::processor::{CallContractEvent, GatewayEvent}; -use axelar_solana_gateway_test_fixtures::gateway::get_gateway_events; +use axelar_solana_gateway_test_fixtures::gateway::{get_gateway_events, ProgramInvocationState}; use axelar_solana_memo_program::get_counter_pda; use axelar_solana_memo_program::instruction::call_gateway_with_memo; use ethers_core::abi::AbiEncode; @@ -42,16 +42,22 @@ async fn test_successfully_send_to_gateway() { // Assert // We can get the memo from the logs - let gateway_event = get_gateway_events(&tx).into_iter().next().unwrap(); + let emitted_events = get_gateway_events(&tx).pop().unwrap(); + let ProgramInvocationState::Succeeded(vec_events) = emitted_events else { + panic!("unexpected event") + }; + let [(_, GatewayEvent::CallContract(emitted_event))] = vec_events.as_slice() else { + panic!("unexpected event") + }; assert_eq!( - gateway_event, - GatewayEvent::CallContract(CallContractEvent { + emitted_event, + &CallContractEvent { sender_key: counter_pda, destination_chain, destination_contract_address: destination_address, payload: memo.as_bytes().to_vec(), payload_hash: solana_sdk::keccak::hash(memo.as_bytes()).0 - }), + }, "Mismatched gateway event" ); } diff --git a/solana/programs/axelar-solana-memo-program/tests/module/validate_message.rs b/solana/programs/axelar-solana-memo-program/tests/module/validate_message.rs index c6682df..7431717 100644 --- a/solana/programs/axelar-solana-memo-program/tests/module/validate_message.rs +++ b/solana/programs/axelar-solana-memo-program/tests/module/validate_message.rs @@ -20,7 +20,9 @@ async fn test_successful_validate_message(#[case] encoding_scheme: EncodingSchem use axelar_solana_gateway::processor::GatewayEvent; // Setup - use axelar_solana_gateway_test_fixtures::gateway::get_gateway_events; + use axelar_solana_gateway_test_fixtures::gateway::{ + get_gateway_events, ProgramInvocationState, + }; use axelar_solana_memo_program::state::Counter; use solana_sdk::pubkey::Pubkey; @@ -130,9 +132,12 @@ async fn test_successful_validate_message(#[case] encoding_scheme: EncodingSchem assert_eq!(counter.counter, 1); // Event was logged - let emitted_event = get_gateway_events(&tx).pop().unwrap(); - let GatewayEvent::MessageExecuted(emitted_event) = emitted_event else { - panic!("unexpected event"); + let emitted_events = get_gateway_events(&tx).pop().unwrap(); + let ProgramInvocationState::Succeeded(vec_events) = emitted_events else { + panic!("unexpected event") + }; + let [(_, GatewayEvent::MessageExecuted(emitted_event))] = vec_events.as_slice() else { + panic!("unexpected event") }; let command_id = command_id( &merkelised_message.leaf.message.cc_id.chain, @@ -149,5 +154,5 @@ async fn test_successful_validate_message(#[case] encoding_scheme: EncodingSchem destination_chain: merkelised_message.leaf.message.destination_chain, }; - assert_eq!(emitted_event, expected_event); + assert_eq!(emitted_event, &expected_event); }