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

Commit 6f83fe0

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

File tree

3 files changed

+127
-28
lines changed

3 files changed

+127
-28
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

+117-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,9 @@ 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>(
2358+
&variable_len,
2359+
)
22862360
.unwrap();
22872361
assert_eq!(
22882362
new_len,
@@ -2297,19 +2371,25 @@ mod test {
22972371

22982372
// Reduce the extension size
22992373
let new_len = state
2300-
.try_get_new_account_len::<VariableLenMintTest>(&small_variable_len)
2374+
.try_get_new_account_len_for_variable_len_extension::<VariableLenMintTest>(
2375+
&small_variable_len,
2376+
)
23012377
.unwrap();
23022378
assert_eq!(current_len.checked_sub(new_len).unwrap(), 1);
23032379

23042380
// Increase the extension size
23052381
let new_len = state
2306-
.try_get_new_account_len::<VariableLenMintTest>(&big_variable_len)
2382+
.try_get_new_account_len_for_variable_len_extension::<VariableLenMintTest>(
2383+
&big_variable_len,
2384+
)
23072385
.unwrap();
23082386
assert_eq!(new_len.checked_sub(current_len).unwrap(), 1);
23092387

23102388
// Maintain the extension size
23112389
let new_len = state
2312-
.try_get_new_account_len::<VariableLenMintTest>(&variable_len)
2390+
.try_get_new_account_len_for_variable_len_extension::<VariableLenMintTest>(
2391+
&variable_len,
2392+
)
23132393
.unwrap();
23142394
assert_eq!(new_len, current_len);
23152395
}
@@ -2382,7 +2462,8 @@ mod test {
23822462
let key = Pubkey::new_unique();
23832463
let account_info = (&key, &mut data).into_account_info();
23842464

2385-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap();
2465+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, false)
2466+
.unwrap();
23862467
let new_account_len = BASE_ACCOUNT_AND_TYPE_LENGTH + add_type_and_length_to_len(value_len);
23872468
assert_eq!(data.len(), new_account_len);
23882469
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
@@ -2395,12 +2476,18 @@ mod test {
23952476

23962477
// alloc again succeeds with "overwrite"
23972478
let account_info = (&key, &mut data).into_account_info();
2398-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2479+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2480+
.unwrap();
23992481

24002482
// alloc again fails without "overwrite"
24012483
let account_info = (&key, &mut data).into_account_info();
24022484
assert_eq!(
2403-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap_err(),
2485+
alloc_and_serialize_variable_len_extension::<Mint, _>(
2486+
&account_info,
2487+
&variable_len,
2488+
false,
2489+
)
2490+
.unwrap_err(),
24042491
TokenError::ExtensionAlreadyInitialized.into()
24052492
);
24062493
}
@@ -2429,7 +2516,8 @@ mod test {
24292516
let key = Pubkey::new_unique();
24302517
let account_info = (&key, &mut data).into_account_info();
24312518

2432-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap();
2519+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, false)
2520+
.unwrap();
24332521
let new_account_len = BASE_ACCOUNT_AND_TYPE_LENGTH
24342522
+ add_type_and_length_to_len(value_len)
24352523
+ add_type_and_length_to_len(size_of::<MetadataPointer>());
@@ -2447,12 +2535,18 @@ mod test {
24472535

24482536
// alloc again succeeds with "overwrite"
24492537
let account_info = (&key, &mut data).into_account_info();
2450-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2538+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2539+
.unwrap();
24512540

24522541
// alloc again fails without "overwrite"
24532542
let account_info = (&key, &mut data).into_account_info();
24542543
assert_eq!(
2455-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap_err(),
2544+
alloc_and_serialize_variable_len_extension::<Mint, _>(
2545+
&account_info,
2546+
&variable_len,
2547+
false,
2548+
)
2549+
.unwrap_err(),
24562550
TokenError::ExtensionAlreadyInitialized.into()
24572551
);
24582552
}
@@ -2488,7 +2582,8 @@ mod test {
24882582
let key = Pubkey::new_unique();
24892583
let account_info = (&key, &mut data).into_account_info();
24902584
let variable_len = VariableLenMintTest { data: vec![1, 2] };
2491-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2585+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2586+
.unwrap();
24922587

24932588
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
24942589
let extension = state.get_extension::<MetadataPointer>().unwrap();
@@ -2505,7 +2600,8 @@ mod test {
25052600
let variable_len = VariableLenMintTest {
25062601
data: vec![1, 2, 3, 4, 5, 6, 7],
25072602
};
2508-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2603+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2604+
.unwrap();
25092605

25102606
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
25112607
let extension = state.get_extension::<MetadataPointer>().unwrap();
@@ -2522,7 +2618,8 @@ mod test {
25222618
let variable_len = VariableLenMintTest {
25232619
data: vec![7, 6, 5, 4, 3, 2, 1],
25242620
};
2525-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2621+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2622+
.unwrap();
25262623

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

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use {
55
check_program_account,
66
error::TokenError,
77
extension::{
8-
alloc_and_serialize, metadata_pointer::MetadataPointer, BaseStateWithExtensions,
9-
StateWithExtensions,
8+
alloc_and_serialize_variable_len_extension, metadata_pointer::MetadataPointer,
9+
BaseStateWithExtensions, StateWithExtensions,
1010
},
1111
state::Mint,
1212
},
@@ -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)