spl_tlv_account_resolution/
pubkey_data.rs

1//! Types for managing extra account meta keys that may be extracted from some
2//! data.
3//!
4//! This can be either account data from some account in the list of accounts
5//! or from the instruction data itself.
6
7#[cfg(feature = "serde-traits")]
8use serde::{Deserialize, Serialize};
9use {crate::error::AccountResolutionError, solana_program_error::ProgramError};
10
11/// Enum to describe a required key stored in some data.
12#[derive(Clone, Debug, PartialEq)]
13#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
14#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
15pub enum PubkeyData {
16    /// Uninitialized configuration byte space.
17    Uninitialized,
18    /// A pubkey to be resolved from the instruction data.
19    ///
20    /// Packed as:
21    ///     * 1 - Discriminator
22    ///     * 1 - Start index of instruction data
23    ///
24    /// Note: Length is always 32 bytes.
25    InstructionData {
26        /// The index where the address bytes begin in the instruction data.
27        index: u8,
28    },
29    /// A pubkey to be resolved from the inner data of some account.
30    ///
31    /// Packed as:
32    ///     * 1 - Discriminator
33    ///     * 1 - Index of account in accounts list
34    ///     * 1 - Start index of account data
35    ///
36    /// Note: Length is always 32 bytes.
37    AccountData {
38        /// The index of the account in the entire accounts list.
39        account_index: u8,
40        /// The index where the address bytes begin in the account data.
41        data_index: u8,
42    },
43}
44impl PubkeyData {
45    /// Get the size of a pubkey data configuration.
46    pub fn tlv_size(&self) -> u8 {
47        match self {
48            Self::Uninitialized => 0,
49            // 1 byte for the discriminator, 1 byte for the index.
50            Self::InstructionData { .. } => 1 + 1,
51            // 1 byte for the discriminator, 1 byte for the account index,
52            // 1 byte for the data index.
53            Self::AccountData { .. } => 1 + 1 + 1,
54        }
55    }
56
57    /// Packs a pubkey data configuration into a slice.
58    pub fn pack(&self, dst: &mut [u8]) -> Result<(), ProgramError> {
59        // Because no `PubkeyData` variant is larger than 3 bytes, this check
60        // is sufficient for the data length.
61        if dst.len() != self.tlv_size() as usize {
62            return Err(AccountResolutionError::NotEnoughBytesForPubkeyData.into());
63        }
64        match &self {
65            Self::Uninitialized => {
66                return Err(AccountResolutionError::InvalidPubkeyDataConfig.into())
67            }
68            Self::InstructionData { index } => {
69                dst[0] = 1;
70                dst[1] = *index;
71            }
72            Self::AccountData {
73                account_index,
74                data_index,
75            } => {
76                dst[0] = 2;
77                dst[1] = *account_index;
78                dst[2] = *data_index;
79            }
80        }
81        Ok(())
82    }
83
84    /// Packs a pubkey data configuration into a 32-byte array, filling the
85    /// rest with zeroes.
86    pub fn pack_into_address_config(key_data: &Self) -> Result<[u8; 32], ProgramError> {
87        let mut packed = [0u8; 32];
88        let tlv_size = key_data.tlv_size() as usize;
89        key_data.pack(&mut packed[..tlv_size])?;
90        Ok(packed)
91    }
92
93    /// Unpacks a pubkey data configuration from a slice.
94    pub fn unpack(bytes: &[u8]) -> Result<Self, ProgramError> {
95        let (discrim, rest) = bytes
96            .split_first()
97            .ok_or::<ProgramError>(ProgramError::InvalidAccountData)?;
98        match discrim {
99            0 => Ok(Self::Uninitialized),
100            1 => {
101                if rest.is_empty() {
102                    return Err(AccountResolutionError::InvalidBytesForPubkeyData.into());
103                }
104                Ok(Self::InstructionData { index: rest[0] })
105            }
106            2 => {
107                if rest.len() < 2 {
108                    return Err(AccountResolutionError::InvalidBytesForPubkeyData.into());
109                }
110                Ok(Self::AccountData {
111                    account_index: rest[0],
112                    data_index: rest[1],
113                })
114            }
115            _ => Err(ProgramError::InvalidAccountData),
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_pack() {
126        // Should fail if the length is too short.
127        let key = PubkeyData::InstructionData { index: 0 };
128        let mut packed = vec![0u8; key.tlv_size() as usize - 1];
129        assert_eq!(
130            key.pack(&mut packed).unwrap_err(),
131            AccountResolutionError::NotEnoughBytesForPubkeyData.into(),
132        );
133
134        // Should fail if the length is too long.
135        let key = PubkeyData::InstructionData { index: 0 };
136        let mut packed = vec![0u8; key.tlv_size() as usize + 1];
137        assert_eq!(
138            key.pack(&mut packed).unwrap_err(),
139            AccountResolutionError::NotEnoughBytesForPubkeyData.into(),
140        );
141
142        // Can't pack a `PubkeyData::Uninitialized`.
143        let key = PubkeyData::Uninitialized;
144        let mut packed = vec![0u8; key.tlv_size() as usize];
145        assert_eq!(
146            key.pack(&mut packed).unwrap_err(),
147            AccountResolutionError::InvalidPubkeyDataConfig.into(),
148        );
149    }
150
151    #[test]
152    fn test_unpack() {
153        // Can unpack zeroes.
154        let zeroes = [0u8; 32];
155        let key = PubkeyData::unpack(&zeroes).unwrap();
156        assert_eq!(key, PubkeyData::Uninitialized);
157
158        // Should fail for empty bytes.
159        let bytes = [];
160        assert_eq!(
161            PubkeyData::unpack(&bytes).unwrap_err(),
162            ProgramError::InvalidAccountData
163        );
164    }
165
166    fn test_pack_unpack_key(key: PubkeyData) {
167        let tlv_size = key.tlv_size() as usize;
168        let mut packed = vec![0u8; tlv_size];
169        key.pack(&mut packed).unwrap();
170        let unpacked = PubkeyData::unpack(&packed).unwrap();
171        assert_eq!(key, unpacked);
172    }
173
174    #[test]
175    fn test_pack_unpack() {
176        // Instruction data.
177        test_pack_unpack_key(PubkeyData::InstructionData { index: 0 });
178
179        // Account data.
180        test_pack_unpack_key(PubkeyData::AccountData {
181            account_index: 0,
182            data_index: 0,
183        });
184    }
185}