Skip to main content

solana_primitives/types/
account.rs

1use crate::types::Pubkey;
2use crate::{Result, SolanaError};
3use borsh::{BorshDeserialize, BorshSerialize};
4use serde::{Deserialize, Serialize};
5
6const LOOKUP_TABLE_META_SIZE: usize = 56;
7/// On-chain `ProgramState` discriminant for an initialized lookup table (0 = `Uninitialized`).
8const LOOKUP_TABLE_DISCRIMINANT: u32 = 1;
9
10/// Address lookup table lookup information
11/// Used to describe which addresses in a lookup table to use in a transaction
12#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
13pub struct MessageAddressTableLookup {
14    /// Address lookup table account key
15    pub account_key: Pubkey,
16    /// List of indices used to load writable account addresses
17    #[serde(with = "crate::short_vec")]
18    pub writable_indexes: Vec<u8>,
19    /// List of indices used to load readonly account addresses
20    #[serde(with = "crate::short_vec")]
21    pub readonly_indexes: Vec<u8>,
22}
23
24impl MessageAddressTableLookup {
25    /// Create a new MessageAddressTableLookup
26    pub fn new(account_key: Pubkey, writable_indexes: Vec<u8>, readonly_indexes: Vec<u8>) -> Self {
27        Self {
28            account_key,
29            writable_indexes,
30            readonly_indexes,
31        }
32    }
33}
34
35/// Address lookup table account
36#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
37pub struct AddressLookupTableAccount {
38    /// The lookup table's public key
39    pub key: Pubkey,
40    /// List of addresses in the lookup table
41    pub addresses: Vec<Pubkey>,
42}
43
44impl AddressLookupTableAccount {
45    /// Create a new address lookup table account
46    pub fn new(key: Pubkey, addresses: Vec<Pubkey>) -> Self {
47        Self { key, addresses }
48    }
49
50    /// Get the number of addresses in the lookup table
51    pub fn len(&self) -> usize {
52        self.addresses.len()
53    }
54
55    /// Check if the lookup table is empty
56    pub fn is_empty(&self) -> bool {
57        self.addresses.is_empty()
58    }
59
60    /// Get an address at the specified index
61    pub fn get(&self, index: usize) -> Option<&Pubkey> {
62        self.addresses.get(index)
63    }
64
65    /// Parse an address lookup table account from raw account data.
66    pub fn from_account_data(key: Pubkey, data: &[u8]) -> Result<Self> {
67        if data.len() < LOOKUP_TABLE_META_SIZE {
68            return Err(SolanaError::InvalidMessage);
69        }
70
71        let discriminant = u32::from_le_bytes(
72            data[0..4]
73                .try_into()
74                .map_err(|_| SolanaError::InvalidMessage)?,
75        );
76        if discriminant != LOOKUP_TABLE_DISCRIMINANT {
77            return Err(SolanaError::InvalidMessage);
78        }
79
80        let address_data = &data[LOOKUP_TABLE_META_SIZE..];
81        if !address_data.len().is_multiple_of(32) {
82            return Err(SolanaError::InvalidMessage);
83        }
84
85        let mut addresses = Vec::with_capacity(address_data.len() / 32);
86        for chunk in address_data.chunks_exact(32) {
87            let bytes: [u8; 32] = chunk.try_into().map_err(|_| SolanaError::InvalidMessage)?;
88            addresses.push(Pubkey::new(bytes));
89        }
90
91        Ok(Self { key, addresses })
92    }
93
94    /// Parse an address lookup table account from a base58 key and raw account data.
95    pub fn from_base58_account_data(key: &str, data: &[u8]) -> Result<Self> {
96        let key = Pubkey::from_base58(key)?;
97        Self::from_account_data(key, data)
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::types::Pubkey;
105
106    #[test]
107    fn test_address_lookup_table_account() {
108        let key = Pubkey::new([1; 32]);
109        let addresses = vec![
110            Pubkey::new([2; 32]),
111            Pubkey::new([3; 32]),
112            Pubkey::new([4; 32]),
113        ];
114        let lookup_table = AddressLookupTableAccount::new(key, addresses);
115
116        // Test initial state
117        assert_eq!(lookup_table.key, Pubkey::new([1; 32]));
118        assert_eq!(lookup_table.len(), 3);
119        assert!(!lookup_table.is_empty());
120
121        // Test getting addresses
122        assert_eq!(lookup_table.get(0), Some(&Pubkey::new([2; 32])));
123        assert_eq!(lookup_table.get(1), Some(&Pubkey::new([3; 32])));
124        assert_eq!(lookup_table.get(2), Some(&Pubkey::new([4; 32])));
125        assert_eq!(lookup_table.get(3), None);
126    }
127
128    #[test]
129    fn test_message_address_table_lookup() {
130        let key = Pubkey::new([1; 32]);
131        let writable_indexes = vec![0, 1];
132        let readonly_indexes = vec![2];
133
134        let lookup = MessageAddressTableLookup::new(key, writable_indexes, readonly_indexes);
135
136        assert_eq!(lookup.account_key, Pubkey::new([1; 32]));
137        assert_eq!(lookup.writable_indexes, vec![0, 1]);
138        assert_eq!(lookup.readonly_indexes, vec![2]);
139    }
140
141    #[test]
142    fn test_address_lookup_table_from_account_data() {
143        let key = Pubkey::new([9; 32]);
144        let mut data = vec![0u8; LOOKUP_TABLE_META_SIZE];
145        data[0..4].copy_from_slice(&LOOKUP_TABLE_DISCRIMINANT.to_le_bytes());
146        data.extend_from_slice(&[2u8; 32]);
147        data.extend_from_slice(&[3u8; 32]);
148
149        let parsed = AddressLookupTableAccount::from_account_data(key, &data).unwrap();
150
151        assert_eq!(parsed.key, key);
152        assert_eq!(parsed.addresses.len(), 2);
153        assert_eq!(parsed.addresses[0], Pubkey::new([2u8; 32]));
154        assert_eq!(parsed.addresses[1], Pubkey::new([3u8; 32]));
155    }
156
157    #[test]
158    fn test_address_lookup_table_from_account_data_rejects_invalid_length() {
159        let key = Pubkey::new([9; 32]);
160        let invalid_data = vec![0u8; LOOKUP_TABLE_META_SIZE + 1];
161
162        let result = AddressLookupTableAccount::from_account_data(key, &invalid_data);
163
164        assert!(matches!(result, Err(SolanaError::InvalidMessage)));
165    }
166
167    #[test]
168    fn test_address_lookup_table_from_account_data_rejects_invalid_discriminant() {
169        let key = Pubkey::new([9; 32]);
170        let mut data = vec![0u8; LOOKUP_TABLE_META_SIZE];
171        data[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
172        data.extend_from_slice(&[2u8; 32]);
173
174        let result = AddressLookupTableAccount::from_account_data(key, &data);
175
176        assert!(matches!(result, Err(SolanaError::InvalidMessage)));
177    }
178}