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

Commit 3ee9e63

Browse files
committed
token 2022: add alloc_and_serialize for fixed-len extensions
1 parent 4cebe89 commit 3ee9e63

File tree

3 files changed

+124
-27
lines changed

3 files changed

+124
-27
lines changed

token/client/src/token.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -3680,7 +3680,8 @@ where
36803680
let account = self.get_account(self.pubkey).await?;
36813681
let account_lamports = account.lamports;
36823682
let mint_state = self.unpack_mint_info(account)?;
3683-
let new_account_len = mint_state.try_get_new_account_len(token_metadata)?;
3683+
let new_account_len =
3684+
mint_state.try_get_new_account_len_for_variable_len_extension(token_metadata)?;
36843685
let new_rent_exempt_minimum = self
36853686
.client
36863687
.get_minimum_balance_for_rent_exemption(new_account_len)
@@ -3762,7 +3763,8 @@ where
37623763
let mint_state = self.unpack_mint_info(account)?;
37633764
let mut token_metadata = mint_state.get_variable_len_extension::<TokenMetadata>()?;
37643765
token_metadata.update(field, value);
3765-
let new_account_len = mint_state.try_get_new_account_len(&token_metadata)?;
3766+
let new_account_len =
3767+
mint_state.try_get_new_account_len_for_variable_len_extension(&token_metadata)?;
37663768
let new_rent_exempt_minimum = self
37673769
.client
37683770
.get_minimum_balance_for_rent_exemption(new_account_len)

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

+115-20
Original file line numberDiff line numberDiff line change
@@ -404,20 +404,20 @@ pub trait BaseStateWithExtensions<S: BaseState> {
404404
///
405405
/// Provides the correct answer regardless if the extension is already present
406406
/// in the TLV data.
407-
fn try_get_new_account_len<V: Extension + VariableLenPack>(
407+
fn try_get_new_account_len_for_variable_len_extension_from_new_extension_len<V: Extension>(
408408
&self,
409-
new_extension: &V,
409+
new_extension_len: usize,
410410
) -> Result<usize, ProgramError> {
411411
// get the new length used by the extension
412-
let new_extension_len = add_type_and_length_to_len(new_extension.get_packed_len()?);
412+
let new_extension_tlv_len = add_type_and_length_to_len(new_extension_len);
413413
let tlv_info = get_tlv_data_info(self.get_tlv_data())?;
414414
// If we're adding an extension, then we must have at least BASE_ACCOUNT_LENGTH
415415
// and account type
416416
let current_len = tlv_info
417417
.used_len
418418
.saturating_add(BASE_ACCOUNT_AND_TYPE_LENGTH);
419419
let new_len = if tlv_info.extension_types.is_empty() {
420-
current_len.saturating_add(new_extension_len)
420+
current_len.saturating_add(new_extension_tlv_len)
421421
} else {
422422
// get the current length used by the extension
423423
let current_extension_len = self
@@ -426,10 +426,29 @@ pub trait BaseStateWithExtensions<S: BaseState> {
426426
.unwrap_or(0);
427427
current_len
428428
.saturating_sub(current_extension_len)
429-
.saturating_add(new_extension_len)
429+
.saturating_add(new_extension_tlv_len)
430430
};
431431
Ok(adjust_len_for_multisig(new_len))
432432
}
433+
434+
/// Calculate the new expected size if the state allocates the required
435+
/// number of bytes for the given extension type.
436+
fn try_get_new_account_len<V: Extension + Pod>(&self) -> Result<usize, ProgramError> {
437+
self.try_get_new_account_len_for_variable_len_extension_from_new_extension_len::<V>(
438+
pod_get_packed_len::<V>(),
439+
)
440+
}
441+
442+
/// Calculate the new expected size if the state allocates the required
443+
/// number of bytes for the given variable-length extension type.
444+
fn try_get_new_account_len_for_variable_len_extension<V: Extension + VariableLenPack>(
445+
&self,
446+
new_extension: &V,
447+
) -> Result<usize, ProgramError> {
448+
self.try_get_new_account_len_for_variable_len_extension_from_new_extension_len::<V>(
449+
new_extension.get_packed_len()?,
450+
)
451+
}
433452
}
434453

435454
/// Encapsulates owned immutable base state data (mint or account) with possible extensions
@@ -1178,6 +1197,58 @@ impl Extension for AccountPaddingTest {
11781197
const TYPE: ExtensionType = ExtensionType::AccountPaddingTest;
11791198
}
11801199

1200+
/// Packs a fixed-length extension into a TLV space
1201+
///
1202+
/// This function reallocates the account as needed to accommodate for the
1203+
/// change in space.
1204+
///
1205+
/// If the extension already exists, it will overwrite the existing extension
1206+
/// if `overwrite` is `true`, otherwise it will return an error.
1207+
///
1208+
/// If the extension does not exist, it will reallocate the account and write
1209+
/// the extension into the TLV buffer.
1210+
///
1211+
/// NOTE: Since this function deals with fixed-size extensions, it does not
1212+
/// handle _decreasing_ the size of an account's data buffer, like the function
1213+
/// `alloc_and_serialize_variable_len_extension` does.
1214+
pub fn alloc_and_serialize<S: BaseState, V: Default + Extension + Pod>(
1215+
account_info: &AccountInfo,
1216+
new_extension: &V,
1217+
overwrite: bool,
1218+
) -> Result<(), ProgramError> {
1219+
let previous_account_len = account_info.try_data_len()?;
1220+
let (new_account_len, extension_already_exists) = {
1221+
let data = account_info.try_borrow_data()?;
1222+
let state = StateWithExtensions::<S>::unpack(&data)?;
1223+
let new_account_len = state.try_get_new_account_len::<V>()?;
1224+
let extension_already_exists = state.get_extension_bytes::<V>().is_ok();
1225+
(new_account_len, extension_already_exists)
1226+
};
1227+
1228+
if extension_already_exists {
1229+
if !overwrite {
1230+
return Err(TokenError::ExtensionAlreadyInitialized.into());
1231+
} else {
1232+
// Overwrite the extension
1233+
let mut buffer = account_info.try_borrow_mut_data()?;
1234+
let mut state = StateWithExtensionsMut::<S>::unpack(&mut buffer)?;
1235+
let extension = state.get_extension_mut::<V>()?;
1236+
*extension = *new_extension;
1237+
}
1238+
} else {
1239+
// Realloc the account, then write the new extension
1240+
account_info.realloc(new_account_len, false)?;
1241+
let mut buffer = account_info.try_borrow_mut_data()?;
1242+
if previous_account_len <= BASE_ACCOUNT_LENGTH {
1243+
set_account_type::<S>(*buffer)?;
1244+
}
1245+
let mut state = StateWithExtensionsMut::<S>::unpack(&mut buffer)?;
1246+
let extension = state.init_extension::<V>(false)?;
1247+
*extension = *new_extension;
1248+
}
1249+
Ok(())
1250+
}
1251+
11811252
/// Packs a variable-length extension into a TLV space
11821253
///
11831254
/// This function reallocates the account as needed to accommodate for the
@@ -1186,7 +1257,7 @@ impl Extension for AccountPaddingTest {
11861257
///
11871258
/// NOTE: Unlike the `reallocate` instruction, this function will reduce the
11881259
/// size of an account if it has too many bytes allocated for the given value.
1189-
pub fn alloc_and_serialize<S: BaseState, V: Extension + VariableLenPack>(
1260+
pub fn alloc_and_serialize_variable_len_extension<S: BaseState, V: Extension + VariableLenPack>(
11901261
account_info: &AccountInfo,
11911262
new_extension: &V,
11921263
overwrite: bool,
@@ -1195,7 +1266,8 @@ pub fn alloc_and_serialize<S: BaseState, V: Extension + VariableLenPack>(
11951266
let (new_account_len, extension_already_exists) = {
11961267
let data = account_info.try_borrow_data()?;
11971268
let state = StateWithExtensions::<S>::unpack(&data)?;
1198-
let new_account_len = state.try_get_new_account_len(new_extension)?;
1269+
let new_account_len =
1270+
state.try_get_new_account_len_for_variable_len_extension(new_extension)?;
11991271
let extension_already_exists = state.get_extension_bytes::<V>().is_ok();
12001272
(new_account_len, extension_already_exists)
12011273
};
@@ -2282,7 +2354,7 @@ mod test {
22822354
let current_len = state.try_get_account_len().unwrap();
22832355
assert_eq!(current_len, Mint::LEN);
22842356
let new_len = state
2285-
.try_get_new_account_len::<VariableLenMintTest>(&variable_len)
2357+
.try_get_new_account_len_for_variable_len_extension::<VariableLenMintTest>(&variable_len)
22862358
.unwrap();
22872359
assert_eq!(
22882360
new_len,
@@ -2297,19 +2369,25 @@ mod test {
22972369

22982370
// Reduce the extension size
22992371
let new_len = state
2300-
.try_get_new_account_len::<VariableLenMintTest>(&small_variable_len)
2372+
.try_get_new_account_len_for_variable_len_extension::<VariableLenMintTest>(
2373+
&small_variable_len,
2374+
)
23012375
.unwrap();
23022376
assert_eq!(current_len.checked_sub(new_len).unwrap(), 1);
23032377

23042378
// Increase the extension size
23052379
let new_len = state
2306-
.try_get_new_account_len::<VariableLenMintTest>(&big_variable_len)
2380+
.try_get_new_account_len_for_variable_len_extension::<VariableLenMintTest>(
2381+
&big_variable_len,
2382+
)
23072383
.unwrap();
23082384
assert_eq!(new_len.checked_sub(current_len).unwrap(), 1);
23092385

23102386
// Maintain the extension size
23112387
let new_len = state
2312-
.try_get_new_account_len::<VariableLenMintTest>(&variable_len)
2388+
.try_get_new_account_len_for_variable_len_extension::<VariableLenMintTest>(
2389+
&variable_len,
2390+
)
23132391
.unwrap();
23142392
assert_eq!(new_len, current_len);
23152393
}
@@ -2382,7 +2460,8 @@ mod test {
23822460
let key = Pubkey::new_unique();
23832461
let account_info = (&key, &mut data).into_account_info();
23842462

2385-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap();
2463+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, false)
2464+
.unwrap();
23862465
let new_account_len = BASE_ACCOUNT_AND_TYPE_LENGTH + add_type_and_length_to_len(value_len);
23872466
assert_eq!(data.len(), new_account_len);
23882467
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
@@ -2395,12 +2474,18 @@ mod test {
23952474

23962475
// alloc again succeeds with "overwrite"
23972476
let account_info = (&key, &mut data).into_account_info();
2398-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2477+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2478+
.unwrap();
23992479

24002480
// alloc again fails without "overwrite"
24012481
let account_info = (&key, &mut data).into_account_info();
24022482
assert_eq!(
2403-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap_err(),
2483+
alloc_and_serialize_variable_len_extension::<Mint, _>(
2484+
&account_info,
2485+
&variable_len,
2486+
false,
2487+
)
2488+
.unwrap_err(),
24042489
TokenError::ExtensionAlreadyInitialized.into()
24052490
);
24062491
}
@@ -2429,7 +2514,8 @@ mod test {
24292514
let key = Pubkey::new_unique();
24302515
let account_info = (&key, &mut data).into_account_info();
24312516

2432-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap();
2517+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, false)
2518+
.unwrap();
24332519
let new_account_len = BASE_ACCOUNT_AND_TYPE_LENGTH
24342520
+ add_type_and_length_to_len(value_len)
24352521
+ add_type_and_length_to_len(size_of::<MetadataPointer>());
@@ -2447,12 +2533,18 @@ mod test {
24472533

24482534
// alloc again succeeds with "overwrite"
24492535
let account_info = (&key, &mut data).into_account_info();
2450-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2536+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2537+
.unwrap();
24512538

24522539
// alloc again fails without "overwrite"
24532540
let account_info = (&key, &mut data).into_account_info();
24542541
assert_eq!(
2455-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap_err(),
2542+
alloc_and_serialize_variable_len_extension::<Mint, _>(
2543+
&account_info,
2544+
&variable_len,
2545+
false,
2546+
)
2547+
.unwrap_err(),
24562548
TokenError::ExtensionAlreadyInitialized.into()
24572549
);
24582550
}
@@ -2488,7 +2580,8 @@ mod test {
24882580
let key = Pubkey::new_unique();
24892581
let account_info = (&key, &mut data).into_account_info();
24902582
let variable_len = VariableLenMintTest { data: vec![1, 2] };
2491-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2583+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2584+
.unwrap();
24922585

24932586
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
24942587
let extension = state.get_extension::<MetadataPointer>().unwrap();
@@ -2505,7 +2598,8 @@ mod test {
25052598
let variable_len = VariableLenMintTest {
25062599
data: vec![1, 2, 3, 4, 5, 6, 7],
25072600
};
2508-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2601+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2602+
.unwrap();
25092603

25102604
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
25112605
let extension = state.get_extension::<MetadataPointer>().unwrap();
@@ -2522,7 +2616,8 @@ mod test {
25222616
let variable_len = VariableLenMintTest {
25232617
data: vec![7, 6, 5, 4, 3, 2, 1],
25242618
};
2525-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2619+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2620+
.unwrap();
25262621

25272622
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
25282623
let extension = state.get_extension::<MetadataPointer>().unwrap();

token/program-2022/src/extension/token_metadata/processor.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use {
55
check_program_account,
66
error::TokenError,
77
extension::{
8-
alloc_and_serialize, metadata_pointer::MetadataPointer, BaseStateWithExtensions,
8+
alloc_and_serialize_variable_len_extension, metadata_pointer::MetadataPointer, BaseStateWithExtensions,
99
StateWithExtensions,
1010
},
1111
state::Mint,
@@ -98,7 +98,7 @@ pub fn process_initialize(
9898

9999
// allocate a TLV entry for the space and write it in, assumes that there's
100100
// enough SOL for the new rent-exemption
101-
alloc_and_serialize::<Mint, _>(metadata_info, &token_metadata, false)?;
101+
alloc_and_serialize_variable_len_extension::<Mint, _>(metadata_info, &token_metadata, false)?;
102102

103103
Ok(())
104104
}
@@ -127,7 +127,7 @@ pub fn process_update_field(
127127
token_metadata.update(data.field, data.value);
128128

129129
// Update / realloc the account
130-
alloc_and_serialize::<Mint, _>(metadata_info, &token_metadata, true)?;
130+
alloc_and_serialize_variable_len_extension::<Mint, _>(metadata_info, &token_metadata, true)?;
131131

132132
Ok(())
133133
}
@@ -154,7 +154,7 @@ pub fn process_remove_key(
154154
if !token_metadata.remove_key(&data.key) && !data.idempotent {
155155
return Err(TokenMetadataError::KeyNotFound.into());
156156
}
157-
alloc_and_serialize::<Mint, _>(metadata_info, &token_metadata, true)?;
157+
alloc_and_serialize_variable_len_extension::<Mint, _>(metadata_info, &token_metadata, true)?;
158158
Ok(())
159159
}
160160

@@ -179,7 +179,7 @@ pub fn process_update_authority(
179179
check_update_authority(update_authority_info, &token_metadata.update_authority)?;
180180
token_metadata.update_authority = data.new_authority;
181181
// Update the account, no realloc needed!
182-
alloc_and_serialize::<Mint, _>(metadata_info, &token_metadata, true)?;
182+
alloc_and_serialize_variable_len_extension::<Mint, _>(metadata_info, &token_metadata, true)?;
183183

184184
Ok(())
185185
}

0 commit comments

Comments
 (0)