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

Commit 115c3c4

Browse files
committed
Add ImmutableOwner extension to block ATA owner authority changes
1 parent 4c0bc4c commit 115c3c4

File tree

8 files changed

+203
-27
lines changed

8 files changed

+203
-27
lines changed

associated-token-account/program/src/processor.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ pub fn process_create_associated_token_account(
7676
&[bump_seed],
7777
];
7878

79-
let account_len = get_account_len(spl_token_mint_info, spl_token_program_info)?;
79+
let account_len = get_account_len(
80+
spl_token_mint_info,
81+
spl_token_program_info,
82+
vec![spl_token::extension::ExtensionType::ImmutableOwner],
83+
)?;
8084

8185
create_pda_account(
8286
funder_info,
@@ -89,6 +93,16 @@ pub fn process_create_associated_token_account(
8993
)?;
9094

9195
msg!("Initialize the associated token account");
96+
invoke(
97+
&spl_token::instruction::initialize_immutable_owner(
98+
spl_token_program_id,
99+
associated_token_account_info.key,
100+
)?,
101+
&[
102+
associated_token_account_info.clone(),
103+
spl_token_program_info.clone(),
104+
],
105+
)?;
92106
invoke(
93107
&spl_token::instruction::initialize_account3(
94108
spl_token_program_id,

associated-token-account/program/src/tools/account.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use {
1010
rent::Rent,
1111
system_instruction,
1212
},
13-
spl_token::check_program_account,
13+
spl_token::{check_program_account, extension::ExtensionType},
1414
std::convert::TryInto,
1515
};
1616

@@ -76,9 +76,14 @@ pub fn create_pda_account<'a>(
7676
pub fn get_account_len<'a>(
7777
mint: &AccountInfo<'a>,
7878
spl_token_program: &AccountInfo<'a>,
79+
extension_types: Vec<ExtensionType>,
7980
) -> Result<usize, ProgramError> {
8081
invoke(
81-
&spl_token::instruction::get_account_data_size(spl_token_program.key, mint.key, vec![])?,
82+
&spl_token::instruction::get_account_data_size(
83+
spl_token_program.key,
84+
mint.key,
85+
extension_types,
86+
)?,
8287
&[mint.clone(), spl_token_program.clone()],
8388
)?;
8489
get_return_data()

associated-token-account/program/tests/process_create_associated_token_account.rs

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ mod program_test;
55

66
use {
77
program_test::program_test,
8-
solana_program::{
9-
instruction::*, program_pack::Pack, pubkey::Pubkey, system_instruction, sysvar,
10-
},
8+
solana_program::{instruction::*, pubkey::Pubkey, system_instruction, sysvar},
119
solana_program_test::*,
1210
solana_sdk::{
1311
signature::Signer,
@@ -16,6 +14,7 @@ use {
1614
spl_associated_token_account::{
1715
get_associated_token_address, instruction::create_associated_token_account,
1816
},
17+
spl_token::extension::ExtensionType,
1918
};
2019

2120
#[allow(deprecated)]
@@ -31,7 +30,12 @@ async fn test_associated_token_address() {
3130
let (mut banks_client, payer, recent_blockhash) =
3231
program_test(token_mint_address, true).start().await;
3332
let rent = banks_client.get_rent().await.unwrap();
34-
let expected_token_account_balance = rent.minimum_balance(spl_token::state::Account::LEN);
33+
34+
let expected_token_account_len =
35+
ExtensionType::get_account_len::<spl_token::state::Account>(&[
36+
ExtensionType::ImmutableOwner,
37+
]);
38+
let expected_token_account_balance = rent.minimum_balance(expected_token_account_len);
3539

3640
// Associated account does not exist
3741
assert_eq!(
@@ -60,10 +64,7 @@ async fn test_associated_token_address() {
6064
.await
6165
.expect("get_account")
6266
.expect("associated_account not none");
63-
assert_eq!(
64-
associated_account.data.len(),
65-
spl_token::state::Account::LEN
66-
);
67+
assert_eq!(associated_account.data.len(), expected_token_account_len,);
6768
assert_eq!(associated_account.owner, spl_token::id());
6869
assert_eq!(associated_account.lamports, expected_token_account_balance);
6970
}
@@ -78,7 +79,11 @@ async fn test_create_with_fewer_lamports() {
7879
let (mut banks_client, payer, recent_blockhash) =
7980
program_test(token_mint_address, true).start().await;
8081
let rent = banks_client.get_rent().await.unwrap();
81-
let expected_token_account_balance = rent.minimum_balance(spl_token::state::Account::LEN);
82+
let expected_token_account_len =
83+
ExtensionType::get_account_len::<spl_token::state::Account>(&[
84+
ExtensionType::ImmutableOwner,
85+
]);
86+
let expected_token_account_balance = rent.minimum_balance(expected_token_account_len);
8287

8388
// Transfer lamports into `associated_token_address` before creating it - enough to be
8489
// rent-exempt for 0 data, but not for an initialized token account
@@ -133,7 +138,12 @@ async fn test_create_with_excess_lamports() {
133138
let (mut banks_client, payer, recent_blockhash) =
134139
program_test(token_mint_address, true).start().await;
135140
let rent = banks_client.get_rent().await.unwrap();
136-
let expected_token_account_balance = rent.minimum_balance(spl_token::state::Account::LEN);
141+
142+
let expected_token_account_len =
143+
ExtensionType::get_account_len::<spl_token::state::Account>(&[
144+
ExtensionType::ImmutableOwner,
145+
]);
146+
let expected_token_account_balance = rent.minimum_balance(expected_token_account_len);
137147

138148
// Transfer 1 lamport into `associated_token_address` before creating it
139149
let mut transaction = Transaction::new_with_payer(
@@ -255,7 +265,11 @@ async fn test_create_associated_token_account_using_legacy_implicit_instruction(
255265
let (mut banks_client, payer, recent_blockhash) =
256266
program_test(token_mint_address, true).start().await;
257267
let rent = banks_client.get_rent().await.unwrap();
258-
let expected_token_account_balance = rent.minimum_balance(spl_token::state::Account::LEN);
268+
let expected_token_account_len =
269+
ExtensionType::get_account_len::<spl_token::state::Account>(&[
270+
ExtensionType::ImmutableOwner,
271+
]);
272+
let expected_token_account_balance = rent.minimum_balance(expected_token_account_len);
259273

260274
// Associated account does not exist
261275
assert_eq!(
@@ -290,10 +304,7 @@ async fn test_create_associated_token_account_using_legacy_implicit_instruction(
290304
.await
291305
.expect("get_account")
292306
.expect("associated_account not none");
293-
assert_eq!(
294-
associated_account.data.len(),
295-
spl_token::state::Account::LEN
296-
);
307+
assert_eq!(associated_account.data.len(), expected_token_account_len);
297308
assert_eq!(associated_account.owner, spl_token::id());
298309
assert_eq!(associated_account.lamports, expected_token_account_balance);
299310
}
@@ -308,7 +319,11 @@ async fn test_create_associated_token_account_using_deprecated_instruction_creat
308319
let (mut banks_client, payer, recent_blockhash) =
309320
program_test(token_mint_address, true).start().await;
310321
let rent = banks_client.get_rent().await.unwrap();
311-
let expected_token_account_balance = rent.minimum_balance(spl_token::state::Account::LEN);
322+
let expected_token_account_len =
323+
ExtensionType::get_account_len::<spl_token::state::Account>(&[
324+
ExtensionType::ImmutableOwner,
325+
]);
326+
let expected_token_account_balance = rent.minimum_balance(expected_token_account_len);
312327

313328
// Associated account does not exist
314329
assert_eq!(
@@ -338,10 +353,7 @@ async fn test_create_associated_token_account_using_deprecated_instruction_creat
338353
.await
339354
.expect("get_account")
340355
.expect("associated_account not none");
341-
assert_eq!(
342-
associated_account.data.len(),
343-
spl_token::state::Account::LEN
344-
);
356+
assert_eq!(associated_account.data.len(), expected_token_account_len);
345357
assert_eq!(associated_account.owner, spl_token::id());
346358
assert_eq!(associated_account.lamports, expected_token_account_balance);
347359
}

token/program-2022/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ pub enum TokenError {
121121
/// Calculated fee does not match expected fee
122122
#[error("Calculated fee does not match expected fee")]
123123
FeeMismatch,
124+
/// The owner authority cannot be changed
125+
#[error("The owner authority cannot be changed")]
126+
ImmutableOwner,
124127
}
125128
impl From<TokenError> for ProgramError {
126129
fn from(e: TokenError) -> Self {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use {
2+
crate::extension::{Extension, ExtensionType},
3+
bytemuck::{Pod, Zeroable},
4+
};
5+
6+
/// Indicates that the Account owner authority cannot be changed
7+
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
8+
#[repr(transparent)]
9+
pub struct ImmutableOwner;
10+
11+
impl Extension for ImmutableOwner {
12+
const TYPE: ExtensionType = ExtensionType::ImmutableOwner;
13+
}

token/program-2022/src/extension/mod.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use {
66
extension::{
77
confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint},
88
default_account_state::DefaultAccountState,
9+
immutable_owner::ImmutableOwner,
910
mint_close_authority::MintCloseAuthority,
1011
transfer_fee::{TransferFeeAmount, TransferFeeConfig},
1112
},
@@ -28,6 +29,8 @@ use {
2829
pub mod confidential_transfer;
2930
/// Default Account State extension
3031
pub mod default_account_state;
32+
/// Immutable Owner extension
33+
pub mod immutable_owner;
3134
/// Mint Close Authority extension
3235
pub mod mint_close_authority;
3336
/// Transfer Fee extension
@@ -64,6 +67,7 @@ fn get_tlv_indices(type_start: usize) -> TlvIndices {
6467

6568
/// Helper struct for returning the indices of the type, length, and value in
6669
/// a TLV entry
70+
#[derive(Debug)]
6771
struct TlvIndices {
6872
pub type_start: usize,
6973
pub length_start: usize,
@@ -77,7 +81,7 @@ fn get_extension_indices<V: Extension>(
7781
let v_account_type = V::TYPE.get_account_type();
7882
while start_index < tlv_data.len() {
7983
let tlv_indices = get_tlv_indices(start_index);
80-
if tlv_data.len() <= tlv_indices.value_start {
84+
if tlv_data.len() < tlv_indices.value_start {
8185
return Err(ProgramError::InvalidAccountData);
8286
}
8387
let extension_type =
@@ -397,6 +401,7 @@ impl<'data, S: BaseState> StateWithExtensionsMut<'data, S> {
397401
length_start,
398402
value_start,
399403
} = get_extension_indices::<V>(self.tlv_data, init)?;
404+
400405
if self.tlv_data[type_start..].len() < V::TYPE.get_tlv_len() {
401406
return Err(ProgramError::InvalidAccountData);
402407
}
@@ -551,6 +556,8 @@ pub enum ExtensionType {
551556
ConfidentialTransferAccount,
552557
/// Specifies the default Account::state for new Accounts
553558
DefaultAccountState,
559+
/// Indicates that the Account owner authority cannot be changed
560+
ImmutableOwner,
554561
/// Padding extension used to make an account exactly Multisig::LEN, used for testing
555562
#[cfg(test)]
556563
AccountPaddingTest = u16::MAX - 1,
@@ -580,6 +587,7 @@ impl ExtensionType {
580587
ExtensionType::TransferFeeConfig => pod_get_packed_len::<TransferFeeConfig>(),
581588
ExtensionType::TransferFeeAmount => pod_get_packed_len::<TransferFeeAmount>(),
582589
ExtensionType::MintCloseAuthority => pod_get_packed_len::<MintCloseAuthority>(),
590+
ExtensionType::ImmutableOwner => pod_get_packed_len::<ImmutableOwner>(),
583591
ExtensionType::ConfidentialTransferMint => {
584592
pod_get_packed_len::<ConfidentialTransferMint>()
585593
}
@@ -631,9 +639,9 @@ impl ExtensionType {
631639
| ExtensionType::MintCloseAuthority
632640
| ExtensionType::ConfidentialTransferMint
633641
| ExtensionType::DefaultAccountState => AccountType::Mint,
634-
ExtensionType::TransferFeeAmount | ExtensionType::ConfidentialTransferAccount => {
635-
AccountType::Account
636-
}
642+
ExtensionType::ImmutableOwner
643+
| ExtensionType::TransferFeeAmount
644+
| ExtensionType::ConfidentialTransferAccount => AccountType::Account,
637645
#[cfg(test)]
638646
ExtensionType::AccountPaddingTest => AccountType::Account,
639647
#[cfg(test)]

token/program-2022/src/instruction.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,19 @@ pub enum TokenInstruction {
487487
/// See `extension::default_account_state::instruction::DefaultAccountStateInstruction` for
488488
/// further details about the extended instructions that share this instruction prefix
489489
DefaultAccountStateExtension,
490+
/// Initialize the Immutable Owner extension for the given token account
491+
///
492+
/// Fails if the account has already been initialized, so must be called before
493+
/// `InitializeAccount`.
494+
///
495+
/// Accounts expected by this instruction:
496+
///
497+
/// 0. `[writable]` The account to initialize.
498+
//
499+
/// Data expected by this instruction:
500+
/// None
501+
///
502+
InitializeImmutableOwner,
490503
}
491504
impl TokenInstruction {
492505
/// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html).
@@ -597,6 +610,7 @@ impl TokenInstruction {
597610
}
598611
24 => Self::ConfidentialTransferExtension,
599612
25 => Self::DefaultAccountStateExtension,
613+
26 => Self::InitializeImmutableOwner,
600614
_ => return Err(TokenError::InvalidInstruction.into()),
601615
})
602616
}
@@ -718,6 +732,9 @@ impl TokenInstruction {
718732
&Self::DefaultAccountStateExtension => {
719733
buf.push(25);
720734
}
735+
&Self::InitializeImmutableOwner => {
736+
buf.push(26);
737+
}
721738
};
722739
buf
723740
}
@@ -1456,6 +1473,19 @@ pub fn initialize_mint_close_authority(
14561473
})
14571474
}
14581475

1476+
/// Create an `InitializeImmutableOwner` instruction
1477+
pub fn initialize_immutable_owner(
1478+
token_program_id: &Pubkey,
1479+
token_account: &Pubkey,
1480+
) -> Result<Instruction, ProgramError> {
1481+
check_program_account(token_program_id)?;
1482+
Ok(Instruction {
1483+
program_id: *token_program_id,
1484+
accounts: vec![AccountMeta::new(*token_account, false)],
1485+
data: TokenInstruction::InitializeImmutableOwner.pack(),
1486+
})
1487+
}
1488+
14591489
/// Utility function that checks index is between MIN_SIGNERS and MAX_SIGNERS
14601490
pub fn is_valid_signer_index(index: usize) -> bool {
14611491
(MIN_SIGNERS..=MAX_SIGNERS).contains(&index)

0 commit comments

Comments
 (0)