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

Commit a29209e

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

File tree

5 files changed

+107
-28
lines changed

5 files changed

+107
-28
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

token/client/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ spl-token = { version = "4.0", path="../program", features = [ "no-entrypoint" ]
2626
spl-token-2022 = { version = "0.9", path="../program-2022" }
2727
spl-token-metadata-interface = { version = "0.2", path="../../token-metadata/interface" }
2828
spl-transfer-hook-interface = { version = "0.3", path="../transfer-hook/interface" }
29+
spl-type-length-value = { version = "0.3", path="../../libraries/type-length-value" }
2930
thiserror = "1.0"
3031

3132
[features]

token/client/src/token.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ use {
6060
state::{Account, AccountState, Mint, Multisig},
6161
},
6262
spl_token_metadata_interface::state::{Field, TokenMetadata},
63+
spl_type_length_value::variable_len_pack::VariableLenPack,
6364
std::{
6465
fmt, io,
6566
mem::size_of,
@@ -3680,7 +3681,8 @@ where
36803681
let account = self.get_account(self.pubkey).await?;
36813682
let account_lamports = account.lamports;
36823683
let mint_state = self.unpack_mint_info(account)?;
3683-
let new_account_len = mint_state.try_get_new_account_len(token_metadata)?;
3684+
let new_account_len = mint_state
3685+
.try_get_new_account_len::<TokenMetadata>(token_metadata.get_packed_len()?)?;
36843686
let new_rent_exempt_minimum = self
36853687
.client
36863688
.get_minimum_balance_for_rent_exemption(new_account_len)
@@ -3762,7 +3764,8 @@ where
37623764
let mint_state = self.unpack_mint_info(account)?;
37633765
let mut token_metadata = mint_state.get_variable_len_extension::<TokenMetadata>()?;
37643766
token_metadata.update(field, value);
3765-
let new_account_len = mint_state.try_get_new_account_len(&token_metadata)?;
3767+
let new_account_len = mint_state
3768+
.try_get_new_account_len::<TokenMetadata>(token_metadata.get_packed_len()?)?;
37663769
let new_rent_exempt_minimum = self
37673770
.client
37683771
.get_minimum_balance_for_rent_exemption(new_account_len)

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

+94-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<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,7 +426,7 @@ 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
}
@@ -1178,6 +1178,58 @@ impl Extension for AccountPaddingTest {
11781178
const TYPE: ExtensionType = ExtensionType::AccountPaddingTest;
11791179
}
11801180

1181+
/// Packs a fixed-length extension into a TLV space
1182+
///
1183+
/// This function reallocates the account as needed to accommodate for the
1184+
/// change in space.
1185+
///
1186+
/// If the extension already exists, it will overwrite the existing extension
1187+
/// if `overwrite` is `true`, otherwise it will return an error.
1188+
///
1189+
/// If the extension does not exist, it will reallocate the account and write
1190+
/// the extension into the TLV buffer.
1191+
///
1192+
/// NOTE: Since this function deals with fixed-size extensions, it does not
1193+
/// handle _decreasing_ the size of an account's data buffer, like the function
1194+
/// `alloc_and_serialize_variable_len_extension` does.
1195+
pub fn alloc_and_serialize<S: BaseState, V: Default + Extension + Pod>(
1196+
account_info: &AccountInfo,
1197+
new_extension: &V,
1198+
overwrite: bool,
1199+
) -> Result<(), ProgramError> {
1200+
let previous_account_len = account_info.try_data_len()?;
1201+
let (new_account_len, extension_already_exists) = {
1202+
let data = account_info.try_borrow_data()?;
1203+
let state = StateWithExtensions::<S>::unpack(&data)?;
1204+
let new_account_len = state.try_get_new_account_len::<V>(pod_get_packed_len::<V>())?;
1205+
let extension_already_exists = state.get_extension_bytes::<V>().is_ok();
1206+
(new_account_len, extension_already_exists)
1207+
};
1208+
1209+
if extension_already_exists {
1210+
if !overwrite {
1211+
return Err(TokenError::ExtensionAlreadyInitialized.into());
1212+
} else {
1213+
// Overwrite the extension
1214+
let mut buffer = account_info.try_borrow_mut_data()?;
1215+
let mut state = StateWithExtensionsMut::<S>::unpack(&mut buffer)?;
1216+
let extension = state.get_extension_mut::<V>()?;
1217+
*extension = *new_extension;
1218+
}
1219+
} else {
1220+
// Realloc the account, then write the new extension
1221+
account_info.realloc(new_account_len, false)?;
1222+
let mut buffer = account_info.try_borrow_mut_data()?;
1223+
if previous_account_len <= BASE_ACCOUNT_LENGTH {
1224+
set_account_type::<S>(*buffer)?;
1225+
}
1226+
let mut state = StateWithExtensionsMut::<S>::unpack(&mut buffer)?;
1227+
let extension = state.init_extension::<V>(false)?;
1228+
*extension = *new_extension;
1229+
}
1230+
Ok(())
1231+
}
1232+
11811233
/// Packs a variable-length extension into a TLV space
11821234
///
11831235
/// This function reallocates the account as needed to accommodate for the
@@ -1186,7 +1238,7 @@ impl Extension for AccountPaddingTest {
11861238
///
11871239
/// NOTE: Unlike the `reallocate` instruction, this function will reduce the
11881240
/// 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>(
1241+
pub fn alloc_and_serialize_variable_len_extension<S: BaseState, V: Extension + VariableLenPack>(
11901242
account_info: &AccountInfo,
11911243
new_extension: &V,
11921244
overwrite: bool,
@@ -1195,7 +1247,8 @@ pub fn alloc_and_serialize<S: BaseState, V: Extension + VariableLenPack>(
11951247
let (new_account_len, extension_already_exists) = {
11961248
let data = account_info.try_borrow_data()?;
11971249
let state = StateWithExtensions::<S>::unpack(&data)?;
1198-
let new_account_len = state.try_get_new_account_len(new_extension)?;
1250+
let new_account_len =
1251+
state.try_get_new_account_len::<V>(new_extension.get_packed_len()?)?;
11991252
let extension_already_exists = state.get_extension_bytes::<V>().is_ok();
12001253
(new_account_len, extension_already_exists)
12011254
};
@@ -2282,7 +2335,7 @@ mod test {
22822335
let current_len = state.try_get_account_len().unwrap();
22832336
assert_eq!(current_len, Mint::LEN);
22842337
let new_len = state
2285-
.try_get_new_account_len::<VariableLenMintTest>(&variable_len)
2338+
.try_get_new_account_len::<VariableLenMintTest>(value_len)
22862339
.unwrap();
22872340
assert_eq!(
22882341
new_len,
@@ -2297,19 +2350,23 @@ mod test {
22972350

22982351
// Reduce the extension size
22992352
let new_len = state
2300-
.try_get_new_account_len::<VariableLenMintTest>(&small_variable_len)
2353+
.try_get_new_account_len::<VariableLenMintTest>(
2354+
small_variable_len.get_packed_len().unwrap(),
2355+
)
23012356
.unwrap();
23022357
assert_eq!(current_len.checked_sub(new_len).unwrap(), 1);
23032358

23042359
// Increase the extension size
23052360
let new_len = state
2306-
.try_get_new_account_len::<VariableLenMintTest>(&big_variable_len)
2361+
.try_get_new_account_len::<VariableLenMintTest>(
2362+
big_variable_len.get_packed_len().unwrap(),
2363+
)
23072364
.unwrap();
23082365
assert_eq!(new_len.checked_sub(current_len).unwrap(), 1);
23092366

23102367
// Maintain the extension size
23112368
let new_len = state
2312-
.try_get_new_account_len::<VariableLenMintTest>(&variable_len)
2369+
.try_get_new_account_len::<VariableLenMintTest>(variable_len.get_packed_len().unwrap())
23132370
.unwrap();
23142371
assert_eq!(new_len, current_len);
23152372
}
@@ -2382,7 +2439,8 @@ mod test {
23822439
let key = Pubkey::new_unique();
23832440
let account_info = (&key, &mut data).into_account_info();
23842441

2385-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap();
2442+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, false)
2443+
.unwrap();
23862444
let new_account_len = BASE_ACCOUNT_AND_TYPE_LENGTH + add_type_and_length_to_len(value_len);
23872445
assert_eq!(data.len(), new_account_len);
23882446
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
@@ -2395,12 +2453,18 @@ mod test {
23952453

23962454
// alloc again succeeds with "overwrite"
23972455
let account_info = (&key, &mut data).into_account_info();
2398-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2456+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2457+
.unwrap();
23992458

24002459
// alloc again fails without "overwrite"
24012460
let account_info = (&key, &mut data).into_account_info();
24022461
assert_eq!(
2403-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap_err(),
2462+
alloc_and_serialize_variable_len_extension::<Mint, _>(
2463+
&account_info,
2464+
&variable_len,
2465+
false,
2466+
)
2467+
.unwrap_err(),
24042468
TokenError::ExtensionAlreadyInitialized.into()
24052469
);
24062470
}
@@ -2429,7 +2493,8 @@ mod test {
24292493
let key = Pubkey::new_unique();
24302494
let account_info = (&key, &mut data).into_account_info();
24312495

2432-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap();
2496+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, false)
2497+
.unwrap();
24332498
let new_account_len = BASE_ACCOUNT_AND_TYPE_LENGTH
24342499
+ add_type_and_length_to_len(value_len)
24352500
+ add_type_and_length_to_len(size_of::<MetadataPointer>());
@@ -2447,12 +2512,18 @@ mod test {
24472512

24482513
// alloc again succeeds with "overwrite"
24492514
let account_info = (&key, &mut data).into_account_info();
2450-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2515+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2516+
.unwrap();
24512517

24522518
// alloc again fails without "overwrite"
24532519
let account_info = (&key, &mut data).into_account_info();
24542520
assert_eq!(
2455-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap_err(),
2521+
alloc_and_serialize_variable_len_extension::<Mint, _>(
2522+
&account_info,
2523+
&variable_len,
2524+
false,
2525+
)
2526+
.unwrap_err(),
24562527
TokenError::ExtensionAlreadyInitialized.into()
24572528
);
24582529
}
@@ -2488,7 +2559,8 @@ mod test {
24882559
let key = Pubkey::new_unique();
24892560
let account_info = (&key, &mut data).into_account_info();
24902561
let variable_len = VariableLenMintTest { data: vec![1, 2] };
2491-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2562+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2563+
.unwrap();
24922564

24932565
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
24942566
let extension = state.get_extension::<MetadataPointer>().unwrap();
@@ -2505,7 +2577,8 @@ mod test {
25052577
let variable_len = VariableLenMintTest {
25062578
data: vec![1, 2, 3, 4, 5, 6, 7],
25072579
};
2508-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2580+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2581+
.unwrap();
25092582

25102583
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
25112584
let extension = state.get_extension::<MetadataPointer>().unwrap();
@@ -2522,7 +2595,8 @@ mod test {
25222595
let variable_len = VariableLenMintTest {
25232596
data: vec![7, 6, 5, 4, 3, 2, 1],
25242597
};
2525-
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
2598+
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
2599+
.unwrap();
25262600

25272601
let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
25282602
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)