Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 7e04983

Browse files
author
Joe C
authored
TLV Account Resolution: Add support for keys in data (#6847)
* tlv-account-resolution: add `KeyData` configuration * tlv-account-resolution: add `KeyData` support to `ExtraAccountMeta` * tlv-account-resolution: add tests for `KeyData` * rename to `PubkeyData` * update docs
1 parent a1205a6 commit 7e04983

File tree

5 files changed

+445
-21
lines changed

5 files changed

+445
-21
lines changed

libraries/tlv-account-resolution/src/account.rs

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
//! collection of seeds
55
66
use {
7-
crate::{error::AccountResolutionError, seeds::Seed},
7+
crate::{error::AccountResolutionError, pubkey_data::PubkeyData, seeds::Seed},
88
bytemuck::{Pod, Zeroable},
99
solana_program::{
10-
account_info::AccountInfo, instruction::AccountMeta, program_error::ProgramError,
11-
pubkey::Pubkey,
10+
account_info::AccountInfo,
11+
instruction::AccountMeta,
12+
program_error::ProgramError,
13+
pubkey::{Pubkey, PUBKEY_BYTES},
1214
},
1315
spl_pod::primitives::PodBool,
1416
};
@@ -66,18 +68,66 @@ where
6668
Ok(Pubkey::find_program_address(&pda_seeds, program_id).0)
6769
}
6870

71+
/// Resolve a pubkey from a pubkey data configuration.
72+
fn resolve_key_data<'a, F>(
73+
key_data: &PubkeyData,
74+
instruction_data: &[u8],
75+
get_account_key_data_fn: F,
76+
) -> Result<Pubkey, ProgramError>
77+
where
78+
F: Fn(usize) -> Option<(&'a Pubkey, Option<&'a [u8]>)>,
79+
{
80+
match key_data {
81+
PubkeyData::Uninitialized => Err(ProgramError::InvalidAccountData),
82+
PubkeyData::InstructionData { index } => {
83+
let key_start = *index as usize;
84+
let key_end = key_start + PUBKEY_BYTES;
85+
if key_end > instruction_data.len() {
86+
return Err(AccountResolutionError::InstructionDataTooSmall.into());
87+
}
88+
Ok(Pubkey::new_from_array(
89+
instruction_data[key_start..key_end].try_into().unwrap(),
90+
))
91+
}
92+
PubkeyData::AccountData {
93+
account_index,
94+
data_index,
95+
} => {
96+
let account_index = *account_index as usize;
97+
let account_data = get_account_key_data_fn(account_index)
98+
.ok_or::<ProgramError>(AccountResolutionError::AccountNotFound.into())?
99+
.1
100+
.ok_or::<ProgramError>(AccountResolutionError::AccountDataNotFound.into())?;
101+
let arg_start = *data_index as usize;
102+
let arg_end = arg_start + PUBKEY_BYTES;
103+
if account_data.len() < arg_end {
104+
return Err(AccountResolutionError::AccountDataTooSmall.into());
105+
}
106+
Ok(Pubkey::new_from_array(
107+
account_data[arg_start..arg_end].try_into().unwrap(),
108+
))
109+
}
110+
}
111+
}
112+
69113
/// `Pod` type for defining a required account in a validation account.
70114
///
71-
/// This can either be a standard `AccountMeta` or a PDA.
115+
/// This can be any of the following:
116+
///
117+
/// * A standard `AccountMeta`
118+
/// * A PDA (with seed configurations)
119+
/// * A pubkey stored in some data (account or instruction data)
120+
///
72121
/// Can be used in TLV-encoded data.
73122
#[repr(C)]
74123
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
75124
pub struct ExtraAccountMeta {
76125
/// Discriminator to tell whether this represents a standard
77-
/// `AccountMeta` or a PDA
126+
/// `AccountMeta`, PDA, or pubkey data.
78127
pub discriminator: u8,
79-
/// This `address_config` field can either be the pubkey of the account
80-
/// or the seeds used to derive the pubkey from provided inputs
128+
/// This `address_config` field can either be the pubkey of the account,
129+
/// the seeds used to derive the pubkey from provided inputs (PDA), or the
130+
/// data used to derive the pubkey (account or instruction data).
81131
pub address_config: [u8; 32],
82132
/// Whether the account should sign
83133
pub is_signer: PodBool,
@@ -118,6 +168,20 @@ impl ExtraAccountMeta {
118168
})
119169
}
120170

171+
/// Create a `ExtraAccountMeta` from a pubkey data configuration.
172+
pub fn new_with_pubkey_data(
173+
key_data: &PubkeyData,
174+
is_signer: bool,
175+
is_writable: bool,
176+
) -> Result<Self, ProgramError> {
177+
Ok(Self {
178+
discriminator: 2,
179+
address_config: PubkeyData::pack_into_address_config(key_data)?,
180+
is_signer: is_signer.into(),
181+
is_writable: is_writable.into(),
182+
})
183+
}
184+
121185
/// Create a `ExtraAccountMeta` from a list of seed configurations,
122186
/// representing a PDA for an external program
123187
///
@@ -173,6 +237,14 @@ impl ExtraAccountMeta {
173237
is_writable: self.is_writable.into(),
174238
})
175239
}
240+
2 => {
241+
let key_data = PubkeyData::unpack(&self.address_config)?;
242+
Ok(AccountMeta {
243+
pubkey: resolve_key_data(&key_data, instruction_data, get_account_key_data_fn)?,
244+
is_signer: self.is_signer.into(),
245+
is_writable: self.is_writable.into(),
246+
})
247+
}
176248
_ => Err(ProgramError::InvalidAccountData),
177249
}
178250
}

libraries/tlv-account-resolution/src/error.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,13 @@ pub enum AccountResolutionError {
6060
/// Failed to fetch account
6161
#[error("Failed to fetch account")]
6262
AccountFetchFailed,
63+
/// Not enough bytes available to pack pubkey data configuration.
64+
#[error("Not enough bytes available to pack pubkey data configuration")]
65+
NotEnoughBytesForPubkeyData,
66+
/// The provided bytes are not valid for a pubkey data configuration
67+
#[error("The provided bytes are not valid for a pubkey data configuration")]
68+
InvalidBytesForPubkeyData,
69+
/// Tried to pack an invalid pubkey data configuration
70+
#[error("Tried to pack an invalid pubkey data configuration")]
71+
InvalidPubkeyDataConfig,
6372
}

libraries/tlv-account-resolution/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
pub mod account;
1111
pub mod error;
12+
pub mod pubkey_data;
1213
pub mod seeds;
1314
pub mod state;
1415

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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")]
8+
use serde::{Deserialize, Serialize};
9+
use {crate::error::AccountResolutionError, solana_program::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"))]
15+
pub 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+
}
44+
impl 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 0s.
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)]
121+
mod 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+
}

0 commit comments

Comments
 (0)