Skip to content

Commit

Permalink
token 2022: add alloc_and_serialize for fixed-len extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Oct 26, 2023
1 parent 4cebe89 commit a29209e
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 28 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions token/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ spl-token = { version = "4.0", path="../program", features = [ "no-entrypoint" ]
spl-token-2022 = { version = "0.9", path="../program-2022" }
spl-token-metadata-interface = { version = "0.2", path="../../token-metadata/interface" }
spl-transfer-hook-interface = { version = "0.3", path="../transfer-hook/interface" }
spl-type-length-value = { version = "0.3", path="../../libraries/type-length-value" }
thiserror = "1.0"

[features]
Expand Down
7 changes: 5 additions & 2 deletions token/client/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ use {
state::{Account, AccountState, Mint, Multisig},
},
spl_token_metadata_interface::state::{Field, TokenMetadata},
spl_type_length_value::variable_len_pack::VariableLenPack,
std::{
fmt, io,
mem::size_of,
Expand Down Expand Up @@ -3680,7 +3681,8 @@ where
let account = self.get_account(self.pubkey).await?;
let account_lamports = account.lamports;
let mint_state = self.unpack_mint_info(account)?;
let new_account_len = mint_state.try_get_new_account_len(token_metadata)?;
let new_account_len = mint_state
.try_get_new_account_len::<TokenMetadata>(token_metadata.get_packed_len()?)?;
let new_rent_exempt_minimum = self
.client
.get_minimum_balance_for_rent_exemption(new_account_len)
Expand Down Expand Up @@ -3762,7 +3764,8 @@ where
let mint_state = self.unpack_mint_info(account)?;
let mut token_metadata = mint_state.get_variable_len_extension::<TokenMetadata>()?;
token_metadata.update(field, value);
let new_account_len = mint_state.try_get_new_account_len(&token_metadata)?;
let new_account_len = mint_state
.try_get_new_account_len::<TokenMetadata>(token_metadata.get_packed_len()?)?;
let new_rent_exempt_minimum = self
.client
.get_minimum_balance_for_rent_exemption(new_account_len)
Expand Down
114 changes: 94 additions & 20 deletions token/program-2022/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,20 +404,20 @@ pub trait BaseStateWithExtensions<S: BaseState> {
///
/// Provides the correct answer regardless if the extension is already present
/// in the TLV data.
fn try_get_new_account_len<V: Extension + VariableLenPack>(
fn try_get_new_account_len<V: Extension>(
&self,
new_extension: &V,
new_extension_len: usize,
) -> Result<usize, ProgramError> {
// get the new length used by the extension
let new_extension_len = add_type_and_length_to_len(new_extension.get_packed_len()?);
let new_extension_tlv_len = add_type_and_length_to_len(new_extension_len);
let tlv_info = get_tlv_data_info(self.get_tlv_data())?;
// If we're adding an extension, then we must have at least BASE_ACCOUNT_LENGTH
// and account type
let current_len = tlv_info
.used_len
.saturating_add(BASE_ACCOUNT_AND_TYPE_LENGTH);
let new_len = if tlv_info.extension_types.is_empty() {
current_len.saturating_add(new_extension_len)
current_len.saturating_add(new_extension_tlv_len)
} else {
// get the current length used by the extension
let current_extension_len = self
Expand All @@ -426,7 +426,7 @@ pub trait BaseStateWithExtensions<S: BaseState> {
.unwrap_or(0);
current_len
.saturating_sub(current_extension_len)
.saturating_add(new_extension_len)
.saturating_add(new_extension_tlv_len)
};
Ok(adjust_len_for_multisig(new_len))
}
Expand Down Expand Up @@ -1178,6 +1178,58 @@ impl Extension for AccountPaddingTest {
const TYPE: ExtensionType = ExtensionType::AccountPaddingTest;
}

/// Packs a fixed-length extension into a TLV space
///
/// This function reallocates the account as needed to accommodate for the
/// change in space.
///
/// If the extension already exists, it will overwrite the existing extension
/// if `overwrite` is `true`, otherwise it will return an error.
///
/// If the extension does not exist, it will reallocate the account and write
/// the extension into the TLV buffer.
///
/// NOTE: Since this function deals with fixed-size extensions, it does not
/// handle _decreasing_ the size of an account's data buffer, like the function
/// `alloc_and_serialize_variable_len_extension` does.
pub fn alloc_and_serialize<S: BaseState, V: Default + Extension + Pod>(
account_info: &AccountInfo,
new_extension: &V,
overwrite: bool,
) -> Result<(), ProgramError> {
let previous_account_len = account_info.try_data_len()?;
let (new_account_len, extension_already_exists) = {
let data = account_info.try_borrow_data()?;
let state = StateWithExtensions::<S>::unpack(&data)?;
let new_account_len = state.try_get_new_account_len::<V>(pod_get_packed_len::<V>())?;
let extension_already_exists = state.get_extension_bytes::<V>().is_ok();
(new_account_len, extension_already_exists)
};

if extension_already_exists {
if !overwrite {
return Err(TokenError::ExtensionAlreadyInitialized.into());
} else {
// Overwrite the extension
let mut buffer = account_info.try_borrow_mut_data()?;
let mut state = StateWithExtensionsMut::<S>::unpack(&mut buffer)?;
let extension = state.get_extension_mut::<V>()?;
*extension = *new_extension;
}
} else {
// Realloc the account, then write the new extension
account_info.realloc(new_account_len, false)?;
let mut buffer = account_info.try_borrow_mut_data()?;
if previous_account_len <= BASE_ACCOUNT_LENGTH {
set_account_type::<S>(*buffer)?;
}
let mut state = StateWithExtensionsMut::<S>::unpack(&mut buffer)?;
let extension = state.init_extension::<V>(false)?;
*extension = *new_extension;
}
Ok(())
}

/// Packs a variable-length extension into a TLV space
///
/// This function reallocates the account as needed to accommodate for the
Expand All @@ -1186,7 +1238,7 @@ impl Extension for AccountPaddingTest {
///
/// NOTE: Unlike the `reallocate` instruction, this function will reduce the
/// size of an account if it has too many bytes allocated for the given value.
pub fn alloc_and_serialize<S: BaseState, V: Extension + VariableLenPack>(
pub fn alloc_and_serialize_variable_len_extension<S: BaseState, V: Extension + VariableLenPack>(
account_info: &AccountInfo,
new_extension: &V,
overwrite: bool,
Expand All @@ -1195,7 +1247,8 @@ pub fn alloc_and_serialize<S: BaseState, V: Extension + VariableLenPack>(
let (new_account_len, extension_already_exists) = {
let data = account_info.try_borrow_data()?;
let state = StateWithExtensions::<S>::unpack(&data)?;
let new_account_len = state.try_get_new_account_len(new_extension)?;
let new_account_len =
state.try_get_new_account_len::<V>(new_extension.get_packed_len()?)?;
let extension_already_exists = state.get_extension_bytes::<V>().is_ok();
(new_account_len, extension_already_exists)
};
Expand Down Expand Up @@ -2282,7 +2335,7 @@ mod test {
let current_len = state.try_get_account_len().unwrap();
assert_eq!(current_len, Mint::LEN);
let new_len = state
.try_get_new_account_len::<VariableLenMintTest>(&variable_len)
.try_get_new_account_len::<VariableLenMintTest>(value_len)
.unwrap();
assert_eq!(
new_len,
Expand All @@ -2297,19 +2350,23 @@ mod test {

// Reduce the extension size
let new_len = state
.try_get_new_account_len::<VariableLenMintTest>(&small_variable_len)
.try_get_new_account_len::<VariableLenMintTest>(
small_variable_len.get_packed_len().unwrap(),
)
.unwrap();
assert_eq!(current_len.checked_sub(new_len).unwrap(), 1);

// Increase the extension size
let new_len = state
.try_get_new_account_len::<VariableLenMintTest>(&big_variable_len)
.try_get_new_account_len::<VariableLenMintTest>(
big_variable_len.get_packed_len().unwrap(),
)
.unwrap();
assert_eq!(new_len.checked_sub(current_len).unwrap(), 1);

// Maintain the extension size
let new_len = state
.try_get_new_account_len::<VariableLenMintTest>(&variable_len)
.try_get_new_account_len::<VariableLenMintTest>(variable_len.get_packed_len().unwrap())
.unwrap();
assert_eq!(new_len, current_len);
}
Expand Down Expand Up @@ -2382,7 +2439,8 @@ mod test {
let key = Pubkey::new_unique();
let account_info = (&key, &mut data).into_account_info();

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

// alloc again succeeds with "overwrite"
let account_info = (&key, &mut data).into_account_info();
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
.unwrap();

// alloc again fails without "overwrite"
let account_info = (&key, &mut data).into_account_info();
assert_eq!(
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap_err(),
alloc_and_serialize_variable_len_extension::<Mint, _>(
&account_info,
&variable_len,
false,
)
.unwrap_err(),
TokenError::ExtensionAlreadyInitialized.into()
);
}
Expand Down Expand Up @@ -2429,7 +2493,8 @@ mod test {
let key = Pubkey::new_unique();
let account_info = (&key, &mut data).into_account_info();

alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap();
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, false)
.unwrap();
let new_account_len = BASE_ACCOUNT_AND_TYPE_LENGTH
+ add_type_and_length_to_len(value_len)
+ add_type_and_length_to_len(size_of::<MetadataPointer>());
Expand All @@ -2447,12 +2512,18 @@ mod test {

// alloc again succeeds with "overwrite"
let account_info = (&key, &mut data).into_account_info();
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
.unwrap();

// alloc again fails without "overwrite"
let account_info = (&key, &mut data).into_account_info();
assert_eq!(
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, false).unwrap_err(),
alloc_and_serialize_variable_len_extension::<Mint, _>(
&account_info,
&variable_len,
false,
)
.unwrap_err(),
TokenError::ExtensionAlreadyInitialized.into()
);
}
Expand Down Expand Up @@ -2488,7 +2559,8 @@ mod test {
let key = Pubkey::new_unique();
let account_info = (&key, &mut data).into_account_info();
let variable_len = VariableLenMintTest { data: vec![1, 2] };
alloc_and_serialize::<Mint, _>(&account_info, &variable_len, true).unwrap();
alloc_and_serialize_variable_len_extension::<Mint, _>(&account_info, &variable_len, true)
.unwrap();

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

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

let state = StateWithExtensions::<Mint>::unpack(data.data()).unwrap();
let extension = state.get_extension::<MetadataPointer>().unwrap();
Expand Down
12 changes: 6 additions & 6 deletions token/program-2022/src/extension/token_metadata/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use {
check_program_account,
error::TokenError,
extension::{
alloc_and_serialize, metadata_pointer::MetadataPointer, BaseStateWithExtensions,
StateWithExtensions,
alloc_and_serialize_variable_len_extension, metadata_pointer::MetadataPointer,
BaseStateWithExtensions, StateWithExtensions,
},
state::Mint,
},
Expand Down Expand Up @@ -98,7 +98,7 @@ pub fn process_initialize(

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

Ok(())
}
Expand Down Expand Up @@ -127,7 +127,7 @@ pub fn process_update_field(
token_metadata.update(data.field, data.value);

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

Ok(())
}
Expand All @@ -154,7 +154,7 @@ pub fn process_remove_key(
if !token_metadata.remove_key(&data.key) && !data.idempotent {
return Err(TokenMetadataError::KeyNotFound.into());
}
alloc_and_serialize::<Mint, _>(metadata_info, &token_metadata, true)?;
alloc_and_serialize_variable_len_extension::<Mint, _>(metadata_info, &token_metadata, true)?;
Ok(())
}

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

Ok(())
}
Expand Down

0 comments on commit a29209e

Please sign in to comment.