Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

token-2022: Support extensions in InitializeAccount variations #2743

Merged
merged 12 commits into from
Jan 15, 2022
Merged
48 changes: 37 additions & 11 deletions token/program-2022/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ impl<'data, S: BaseState> StateWithExtensionsMut<'data, S> {
let (base_data, rest) = input.split_at_mut(S::LEN);
let base = S::unpack_unchecked(base_data)?;
if base.is_initialized() {
return Err(ProgramError::InvalidAccountData);
return Err(TokenError::AlreadyInUse.into());
CriesofCarrots marked this conversation as resolved.
Show resolved Hide resolved
}
if let Some((account_type_index, tlv_start_index)) = type_and_tlv_indices::<S>(rest)? {
let account_type = AccountType::try_from(rest[account_type_index])
Expand Down Expand Up @@ -368,11 +368,37 @@ impl<'data, S: BaseState> StateWithExtensionsMut<'data, S> {
}

/// Packs the extension data into an open slot if not already found in the
/// data buffer, otherwise overwrites itself
/// data buffer
pub fn init_extension<V: Extension>(&mut self) -> Result<&mut V, ProgramError> {
self.init_or_get_extension(true)
}

/// If `extension_type` is an Account-associated ExtensionType that requires initialization on
/// InitializeAccount, this method packs the relevant Extension of an ExtensionType into an
/// open slot if not already found in the data buffer. For all other ExtensionTypes, this is a
/// no-op.
pub fn init_account_extension_from_type(
&mut self,
extension_type: ExtensionType,
) -> Result<(), ProgramError> {
if extension_type.get_account_type() != AccountType::Account {
return Ok(());
}
match extension_type {
CriesofCarrots marked this conversation as resolved.
Show resolved Hide resolved
ExtensionType::TransferFeeAmount => {
self.init_extension::<TransferFeeAmount>().map(|_| ())
}
// ConfidentialTransfers are currently opt-in only, so this is a no-op for extra safety
// on InitializeAccount
ExtensionType::ConfidentialTransferAccount => Ok(()),
#[cfg(test)]
ExtensionType::AccountPaddingTest => {
self.init_extension::<AccountPaddingTest>().map(|_| ())
}
_ => unreachable!(),
}
}

/// Write the account type into the buffer, done during the base
/// state initialization
/// Noops if there is no room for an extension in the account, needed for
Expand Down Expand Up @@ -521,9 +547,9 @@ impl ExtensionType {
}
}

/// Get the list of required AccountType::Account ExtensionTypes based on a set of
/// AccountType::Mint ExtensionTypes
pub fn get_account_extensions(mint_extension_types: &[Self]) -> Vec<Self> {
/// Based on a set of AccountType::Mint ExtensionTypes, get the list of AccountType::Account
/// ExtensionTypes required on InitializeAccount
pub fn get_required_init_account_extensions(mint_extension_types: &[Self]) -> Vec<Self> {
let mut account_extension_types = vec![];
for extension_type in mint_extension_types {
#[allow(clippy::single_match)]
Expand Down Expand Up @@ -783,7 +809,7 @@ mod test {
// unpack uninitialized will now fail because the Mint is now initialized
assert_eq!(
StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut buffer.clone()),
Err(ProgramError::InvalidAccountData),
Err(TokenError::AlreadyInUse.into()),
);

// check unpacking
Expand Down Expand Up @@ -1129,14 +1155,14 @@ mod test {
}

#[test]
fn test_get_account_extensions() {
fn test_get_required_init_account_extensions() {
// Some mint extensions with no required account extensions
let mint_extensions = vec![
ExtensionType::MintCloseAuthority,
ExtensionType::Uninitialized,
];
assert_eq!(
ExtensionType::get_account_extensions(&mint_extensions),
ExtensionType::get_required_init_account_extensions(&mint_extensions),
vec![]
);

Expand All @@ -1146,7 +1172,7 @@ mod test {
ExtensionType::MintCloseAuthority,
];
assert_eq!(
ExtensionType::get_account_extensions(&mint_extensions),
ExtensionType::get_required_init_account_extensions(&mint_extensions),
vec![ExtensionType::TransferFeeAmount]
);

Expand All @@ -1156,7 +1182,7 @@ mod test {
ExtensionType::MintPaddingTest,
];
assert_eq!(
ExtensionType::get_account_extensions(&mint_extensions),
ExtensionType::get_required_init_account_extensions(&mint_extensions),
vec![
ExtensionType::TransferFeeAmount,
ExtensionType::AccountPaddingTest
Expand All @@ -1169,7 +1195,7 @@ mod test {
ExtensionType::TransferFeeConfig,
];
assert_eq!(
ExtensionType::get_account_extensions(&mint_extensions),
ExtensionType::get_required_init_account_extensions(&mint_extensions),
vec![
ExtensionType::TransferFeeAmount,
ExtensionType::TransferFeeAmount
Expand Down
99 changes: 72 additions & 27 deletions token/program-2022/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use solana_program::{
program::set_return_data,
program_error::{PrintProgramError, ProgramError},
program_option::COption,
program_pack::{IsInitialized, Pack},
program_pack::Pack,
pubkey::Pubkey,
sysvar::{rent::Rent, Sysvar},
};
Expand Down Expand Up @@ -102,38 +102,44 @@ impl Processor {
Rent::get()?
};

let mut account = Account::unpack_unchecked(&new_account_info.data.borrow())?;
if account.is_initialized() {
return Err(TokenError::AlreadyInUse.into());
}
let mut account_data = new_account_info.data.borrow_mut();
// unpack_uninitialized checks account.base.is_initialized() under the hood
let mut account =
StateWithExtensionsMut::<Account>::unpack_uninitialized(&mut account_data)?;

if !rent.is_exempt(new_account_info.lamports(), new_account_info_data_len) {
return Err(TokenError::NotRentExempt.into());
}

if *mint_info.key != crate::native_mint::id() {
let _ = Mint::unpack(&mint_info.data.borrow_mut())
.map_err(|_| Into::<ProgramError>::into(TokenError::InvalidMint))?;
// get_required_account_extensions checks mint validity
let required_extensions = Self::get_required_account_extensions(mint_info)?;
if ExtensionType::get_account_len::<Account>(&required_extensions)
!= new_account_info_data_len
{
return Err(ProgramError::InvalidAccountData);
}
for extension in required_extensions {
account.init_account_extension_from_type(extension)?;
}

account.mint = *mint_info.key;
account.owner = *owner;
account.delegate = COption::None;
account.delegated_amount = 0;
account.state = AccountState::Initialized;
account.base.mint = *mint_info.key;
account.base.owner = *owner;
account.base.delegate = COption::None;
account.base.delegated_amount = 0;
account.base.state = AccountState::Initialized;
if *mint_info.key == crate::native_mint::id() {
let rent_exempt_reserve = rent.minimum_balance(new_account_info_data_len);
account.is_native = COption::Some(rent_exempt_reserve);
account.amount = new_account_info
account.base.is_native = COption::Some(rent_exempt_reserve);
account.base.amount = new_account_info
.lamports()
.checked_sub(rent_exempt_reserve)
.ok_or(TokenError::Overflow)?;
} else {
account.is_native = COption::None;
account.amount = 0;
account.base.is_native = COption::None;
account.base.amount = 0;
};

Account::pack(account, &mut new_account_info.data.borrow_mut())?;
account.pack_base();

Ok(())
}
Expand Down Expand Up @@ -768,12 +774,7 @@ impl Processor {
let account_info_iter = &mut accounts.iter();
let mint_account_info = next_account_info(account_info_iter)?;

check_program_account(mint_account_info.owner)?;
let mint_data = mint_account_info.data.borrow();
let state = StateWithExtensions::<Mint>::unpack(&mint_data)?;
let mint_extensions: Vec<ExtensionType> = state.get_extension_types()?;

let account_extensions = ExtensionType::get_account_extensions(&mint_extensions);
let account_extensions = Self::get_required_account_extensions(mint_account_info)?;

let account_len = ExtensionType::get_account_len::<Account>(&account_extensions);
set_return_data(&account_len.to_le_bytes());
Expand Down Expand Up @@ -947,6 +948,19 @@ impl Processor {
}
Ok(())
}

fn get_required_account_extensions(
mint_account_info: &AccountInfo,
CriesofCarrots marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<Vec<ExtensionType>, ProgramError> {
check_program_account(mint_account_info.owner)?;
let mint_data = mint_account_info.data.borrow();
let state = StateWithExtensions::<Mint>::unpack(&mint_data)
.map_err(|_| Into::<ProgramError>::into(TokenError::InvalidMint))?;
let mint_extensions: Vec<ExtensionType> = state.get_extension_types()?;
Ok(ExtensionType::get_required_init_account_extensions(
&mint_extensions,
))
}
}

impl PrintProgramError for TokenError {
Expand Down Expand Up @@ -1127,6 +1141,25 @@ mod tests {
Rent::default().minimum_balance(Multisig::get_packed_len())
}

fn native_mint() -> SolanaAccount {
let mut rent_sysvar = rent_sysvar();
let mut mint_account =
SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &crate::id());
do_process_instruction(
initialize_mint(
&crate::id(),
&crate::native_mint::id(),
&Pubkey::default(),
None,
9,
)
.unwrap(),
vec![&mut mint_account, &mut rent_sysvar],
)
.unwrap();
mint_account
}

#[test]
fn test_print_error() {
let error = return_token_error_as_program_error();
Expand Down Expand Up @@ -5345,8 +5378,7 @@ mod tests {
#[test]
fn test_native_token() {
let program_id = crate::id();
let mut mint_account =
SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id);
let mut mint_account = native_mint();
let account_key = Pubkey::new_unique();
let mut account_account = SolanaAccount::new(
account_minimum_balance() + 40,
Expand Down Expand Up @@ -6315,6 +6347,19 @@ mod tests {
)
.unwrap();

// Native mint
let mut mint_account = native_mint();
set_expected_data(
ExtensionType::get_account_len::<Account>(&[])
.to_le_bytes()
.to_vec(),
);
do_process_instruction(
get_account_data_size(&program_id, &mint_key).unwrap(),
vec![&mut mint_account],
)
.unwrap();

// TODO: Extended mint

// Invalid mint
Expand All @@ -6340,7 +6385,7 @@ mod tests {
get_account_data_size(&program_id, &invalid_mint_key).unwrap(),
vec![&mut invalid_mint_account],
),
Err(ProgramError::InvalidAccountData)
Err(TokenError::InvalidMint.into())
);

// Invalid mint owner
Expand Down