From 2a5acff2d8671b2412ec2d015f9ae7bbb6e8f5c2 Mon Sep 17 00:00:00 2001 From: Jack May Date: Thu, 27 Aug 2020 21:19:15 -0700 Subject: [PATCH] Safer pack/unpack (#349) * Safer pack/unpack * fix cli * clippy * fix swap * nit * clippy Co-authored-by: Michael Vines --- Cargo.lock | 44 + token-swap/program/src/processor.rs | 14 +- token/cli/src/main.rs | 8 +- token/program/Cargo.toml | 2 + token/program/src/instruction.rs | 447 ++++------ token/program/src/lib.rs | 1 + token/program/src/option.rs | 109 --- token/program/src/pack.rs | 76 ++ token/program/src/processor.rs | 1201 +++++++++++++++------------ token/program/src/state.rs | 199 ++++- 10 files changed, 1159 insertions(+), 942 deletions(-) create mode 100644 token/program/src/pack.rs diff --git a/Cargo.lock b/Cargo.lock index 969eb0d24bcf6e..6b6f6aca41719b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "derivative" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" +dependencies = [ + "proc-macro2 1.0.19", + "quote 1.0.7", + "syn 1.0.31", +] + [[package]] name = "dialoguer" version = "0.6.2" @@ -1183,6 +1194,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066" +dependencies = [ + "derivative", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e" +dependencies = [ + "proc-macro-crate", + "proc-macro2 1.0.19", + "quote 1.0.7", + "syn 1.0.31", +] + [[package]] name = "number_prefix" version = "0.3.0" @@ -1353,6 +1386,15 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + [[package]] name = "proc-macro-hack" version = "0.5.18" @@ -2203,9 +2245,11 @@ dependencies = [ name = "spl-token" version = "1.1.0" dependencies = [ + "arrayref", "cbindgen", "num-derive 0.3.2", "num-traits", + "num_enum", "rand", "remove_dir_all", "solana-sdk", diff --git a/token-swap/program/src/processor.rs b/token-swap/program/src/processor.rs index 470dd2b77b9074..bb8d5bfa275cba 100644 --- a/token-swap/program/src/processor.rs +++ b/token-swap/program/src/processor.rs @@ -17,6 +17,7 @@ use solana_sdk::{ entrypoint::ProgramResult, info, program_error::PrintProgramError, program_error::ProgramError, pubkey::Pubkey, }; +use spl_token::pack::Pack; use std::mem::size_of; impl State { @@ -68,14 +69,19 @@ impl State { pub fn token_account_deserialize( info: &AccountInfo, ) -> Result { - Ok(*spl_token::state::unpack(&mut info.data.borrow_mut()) - .map_err(|_| Error::ExpectedAccount)?) + spl_token::state::Account::unpack_from_slice(&info.data.borrow_mut()) + .map_err(|_| Error::ExpectedAccount) + // Ok(*spl_token::state::unpack(&mut info.data.borrow_mut()) + // .map_err(|_| Error::ExpectedAccount)?) } /// Deserializes a spl_token `Mint`. pub fn mint_deserialize(info: &AccountInfo) -> Result { - Ok(*spl_token::state::unpack(&mut info.data.borrow_mut()) - .map_err(|_| Error::ExpectedToken)?) + spl_token::state::Mint::unpack_from_slice(&info.data.borrow_mut()) + .map_err(|_| Error::ExpectedAccount) + + // Ok(*spl_token::state::unpack(&mut info.data.borrow_mut()) + // .map_err(|_| Error::ExpectedToken)?) } /// Calculates the authority id by generating a program address. diff --git a/token/cli/src/main.rs b/token/cli/src/main.rs index 4eefc9ee629fd2..88e039823e35d5 100644 --- a/token/cli/src/main.rs +++ b/token/cli/src/main.rs @@ -20,7 +20,8 @@ use spl_token::{ self, instruction::*, native_mint, - state::{self, Account, Mint}, + pack::Pack, + state::{Account, Mint}, }; use std::{mem::size_of, process::exit}; @@ -218,9 +219,8 @@ fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResu .get_account_with_commitment(&source, config.commitment_config)? .value .unwrap_or_default(); - let mut data = source_account.data.to_vec(); - let mint_pubkey = state::unpack::(&mut data)?.mint; - + let data = source_account.data.to_vec(); + let mint_pubkey = Account::unpack_from_slice(&data)?.mint; let amount = spl_token::ui_amount_to_amount(ui_amount, source_token_balance.decimals); let mut transaction = Transaction::new_with_payer( &[burn( diff --git a/token/program/Cargo.toml b/token/program/Cargo.toml index e1120882aa89f6..2c8c9cadac26fb 100644 --- a/token/program/Cargo.toml +++ b/token/program/Cargo.toml @@ -23,6 +23,8 @@ num-traits = "0.2" remove_dir_all = "=0.5.0" solana-sdk = { version = "1.3.4", default-features = false, optional = true } thiserror = "1.0" +arrayref = "0.3.6" +num_enum = "0.5.1" [dev-dependencies] rand = { version = "0.7.0"} diff --git a/token/program/src/instruction.rs b/token/program/src/instruction.rs index 1d21ce7ee3973c..8e1a04576f9f30 100644 --- a/token/program/src/instruction.rs +++ b/token/program/src/instruction.rs @@ -7,6 +7,7 @@ use solana_sdk::{ pubkey::Pubkey, sysvar, }; +use std::convert::TryInto; use std::mem::size_of; /// Minimum number of multisignature signers (min N) @@ -332,29 +333,14 @@ pub enum TokenInstruction { impl TokenInstruction { /// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html). pub fn unpack(input: &[u8]) -> Result { - if input.len() < size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - Ok(match input[0] { - 0 => { - if input.len() < size_of::() + size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - let mut input_len = 0; - input_len += size_of::(); - - let decimals = unsafe { *(&input[input_len] as *const u8) }; - input_len += size_of::(); - - let mint_authority = unsafe { *(&input[input_len] as *const u8 as *const Pubkey) }; - input_len += size_of::(); - - let freeze_authority = COption::unpack_or( - input, - &mut input_len, - Into::::into(TokenError::InvalidInstruction), - )?; + use TokenError::InvalidInstruction; + let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?; + Ok(match tag { + 0 => { + let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?; + let (mint_authority, rest) = Self::unpack_pubkey(rest)?; + let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?; Self::InitializeMint { mint_authority, freeze_authority, @@ -363,126 +349,80 @@ impl TokenInstruction { } 1 => Self::InitializeAccount, 2 => { - if input.len() < size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - #[allow(clippy::cast_ptr_alignment)] - let m = unsafe { *(&input[1] as *const u8) }; + let &m = rest.get(0).ok_or(InvalidInstruction)?; Self::InitializeMultisig { m } } - 3 => { - if input.len() < size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[size_of::()] as *const u8 as *const u64) }; - Self::Transfer { amount } - } - 4 => { - if input.len() < size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); + 3 | 4 | 7 | 8 => { + let amount = rest + .get(..8) + .and_then(|slice| slice.try_into().ok()) + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + match tag { + 3 => Self::Transfer { amount }, + 4 => Self::Approve { amount }, + 7 => Self::MintTo { amount }, + 8 => Self::Burn { amount }, + _ => unreachable!(), } - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[size_of::()] as *const u8 as *const u64) }; - Self::Approve { amount } } 5 => Self::Revoke, 6 => { - if input.len() < size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - let mut input_len = 0; - input_len += size_of::(); - let authority_type = AuthorityType::from(input[1])?; - input_len += size_of::(); - - let new_authority = COption::unpack_or( - input, - &mut input_len, - Into::::into(TokenError::InvalidInstruction), - )?; + let (authority_type, rest) = rest + .split_first() + .ok_or_else(|| ProgramError::from(InvalidInstruction)) + .and_then(|(&t, rest)| Ok((AuthorityType::from(t)?, rest)))?; + let (new_authority, _rest) = Self::unpack_pubkey_option(rest)?; Self::SetAuthority { authority_type, new_authority, } } - 7 => { - if input.len() < size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[size_of::()] as *const u8 as *const u64) }; - Self::MintTo { amount } - } - 8 => { - if input.len() < size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[size_of::()] as *const u8 as *const u64) }; - Self::Burn { amount } - } 9 => Self::CloseAccount, 10 => Self::FreezeAccount, 11 => Self::ThawAccount, 12 => { - if input.len() < size_of::() + size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - let mut input_len = 0; - input_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) }; - input_len += size_of::(); - - let decimals = unsafe { *(&input[input_len] as *const u8) }; + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; Self::Transfer2 { amount, decimals } } 13 => { - if input.len() < size_of::() + size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - let mut input_len = 0; - input_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) }; - input_len += size_of::(); - - let decimals = unsafe { *(&input[input_len] as *const u8) }; + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; Self::Approve2 { amount, decimals } } 14 => { - if input.len() < size_of::() + size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - let mut input_len = 0; - input_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) }; - input_len += size_of::(); - - let decimals = unsafe { *(&input[input_len] as *const u8) }; + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; Self::MintTo2 { amount, decimals } } 15 => { - if input.len() < size_of::() + size_of::() + size_of::() { - return Err(TokenError::InvalidInstruction.into()); - } - let mut input_len = 0; - input_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) }; - input_len += size_of::(); - - let decimals = unsafe { *(&input[input_len] as *const u8) }; + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; Self::Burn2 { amount, decimals } } @@ -492,163 +432,106 @@ impl TokenInstruction { } /// Packs a [TokenInstruction](enum.TokenInstruction.html) into a byte buffer. - pub fn pack(&self) -> Result, ProgramError> { - let mut output = vec![0u8; size_of::()]; - let mut output_len = 0; + pub fn pack(&self) -> Vec { + let mut buf = Vec::with_capacity(size_of::()); match self { - Self::InitializeMint { - mint_authority, - freeze_authority, + &Self::InitializeMint { + ref mint_authority, + ref freeze_authority, decimals, } => { - output[output_len] = 0; - output_len += size_of::(); - - let value = unsafe { &mut *(&mut output[output_len] as *mut u8) }; - *value = *decimals; - output_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut Pubkey) }; - *value = *mint_authority; - output_len += size_of::(); - - freeze_authority.pack(&mut output, &mut output_len); + buf.push(0); + buf.push(decimals); + buf.extend_from_slice(mint_authority.as_ref()); + Self::pack_pubkey_option(freeze_authority, &mut buf); } - Self::InitializeAccount => { - output[output_len] = 1; - output_len += size_of::(); + Self::InitializeAccount => buf.push(1), + &Self::InitializeMultisig { m } => { + buf.push(2); + buf.push(m); } - Self::InitializeMultisig { m } => { - output[output_len] = 2; - output_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u8) }; - *value = *m; - output_len += size_of::(); + &Self::Transfer { amount } => { + buf.push(3); + buf.extend_from_slice(&amount.to_le_bytes()); } - Self::Transfer { amount } => { - output[output_len] = 3; - output_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) }; - *value = *amount; - output_len += size_of::(); + &Self::Approve { amount } => { + buf.push(4); + buf.extend_from_slice(&amount.to_le_bytes()); } - Self::Approve { amount } => { - output[output_len] = 4; - output_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) }; - *value = *amount; - output_len += size_of::(); + &Self::MintTo { amount } => { + buf.push(7); + buf.extend_from_slice(&amount.to_le_bytes()); } - Self::Revoke => { - output[output_len] = 5; - output_len += size_of::(); + &Self::Burn { amount } => { + buf.push(8); + buf.extend_from_slice(&amount.to_le_bytes()); } + Self::Revoke => buf.push(5), Self::SetAuthority { authority_type, - new_authority, + ref new_authority, } => { - output[output_len] = 6; - output_len += size_of::(); - - output[output_len] = authority_type.into(); - output_len += size_of::(); - - new_authority.pack(&mut output, &mut output_len); - } - Self::MintTo { amount } => { - output[output_len] = 7; - output_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) }; - *value = *amount; - output_len += size_of::(); - } - Self::Burn { amount } => { - output[output_len] = 8; - output_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) }; - *value = *amount; - output_len += size_of::(); + buf.push(6); + buf.push(authority_type.into()); + Self::pack_pubkey_option(new_authority, &mut buf); } - Self::CloseAccount => { - output[output_len] = 9; - output_len += size_of::(); + Self::CloseAccount => buf.push(9), + Self::FreezeAccount => buf.push(10), + Self::ThawAccount => buf.push(11), + &Self::Transfer2 { amount, decimals } => { + buf.push(12); + buf.extend_from_slice(&amount.to_le_bytes()); + buf.push(decimals); } - Self::FreezeAccount => { - output[output_len] = 10; - output_len += size_of::(); + &Self::Approve2 { amount, decimals } => { + buf.push(13); + buf.extend_from_slice(&amount.to_le_bytes()); + buf.push(decimals); } - Self::ThawAccount => { - output[output_len] = 11; - output_len += size_of::(); + &Self::MintTo2 { amount, decimals } => { + buf.push(14); + buf.extend_from_slice(&amount.to_le_bytes()); + buf.push(decimals); } - Self::Transfer2 { amount, decimals } => { - output[output_len] = 12; - output_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) }; - *value = *amount; - output_len += size_of::(); - - let value = unsafe { &mut *(&mut output[output_len] as *mut u8) }; - *value = *decimals; - output_len += size_of::(); + &Self::Burn2 { amount, decimals } => { + buf.push(15); + buf.extend_from_slice(&amount.to_le_bytes()); + buf.push(decimals); } - Self::Approve2 { amount, decimals } => { - output[output_len] = 13; - output_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) }; - *value = *amount; - output_len += size_of::(); - - let value = unsafe { &mut *(&mut output[output_len] as *mut u8) }; - *value = *decimals; - output_len += size_of::(); - } - Self::MintTo2 { amount, decimals } => { - output[output_len] = 14; - output_len += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) }; - *value = *amount; - output_len += size_of::(); - - let value = unsafe { &mut *(&mut output[output_len] as *mut u8) }; - *value = *decimals; - output_len += size_of::(); - } - - Self::Burn2 { amount, decimals } => { - output[output_len] = 15; - output_len += size_of::(); + }; + buf + } - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) }; - *value = *amount; - output_len += size_of::(); + fn unpack_pubkey(input: &[u8]) -> Result<(Pubkey, &[u8]), ProgramError> { + if input.len() >= 32 { + let (key, rest) = input.split_at(32); + let pk = Pubkey::new(key); + Ok((pk, rest)) + } else { + Err(TokenError::InvalidInstruction.into()) + } + } - let value = unsafe { &mut *(&mut output[output_len] as *mut u8) }; - *value = *decimals; - output_len += size_of::(); + fn unpack_pubkey_option(input: &[u8]) -> Result<(COption, &[u8]), ProgramError> { + match input.split_first() { + Option::Some((&0, rest)) => Ok((COption::None, rest)), + Option::Some((&1, rest)) if rest.len() >= 32 => { + let (key, rest) = rest.split_at(32); + let pk = Pubkey::new(key); + Ok((COption::Some(pk), rest)) } + _ => Err(TokenError::InvalidInstruction.into()), } + } - output.truncate(output_len); - Ok(output) + fn pack_pubkey_option(value: &COption, buf: &mut Vec) { + match *value { + COption::Some(ref key) => { + buf.push(1); + buf.extend_from_slice(&key.to_bytes()); + } + COption::None => buf.push(0), + } } } @@ -701,7 +584,7 @@ pub fn initialize_mint( freeze_authority, decimals, } - .pack()?; + .pack(); let accounts = vec![ AccountMeta::new(*mint_pubkey, false), @@ -722,7 +605,7 @@ pub fn initialize_account( mint_pubkey: &Pubkey, owner_pubkey: &Pubkey, ) -> Result { - let data = TokenInstruction::InitializeAccount.pack()?; + let data = TokenInstruction::InitializeAccount.pack(); // TODO do we need to return result? let accounts = vec![ AccountMeta::new(*account_pubkey, false), @@ -751,7 +634,7 @@ pub fn initialize_multisig( { return Err(ProgramError::MissingRequiredSignature); } - let data = TokenInstruction::InitializeMultisig { m }.pack()?; + let data = TokenInstruction::InitializeMultisig { m }.pack(); let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*multisig_pubkey, false)); @@ -776,7 +659,7 @@ pub fn transfer( signer_pubkeys: &[&Pubkey], amount: u64, ) -> Result { - let data = TokenInstruction::Transfer { amount }.pack()?; + let data = TokenInstruction::Transfer { amount }.pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*source_pubkey, false)); @@ -805,7 +688,7 @@ pub fn approve( signer_pubkeys: &[&Pubkey], amount: u64, ) -> Result { - let data = TokenInstruction::Approve { amount }.pack()?; + let data = TokenInstruction::Approve { amount }.pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*source_pubkey, false)); @@ -832,7 +715,7 @@ pub fn revoke( owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], ) -> Result { - let data = TokenInstruction::Revoke.pack()?; + let data = TokenInstruction::Revoke.pack(); let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len()); accounts.push(AccountMeta::new_readonly(*source_pubkey, false)); @@ -865,7 +748,7 @@ pub fn set_authority( authority_type, new_authority, } - .pack()?; + .pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*owned_pubkey, false)); @@ -893,7 +776,7 @@ pub fn mint_to( signer_pubkeys: &[&Pubkey], amount: u64, ) -> Result { - let data = TokenInstruction::MintTo { amount }.pack()?; + let data = TokenInstruction::MintTo { amount }.pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*mint_pubkey, false)); @@ -922,7 +805,7 @@ pub fn burn( signer_pubkeys: &[&Pubkey], amount: u64, ) -> Result { - let data = TokenInstruction::Burn { amount }.pack()?; + let data = TokenInstruction::Burn { amount }.pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*account_pubkey, false)); @@ -950,7 +833,7 @@ pub fn close_account( owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], ) -> Result { - let data = TokenInstruction::CloseAccount.pack()?; + let data = TokenInstruction::CloseAccount.pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*account_pubkey, false)); @@ -978,7 +861,7 @@ pub fn freeze_account( owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], ) -> Result { - let data = TokenInstruction::FreezeAccount.pack()?; + let data = TokenInstruction::FreezeAccount.pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*account_pubkey, false)); @@ -1006,7 +889,7 @@ pub fn thaw_account( owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], ) -> Result { - let data = TokenInstruction::ThawAccount.pack()?; + let data = TokenInstruction::ThawAccount.pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*account_pubkey, false)); @@ -1038,7 +921,7 @@ pub fn transfer2( amount: u64, decimals: u8, ) -> Result { - let data = TokenInstruction::Transfer2 { amount, decimals }.pack()?; + let data = TokenInstruction::Transfer2 { amount, decimals }.pack(); let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*source_pubkey, false)); @@ -1071,7 +954,7 @@ pub fn approve2( amount: u64, decimals: u8, ) -> Result { - let data = TokenInstruction::Approve2 { amount, decimals }.pack()?; + let data = TokenInstruction::Approve2 { amount, decimals }.pack(); let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*source_pubkey, false)); @@ -1102,7 +985,7 @@ pub fn mint_to2( amount: u64, decimals: u8, ) -> Result { - let data = TokenInstruction::MintTo2 { amount, decimals }.pack()?; + let data = TokenInstruction::MintTo2 { amount, decimals }.pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*mint_pubkey, false)); @@ -1132,7 +1015,7 @@ pub fn burn2( amount: u64, decimals: u8, ) -> Result { - let data = TokenInstruction::Burn2 { amount, decimals }.pack()?; + let data = TokenInstruction::Burn2 { amount, decimals }.pack(); let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*account_pubkey, false)); @@ -1168,7 +1051,7 @@ mod test { mint_authority: Pubkey::new(&[1u8; 32]), freeze_authority: COption::None, }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let mut expect = Vec::from([0u8, 2]); expect.extend_from_slice(&[1u8; 32]); expect.extend_from_slice(&[0]); @@ -1181,7 +1064,7 @@ mod test { mint_authority: Pubkey::new(&[2u8; 32]), freeze_authority: COption::Some(Pubkey::new(&[3u8; 32])), }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let mut expect = vec![0u8, 2]; expect.extend_from_slice(&[2u8; 32]); expect.extend_from_slice(&[1]); @@ -1191,35 +1074,35 @@ mod test { assert_eq!(unpacked, check); let check = TokenInstruction::InitializeAccount; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([1u8]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); let check = TokenInstruction::InitializeMultisig { m: 1 }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([2u8, 1]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); let check = TokenInstruction::Transfer { amount: 1 }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([3u8, 1, 0, 0, 0, 0, 0, 0, 0]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); let check = TokenInstruction::Approve { amount: 1 }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([4u8, 1, 0, 0, 0, 0, 0, 0, 0]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); let check = TokenInstruction::Revoke; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([5u8]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); @@ -1229,7 +1112,7 @@ mod test { authority_type: AuthorityType::FreezeAccount, new_authority: COption::Some(Pubkey::new(&[4u8; 32])), }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let mut expect = Vec::from([6u8, 1]); expect.extend_from_slice(&[1]); expect.extend_from_slice(&[4u8; 32]); @@ -1238,35 +1121,35 @@ mod test { assert_eq!(unpacked, check); let check = TokenInstruction::MintTo { amount: 1 }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([7u8, 1, 0, 0, 0, 0, 0, 0, 0]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); let check = TokenInstruction::Burn { amount: 1 }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([8u8, 1, 0, 0, 0, 0, 0, 0, 0]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); let check = TokenInstruction::CloseAccount; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([9u8]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); let check = TokenInstruction::FreezeAccount; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([10u8]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); let check = TokenInstruction::ThawAccount; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([11u8]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); @@ -1276,7 +1159,7 @@ mod test { amount: 1, decimals: 2, }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([12u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); @@ -1286,7 +1169,7 @@ mod test { amount: 1, decimals: 2, }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([13u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); @@ -1296,7 +1179,7 @@ mod test { amount: 1, decimals: 2, }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([14u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); @@ -1306,7 +1189,7 @@ mod test { amount: 1, decimals: 2, }; - let packed = check.pack().unwrap(); + let packed = check.pack(); let expect = Vec::from([15u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); diff --git a/token/program/src/lib.rs b/token/program/src/lib.rs index e1887551f2430b..cb58c529ed34f7 100644 --- a/token/program/src/lib.rs +++ b/token/program/src/lib.rs @@ -7,6 +7,7 @@ pub mod error; pub mod instruction; pub mod native_mint; pub mod option; +pub mod pack; pub mod processor; pub mod state; diff --git a/token/program/src/option.rs b/token/program/src/option.rs index 9e6f16da3b55d2..677408bafa09bb 100644 --- a/token/program/src/option.rs +++ b/token/program/src/option.rs @@ -676,55 +676,6 @@ impl COption { pub fn replace(&mut self, value: T) -> COption { mem::replace(self, COption::Some(value)) } - - ///////////////////////////////////////////////////////////////////////// - // SPL Token-Specific Methods - ///////////////////////////////////////////////////////////////////////// - - /// Packs a COption into a mutable slice as compactly as possible - #[inline] - pub fn pack(&self, output: &mut [u8], cursor: &mut usize) - where - T: Copy, - { - match self { - COption::Some(some_value) => { - output[*cursor] = 1; - *cursor += mem::size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[*cursor] as *mut u8 as *mut T) }; - *value = *some_value; - *cursor += mem::size_of::(); - } - COption::None => { - output[*cursor] = 0; - *cursor += mem::size_of::(); - } - } - } - - /// Unpacks a COption from a compact slice - #[inline] - pub fn unpack_or(input: &[u8], cursor: &mut usize, error: E) -> Result, E> - where - T: Copy, - { - match input[*cursor] { - 0 => { - *cursor += mem::size_of::(); - Ok(COption::None) - } - 1 => { - *cursor += mem::size_of::(); - #[allow(clippy::cast_ptr_alignment)] - let result = unsafe { *(&input[*cursor] as *const u8 as *const T) }; - *cursor += mem::size_of::(); - Ok(COption::Some(result)) - } - _ => Err(error), - } - } } impl COption<&T> { @@ -1034,7 +985,6 @@ impl Into> for COption { #[cfg(test)] mod test { use super::*; - use solana_sdk::pubkey::Pubkey; #[test] fn test_from_rust_option() { @@ -1050,63 +1000,4 @@ mod test { let expected = c_option.into(); assert_eq!(option, expected); } - - #[test] - fn test_coption_packing() { - // Solana Pubkey - let option_pubkey = COption::Some(Pubkey::new(&[2u8; 32])); - let expected_size = mem::size_of::() + mem::size_of::(); - let mut output = vec![0u8; expected_size]; - let mut cursor = 0; - option_pubkey.pack(&mut output, &mut cursor); - - let mut expected = vec![1u8]; - expected.extend_from_slice(&[2u8; 32]); - assert_eq!(output, expected); - - let mut cursor = 0; - let unpacked = COption::unpack_or(&expected, &mut cursor, "Error".to_string()).unwrap(); - assert_eq!(unpacked, option_pubkey); - - let option_pubkey: COption = COption::None; - let expected_size = mem::size_of::(); - let mut output = vec![0u8; expected_size]; - let mut cursor = 0; - option_pubkey.pack(&mut output, &mut cursor); - - let expected = vec![0u8]; - assert_eq!(output, expected); - - let mut cursor = 0; - let unpacked = COption::unpack_or(&expected, &mut cursor, "Error".to_string()).unwrap(); - assert_eq!(unpacked, option_pubkey); - - // u64 - let option_pubkey = COption::Some(99u64); - let expected_size = mem::size_of::() + mem::size_of::(); - let mut output = vec![0u8; expected_size]; - let mut cursor = 0; - option_pubkey.pack(&mut output, &mut cursor); - - let mut expected = vec![1u8]; - expected.extend_from_slice(&[99, 0, 0, 0, 0, 0, 0, 0]); - assert_eq!(output, expected); - - let mut cursor = 0; - let unpacked = COption::unpack_or(&expected, &mut cursor, "Error".to_string()).unwrap(); - assert_eq!(unpacked, option_pubkey); - - let option_pubkey: COption = COption::None; - let expected_size = mem::size_of::(); - let mut output = vec![0u8; expected_size]; - let mut cursor = 0; - option_pubkey.pack(&mut output, &mut cursor); - - let expected = vec![0u8]; - assert_eq!(output, expected); - - let mut cursor = 0; - let unpacked = COption::unpack_or(&expected, &mut cursor, "Error".to_string()).unwrap(); - assert_eq!(unpacked, option_pubkey); - } } diff --git a/token/program/src/pack.rs b/token/program/src/pack.rs new file mode 100644 index 00000000000000..bf6a205e8aeb56 --- /dev/null +++ b/token/program/src/pack.rs @@ -0,0 +1,76 @@ +//! State transition types + +use crate::error::TokenError; +use solana_sdk::program_error::ProgramError; + +/// Check is a token state is initialized +pub trait IsInitialized { + /// Is initialized + fn is_initialized(&self) -> bool; +} + +/// Depends on Sized +pub trait Sealed: Sized {} + +/// Safely and efficiently (de)serialize account state +pub trait Pack: Sealed { + /// The length, in bytes, of the packed representation + const LEN: usize; + #[doc(hidden)] + fn pack_into_slice(&self, dst: &mut [u8]); + #[doc(hidden)] + fn unpack_from_slice(src: &[u8]) -> Result; + + /// Borrow `Self` from `input` for the duration of the call to `f`, but first check that `Self` + /// is initialized + #[inline(never)] + fn unpack_mut(input: &mut [u8], f: &mut F) -> Result + where + F: FnMut(&mut Self) -> Result, + Self: IsInitialized, + { + let mut t = unpack(input)?; + let u = f(&mut t)?; + pack(t, input)?; + Ok(u) + } + + /// Borrow `Self` from `input` for the duration of the call to `f`, without checking that + /// `Self` has been initialized + #[inline(never)] + fn unpack_unchecked_mut(input: &mut [u8], f: &mut F) -> Result + where + F: FnMut(&mut Self) -> Result, + { + let mut t = unpack_unchecked(input)?; + let u = f(&mut t)?; + pack(t, input)?; + Ok(u) + } +} + +fn pack(src: T, dst: &mut [u8]) -> Result<(), ProgramError> { + if dst.len() < T::LEN { + println!("dlen {:?} tlen {:?}", dst.len(), T::LEN); + return Err(ProgramError::InvalidAccountData); + } + src.pack_into_slice(dst); + Ok(()) +} + +fn unpack(input: &[u8]) -> Result { + let value: T = unpack_unchecked(input)?; + if value.is_initialized() { + Ok(value) + } else { + Err(TokenError::UninitializedState.into()) + } +} + +fn unpack_unchecked(input: &[u8]) -> Result { + if input.len() < T::LEN { + println!("ilen {:?} tlen {:?}", input.len(), T::LEN); + return Err(ProgramError::InvalidAccountData); + } + Ok(T::unpack_from_slice(input)?) +} diff --git a/token/program/src/processor.rs b/token/program/src/processor.rs index 4826f2a7fa1d75..d2f647e8660623 100644 --- a/token/program/src/processor.rs +++ b/token/program/src/processor.rs @@ -6,7 +6,8 @@ use crate::{ error::TokenError, instruction::{is_valid_signer_index, AuthorityType, TokenInstruction}, option::COption, - state::{self, Account, AccountState, IsInitialized, Mint, Multisig}, + pack::{IsInitialized, Pack}, + state::{Account, AccountState, Mint, Multisig}, }; use num_traits::FromPrimitive; use solana_sdk::{ @@ -32,25 +33,26 @@ impl Processor { ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let mint_info = next_account_info(account_info_iter)?; - let mint_info_data_len = mint_info.data_len(); + let mint_data_len = mint_info.data_len(); let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?; - let mut mint_info_data = mint_info.data.borrow_mut(); - let mut mint: &mut Mint = state::unpack_unchecked(&mut mint_info_data)?; - if mint.is_initialized { - return Err(TokenError::AlreadyInUse.into()); - } + let mut mint_data = mint_info.data.borrow_mut(); + Mint::unpack_unchecked_mut(&mut mint_data, &mut |mint: &mut Mint| { + if mint.is_initialized { + return Err(TokenError::AlreadyInUse.into()); + } - if !rent.is_exempt(mint_info.lamports(), mint_info_data_len) { - return Err(TokenError::NotRentExempt.into()); - } + if !rent.is_exempt(mint_info.lamports(), mint_data_len) { + return Err(TokenError::NotRentExempt.into()); + } - mint.mint_authority = COption::Some(mint_authority); - mint.decimals = decimals; - mint.is_initialized = true; - mint.freeze_authority = freeze_authority; + mint.mint_authority = COption::Some(mint_authority); + mint.decimals = decimals; + mint.is_initialized = true; + mint.freeze_authority = freeze_authority; - Ok(()) + Ok(()) + }) } /// Processes an [InitializeAccount](enum.TokenInstruction.html) instruction. @@ -63,39 +65,40 @@ impl Processor { let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?; let mut new_account_data = new_account_info.data.borrow_mut(); - let mut account: &mut Account = state::unpack_unchecked(&mut new_account_data)?; - if account.is_initialized() { - return Err(TokenError::AlreadyInUse.into()); - } + Account::unpack_unchecked_mut(&mut new_account_data, &mut |account: &mut Account| { + if account.is_initialized() { + return Err(TokenError::AlreadyInUse.into()); + } - if !rent.is_exempt(new_account_info.lamports(), new_account_info_data_len) { - return Err(TokenError::NotRentExempt.into()); - } + 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 mut mint_info_data = mint_info.data.borrow_mut(); - let _: &mut Mint = state::unpack(&mut mint_info_data) - .map_err(|_| Into::::into(TokenError::InvalidMint))?; - } + if *mint_info.key != crate::native_mint::id() { + let mut mint_info_data = mint_info.data.borrow_mut(); + Mint::unpack_mut(&mut mint_info_data, &mut |_| Ok(())) + .map_err(|_| Into::::into(TokenError::InvalidMint))?; + } - account.mint = *mint_info.key; - account.owner = *owner_info.key; - account.delegate = COption::None; - account.delegated_amount = 0; - account.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 - .lamports() - .checked_sub(rent_exempt_reserve) - .ok_or(TokenError::Overflow)?; - } else { - account.is_native = COption::None; - account.amount = 0; - }; + account.mint = *mint_info.key; + account.owner = *owner_info.key; + account.delegate = COption::None; + account.delegated_amount = 0; + account.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 + .lamports() + .checked_sub(rent_exempt_reserve) + .ok_or(TokenError::Overflow)?; + } else { + account.is_native = COption::None; + account.amount = 0; + }; - Ok(()) + Ok(()) + }) } /// Processes a [InitializeMultisig](enum.TokenInstruction.html) instruction. @@ -106,30 +109,34 @@ impl Processor { let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?; let mut multisig_account_data = multisig_info.data.borrow_mut(); - let mut multisig: &mut Multisig = state::unpack_unchecked(&mut multisig_account_data)?; - if multisig.is_initialized { - return Err(TokenError::AlreadyInUse.into()); - } + Multisig::unpack_unchecked_mut( + &mut multisig_account_data, + &mut |multisig: &mut Multisig| { + if multisig.is_initialized { + return Err(TokenError::AlreadyInUse.into()); + } - if !rent.is_exempt(multisig_info.lamports(), multisig_info_data_len) { - return Err(TokenError::NotRentExempt.into()); - } + if !rent.is_exempt(multisig_info.lamports(), multisig_info_data_len) { + return Err(TokenError::NotRentExempt.into()); + } - let signer_infos = account_info_iter.as_slice(); - multisig.m = m; - multisig.n = signer_infos.len() as u8; - if !is_valid_signer_index(multisig.n as usize) { - return Err(TokenError::InvalidNumberOfProvidedSigners.into()); - } - if !is_valid_signer_index(multisig.m as usize) { - return Err(TokenError::InvalidNumberOfRequiredSigners.into()); - } - for (i, signer_info) in signer_infos.iter().enumerate() { - multisig.signers[i] = *signer_info.key; - } - multisig.is_initialized = true; + let signer_infos = account_info_iter.as_slice(); + multisig.m = m; + multisig.n = signer_infos.len() as u8; + if !is_valid_signer_index(multisig.n as usize) { + return Err(TokenError::InvalidNumberOfProvidedSigners.into()); + } + if !is_valid_signer_index(multisig.m as usize) { + return Err(TokenError::InvalidNumberOfRequiredSigners.into()); + } + for (i, signer_info) in signer_infos.iter().enumerate() { + multisig.signers[i] = *signer_info.key; + } + multisig.is_initialized = true; - Ok(()) + Ok(()) + }, + ) } /// Processes a [Transfer](enum.TokenInstruction.html) instruction. @@ -157,82 +164,84 @@ impl Processor { } let mut source_data = source_account_info.data.borrow_mut(); - let mut source_account: &mut Account = state::unpack(&mut source_data)?; let mut dest_data = dest_account_info.data.borrow_mut(); - let mut dest_account: &mut Account = state::unpack(&mut dest_data)?; - - if source_account.amount < amount { - return Err(TokenError::InsufficientFunds.into()); - } - if source_account.mint != dest_account.mint { - return Err(TokenError::MintMismatch.into()); - } - if source_account.is_frozen() || dest_account.is_frozen() { - return Err(TokenError::AccountFrozen.into()); - } + Account::unpack_mut(&mut source_data, &mut |source_account: &mut Account| { + Account::unpack_mut(&mut dest_data, &mut |dest_account: &mut Account| { + if source_account.amount < amount { + return Err(TokenError::InsufficientFunds.into()); + } + if source_account.mint != dest_account.mint { + return Err(TokenError::MintMismatch.into()); + } + if source_account.is_frozen() || dest_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } - if let Some((mint_account_info, expected_decimals)) = expected_mint_info { - if source_account.mint != *mint_account_info.key { - return Err(TokenError::MintMismatch.into()); - } + if let Some((mint_account_info, expected_decimals)) = expected_mint_info { + if source_account.mint != *mint_account_info.key { + return Err(TokenError::MintMismatch.into()); + } - let mut mint_info_data = mint_account_info.data.borrow_mut(); - let mint: &Mint = state::unpack_unchecked(&mut mint_info_data)?; + let mut mint_info_data = mint_account_info.data.borrow_mut(); + Mint::unpack_mut(&mut mint_info_data, &mut |mint: &mut Mint| { + if expected_decimals != mint.decimals { + return Err(TokenError::MintDecimalsMismatch.into()); + } + Ok(()) + })?; + } - if expected_decimals != mint.decimals { - return Err(TokenError::MintDecimalsMismatch.into()); - } - } + match source_account.delegate { + COption::Some(ref delegate) if authority_info.key == delegate => { + Self::validate_owner( + program_id, + delegate, + authority_info, + account_info_iter.as_slice(), + )?; + if source_account.delegated_amount < amount { + return Err(TokenError::InsufficientFunds.into()); + } + source_account.delegated_amount = source_account + .delegated_amount + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + if source_account.delegated_amount == 0 { + source_account.delegate = COption::None; + } + } + _ => Self::validate_owner( + program_id, + &source_account.owner, + authority_info, + account_info_iter.as_slice(), + )?, + }; - match source_account.delegate { - COption::Some(ref delegate) if authority_info.key == delegate => { - Self::validate_owner( - program_id, - delegate, - authority_info, - account_info_iter.as_slice(), - )?; - if source_account.delegated_amount < amount { - return Err(TokenError::InsufficientFunds.into()); - } - source_account.delegated_amount = source_account - .delegated_amount + source_account.amount = source_account + .amount .checked_sub(amount) .ok_or(TokenError::Overflow)?; - if source_account.delegated_amount == 0 { - source_account.delegate = COption::None; - } - } - _ => Self::validate_owner( - program_id, - &source_account.owner, - authority_info, - account_info_iter.as_slice(), - )?, - }; + dest_account.amount = dest_account + .amount + .checked_add(amount) + .ok_or(TokenError::Overflow)?; - source_account.amount = source_account - .amount - .checked_sub(amount) - .ok_or(TokenError::Overflow)?; - dest_account.amount = dest_account - .amount - .checked_add(amount) - .ok_or(TokenError::Overflow)?; - - if source_account.is_native() { - let source_starting_lamports = source_account_info.lamports(); - **source_account_info.lamports.borrow_mut() = source_starting_lamports - .checked_sub(amount) - .ok_or(TokenError::Overflow)?; + if source_account.is_native() { + let source_starting_lamports = source_account_info.lamports(); + **source_account_info.lamports.borrow_mut() = source_starting_lamports + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; - let dest_starting_lamports = dest_account_info.lamports(); - **dest_account_info.lamports.borrow_mut() = dest_starting_lamports - .checked_add(amount) - .ok_or(TokenError::Overflow)?; - } + let dest_starting_lamports = dest_account_info.lamports(); + **dest_account_info.lamports.borrow_mut() = dest_starting_lamports + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + } - Ok(()) + Ok(()) + }) + }) } /// Processes an [Approve](enum.TokenInstruction.html) instruction. @@ -254,36 +263,37 @@ impl Processor { let owner_info = next_account_info(account_info_iter)?; let mut source_data = source_account_info.data.borrow_mut(); - let mut source_account: &mut Account = state::unpack(&mut source_data)?; - - if source_account.is_frozen() { - return Err(TokenError::AccountFrozen.into()); - } - - if let Some((mint_account_info, expected_decimals)) = expected_mint_info { - if source_account.mint != *mint_account_info.key { - return Err(TokenError::MintMismatch.into()); + Account::unpack_mut(&mut source_data, &mut |source_account: &mut Account| { + if source_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); } - let mut mint_info_data = mint_account_info.data.borrow_mut(); - let mint: &Mint = state::unpack_unchecked(&mut mint_info_data)?; + if let Some((mint_account_info, expected_decimals)) = expected_mint_info { + if source_account.mint != *mint_account_info.key { + return Err(TokenError::MintMismatch.into()); + } - if expected_decimals != mint.decimals { - return Err(TokenError::MintDecimalsMismatch.into()); + let mut mint_info_data = mint_account_info.data.borrow_mut(); + Mint::unpack_mut(&mut mint_info_data, &mut |mint: &mut Mint| { + if expected_decimals != mint.decimals { + return Err(TokenError::MintDecimalsMismatch.into()); + } + Ok(()) + })?; } - } - Self::validate_owner( - program_id, - &source_account.owner, - owner_info, - account_info_iter.as_slice(), - )?; + Self::validate_owner( + program_id, + &source_account.owner, + owner_info, + account_info_iter.as_slice(), + )?; - source_account.delegate = COption::Some(*delegate_info.key); - source_account.delegated_amount = amount; + source_account.delegate = COption::Some(*delegate_info.key); + source_account.delegated_amount = amount; - Ok(()) + Ok(()) + }) } /// Processes an [Revoke](enum.TokenInstruction.html) instruction. @@ -292,24 +302,25 @@ impl Processor { let source_account_info = next_account_info(account_info_iter)?; let mut source_data = source_account_info.data.borrow_mut(); - let mut source_account: &mut Account = state::unpack(&mut source_data)?; - let owner_info = next_account_info(account_info_iter)?; + Account::unpack_mut(&mut source_data, &mut |source_account: &mut Account| { + let owner_info = next_account_info(account_info_iter)?; - if source_account.is_frozen() { - return Err(TokenError::AccountFrozen.into()); - } + if source_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } - Self::validate_owner( - program_id, - &source_account.owner, - owner_info, - account_info_iter.as_slice(), - )?; + Self::validate_owner( + program_id, + &source_account.owner, + owner_info, + account_info_iter.as_slice(), + )?; - source_account.delegate = COption::None; - source_account.delegated_amount = 0; + source_account.delegate = COption::None; + source_account.delegated_amount = 0; - Ok(()) + Ok(()) + }) } /// Processes a [SetAuthority](enum.TokenInstruction.html) instruction. @@ -325,78 +336,80 @@ impl Processor { if account_info.data_len() == size_of::() { let mut account_data = account_info.data.borrow_mut(); - let mut account: &mut Account = state::unpack(&mut account_data)?; - - if account.is_frozen() { - return Err(TokenError::AccountFrozen.into()); - } - - match authority_type { - AuthorityType::AccountHolder => { - Self::validate_owner( - program_id, - &account.owner, - authority_info, - account_info_iter.as_slice(), - )?; + Account::unpack_mut(&mut account_data, &mut |account: &mut Account| { + if account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } - if let COption::Some(authority) = new_authority { - account.owner = authority; - } else { - return Err(TokenError::InvalidInstruction.into()); + match authority_type { + AuthorityType::AccountHolder => { + Self::validate_owner( + program_id, + &account.owner, + authority_info, + account_info_iter.as_slice(), + )?; + + if let COption::Some(authority) = new_authority { + account.owner = authority; + } else { + return Err(TokenError::InvalidInstruction.into()); + } + } + AuthorityType::CloseAccount => { + let authority = account.close_authority.unwrap_or(account.owner); + Self::validate_owner( + program_id, + &authority, + authority_info, + account_info_iter.as_slice(), + )?; + account.close_authority = new_authority; + } + _ => { + return Err(TokenError::AuthorityTypeNotSupported.into()); } } - AuthorityType::CloseAccount => { - let authority = account.close_authority.unwrap_or(account.owner); - Self::validate_owner( - program_id, - &authority, - authority_info, - account_info_iter.as_slice(), - )?; - account.close_authority = new_authority; - } - _ => { - return Err(TokenError::AuthorityTypeNotSupported.into()); - } - } + Ok(()) + })?; } else if account_info.data_len() == size_of::() { - let mut account_data = account_info.data.borrow_mut(); - let mut mint: &mut Mint = state::unpack(&mut account_data)?; - - match authority_type { - AuthorityType::MintTokens => { - // Once a mint's supply is fixed, it cannot be undone by setting a new - // mint_authority - let mint_authority = mint - .mint_authority - .ok_or(Into::::into(TokenError::FixedSupply))?; - Self::validate_owner( - program_id, - &mint_authority, - authority_info, - account_info_iter.as_slice(), - )?; - mint.mint_authority = new_authority; - } - AuthorityType::FreezeAccount => { - // Once a mint's freeze authority is disabled, it cannot be re-enabled by - // setting a new freeze_authority - let freeze_authority = mint - .freeze_authority - .ok_or(Into::::into(TokenError::MintCannotFreeze))?; - Self::validate_owner( - program_id, - &freeze_authority, - authority_info, - account_info_iter.as_slice(), - )?; - mint.freeze_authority = new_authority; - } - _ => { - return Err(TokenError::AuthorityTypeNotSupported.into()); + let mut mint_data = account_info.data.borrow_mut(); + Mint::unpack_mut(&mut mint_data, &mut |mint: &mut Mint| { + match authority_type { + AuthorityType::MintTokens => { + // Once a mint's supply is fixed, it cannot be undone by setting a new + // mint_authority + let mint_authority = mint + .mint_authority + .ok_or(Into::::into(TokenError::FixedSupply))?; + Self::validate_owner( + program_id, + &mint_authority, + authority_info, + account_info_iter.as_slice(), + )?; + mint.mint_authority = new_authority; + } + AuthorityType::FreezeAccount => { + // Once a mint's freeze authority is disabled, it cannot be re-enabled by + // setting a new freeze_authority + let freeze_authority = mint + .freeze_authority + .ok_or(Into::::into(TokenError::MintCannotFreeze))?; + Self::validate_owner( + program_id, + &freeze_authority, + authority_info, + account_info_iter.as_slice(), + )?; + mint.freeze_authority = new_authority; + } + _ => { + return Err(TokenError::AuthorityTypeNotSupported.into()); + } } - } + Ok(()) + })?; } else { return Err(ProgramError::InvalidArgument); } @@ -417,53 +430,49 @@ impl Processor { let owner_info = next_account_info(account_info_iter)?; let mut dest_account_data = dest_account_info.data.borrow_mut(); - let mut dest_account: &mut Account = state::unpack(&mut dest_account_data)?; - - if dest_account.is_frozen() { - return Err(TokenError::AccountFrozen.into()); - } - - if dest_account.is_native() { - return Err(TokenError::NativeNotSupported.into()); - } - if mint_info.key != &dest_account.mint { - return Err(TokenError::MintMismatch.into()); - } - - let mut mint_info_data = mint_info.data.borrow_mut(); - let mint: &mut Mint = state::unpack(&mut mint_info_data)?; - - if let Some(expected_decimals) = expected_decimals { - if expected_decimals != mint.decimals { - return Err(TokenError::MintDecimalsMismatch.into()); + Account::unpack_mut(&mut dest_account_data, &mut |dest_account: &mut Account| { + if dest_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); } - } - match mint.mint_authority { - COption::Some(mint_authority) => { - Self::validate_owner( - program_id, - &mint_authority, - owner_info, - account_info_iter.as_slice(), - )?; + if dest_account.is_native() { + return Err(TokenError::NativeNotSupported.into()); } - COption::None => { - return Err(TokenError::FixedSupply.into()); + if mint_info.key != &dest_account.mint { + return Err(TokenError::MintMismatch.into()); } - } - dest_account.amount = dest_account - .amount - .checked_add(amount) - .ok_or(TokenError::Overflow)?; + let mut mint_info_data = mint_info.data.borrow_mut(); + Mint::unpack_mut(&mut mint_info_data, &mut |mint: &mut Mint| { + if let Some(expected_decimals) = expected_decimals { + if expected_decimals != mint.decimals { + return Err(TokenError::MintDecimalsMismatch.into()); + } + } + + match mint.mint_authority { + COption::Some(mint_authority) => Self::validate_owner( + program_id, + &mint_authority, + owner_info, + account_info_iter.as_slice(), + )?, + COption::None => return Err(TokenError::FixedSupply.into()), + } - mint.supply = mint - .supply - .checked_add(amount) - .ok_or(TokenError::Overflow)?; + dest_account.amount = dest_account + .amount + .checked_add(amount) + .ok_or(TokenError::Overflow)?; - Ok(()) + mint.supply = mint + .supply + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + + Ok(()) + }) + }) } /// Processes a [Burn](enum.TokenInstruction.html) instruction. @@ -480,68 +489,68 @@ impl Processor { let authority_info = next_account_info(account_info_iter)?; let mut mint_data = mint_info.data.borrow_mut(); - let mint: &mut Mint = state::unpack(&mut mint_data)?; - let mut source_data = source_account_info.data.borrow_mut(); - let source_account: &mut Account = state::unpack(&mut source_data)?; - - if source_account.is_native() { - return Err(TokenError::NativeNotSupported.into()); - } - if mint_info.key != &source_account.mint { - return Err(TokenError::MintMismatch.into()); - } - if source_account.amount < amount { - return Err(TokenError::InsufficientFunds.into()); - } - if source_account.is_frozen() { - return Err(TokenError::AccountFrozen.into()); - } - - if let Some(expected_decimals) = expected_decimals { - if expected_decimals != mint.decimals { - return Err(TokenError::MintDecimalsMismatch.into()); - } - } + Mint::unpack_mut(&mut mint_data, &mut |mint: &mut Mint| { + Account::unpack_mut(&mut source_data, &mut |source_account: &mut Account| { + if source_account.is_native() { + return Err(TokenError::NativeNotSupported.into()); + } + if mint_info.key != &source_account.mint { + return Err(TokenError::MintMismatch.into()); + } + if source_account.amount < amount { + return Err(TokenError::InsufficientFunds.into()); + } + if source_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } - match source_account.delegate { - COption::Some(ref delegate) if authority_info.key == delegate => { - Self::validate_owner( - program_id, - delegate, - authority_info, - account_info_iter.as_slice(), - )?; + if let Some(expected_decimals) = expected_decimals { + if expected_decimals != mint.decimals { + return Err(TokenError::MintDecimalsMismatch.into()); + } + } - if source_account.delegated_amount < amount { - return Err(TokenError::InsufficientFunds.into()); + match source_account.delegate { + COption::Some(ref delegate) if authority_info.key == delegate => { + Self::validate_owner( + program_id, + delegate, + authority_info, + account_info_iter.as_slice(), + )?; + + if source_account.delegated_amount < amount { + return Err(TokenError::InsufficientFunds.into()); + } + source_account.delegated_amount = source_account + .delegated_amount + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + if source_account.delegated_amount == 0 { + source_account.delegate = COption::None; + } + } + _ => Self::validate_owner( + program_id, + &source_account.owner, + authority_info, + account_info_iter.as_slice(), + )?, } - source_account.delegated_amount = source_account - .delegated_amount + + source_account.amount = source_account + .amount + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + mint.supply = mint + .supply .checked_sub(amount) .ok_or(TokenError::Overflow)?; - if source_account.delegated_amount == 0 { - source_account.delegate = COption::None; - } - } - _ => Self::validate_owner( - program_id, - &source_account.owner, - authority_info, - account_info_iter.as_slice(), - )?, - } - - source_account.amount = source_account - .amount - .checked_sub(amount) - .ok_or(TokenError::Overflow)?; - mint.supply = mint - .supply - .checked_sub(amount) - .ok_or(TokenError::Overflow)?; - Ok(()) + Ok(()) + }) + }) } /// Processes a [CloseAccount](enum.TokenInstruction.html) instruction. @@ -552,31 +561,31 @@ impl Processor { let authority_info = next_account_info(account_info_iter)?; let mut source_data = source_account_info.data.borrow_mut(); - let source_account: &mut Account = state::unpack(&mut source_data)?; - - if !source_account.is_native() && source_account.amount != 0 { - return Err(TokenError::NonNativeHasBalance.into()); - } + Account::unpack_mut(&mut source_data, &mut |source_account: &mut Account| { + if !source_account.is_native() && source_account.amount != 0 { + return Err(TokenError::NonNativeHasBalance.into()); + } - let authority = source_account - .close_authority - .unwrap_or(source_account.owner); - Self::validate_owner( - program_id, - &authority, - authority_info, - account_info_iter.as_slice(), - )?; + let authority = source_account + .close_authority + .unwrap_or(source_account.owner); + Self::validate_owner( + program_id, + &authority, + authority_info, + account_info_iter.as_slice(), + )?; - let dest_starting_lamports = dest_account_info.lamports(); - **dest_account_info.lamports.borrow_mut() = dest_starting_lamports - .checked_add(source_account_info.lamports()) - .ok_or(TokenError::Overflow)?; + let dest_starting_lamports = dest_account_info.lamports(); + **dest_account_info.lamports.borrow_mut() = dest_starting_lamports + .checked_add(source_account_info.lamports()) + .ok_or(TokenError::Overflow)?; - **source_account_info.lamports.borrow_mut() = 0; - source_account.amount = 0; + **source_account_info.lamports.borrow_mut() = 0; + source_account.amount = 0; - Ok(()) + Ok(()) + }) } /// Processes a [FreezeAccount](enum.TokenInstruction.html) or a @@ -592,42 +601,39 @@ impl Processor { let authority_info = next_account_info(account_info_iter)?; let mut source_data = source_account_info.data.borrow_mut(); - let source_account: &mut Account = state::unpack(&mut source_data)?; - - if source_account.is_native() { - return Err(TokenError::NativeNotSupported.into()); - } - if mint_info.key != &source_account.mint { - return Err(TokenError::MintMismatch.into()); - } - if freeze && source_account.is_frozen() || !freeze && !source_account.is_frozen() { - return Err(TokenError::InvalidState.into()); - } - - let mut mint_info_data = mint_info.data.borrow_mut(); - let mint: &mut Mint = state::unpack(&mut mint_info_data)?; - - match mint.freeze_authority { - COption::Some(authority) => { - Self::validate_owner( - program_id, - &authority, - authority_info, - account_info_iter.as_slice(), - )?; + Account::unpack_mut(&mut source_data, &mut |source_account: &mut Account| { + if source_account.is_native() { + return Err(TokenError::NativeNotSupported.into()); } - COption::None => { - return Err(TokenError::MintCannotFreeze.into()); + if mint_info.key != &source_account.mint { + return Err(TokenError::MintMismatch.into()); + } + if freeze && source_account.is_frozen() || !freeze && !source_account.is_frozen() { + return Err(TokenError::InvalidState.into()); } - } - - source_account.state = if freeze { - AccountState::Frozen - } else { - AccountState::Initialized - }; - Ok(()) + let mut mint_data = mint_info.data.borrow_mut(); + Mint::unpack_mut( + &mut mint_data, + &mut |mint: &mut Mint| match mint.freeze_authority { + COption::Some(authority) => Self::validate_owner( + program_id, + &authority, + authority_info, + account_info_iter.as_slice(), + ), + COption::None => Err(TokenError::MintCannotFreeze.into()), + }, + )?; + + source_account.state = if freeze { + AccountState::Frozen + } else { + AccountState::Initialized + }; + + Ok(()) + }) } /// Processes an [Instruction](enum.Instruction.html). @@ -723,19 +729,21 @@ impl Processor { && owner_account_info.data_len() == std::mem::size_of::() { let mut owner_data = owner_account_info.data.borrow_mut(); - let multisig: &mut Multisig = state::unpack(&mut owner_data)?; - let mut num_signers = 0; - for signer in signers.iter() { - if multisig.signers[0..multisig.n as usize].contains(signer.key) { - if !signer.is_signer { - return Err(ProgramError::MissingRequiredSignature); + Multisig::unpack_mut(&mut owner_data, &mut |multisig: &mut Multisig| { + let mut num_signers = 0; + for signer in signers.iter() { + if multisig.signers[0..multisig.n as usize].contains(signer.key) { + if !signer.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + num_signers += 1; } - num_signers += 1; } - } - if num_signers < multisig.m { - return Err(ProgramError::MissingRequiredSignature); - } + if num_signers < multisig.m { + return Err(ProgramError::MissingRequiredSignature); + } + Ok(()) + })?; } else if !owner_account_info.is_signer { return Err(ProgramError::MissingRequiredSignature); } @@ -904,8 +912,11 @@ mod tests { vec![&mut mint2_account, &mut rent_sysvar], ) .unwrap(); - let mint2: &mut Mint = state::unpack(&mut mint2_account.data).unwrap(); - assert_eq!(mint2.freeze_authority, COption::Some(owner_key)); + Mint::unpack_unchecked_mut(&mut mint2_account.data, &mut |mint: &mut Mint| { + assert_eq!(mint.freeze_authority, COption::Some(owner_key)); + Ok(()) + }) + .unwrap(); } #[test] @@ -1065,8 +1076,11 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack(&mut mismatch_account.data).unwrap(); - account.mint = mint2_key; + Account::unpack_unchecked_mut(&mut mismatch_account.data, &mut |account: &mut Account| { + account.mint = mint2_key; + Ok(()) + }) + .unwrap(); // mint to account do_process_instruction( @@ -1292,8 +1306,11 @@ mod tests { ) .unwrap() } - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.amount, 1000); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 1000); + Ok(()) + }) + .unwrap(); // insufficient funds assert_eq!( @@ -1447,17 +1464,20 @@ mod tests { vec![&mut mint_account, &mut rent_sysvar], ) .unwrap(); - let mint: &mut Mint = state::unpack(&mut mint_account.data).unwrap(); - assert_eq!( - *mint, - Mint { - mint_authority: COption::Some(owner_key), - supply: 0, - decimals, - is_initialized: true, - freeze_authority: COption::None, - } - ); + Mint::unpack_unchecked_mut(&mut mint_account.data, &mut |mint: &mut Mint| { + assert_eq!( + *mint, + Mint { + mint_authority: COption::Some(owner_key), + supply: 0, + decimals, + is_initialized: true, + freeze_authority: COption::None, + } + ); + Ok(()) + }) + .unwrap(); // create account do_process_instruction( @@ -1477,6 +1497,12 @@ mod tests { vec![&mut mint_account, &mut account_account, &mut owner_account], ) .unwrap(); + Mint::unpack_unchecked_mut(&mut mint_account.data, &mut |_| Ok(())).unwrap(); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 42); + Ok(()) + }) + .unwrap(); // mint to 2, with incorrect decimals assert_eq!( @@ -1496,9 +1522,12 @@ mod tests { ) ); - let _: &mut Mint = state::unpack(&mut mint_account.data).unwrap(); - let dest_account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(dest_account.amount, 42); + Mint::unpack_unchecked_mut(&mut mint_account.data, &mut |_| Ok(())).unwrap(); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 42); + Ok(()) + }) + .unwrap(); // mint to 2 do_process_instruction( @@ -1515,10 +1544,12 @@ mod tests { vec![&mut mint_account, &mut account_account, &mut owner_account], ) .unwrap(); - - let _: &mut Mint = state::unpack(&mut mint_account.data).unwrap(); - let dest_account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(dest_account.amount, 84); + Mint::unpack_unchecked_mut(&mut mint_account.data, &mut |_| Ok(())).unwrap(); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 84); + Ok(()) + }) + .unwrap(); } #[test] @@ -2135,8 +2166,11 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack(&mut mismatch_account.data).unwrap(); - account.mint = mint2_key; + Account::unpack_unchecked_mut(&mut mismatch_account.data, &mut |account: &mut Account| { + account.mint = mint2_key; + Ok(()) + }) + .unwrap(); // mint to do_process_instruction( @@ -2145,10 +2179,16 @@ mod tests { ) .unwrap(); - let mint: &mut Mint = state::unpack(&mut mint_account.data).unwrap(); - assert_eq!(mint.supply, 42); - let dest_account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(dest_account.amount, 42); + Mint::unpack_unchecked_mut(&mut mint_account.data, &mut |mint: &mut Mint| { + assert_eq!(mint.supply, 42); + Ok(()) + }) + .unwrap(); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 42); + Ok(()) + }) + .unwrap(); // mint to another account to test supply accumulation do_process_instruction( @@ -2157,10 +2197,16 @@ mod tests { ) .unwrap(); - let mint: &mut Mint = state::unpack(&mut mint_account.data).unwrap(); - assert_eq!(mint.supply, 84); - let dest_account: &mut Account = state::unpack(&mut account2_account.data).unwrap(); - assert_eq!(dest_account.amount, 42); + Mint::unpack_unchecked_mut(&mut mint_account.data, &mut |mint: &mut Mint| { + assert_eq!(mint.supply, 84); + Ok(()) + }) + .unwrap(); + Account::unpack_unchecked_mut(&mut account2_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 42); + Ok(()) + }) + .unwrap(); // missing signer let mut instruction = @@ -2321,8 +2367,11 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack(&mut mismatch_account.data).unwrap(); - account.mint = mint2_key; + Account::unpack_unchecked_mut(&mut mismatch_account.data, &mut |account: &mut Account| { + account.mint = mint2_key; + Ok(()) + }) + .unwrap(); // mint to account do_process_instruction( @@ -2388,10 +2437,17 @@ mod tests { ) .unwrap(); - let mint: &mut Mint = state::unpack(&mut mint_account.data).unwrap(); - assert_eq!(mint.supply, 1000 - 42); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.amount, 1000 - 42); + Mint::unpack_unchecked_mut(&mut mint_account.data, &mut |mint: &mut Mint| { + assert_eq!(mint.supply, 1000 - 42); + + Ok(()) + }) + .unwrap(); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 1000 - 42); + Ok(()) + }) + .unwrap(); // insufficient funds assert_eq!( @@ -2458,10 +2514,16 @@ mod tests { .unwrap(); // match - let mint: &mut Mint = state::unpack(&mut mint_account.data).unwrap(); - assert_eq!(mint.supply, 1000 - 42 - 84); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.amount, 1000 - 42 - 84); + Mint::unpack_unchecked_mut(&mut mint_account.data, &mut |mint: &mut Mint| { + assert_eq!(mint.supply, 1000 - 42 - 84); + Ok(()) + }) + .unwrap(); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 1000 - 42 - 84); + Ok(()) + }) + .unwrap(); // insufficient funds approved via delegate assert_eq!( @@ -2908,11 +2970,14 @@ mod tests { } let mut lamports = 0; let mut data = vec![0; size_of::()]; - let mut multisig: &mut Multisig = state::unpack_unchecked(&mut data).unwrap(); - multisig.m = MAX_SIGNERS as u8; - multisig.n = MAX_SIGNERS as u8; - multisig.signers = signer_keys; - multisig.is_initialized = true; + Multisig::unpack_unchecked_mut(&mut data, &mut |multisig: &mut Multisig| { + multisig.m = MAX_SIGNERS as u8; + multisig.n = MAX_SIGNERS as u8; + multisig.signers = signer_keys; + multisig.is_initialized = true; + Ok(()) + }) + .unwrap(); let owner_account_info = AccountInfo::new( &owner_key, false, @@ -2930,17 +2995,23 @@ mod tests { // 1 of 11 { let mut data_ref_mut = owner_account_info.data.borrow_mut(); - let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap(); - multisig.m = 1; + Multisig::unpack_unchecked_mut(&mut data_ref_mut, &mut |multisig: &mut Multisig| { + multisig.m = 1; + Ok(()) + }) + .unwrap(); } Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers).unwrap(); // 2:1 { let mut data_ref_mut = owner_account_info.data.borrow_mut(); - let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap(); - multisig.m = 2; - multisig.n = 1; + Multisig::unpack_unchecked_mut(&mut data_ref_mut, &mut |multisig: &mut Multisig| { + multisig.m = 2; + multisig.n = 1; + Ok(()) + }) + .unwrap(); } assert_eq!( Err(ProgramError::MissingRequiredSignature), @@ -2950,18 +3021,24 @@ mod tests { // 0:11 { let mut data_ref_mut = owner_account_info.data.borrow_mut(); - let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap(); - multisig.m = 0; - multisig.n = 11; + Multisig::unpack_unchecked_mut(&mut data_ref_mut, &mut |multisig: &mut Multisig| { + multisig.m = 0; + multisig.n = 11; + Ok(()) + }) + .unwrap(); } Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers).unwrap(); // 2:11 but 0 provided { let mut data_ref_mut = owner_account_info.data.borrow_mut(); - let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap(); - multisig.m = 2; - multisig.n = 11; + Multisig::unpack_unchecked_mut(&mut data_ref_mut, &mut |multisig: &mut Multisig| { + multisig.m = 2; + multisig.n = 11; + Ok(()) + }) + .unwrap(); } assert_eq!( Err(ProgramError::MissingRequiredSignature), @@ -2970,9 +3047,12 @@ mod tests { // 2:11 but 1 provided { let mut data_ref_mut = owner_account_info.data.borrow_mut(); - let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap(); - multisig.m = 2; - multisig.n = 11; + Multisig::unpack_unchecked_mut(&mut data_ref_mut, &mut |multisig: &mut Multisig| { + multisig.m = 2; + multisig.n = 11; + Ok(()) + }) + .unwrap(); } assert_eq!( Err(ProgramError::MissingRequiredSignature), @@ -2982,9 +3062,12 @@ mod tests { // 2:11, 2 from middle provided { let mut data_ref_mut = owner_account_info.data.borrow_mut(); - let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap(); - multisig.m = 2; - multisig.n = 11; + Multisig::unpack_unchecked_mut(&mut data_ref_mut, &mut |multisig: &mut Multisig| { + multisig.m = 2; + multisig.n = 11; + Ok(()) + }) + .unwrap(); } Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers[5..7]) .unwrap(); @@ -2992,9 +3075,12 @@ mod tests { // 11:11, one is not a signer { let mut data_ref_mut = owner_account_info.data.borrow_mut(); - let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap(); - multisig.m = 2; - multisig.n = 11; + Multisig::unpack_unchecked_mut(&mut data_ref_mut, &mut |multisig: &mut Multisig| { + multisig.m = 2; // TODO 11? + multisig.n = 11; + Ok(()) + }) + .unwrap(); } signers[5].is_signer = false; assert_eq!( @@ -3067,8 +3153,11 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.amount, 42); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 42); + Ok(()) + }) + .unwrap(); // initialize native account do_process_instruction( @@ -3087,9 +3176,12 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack(&mut account2_account.data).unwrap(); - assert!(account.is_native()); - assert_eq!(account.amount, 42); + Account::unpack_unchecked_mut(&mut account2_account.data, &mut |account: &mut Account| { + assert!(account.is_native()); + assert_eq!(account.amount, 42); + Ok(()) + }) + .unwrap(); // close non-native account with balance assert_eq!( @@ -3135,10 +3227,13 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack_unchecked(&mut account_account.data).unwrap(); assert_eq!(account_account.lamports, 0); - assert_eq!(account.amount, 0); assert_eq!(account3_account.lamports, 2 * account_minimum_balance()); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 0); + Ok(()) + }) + .unwrap(); // fund and initialize new non-native account to test close authority let account_key = pubkey_rand(); @@ -3197,8 +3292,12 @@ mod tests { ) .unwrap(); assert_eq!(account_account.lamports, 0); - assert_eq!(account.amount, 0); assert_eq!(account3_account.lamports, 2 * account_minimum_balance() + 2); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, 0); + Ok(()) + }) + .unwrap(); // close native account do_process_instruction( @@ -3210,14 +3309,17 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack_unchecked(&mut account2_account.data).unwrap(); - assert!(account.is_native()); - assert_eq!(account_account.lamports, 0); - assert_eq!(account.amount, 0); - assert_eq!( - account3_account.lamports, - 3 * account_minimum_balance() + 2 + 42 - ); + Account::unpack_unchecked_mut(&mut account2_account.data, &mut |account: &mut Account| { + assert!(account.is_native()); + assert_eq!(account_account.lamports, 0); + assert_eq!(account.amount, 0); + assert_eq!( + account3_account.lamports, + 3 * account_minimum_balance() + 2 + 42 + ); + Ok(()) + }) + .unwrap(); } #[test] @@ -3257,9 +3359,12 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert!(account.is_native()); - assert_eq!(account.amount, 40); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert!(account.is_native()); + assert_eq!(account.amount, 40); + Ok(()) + }) + .unwrap(); // initialize native account do_process_instruction( @@ -3278,9 +3383,12 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack(&mut account2_account.data).unwrap(); - assert!(account.is_native()); - assert_eq!(account.amount, 0); + Account::unpack_unchecked_mut(&mut account2_account.data, &mut |account: &mut Account| { + assert!(account.is_native()); + assert_eq!(account.amount, 0); + Ok(()) + }) + .unwrap(); // mint_to unsupported assert_eq!( @@ -3368,15 +3476,20 @@ mod tests { ], ) .unwrap(); - - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert!(account.is_native()); assert_eq!(account_account.lamports, account_minimum_balance()); - assert_eq!(account.amount, 0); - let account: &mut Account = state::unpack(&mut account2_account.data).unwrap(); - assert!(account.is_native()); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert!(account.is_native()); + assert_eq!(account.amount, 0); + Ok(()) + }) + .unwrap(); assert_eq!(account2_account.lamports, account_minimum_balance() + 40); - assert_eq!(account.amount, 40); + Account::unpack_unchecked_mut(&mut account2_account.data, &mut |account: &mut Account| { + assert!(account.is_native()); + assert_eq!(account.amount, 40); + Ok(()) + }) + .unwrap(); // close native account do_process_instruction( @@ -3388,11 +3501,14 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack_unchecked(&mut account_account.data).unwrap(); - assert!(account.is_native()); assert_eq!(account_account.lamports, 0); - assert_eq!(account.amount, 0); assert_eq!(account3_account.lamports, 2 * account_minimum_balance()); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert!(account.is_native()); + assert_eq!(account.amount, 0); + Ok(()) + }) + .unwrap(); } #[test] @@ -3464,8 +3580,11 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.amount, u64::MAX); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, u64::MAX); + Ok(()) + }) + .unwrap(); // attempt to mint one more to account assert_eq!( @@ -3487,8 +3606,11 @@ mod tests { ], ) ); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.amount, u64::MAX); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, u64::MAX); + Ok(()) + }) + .unwrap(); // atttempt to mint one more to the other account assert_eq!( @@ -3517,8 +3639,11 @@ mod tests { vec![&mut account_account, &mut mint_account, &mut owner_account], ) .unwrap(); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.amount, u64::MAX - 100); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, u64::MAX - 100); + Ok(()) + }) + .unwrap(); do_process_instruction( mint_to( @@ -3537,12 +3662,18 @@ mod tests { ], ) .unwrap(); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.amount, u64::MAX); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.amount, u64::MAX); + Ok(()) + }) + .unwrap(); // manipulate account balance to attempt overflow transfer - let account: &mut Account = state::unpack(&mut account2_account.data).unwrap(); - account.amount = 1; + Account::unpack_unchecked_mut(&mut account2_account.data, &mut |account: &mut Account| { + account.amount = 1; + Ok(()) + }) + .unwrap(); assert_eq!( Err(TokenError::Overflow.into()), @@ -3620,8 +3751,11 @@ mod tests { .unwrap(); // no transfer if either account is frozen - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - account.state = AccountState::Frozen; + Account::unpack_unchecked_mut(&mut account2_account.data, &mut |account: &mut Account| { + account.state = AccountState::Frozen; + Ok(()) + }) + .unwrap(); assert_eq!( Err(TokenError::AccountFrozen.into()), do_process_instruction( @@ -3642,10 +3776,16 @@ mod tests { ) ); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - account.state = AccountState::Initialized; - let account2: &mut Account = state::unpack(&mut account2_account.data).unwrap(); - account2.state = AccountState::Frozen; + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + account.state = AccountState::Initialized; + Ok(()) + }) + .unwrap(); + Account::unpack_unchecked_mut(&mut account2_account.data, &mut |account: &mut Account| { + account.state = AccountState::Frozen; + Ok(()) + }) + .unwrap(); assert_eq!( Err(TokenError::AccountFrozen.into()), do_process_instruction( @@ -3667,8 +3807,11 @@ mod tests { ); // no approve if account is frozen - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - account.state = AccountState::Frozen; + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + account.state = AccountState::Frozen; + Ok(()) + }) + .unwrap(); let delegate_key = pubkey_rand(); let mut delegate_account = SolanaAccount::default(); assert_eq!( @@ -3692,9 +3835,12 @@ mod tests { ); // no revoke if account is frozen - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - account.delegate = COption::Some(delegate_key); - account.delegated_amount = 100; + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + account.delegate = COption::Some(delegate_key); + account.delegated_amount = 100; + Ok(()) + }) + .unwrap(); assert_eq!( Err(TokenError::AccountFrozen.into()), do_process_instruction( @@ -3793,8 +3939,11 @@ mod tests { ); // missing freeze_authority - let mint: &mut Mint = state::unpack(&mut mint_account.data).unwrap(); - mint.freeze_authority = COption::Some(owner_key); + Mint::unpack_unchecked_mut(&mut mint_account.data, &mut |mint: &mut Mint| { + mint.freeze_authority = COption::Some(owner_key); + Ok(()) + }) + .unwrap(); assert_eq!( Err(TokenError::OwnerMismatch.into()), do_process_instruction( @@ -3818,8 +3967,11 @@ mod tests { vec![&mut account_account, &mut mint_account, &mut owner_account], ) .unwrap(); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.state, AccountState::Frozen); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.state, AccountState::Frozen); + Ok(()) + }) + .unwrap(); // check explicit freeze assert_eq!( @@ -3845,7 +3997,10 @@ mod tests { vec![&mut account_account, &mut mint_account, &mut owner_account], ) .unwrap(); - let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - assert_eq!(account.state, AccountState::Initialized); + Account::unpack_unchecked_mut(&mut account_account.data, &mut |account: &mut Account| { + assert_eq!(account.state, AccountState::Initialized); + Ok(()) + }) + .unwrap(); } } diff --git a/token/program/src/state.rs b/token/program/src/state.rs index 5b3981c953fd6e..08de37e349c121 100644 --- a/token/program/src/state.rs +++ b/token/program/src/state.rs @@ -1,8 +1,18 @@ //! State transition types -use crate::{error::TokenError, instruction::MAX_SIGNERS, option::COption}; +use crate::{ + instruction::MAX_SIGNERS, + option::COption, + pack::{IsInitialized, Pack, Sealed}, +}; +use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; +use num_enum::TryFromPrimitive; use solana_sdk::{program_error::ProgramError, pubkey::Pubkey}; -use std::mem::size_of; + +impl Sealed for Option {} +impl Sealed for Mint {} +impl Sealed for Account {} +impl Sealed for Multisig {} /// Mint data. #[repr(C)] @@ -26,6 +36,52 @@ impl IsInitialized for Mint { self.is_initialized } } +impl Pack for Mint { + const LEN: usize = 82; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, 82]; + let (mint_authority, supply, decimals, is_initialized, freeze_authority) = + array_refs![src, 36, 8, 1, 1, 36]; + let mint_authority = unpack_coption_key(mint_authority)?; + let supply = u64::from_le_bytes(*supply); + let decimals = decimals[0]; + let is_initialized = match is_initialized { + [0] => false, + [1] => true, + _ => return Err(ProgramError::InvalidAccountData), + }; + let freeze_authority = unpack_coption_key(freeze_authority)?; + Ok(Mint { + mint_authority, + supply, + decimals, + is_initialized, + freeze_authority, + }) + } + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, 82]; + let ( + mint_authority_dst, + supply_dst, + decimals_dst, + is_initialized_dst, + freeze_authority_dst, + ) = mut_array_refs![dst, 36, 8, 1, 1, 36]; + let &Mint { + ref mint_authority, + supply, + decimals, + is_initialized, + ref freeze_authority, + } = self; + pack_coption_key(mint_authority, mint_authority_dst); + *supply_dst = supply.to_le_bytes(); + decimals_dst[0] = decimals; + is_initialized_dst[0] = is_initialized as u8; + pack_coption_key(freeze_authority, freeze_authority_dst); + } +} /// Account data. #[repr(C)] @@ -66,10 +122,60 @@ impl IsInitialized for Account { self.state != AccountState::Uninitialized } } +impl Pack for Account { + const LEN: usize = 165; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, 165]; + let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) = + array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36]; + Ok(Account { + mint: Pubkey::new_from_array(*mint), + owner: Pubkey::new_from_array(*owner), + amount: u64::from_le_bytes(*amount), + delegate: unpack_coption_key(delegate)?, + state: AccountState::try_from_primitive(state[0]) + .or(Err(ProgramError::InvalidAccountData))?, + is_native: unpack_coption_u64(is_native)?, + delegated_amount: u64::from_le_bytes(*delegated_amount), + close_authority: unpack_coption_key(close_authority)?, + }) + } + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, 165]; + let ( + mint_dst, + owner_dst, + amount_dst, + delegate_dst, + state_dst, + is_native_dst, + delegated_amount_dst, + close_authority_dst, + ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36]; + let &Account { + ref mint, + ref owner, + amount, + ref delegate, + state, + ref is_native, + delegated_amount, + ref close_authority, + } = self; + mint_dst.copy_from_slice(mint.as_ref()); + owner_dst.copy_from_slice(owner.as_ref()); + *amount_dst = amount.to_le_bytes(); + pack_coption_key(delegate, delegate_dst); + state_dst[0] = state as u8; + pack_coption_u64(is_native, is_native_dst); + *delegated_amount_dst = delegated_amount.to_le_bytes(); + pack_coption_key(close_authority, close_authority_dst); + } +} /// Account state. #[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)] pub enum AccountState { /// Account is not yet initialized Uninitialized, @@ -105,26 +211,79 @@ impl IsInitialized for Multisig { self.is_initialized } } - -/// Check is a token state is initialized -pub trait IsInitialized { - /// Is initialized - fn is_initialized(&self) -> bool; +impl Pack for Multisig { + const LEN: usize = 355; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, 355]; + #[allow(clippy::ptr_offset_with_cast)] + let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS]; + let mut result = Multisig { + m: m[0], + n: n[0], + is_initialized: match is_initialized { + [0] => false, + [1] => true, + _ => return Err(ProgramError::InvalidAccountData), + }, + signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS], + }; + for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) { + *dst = Pubkey::new(src); + } + Ok(result) + } + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, 355]; + #[allow(clippy::ptr_offset_with_cast)] + let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS]; + *m = [self.m]; + *n = [self.n]; + *is_initialized = [self.is_initialized as u8]; + for (i, src) in self.signers.iter().enumerate() { + let dst_array = array_mut_ref![signers_flat, 32 * i, 32]; + dst_array.copy_from_slice(src.as_ref()); + } + } } -/// Unpacks a token state from a bytes buffer while assuring that the state is initialized. -pub fn unpack(input: &mut [u8]) -> Result<&mut T, ProgramError> { - let mut_ref: &mut T = unpack_unchecked(input)?; - if !mut_ref.is_initialized() { - return Err(TokenError::UninitializedState.into()); +// Helpers +fn pack_coption_key(src: &COption, dst: &mut [u8; 36]) { + let (tag, body) = mut_array_refs![dst, 4, 32]; + match src { + COption::Some(key) => { + *tag = [1, 0, 0, 0]; + body.copy_from_slice(key.as_ref()); + } + COption::None => { + *tag = [0; 4]; + } + } +} +fn unpack_coption_key(src: &[u8; 36]) -> Result, ProgramError> { + let (tag, body) = array_refs![src, 4, 32]; + match *tag { + [0, 0, 0, 0] => Ok(COption::None), + [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))), + _ => Err(ProgramError::InvalidAccountData), + } +} +fn pack_coption_u64(src: &COption, dst: &mut [u8; 12]) { + let (tag, body) = mut_array_refs![dst, 4, 8]; + match src { + COption::Some(amount) => { + *tag = [1, 0, 0, 0]; + *body = amount.to_le_bytes(); + } + COption::None => { + *tag = [0; 4]; + } } - Ok(mut_ref) } -/// Unpacks a token state from a bytes buffer without checking that the state is initialized. -pub fn unpack_unchecked(input: &mut [u8]) -> Result<&mut T, ProgramError> { - if input.len() != size_of::() { - return Err(ProgramError::InvalidAccountData); +fn unpack_coption_u64(src: &[u8; 12]) -> Result, ProgramError> { + let (tag, body) = array_refs![src, 4, 8]; + match *tag { + [0, 0, 0, 0] => Ok(COption::None), + [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))), + _ => Err(ProgramError::InvalidAccountData), } - #[allow(clippy::cast_ptr_alignment)] - Ok(unsafe { &mut *(&mut input[0] as *mut u8 as *mut T) }) }