shank-parse 0.1.1

A proc-macro crate that generates Rust client code from Shank/Anchor IDL JSON files for Solana programs.
Documentation
shank_parse::shank_parse!("../idl/counter.json");

#[cfg(test)]
mod tests {
    use super::counter::accounts::*;
    use super::counter::events::*;
    use super::counter::instructions::*;
    use super::counter::ID;
    use shank_parse::__private::{base64_encode, Pubkey};

    // ── Program ID ────────────────────────────────────────────────────────────

    #[test]
    fn test_program_id() {
        assert_eq!(ID.to_string(), "8F1XtWR4wTs37nnutBvd2MWpCTfb7XAciFYkw5XHaENj");
    }

    // ── accounts ─────────────────────────────────────────────────────────────

    #[test]
    fn test_counter_from_account_data() {
        let mut data = Vec::new();
        data.push(1u8);
        data.extend_from_slice(&42u64.to_le_bytes());

        let counter = Counter::from_account_data(&data).unwrap();
        assert_eq!(counter.bump, 1);
        assert_eq!(counter.count, 42);
    }

    #[test]
    fn test_counter_authority_from_account_data() {
        let authority = [7u8; 32];
        let mut data = Vec::new();
        data.extend_from_slice(&authority);
        data.push(5u8);
        data.extend_from_slice(&100u64.to_le_bytes());

        let ca = CounterAuthority::from_account_data(&data).unwrap();
        assert_eq!(ca.authority, authority);
        assert_eq!(ca.bump, 5);
        assert_eq!(ca.count, 100);
    }

    #[test]
    fn test_counter_from_account_data_insufficient() {
        let data = vec![1u8];
        assert!(Counter::from_account_data(&data).is_err());
    }

    // ── instructions ─────────────────────────────────────────────────────────

    #[test]
    fn test_init_counter_instruction() {
        let program_id = Pubkey::new_from_array([1u8; 32]);
        let accounts = InitCounterAccounts {
            payer: Pubkey::new_from_array([2u8; 32]),
            counter: Pubkey::new_from_array([3u8; 32]),
            system_program: Pubkey::new_from_array([4u8; 32]),
        };
        let args = InitCounterArgs { count: 42 };
        let ix = init_counter(&program_id, &accounts, &args);

        assert_eq!(ix.program_id, program_id);
        assert_eq!(ix.accounts.len(), 3);
        assert_eq!(ix.data[0], 0); // discriminant
        assert_eq!(
            u64::from_le_bytes(ix.data[1..9].try_into().expect("slice")),
            42
        );
        assert!(!ix.accounts[0].is_writable); // payer: not mut
        assert!(ix.accounts[0].is_signer); // payer: signer
        assert!(ix.accounts[1].is_writable); // counter: mut
        assert!(!ix.accounts[1].is_signer); // counter: not signer
    }

    #[test]
    fn test_increase_counter_instruction() {
        let program_id = Pubkey::new_from_array([1u8; 32]);
        let accounts = IncreaseCounterAccounts {
            counter: Pubkey::new_from_array([3u8; 32]),
        };
        let ix = increase_counter(&program_id, &accounts);

        assert_eq!(ix.program_id, program_id);
        assert_eq!(ix.accounts.len(), 1);
        assert_eq!(ix.data, vec![1u8]); // discriminant only
    }

    #[test]
    fn test_init_counter_authority_instruction() {
        let program_id = Pubkey::new_from_array([1u8; 32]);
        let accounts = InitCounterAuhthorityAccounts {
            payer: Pubkey::new_from_array([2u8; 32]),
            counter_authority: Pubkey::new_from_array([3u8; 32]),
            system_program: Pubkey::new_from_array([4u8; 32]),
        };
        let args = InitCounterAuthorityArgs { count: 99 };
        let ix = init_counter_auhthority(&program_id, &accounts, &args);

        assert_eq!(ix.program_id, program_id);
        assert_eq!(ix.accounts.len(), 3);
        assert_eq!(ix.data[0], 2); // discriminant
    }

    #[test]
    fn test_increase_counter_authority_instruction() {
        let program_id = Pubkey::new_from_array([1u8; 32]);
        let accounts = IncreaseCounterAuthorityAccounts {
            authority: Pubkey::new_from_array([2u8; 32]),
            counter_authority: Pubkey::new_from_array([3u8; 32]),
        };
        let ix = increase_counter_authority(&program_id, &accounts);

        assert_eq!(ix.data, vec![3u8]); // discriminant only
        assert!(ix.accounts[0].is_signer); // authority
    }

    // ── events ───────────────────────────────────────────────────────────────

    #[test]
    fn test_counter_increased_from_logs() {
        let mut event_data = vec![1u8]; // discriminant for CounterIncreased
        event_data.extend_from_slice(&42u64.to_le_bytes());
        let log = format!("Program data: {}", base64_encode(&event_data));

        let events = CounterIncreased::from_logs(&[log.as_str()]);
        assert_eq!(events.len(), 1);
        assert_eq!(events[0].new_count, 42);
    }

    #[test]
    fn test_counter_initialized_from_logs() {
        let mut event_data = vec![0u8]; // discriminant for CounterInitialized
        event_data.extend_from_slice(&10u64.to_le_bytes());
        let log = format!("Program data: {}", base64_encode(&event_data));

        let events = CounterInitialized::from_logs(&[log.as_str()]);
        assert_eq!(events.len(), 1);
        assert_eq!(events[0].count, 10);
    }

    #[test]
    fn test_counter_event_enum_from_logs() {
        let mut data0 = vec![0u8];
        data0.extend_from_slice(&10u64.to_le_bytes());
        let log0 = format!("Program data: {}", base64_encode(&data0));

        let mut data1 = vec![1u8];
        data1.extend_from_slice(&20u64.to_le_bytes());
        let log1 = format!("Program data: {}", base64_encode(&data1));

        let logs: Vec<&str> = vec![log0.as_str(), log1.as_str(), "some other log"];
        let events = CounterEvent::from_logs(&logs);
        assert_eq!(events.len(), 2);
        assert_eq!(
            events[0],
            CounterEvent::CounterInitialized(CounterInitialized { count: 10 })
        );
        assert_eq!(
            events[1],
            CounterEvent::CounterIncreased(CounterIncreased { new_count: 20 })
        );
    }

    #[test]
    fn test_from_logs_ignores_non_event_logs() {
        let logs: Vec<&str> = vec![
            "Program log: something",
            "random log",
            "Program data: invalid_base64!!!",
        ];
        let events = CounterEvent::from_logs(&logs);
        assert!(events.is_empty());
    }

    #[test]
    fn test_from_logs_with_string_vec() {
        let mut event_data = vec![0u8];
        event_data.extend_from_slice(&5u64.to_le_bytes());
        let log = format!("Program data: {}", base64_encode(&event_data));

        let logs: Vec<String> = vec![log];
        let events = CounterInitialized::from_logs(&logs);
        assert_eq!(events.len(), 1);
        assert_eq!(events[0].count, 5);
    }
}