Skip to content

Commit

Permalink
Add instruction helpers (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
jackcmay authored Jun 23, 2020
1 parent 437d2d1 commit 87331fd
Show file tree
Hide file tree
Showing 2 changed files with 1,648 additions and 998 deletions.
276 changes: 275 additions & 1 deletion token/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
//! Instruction types
use crate::error::TokenError;
use solana_sdk::{
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
pubkey::Pubkey,
};
use std::mem::size_of;

/// Specifies the financial specifics of a token.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
Expand All @@ -13,7 +21,7 @@ pub struct TokenInfo {
/// Instructions supported by the token program.
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub enum Instruction {
pub enum TokenInstruction {
/// Creates a new token and deposit all the newly minted tokens in an account.
///
/// # Accounts expected by this instruction:
Expand Down Expand Up @@ -75,5 +83,271 @@ pub enum Instruction {
/// 0. `[signer]` Owner of the account to burn from.
/// 1. `[writable]` Account to burn from.
/// 2. `[writable]` Token being burned.
/// 3. Optional: `[writable]` Source account if key 1 is a delegate account.
Burn(u64),
}
impl TokenInstruction {
/// Deserializes a byte buffer into an [TokenInstruction](enum.TokenInstruction.html)
pub fn deserialize(input: &[u8]) -> Result<Self, ProgramError> {
if input.len() < size_of::<TokenInstruction>() {
return Err(ProgramError::InvalidAccountData);
}
Ok(match input[0] {
0 => {
#[allow(clippy::cast_ptr_alignment)]
let info: &TokenInfo = unsafe { &*(&input[1] as *const u8 as *const TokenInfo) };
Self::NewToken(*info)
}
1 => Self::NewAccount,
2 => {
#[allow(clippy::cast_ptr_alignment)]
let amount: &u64 = unsafe { &*(&input[1] as *const u8 as *const u64) };
Self::Transfer(*amount)
}
3 => {
#[allow(clippy::cast_ptr_alignment)]
let amount: &u64 = unsafe { &*(&input[1] as *const u8 as *const u64) };
Self::Approve(*amount)
}
4 => Self::SetOwner,
5 => {
#[allow(clippy::cast_ptr_alignment)]
let amount: &u64 = unsafe { &*(&input[1] as *const u8 as *const u64) };
Self::MintTo(*amount)
}
6 => {
#[allow(clippy::cast_ptr_alignment)]
let amount: &u64 = unsafe { &*(&input[1] as *const u8 as *const u64) };
Self::Burn(*amount)
}
_ => return Err(ProgramError::InvalidAccountData),
})
}

/// Serializes an [TokenInstruction](enum.TokenInstruction.html) into a byte buffer
pub fn serialize(self: &Self) -> Result<Vec<u8>, ProgramError> {
let mut output = vec![0u8; size_of::<TokenInstruction>()];
match self {
Self::NewToken(info) => {
output[0] = 0;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[1] as *mut u8 as *mut TokenInfo) };
*value = *info;
}
Self::NewAccount => output[0] = 1,
Self::Transfer(amount) => {
output[0] = 2;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[1] as *mut u8 as *mut u64) };
*value = *amount;
}
Self::Approve(amount) => {
output[0] = 3;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[1] as *mut u8 as *mut u64) };
*value = *amount;
}
Self::SetOwner => output[0] = 4,
Self::MintTo(amount) => {
output[0] = 5;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[1] as *mut u8 as *mut u64) };
*value = *amount;
}
Self::Burn(amount) => {
output[0] = 6;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[1] as *mut u8 as *mut u64) };
*value = *amount;
}
}
Ok(output)
}
}

/// Creates a 'NewToken' instruction
pub fn new_token(
token_program_id: &Pubkey,
token_pubkey: &Pubkey,
account_pubkey: Option<&Pubkey>,
owner_pubkey: Option<&Pubkey>,
token_info: TokenInfo,
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::NewToken(token_info).serialize()?;

let mut accounts = vec![AccountMeta::new(*token_pubkey, true)];
if token_info.supply != 0 {
match account_pubkey {
Some(pubkey) => accounts.push(AccountMeta::new(*pubkey, false)),
None => {
return Err(ProgramError::NotEnoughAccountKeys);
}
}
}
match owner_pubkey {
Some(pubkey) => accounts.push(AccountMeta::new_readonly(*pubkey, false)),
None => {
if token_info.supply == 0 {
return Err(TokenError::OwnerRequiredIfNoInitialSupply.into());
}
}
}

Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}

/// Creates a `NewAccount` instruction
pub fn new_account(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
token_pubkey: &Pubkey,
source_pubkey: Option<&Pubkey>,
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::NewAccount.serialize()?;

let mut accounts = vec![
AccountMeta::new(*account_pubkey, true),
AccountMeta::new_readonly(*owner_pubkey, false),
AccountMeta::new_readonly(*token_pubkey, false),
];
if let Some(pubkey) = source_pubkey {
accounts.push(AccountMeta::new_readonly(*pubkey, false));
}

Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}

/// Creates a `Transfer` instruction
pub fn transfer(
token_program_id: &Pubkey,
owner_pubkey: &Pubkey,
account_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
source_pubkey: Option<&Pubkey>,
amount: u64,
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::Transfer(amount).serialize()?;

let mut accounts = vec![
AccountMeta::new_readonly(*owner_pubkey, true),
AccountMeta::new(*account_pubkey, false),
AccountMeta::new(*destination_pubkey, false),
];
if let Some(pubkey) = source_pubkey {
accounts.push(AccountMeta::new(*pubkey, false));
}

Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}

/// Creates an `Approve` instruction
pub fn approve(
token_program_id: &Pubkey,
owner_pubkey: &Pubkey,
source_pubkey: &Pubkey,
delegate_pubkey: &Pubkey,
amount: u64,
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::Approve(amount).serialize()?;

let accounts = vec![
AccountMeta::new_readonly(*owner_pubkey, true),
AccountMeta::new_readonly(*source_pubkey, false),
AccountMeta::new(*delegate_pubkey, false),
];

Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}

/// 0. `[signer]` Current owner of the account.
/// 1. `[writable]` account to change the owner of.
/// 2. `[]` New owner of the account.
/// Creates an `SetOwner` instruction
pub fn set_owner(
token_program_id: &Pubkey,
owner_pubkey: &Pubkey,
account_pubkey: &Pubkey,
new_owner_pubkey: &Pubkey,
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::SetOwner.serialize()?;

let accounts = vec![
AccountMeta::new_readonly(*owner_pubkey, true),
AccountMeta::new(*account_pubkey, false),
AccountMeta::new_readonly(*new_owner_pubkey, false),
];

Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}

/// Creates an `MintTo` instruction
pub fn mint_to(
token_program_id: &Pubkey,
owner_pubkey: &Pubkey,
token_pubkey: &Pubkey,
account_pubkey: &Pubkey,
amount: u64,
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::MintTo(amount).serialize()?;

let accounts = vec![
AccountMeta::new_readonly(*owner_pubkey, true),
AccountMeta::new(*token_pubkey, false),
AccountMeta::new(*account_pubkey, false),
];

Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}

/// Creates an `Burn` instruction
pub fn burn(
token_program_id: &Pubkey,
owner_pubkey: &Pubkey,
account_pubkey: &Pubkey,
token_pubkey: &Pubkey,
source_pubkey: Option<&Pubkey>,
amount: u64,
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::Burn(amount).serialize()?;

let mut accounts = vec![
AccountMeta::new_readonly(*owner_pubkey, true),
AccountMeta::new(*account_pubkey, false),
AccountMeta::new(*token_pubkey, false),
];
if let Some(pubkey) = source_pubkey {
accounts.push(AccountMeta::new(*pubkey, false));
}

Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
Loading

0 comments on commit 87331fd

Please sign in to comment.