Browse Source

Safer pack/unpack (#349)

* Safer pack/unpack

* fix cli

* clippy

* fix swap

* nit

* clippy

Co-authored-by: Michael Vines <mvines@gmail.com>
Jack May 5 years ago
parent
commit
fe85b19606
7 changed files with 855 additions and 823 deletions
  1. 2 0
      program/Cargo.toml
  2. 165 282
      program/src/instruction.rs
  3. 1 0
      program/src/lib.rs
  4. 0 109
      program/src/option.rs
  5. 76 0
      program/src/pack.rs
  6. 432 412
      program/src/processor.rs
  7. 179 20
      program/src/state.rs

+ 2 - 0
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"}

+ 165 - 282
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<Self, ProgramError> {
-        if input.len() < size_of::<u8>() {
-            return Err(TokenError::InvalidInstruction.into());
-        }
-        Ok(match input[0] {
-            0 => {
-                if input.len() < size_of::<u8>() + size_of::<Pubkey>() + size_of::<bool>() {
-                    return Err(TokenError::InvalidInstruction.into());
-                }
-                let mut input_len = 0;
-                input_len += size_of::<u8>();
-
-                let decimals = unsafe { *(&input[input_len] as *const u8) };
-                input_len += size_of::<u8>();
-
-                let mint_authority = unsafe { *(&input[input_len] as *const u8 as *const Pubkey) };
-                input_len += size_of::<Pubkey>();
-
-                let freeze_authority = COption::unpack_or(
-                    input,
-                    &mut input_len,
-                    Into::<ProgramError>::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::<u8>() + size_of::<u8>() {
-                    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::<u8>() + size_of::<u64>() {
-                    return Err(TokenError::InvalidInstruction.into());
-                }
-                #[allow(clippy::cast_ptr_alignment)]
-                let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
-                Self::Transfer { amount }
-            }
-            4 => {
-                if input.len() < size_of::<u8>() + size_of::<u64>() {
-                    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::<u8>()] as *const u8 as *const u64) };
-                Self::Approve { amount }
             }
             5 => Self::Revoke,
             6 => {
-                if input.len() < size_of::<u8>() + size_of::<u8>() {
-                    return Err(TokenError::InvalidInstruction.into());
-                }
-                let mut input_len = 0;
-                input_len += size_of::<u8>();
-                let authority_type = AuthorityType::from(input[1])?;
-                input_len += size_of::<u8>();
-
-                let new_authority = COption::unpack_or(
-                    input,
-                    &mut input_len,
-                    Into::<ProgramError>::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::<u8>() + size_of::<u64>() {
-                    return Err(TokenError::InvalidInstruction.into());
-                }
-                #[allow(clippy::cast_ptr_alignment)]
-                let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
-                Self::MintTo { amount }
-            }
-            8 => {
-                if input.len() < size_of::<u8>() + size_of::<u64>() {
-                    return Err(TokenError::InvalidInstruction.into());
-                }
-                #[allow(clippy::cast_ptr_alignment)]
-                let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
-                Self::Burn { amount }
-            }
             9 => Self::CloseAccount,
             10 => Self::FreezeAccount,
             11 => Self::ThawAccount,
             12 => {
-                if input.len() < size_of::<u8>() + size_of::<u64>() + size_of::<u8>() {
-                    return Err(TokenError::InvalidInstruction.into());
-                }
-                let mut input_len = 0;
-                input_len += size_of::<u8>();
-
-                #[allow(clippy::cast_ptr_alignment)]
-                let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) };
-                input_len += size_of::<u64>();
-
-                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::<u8>() + size_of::<u64>() + size_of::<u8>() {
-                    return Err(TokenError::InvalidInstruction.into());
-                }
-                let mut input_len = 0;
-                input_len += size_of::<u8>();
-
-                #[allow(clippy::cast_ptr_alignment)]
-                let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) };
-                input_len += size_of::<u64>();
-
-                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::<u8>() + size_of::<u64>() + size_of::<u8>() {
-                    return Err(TokenError::InvalidInstruction.into());
-                }
-                let mut input_len = 0;
-                input_len += size_of::<u8>();
-
-                #[allow(clippy::cast_ptr_alignment)]
-                let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) };
-                input_len += size_of::<u64>();
-
-                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::<u8>() + size_of::<u64>() + size_of::<u8>() {
-                    return Err(TokenError::InvalidInstruction.into());
-                }
-                let mut input_len = 0;
-                input_len += size_of::<u8>();
-
-                #[allow(clippy::cast_ptr_alignment)]
-                let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) };
-                input_len += size_of::<u64>();
-
-                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<Vec<u8>, ProgramError> {
-        let mut output = vec![0u8; size_of::<TokenInstruction>()];
-        let mut output_len = 0;
+    pub fn pack(&self) -> Vec<u8> {
+        let mut buf = Vec::with_capacity(size_of::<Self>());
         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::<u8>();
-
-                let value = unsafe { &mut *(&mut output[output_len] as *mut u8) };
-                *value = *decimals;
-                output_len += size_of::<u8>();
-
-                #[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::<Pubkey>();
-
-                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::<u8>();
+            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::<u8>();
-
-                #[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::<u8>();
+            &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::<u8>();
-
-                #[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::<u64>();
+            &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::<u8>();
-
-                #[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::<u64>();
+            &Self::MintTo { amount } => {
+                buf.push(7);
+                buf.extend_from_slice(&amount.to_le_bytes());
             }
-            Self::Revoke => {
-                output[output_len] = 5;
-                output_len += size_of::<u8>();
+            &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::<u8>();
-
-                output[output_len] = authority_type.into();
-                output_len += size_of::<u8>();
-
-                new_authority.pack(&mut output, &mut output_len);
-            }
-            Self::MintTo { amount } => {
-                output[output_len] = 7;
-                output_len += size_of::<u8>();
-
-                #[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::<u64>();
-            }
-            Self::Burn { amount } => {
-                output[output_len] = 8;
-                output_len += size_of::<u8>();
-
-                #[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::<u64>();
+                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::<u8>();
+            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::<u8>();
+            &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::<u8>();
+            &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::<u8>();
-
-                #[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::<u64>();
-
-                let value = unsafe { &mut *(&mut output[output_len] as *mut u8) };
-                *value = *decimals;
-                output_len += size_of::<u8>();
+            &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::<u8>();
-
-                #[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::<u64>();
-
-                let value = unsafe { &mut *(&mut output[output_len] as *mut u8) };
-                *value = *decimals;
-                output_len += size_of::<u8>();
-            }
-            Self::MintTo2 { amount, decimals } => {
-                output[output_len] = 14;
-                output_len += size_of::<u8>();
-
-                #[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::<u64>();
-
-                let value = unsafe { &mut *(&mut output[output_len] as *mut u8) };
-                *value = *decimals;
-                output_len += size_of::<u8>();
-            }
-
-            Self::Burn2 { amount, decimals } => {
-                output[output_len] = 15;
-                output_len += size_of::<u8>();
+        };
+        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::<u64>();
+    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::<u8>();
+    fn unpack_pubkey_option(input: &[u8]) -> Result<(COption<Pubkey>, &[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<Pubkey>, buf: &mut Vec<u8>) {
+        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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
-    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();

+ 1 - 0
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;
 

+ 0 - 109
program/src/option.rs

@@ -676,55 +676,6 @@ impl<T> COption<T> {
     pub fn replace(&mut self, value: T) -> COption<T> {
         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::<u8>();
-
-                #[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::<T>();
-            }
-            COption::None => {
-                output[*cursor] = 0;
-                *cursor += mem::size_of::<u8>();
-            }
-        }
-    }
-
-    /// Unpacks a COption from a compact slice
-    #[inline]
-    pub fn unpack_or<E>(input: &[u8], cursor: &mut usize, error: E) -> Result<COption<T>, E>
-    where
-        T: Copy,
-    {
-        match input[*cursor] {
-            0 => {
-                *cursor += mem::size_of::<u8>();
-                Ok(COption::None)
-            }
-            1 => {
-                *cursor += mem::size_of::<u8>();
-                #[allow(clippy::cast_ptr_alignment)]
-                let result = unsafe { *(&input[*cursor] as *const u8 as *const T) };
-                *cursor += mem::size_of::<T>();
-                Ok(COption::Some(result))
-            }
-            _ => Err(error),
-        }
-    }
 }
 
 impl<T: Copy> COption<&T> {
@@ -1034,7 +985,6 @@ impl<T> Into<Option<T>> for COption<T> {
 #[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::<u8>() + mem::size_of::<Pubkey>();
-        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<Pubkey> = COption::None;
-        let expected_size = mem::size_of::<u8>();
-        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::<u8>() + mem::size_of::<u64>();
-        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<u64> = COption::None;
-        let expected_size = mem::size_of::<u8>();
-        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);
-    }
 }

+ 76 - 0
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<Self, ProgramError>;
+
+    /// Borrow `Self` from `input` for the duration of the call to `f`, but first check that `Self`
+    /// is initialized
+    #[inline(never)]
+    fn unpack_mut<F, U>(input: &mut [u8], f: &mut F) -> Result<U, ProgramError>
+    where
+        F: FnMut(&mut Self) -> Result<U, ProgramError>,
+        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<F, U>(input: &mut [u8], f: &mut F) -> Result<U, ProgramError>
+    where
+        F: FnMut(&mut Self) -> Result<U, ProgramError>,
+    {
+        let mut t = unpack_unchecked(input)?;
+        let u = f(&mut t)?;
+        pack(t, input)?;
+        Ok(u)
+    }
+}
+
+fn pack<T: 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<T: Pack + IsInitialized>(input: &[u8]) -> Result<T, ProgramError> {
+    let value: T = unpack_unchecked(input)?;
+    if value.is_initialized() {
+        Ok(value)
+    } else {
+        Err(TokenError::UninitializedState.into())
+    }
+}
+
+fn unpack_unchecked<T: Pack>(input: &[u8]) -> Result<T, ProgramError> {
+    if input.len() < T::LEN {
+        println!("ilen {:?} tlen {:?}", input.len(), T::LEN);
+        return Err(ProgramError::InvalidAccountData);
+    }
+    Ok(T::unpack_from_slice(input)?)
+}

File diff suppressed because it is too large
+ 432 - 412
program/src/processor.rs


+ 179 - 20
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<Pubkey> {}
+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<Self, ProgramError> {
+        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<Self, ProgramError> {
+        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<Self, ProgramError> {
+        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<T: IsInitialized>(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<Pubkey>, 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<COption<Pubkey>, 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<u64>, 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<T: IsInitialized>(input: &mut [u8]) -> Result<&mut T, ProgramError> {
-    if input.len() != size_of::<T>() {
-        return Err(ProgramError::InvalidAccountData);
+fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, 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) })
 }

Some files were not shown because too many files changed in this diff