solana_primitives/types/
account.rs1use crate::types::Pubkey;
2use crate::{Result, SolanaError};
3use borsh::{BorshDeserialize, BorshSerialize};
4use serde::{Deserialize, Serialize};
5
6const LOOKUP_TABLE_META_SIZE: usize = 56;
7const LOOKUP_TABLE_DISCRIMINANT: u32 = 1;
9
10#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
13pub struct MessageAddressTableLookup {
14 pub account_key: Pubkey,
16 #[serde(with = "crate::short_vec")]
18 pub writable_indexes: Vec<u8>,
19 #[serde(with = "crate::short_vec")]
21 pub readonly_indexes: Vec<u8>,
22}
23
24impl MessageAddressTableLookup {
25 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#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
37pub struct AddressLookupTableAccount {
38 pub key: Pubkey,
40 pub addresses: Vec<Pubkey>,
42}
43
44impl AddressLookupTableAccount {
45 pub fn new(key: Pubkey, addresses: Vec<Pubkey>) -> Self {
47 Self { key, addresses }
48 }
49
50 pub fn len(&self) -> usize {
52 self.addresses.len()
53 }
54
55 pub fn is_empty(&self) -> bool {
57 self.addresses.is_empty()
58 }
59
60 pub fn get(&self, index: usize) -> Option<&Pubkey> {
62 self.addresses.get(index)
63 }
64
65 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 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 assert_eq!(lookup_table.key, Pubkey::new([1; 32]));
118 assert_eq!(lookup_table.len(), 3);
119 assert!(!lookup_table.is_empty());
120
121 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}