1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
use {
    crate::parse_instruction::parse_memo_data,
    solana_sdk::{message::Message, pubkey::Pubkey},
};

// A helper function to convert spl_memo::v1::id() as spl_sdk::pubkey::Pubkey to
// solana_sdk::pubkey::Pubkey
pub fn spl_memo_id_v1() -> Pubkey {
    Pubkey::new_from_array(spl_memo::v1::id().to_bytes())
}

// A helper function to convert spl_memo::id() as spl_sdk::pubkey::Pubkey to
// solana_sdk::pubkey::Pubkey
pub fn spl_memo_id_v3() -> Pubkey {
    Pubkey::new_from_array(spl_memo::id().to_bytes())
}

pub fn extract_and_fmt_memos<T: ExtractMemos>(message: &T) -> Option<String> {
    let memos = message.extract_memos();
    if memos.is_empty() {
        None
    } else {
        Some(memos.join("; "))
    }
}

fn maybe_push_parsed_memo(memos: &mut Vec<String>, program_id: Pubkey, data: &[u8]) {
    if program_id == spl_memo_id_v1() || program_id == spl_memo_id_v3() {
        let memo_len = data.len();
        let parsed_memo = parse_memo_data(data).unwrap_or_else(|_| "(unparseable)".to_string());
        memos.push(format!("[{}] {}", memo_len, parsed_memo));
    }
}

pub trait ExtractMemos {
    fn extract_memos(&self) -> Vec<String>;
}

impl ExtractMemos for Message {
    fn extract_memos(&self) -> Vec<String> {
        let mut memos = vec![];
        if self.account_keys.contains(&spl_memo_id_v1())
            || self.account_keys.contains(&spl_memo_id_v3())
        {
            for instruction in &self.instructions {
                let program_id = self.account_keys[instruction.program_id_index as usize];
                maybe_push_parsed_memo(&mut memos, program_id, &instruction.data);
            }
        }
        memos
    }
}

#[cfg(test)]
mod test {
    use {
        super::*,
        solana_sdk::{hash::Hash, instruction::CompiledInstruction},
    };

    #[test]
    fn test_extract_memos() {
        let fee_payer = Pubkey::new_unique();
        let another_program_id = Pubkey::new_unique();
        let memo0 = "Test memo";
        let memo1 = "🦖";
        let expected_memos = vec![
            format!("[{}] {}", memo0.len(), memo0),
            format!("[{}] {}", memo1.len(), memo1),
        ];
        let memo_instructions = vec![
            CompiledInstruction {
                program_id_index: 1,
                accounts: vec![],
                data: memo0.as_bytes().to_vec(),
            },
            CompiledInstruction {
                program_id_index: 2,
                accounts: vec![],
                data: memo1.as_bytes().to_vec(),
            },
            CompiledInstruction {
                program_id_index: 3,
                accounts: vec![],
                data: memo1.as_bytes().to_vec(),
            },
        ];
        let message = Message::new_with_compiled_instructions(
            1,
            0,
            3,
            vec![
                fee_payer,
                spl_memo_id_v1(),
                another_program_id,
                spl_memo_id_v3(),
            ],
            Hash::default(),
            memo_instructions,
        );
        assert_eq!(message.extract_memos(), expected_memos);
    }
}