diff --git a/token-swap/program/src/processor.rs b/token-swap/program/src/processor.rs index 0f0153f1..470dd2b7 100644 --- a/token-swap/program/src/processor.rs +++ b/token-swap/program/src/processor.rs @@ -89,13 +89,20 @@ impl State { token_program_id: &Pubkey, swap: &Pubkey, burn_account: &Pubkey, + mint: &Pubkey, authority: &Pubkey, amount: u64, ) -> Result<(), ProgramError> { let swap_bytes = swap.to_bytes(); let signers = &[&[&swap_bytes[..32]][..]]; - let ix = - spl_token::instruction::burn(token_program_id, burn_account, authority, &[], amount)?; + let ix = spl_token::instruction::burn( + token_program_id, + burn_account, + mint, + authority, + &[], + amount, + )?; invoke_signed(&ix, accounts, signers) } @@ -414,6 +421,7 @@ impl State { token_program_info.key, swap_info.key, source_info.key, + &token_swap.pool_mint, authority_info.key, amount, )?; diff --git a/token/cli/src/main.rs b/token/cli/src/main.rs index 7a67eaeb..4eefc9ee 100644 --- a/token/cli/src/main.rs +++ b/token/cli/src/main.rs @@ -20,7 +20,7 @@ use spl_token::{ self, instruction::*, native_mint, - state::{Account, Mint}, + state::{self, Account, Mint}, }; use std::{mem::size_of, process::exit}; @@ -213,12 +213,20 @@ fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResu .rpc_client .get_token_account_balance_with_commitment(&source, config.commitment_config)? .value; + let source_account = config + .rpc_client + .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 amount = spl_token::ui_amount_to_amount(ui_amount, source_token_balance.decimals); let mut transaction = Transaction::new_with_payer( &[burn( &spl_token::id(), &source, + &mint_pubkey, &config.owner.pubkey(), &[], amount, diff --git a/token/program/inc/token.h b/token/program/inc/token.h index ac8cc98e..a05c0873 100644 --- a/token/program/inc/token.h +++ b/token/program/inc/token.h @@ -152,8 +152,8 @@ typedef enum Token_TokenInstruction_Tag { * Accounts expected by this instruction: * * 0. `[writable]` The multisignature account to initialize. - * 2. `[]` Rent sysvar - * 3. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= 11. + * 1. `[]` Rent sysvar + * 2. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= 11. */ Token_TokenInstruction_InitializeMultisig, /** @@ -219,7 +219,7 @@ typedef enum Token_TokenInstruction_Tag { * * Multisignature authority * 0. `[writable]` The mint or account to change the authority of. * 1. `[]` The mint's or account's multisignature authority. - * 3. ..3+M '[signer]' M signer accounts + * 2. ..2+M '[signer]' M signer accounts */ Token_TokenInstruction_SetAuthority, /** @@ -247,12 +247,14 @@ typedef enum Token_TokenInstruction_Tag { * * * Single owner/delegate * 0. `[writable]` The account to burn from. - * 1. `[signer]` The account's owner/delegate. + * 1. '[writable]' The token mint. + * 2. `[signer]` The account's owner/delegate. * * * Multisignature owner/delegate * 0. `[writable]` The account to burn from. - * 1. `[]` The account's multisignature owner/delegate. - * 2. ..2+M '[signer]' M signer accounts. + * 1. '[writable]' The token mint. + * 2. `[]` The account's multisignature owner/delegate. + * 3. ..3+M '[signer]' M signer accounts. */ Token_TokenInstruction_Burn, /** @@ -393,6 +395,10 @@ typedef struct Token_Mint { * further tokens may be minted. */ Token_COption_Pubkey mint_authority; + /** + * Total supply of tokens. + */ + uint64_t supply; /** * Number of base 10 digits to the right of the decimal place. */ diff --git a/token/program/src/instruction.rs b/token/program/src/instruction.rs index 79bd0a0a..01656008 100644 --- a/token/program/src/instruction.rs +++ b/token/program/src/instruction.rs @@ -64,8 +64,8 @@ pub enum TokenInstruction { /// Accounts expected by this instruction: /// /// 0. `[writable]` The multisignature account to initialize. - /// 2. `[]` Rent sysvar - /// 3. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= 11. + /// 1. `[]` Rent sysvar + /// 2. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= 11. InitializeMultisig { /// The number of signers (M) required to validate this multisignature account. m: u8, @@ -133,7 +133,7 @@ pub enum TokenInstruction { /// * Multisignature authority /// 0. `[writable]` The mint or account to change the authority of. /// 1. `[]` The mint's or account's multisignature authority. - /// 3. ..3+M '[signer]' M signer accounts + /// 2. ..2+M '[signer]' M signer accounts SetAuthority { /// The type of authority to update. authority_type: AuthorityType, @@ -165,12 +165,14 @@ pub enum TokenInstruction { /// /// * Single owner/delegate /// 0. `[writable]` The account to burn from. - /// 1. `[signer]` The account's owner/delegate. + /// 1. '[writable]' The token mint. + /// 2. `[signer]` The account's owner/delegate. /// /// * Multisignature owner/delegate /// 0. `[writable]` The account to burn from. - /// 1. `[]` The account's multisignature owner/delegate. - /// 2. ..2+M '[signer]' M signer accounts. + /// 1. '[writable]' The token mint. + /// 2. `[]` The account's multisignature owner/delegate. + /// 3. ..3+M '[signer]' M signer accounts. Burn { /// The amount of tokens to burn. amount: u64, @@ -696,14 +698,16 @@ pub fn mint_to( pub fn burn( token_program_id: &Pubkey, account_pubkey: &Pubkey, + mint_pubkey: &Pubkey, authority_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], amount: u64, ) -> Result { let data = TokenInstruction::Burn { amount }.pack()?; - let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len()); + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); accounts.push(AccountMeta::new(*account_pubkey, false)); + accounts.push(AccountMeta::new(*mint_pubkey, false)); accounts.push(AccountMeta::new_readonly( *authority_pubkey, signer_pubkeys.is_empty(), diff --git a/token/program/src/processor.rs b/token/program/src/processor.rs index 7c1302ea..15181e04 100644 --- a/token/program/src/processor.rs +++ b/token/program/src/processor.rs @@ -387,6 +387,11 @@ impl Processor { .checked_add(amount) .ok_or(TokenError::Overflow)?; + mint.supply = mint + .supply + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + Ok(()) } @@ -398,14 +403,21 @@ impl Processor { ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let source_account_info = next_account_info(account_info_iter)?; + let mint_info = next_account_info(account_info_iter)?; 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()); } @@ -439,6 +451,7 @@ impl Processor { } source_account.amount -= amount; + mint.supply -= amount; Ok(()) } @@ -1268,6 +1281,7 @@ mod tests { *mint, Mint { mint_authority: COption::Some(owner_key), + supply: 0, decimals, is_initialized: true, freeze_authority: COption::None, @@ -1832,13 +1846,26 @@ mod tests { .unwrap(); // mint to + do_process_instruction( + mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 42).unwrap(), + vec![&mut mint_account, &mut account_account, &mut owner_account], + ) + .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 to another account to test supply accumulation do_process_instruction( mint_to(&program_id, &mint_key, &account2_key, &owner_key, &[], 42).unwrap(), vec![&mut mint_account, &mut account2_account, &mut owner_account], ) .unwrap(); - let _: &mut Mint = state::unpack(&mut mint_account.data).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); @@ -2010,13 +2037,18 @@ mod tests { .unwrap(); // missing signer - let mut instruction = burn(&program_id, &account_key, &delegate_key, &[], 42).unwrap(); + let mut instruction = + burn(&program_id, &account_key, &mint_key, &delegate_key, &[], 42).unwrap(); instruction.accounts[1].is_signer = false; assert_eq!( Err(TokenError::OwnerMismatch.into()), do_process_instruction( instruction, - vec![&mut account_account, &mut delegate_account], + vec![ + &mut account_account, + &mut mint_account, + &mut delegate_account + ], ) ); @@ -2024,19 +2056,29 @@ mod tests { assert_eq!( Err(TokenError::OwnerMismatch.into()), do_process_instruction( - burn(&program_id, &account_key, &owner2_key, &[], 42).unwrap(), - vec![&mut account_account, &mut owner2_account], + burn(&program_id, &account_key, &mint_key, &owner2_key, &[], 42).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner2_account], + ) + ); + + // mint mismatch + assert_eq!( + Err(TokenError::MintMismatch.into()), + do_process_instruction( + burn(&program_id, &mismatch_key, &mint_key, &owner_key, &[], 42).unwrap(), + vec![&mut mismatch_account, &mut mint_account, &mut owner_account], ) ); // burn do_process_instruction( - burn(&program_id, &account_key, &owner_key, &[], 42).unwrap(), - vec![&mut account_account, &mut owner_account], + burn(&program_id, &account_key, &mint_key, &owner_key, &[], 42).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], ) .unwrap(); - let _: &mut Mint = state::unpack(&mut mint_account.data).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); @@ -2044,8 +2086,16 @@ mod tests { assert_eq!( Err(TokenError::InsufficientFunds.into()), do_process_instruction( - burn(&program_id, &account_key, &owner_key, &[], 100_000_000).unwrap(), - vec![&mut account_account, &mut owner_account], + burn( + &program_id, + &account_key, + &mint_key, + &owner_key, + &[], + 100_000_000 + ) + .unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], ) ); @@ -2072,20 +2122,33 @@ mod tests { assert_eq!( Err(TokenError::InsufficientFunds.into()), do_process_instruction( - burn(&program_id, &account_key, &owner_key, &[], 100_000_000).unwrap(), - vec![&mut account_account, &mut owner_account], + burn( + &program_id, + &account_key, + &mint_key, + &owner_key, + &[], + 100_000_000 + ) + .unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], ) ); // burn via delegate do_process_instruction( - burn(&program_id, &account_key, &delegate_key, &[], 84).unwrap(), - vec![&mut account_account, &mut delegate_account], + burn(&program_id, &account_key, &mint_key, &delegate_key, &[], 84).unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut delegate_account, + ], ) .unwrap(); // match - let _: &mut Mint = state::unpack(&mut mint_account.data).unwrap(); + 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); @@ -2093,8 +2156,20 @@ mod tests { assert_eq!( Err(TokenError::OwnerMismatch.into()), do_process_instruction( - burn(&program_id, &account_key, &delegate_key, &[], 100).unwrap(), - vec![&mut account_account, &mut delegate_account], + burn( + &program_id, + &account_key, + &mint_key, + &delegate_key, + &[], + 100 + ) + .unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut delegate_account + ], ) ); } @@ -2338,6 +2413,7 @@ mod tests { burn( &program_id, &account_key, + &mint_key, &multisig_key, &[&signer_keys[0]], 42, @@ -2345,6 +2421,7 @@ mod tests { .unwrap(), vec![ &mut account, + &mut mint_account, &mut multisig_account, &mut account_info_iter.next().unwrap(), ], @@ -2357,6 +2434,7 @@ mod tests { burn( &program_id, &account_key, + &mint_key, &multisig_delegate_key, &signer_key_refs, 42, @@ -2364,6 +2442,7 @@ mod tests { .unwrap(), vec![ &mut account, + &mut mint_account, &mut multisig_delegate_account, &mut account_info_iter.next().unwrap(), &mut account_info_iter.next().unwrap(), @@ -2715,8 +2794,8 @@ mod tests { // empty account do_process_instruction( - burn(&program_id, &account_key, &owner_key, &[], 42).unwrap(), - vec![&mut account_account, &mut owner_account], + burn(&program_id, &account_key, &mint_key, &owner_key, &[], 42).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], ) .unwrap(); @@ -2908,11 +2987,32 @@ mod tests { ); // burn unsupported + let bogus_mint_key = pubkey_rand(); + let mut bogus_mint_account = + SolanaAccount::new(mint_minimum_balance(), size_of::(), &program_id); + do_process_instruction( + initialize_mint(&program_id, &bogus_mint_key, &owner_key, None, 2).unwrap(), + vec![&mut bogus_mint_account, &mut rent_sysvar], + ) + .unwrap(); + assert_eq!( Err(TokenError::NativeNotSupported.into()), do_process_instruction( - burn(&program_id, &account_key, &owner_key, &[], 42).unwrap(), - vec![&mut account_account, &mut owner_account], + burn( + &program_id, + &account_key, + &bogus_mint_key, + &owner_key, + &[], + 42 + ) + .unwrap(), + vec![ + &mut account_account, + &mut bogus_mint_account, + &mut owner_account + ], ) ); @@ -3002,7 +3102,7 @@ mod tests { SolanaAccount::new(mint_minimum_balance(), size_of::(), &program_id); let mut rent_sysvar = rent_sysvar(); - // create victim account + // create an account do_process_instruction( initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(), vec![ @@ -3033,28 +3133,7 @@ mod tests { ) .unwrap(); - // mint the max to attacker - do_process_instruction( - mint_to( - &program_id, - &mint_key, - &account2_key, - &mint_owner_key, - &[], - 42, - ) - .unwrap(), - vec![ - &mut mint_account, - &mut account2_account, - &mut mint_owner_account, - ], - ) - .unwrap(); - let account: &mut Account = state::unpack(&mut account2_account.data).unwrap(); - assert_eq!(account.amount, 42); - - // mint the max to victum + // mint the max to an account do_process_instruction( mint_to( &program_id, @@ -3075,7 +3154,7 @@ mod tests { let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); assert_eq!(account.amount, u64::MAX); - // mint one more + // attempt to mint one more to account assert_eq!( Err(TokenError::Overflow.into()), do_process_instruction( @@ -3095,10 +3174,39 @@ mod tests { ], ) ); - - // mint back to large amount let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); - account.amount = 0; + assert_eq!(account.amount, u64::MAX); + + // atttempt to mint one more to the other account + assert_eq!( + Err(TokenError::Overflow.into()), + do_process_instruction( + mint_to( + &program_id, + &mint_key, + &account2_key, + &mint_owner_key, + &[], + 1, + ) + .unwrap(), + vec![ + &mut mint_account, + &mut account2_account, + &mut mint_owner_account, + ], + ) + ); + + // burn some of the supply + do_process_instruction( + burn(&program_id, &account_key, &mint_key, &owner_key, &[], 100).unwrap(), + 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); + do_process_instruction( mint_to( &program_id, @@ -3106,7 +3214,7 @@ mod tests { &account_key, &mint_owner_key, &[], - u64::MAX, + 100, ) .unwrap(), vec![ @@ -3119,7 +3227,10 @@ mod tests { let account: &mut Account = state::unpack(&mut account_account.data).unwrap(); assert_eq!(account.amount, u64::MAX); - // transfer to burn victim + // manipulate account balance to attempt overflow transfer + let account: &mut Account = state::unpack(&mut account2_account.data).unwrap(); + account.amount = 1; + assert_eq!( Err(TokenError::Overflow.into()), do_process_instruction( @@ -3308,8 +3419,8 @@ mod tests { assert_eq!( Err(TokenError::AccountFrozen.into()), do_process_instruction( - burn(&program_id, &account_key, &owner_key, &[], 100).unwrap(), - vec![&mut account_account, &mut owner_account,], + burn(&program_id, &account_key, &mint_key, &owner_key, &[], 100).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], ) ); } diff --git a/token/program/src/state.rs b/token/program/src/state.rs index 7f920ef9..5b3981c9 100644 --- a/token/program/src/state.rs +++ b/token/program/src/state.rs @@ -12,6 +12,8 @@ pub struct Mint { /// mint creation. If no mint authority is present then the mint has a fixed supply and no /// further tokens may be minted. pub mint_authority: COption, + /// Total supply of tokens. + pub supply: u64, /// Number of base 10 digits to the right of the decimal place. pub decimals: u8, /// Is `true` if this structure has been initialized