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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
//! Types for managing extra account meta keys that may be extracted from some
//! data.
//!
//! This can be either account data from some account in the list of accounts
//! or from the instruction data itself.
#[cfg(feature = "serde-traits")]
use serde::{Deserialize, Serialize};
use {crate::error::AccountResolutionError, solana_program::program_error::ProgramError};
/// Enum to describe a required key stored in some data.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
pub enum PubkeyData {
/// Uninitialized configuration byte space.
Uninitialized,
/// A pubkey to be resolved from the instruction data.
///
/// Packed as:
/// * 1 - Discriminator
/// * 1 - Start index of instruction data
///
/// Note: Length is always 32 bytes.
InstructionData {
/// The index where the address bytes begin in the instruction data.
index: u8,
},
/// A pubkey to be resolved from the inner data of some account.
///
/// Packed as:
/// * 1 - Discriminator
/// * 1 - Index of account in accounts list
/// * 1 - Start index of account data
///
/// Note: Length is always 32 bytes.
AccountData {
/// The index of the account in the entire accounts list.
account_index: u8,
/// The index where the address bytes begin in the account data.
data_index: u8,
},
}
impl PubkeyData {
/// Get the size of a pubkey data configuration.
pub fn tlv_size(&self) -> u8 {
match self {
Self::Uninitialized => 0,
// 1 byte for the discriminator, 1 byte for the index.
Self::InstructionData { .. } => 1 + 1,
// 1 byte for the discriminator, 1 byte for the account index,
// 1 byte for the data index.
Self::AccountData { .. } => 1 + 1 + 1,
}
}
/// Packs a pubkey data configuration into a slice.
pub fn pack(&self, dst: &mut [u8]) -> Result<(), ProgramError> {
// Because no `PubkeyData` variant is larger than 3 bytes, this check
// is sufficient for the data length.
if dst.len() != self.tlv_size() as usize {
return Err(AccountResolutionError::NotEnoughBytesForPubkeyData.into());
}
match &self {
Self::Uninitialized => {
return Err(AccountResolutionError::InvalidPubkeyDataConfig.into())
}
Self::InstructionData { index } => {
dst[0] = 1;
dst[1] = *index;
}
Self::AccountData {
account_index,
data_index,
} => {
dst[0] = 2;
dst[1] = *account_index;
dst[2] = *data_index;
}
}
Ok(())
}
/// Packs a pubkey data configuration into a 32-byte array, filling the
/// rest with 0s.
pub fn pack_into_address_config(key_data: &Self) -> Result<[u8; 32], ProgramError> {
let mut packed = [0u8; 32];
let tlv_size = key_data.tlv_size() as usize;
key_data.pack(&mut packed[..tlv_size])?;
Ok(packed)
}
/// Unpacks a pubkey data configuration from a slice.
pub fn unpack(bytes: &[u8]) -> Result<Self, ProgramError> {
let (discrim, rest) = bytes
.split_first()
.ok_or::<ProgramError>(ProgramError::InvalidAccountData)?;
match discrim {
0 => Ok(Self::Uninitialized),
1 => {
if rest.is_empty() {
return Err(AccountResolutionError::InvalidBytesForPubkeyData.into());
}
Ok(Self::InstructionData { index: rest[0] })
}
2 => {
if rest.len() < 2 {
return Err(AccountResolutionError::InvalidBytesForPubkeyData.into());
}
Ok(Self::AccountData {
account_index: rest[0],
data_index: rest[1],
})
}
_ => Err(ProgramError::InvalidAccountData),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pack() {
// Should fail if the length is too short.
let key = PubkeyData::InstructionData { index: 0 };
let mut packed = vec![0u8; key.tlv_size() as usize - 1];
assert_eq!(
key.pack(&mut packed).unwrap_err(),
AccountResolutionError::NotEnoughBytesForPubkeyData.into(),
);
// Should fail if the length is too long.
let key = PubkeyData::InstructionData { index: 0 };
let mut packed = vec![0u8; key.tlv_size() as usize + 1];
assert_eq!(
key.pack(&mut packed).unwrap_err(),
AccountResolutionError::NotEnoughBytesForPubkeyData.into(),
);
// Can't pack a `PubkeyData::Uninitialized`.
let key = PubkeyData::Uninitialized;
let mut packed = vec![0u8; key.tlv_size() as usize];
assert_eq!(
key.pack(&mut packed).unwrap_err(),
AccountResolutionError::InvalidPubkeyDataConfig.into(),
);
}
#[test]
fn test_unpack() {
// Can unpack zeroes.
let zeroes = [0u8; 32];
let key = PubkeyData::unpack(&zeroes).unwrap();
assert_eq!(key, PubkeyData::Uninitialized);
// Should fail for empty bytes.
let bytes = [];
assert_eq!(
PubkeyData::unpack(&bytes).unwrap_err(),
ProgramError::InvalidAccountData
);
}
fn test_pack_unpack_key(key: PubkeyData) {
let tlv_size = key.tlv_size() as usize;
let mut packed = vec![0u8; tlv_size];
key.pack(&mut packed).unwrap();
let unpacked = PubkeyData::unpack(&packed).unwrap();
assert_eq!(key, unpacked);
}
#[test]
fn test_pack_unpack() {
// Instruction data.
test_pack_unpack_key(PubkeyData::InstructionData { index: 0 });
// Account data.
test_pack_unpack_key(PubkeyData::AccountData {
account_index: 0,
data_index: 0,
});
}
}