Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 2e96811

Browse files
committed
Add CreateNativeMint instruction
1 parent 290d8d8 commit 2e96811

File tree

6 files changed

+168
-8
lines changed

6 files changed

+168
-8
lines changed

token/program-2022-test/tests/initialize_mint.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use {
1717
spl_token_2022::{
1818
error::TokenError,
1919
extension::{mint_close_authority::MintCloseAuthority, transfer_fee, ExtensionType},
20-
instruction,
20+
instruction, native_mint,
2121
state::Mint,
2222
},
2323
spl_token_client::token::ExtensionInitializationParams,
@@ -474,3 +474,17 @@ async fn fail_fee_init_after_mint_init() {
474474
TransactionError::InstructionError(1, InstructionError::InvalidAccountData)
475475
);
476476
}
477+
478+
#[tokio::test]
479+
async fn create_native_mint() {
480+
let mut context = TestContext::new().await;
481+
context.init_token_with_native_mint().await.unwrap();
482+
let TokenContext { token, .. } = context.token_context.unwrap();
483+
484+
let mint = token.get_mint_info().await.unwrap();
485+
assert_eq!(mint.base.decimals, native_mint::DECIMALS);
486+
assert_eq!(mint.base.mint_authority, COption::None,);
487+
assert_eq!(mint.base.supply, 0);
488+
assert!(mint.base.is_initialized);
489+
assert_eq!(mint.base.freeze_authority, COption::None);
490+
}

token/program-2022-test/tests/program_test.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use {
44
solana_program_test::{processor, tokio::sync::Mutex, ProgramTest, ProgramTestContext},
55
solana_sdk::signer::{keypair::Keypair, Signer},
6-
spl_token_2022::{id, processor::Processor},
6+
spl_token_2022::{id, native_mint, processor::Processor},
77
spl_token_client::{
88
client::{ProgramBanksClient, ProgramBanksClientProcessTransaction, ProgramClient},
99
token::{ExtensionInitializationParams, Token, TokenResult},
@@ -97,6 +97,26 @@ impl TestContext {
9797

9898
Ok(())
9999
}
100+
101+
pub async fn init_token_with_native_mint(&mut self) -> TokenResult<()> {
102+
let payer = keypair_clone(&self.context.lock().await.payer);
103+
let client: Arc<dyn ProgramClient<ProgramBanksClientProcessTransaction>> =
104+
Arc::new(ProgramBanksClient::new_from_context(
105+
Arc::clone(&self.context),
106+
ProgramBanksClientProcessTransaction,
107+
));
108+
109+
let token = Token::create_native_mint(Arc::clone(&client), &id(), payer).await?;
110+
self.token_context = Some(TokenContext {
111+
decimals: native_mint::DECIMALS,
112+
mint_authority: Keypair::new(), /*bogus*/
113+
token,
114+
alice: Keypair::new(),
115+
bob: Keypair::new(),
116+
freeze_authority: None,
117+
});
118+
Ok(())
119+
}
100120
}
101121

102122
fn keypair_clone(kp: &Keypair) -> Keypair {

token/program-2022/src/instruction.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,19 @@ pub enum TokenInstruction {
527527
/// See `extension::memo_transfer::instruction::RequiredMemoTransfersInstruction` for
528528
/// further details about the extended instructions that share this instruction prefix
529529
MemoTransferExtension,
530+
/// Creates the native mint.
531+
///
532+
/// This instruction only needs to be invoked once after deployment and is permissionless,
533+
/// Wrapped SOL (`native_mint::id()`) will not be available until this instruction is
534+
/// successfully executed.
535+
///
536+
/// Accounts expected by this instruction:
537+
///
538+
/// 0. `[writeable,signer]` Funding account (must be a system account)
539+
/// 1. `[writable]` The native mint address
540+
/// 2. `[]` System program for mint account funding
541+
///
542+
CreateNativeMint,
530543
}
531544
impl TokenInstruction {
532545
/// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html).
@@ -646,6 +659,7 @@ impl TokenInstruction {
646659
Self::Reallocate { extension_types }
647660
}
648661
28 => Self::MemoTransferExtension,
662+
29 => Self::CreateNativeMint,
649663
_ => return Err(TokenError::InvalidInstruction.into()),
650664
})
651665
}
@@ -781,6 +795,9 @@ impl TokenInstruction {
781795
&Self::MemoTransferExtension => {
782796
buf.push(28);
783797
}
798+
&Self::CreateNativeMint => {
799+
buf.push(29);
800+
}
784801
};
785802
buf
786803
}
@@ -1568,6 +1585,24 @@ pub fn reallocate(
15681585
})
15691586
}
15701587

1588+
/// Creates a `CreateNativeMint` instruction
1589+
pub fn create_native_mint(
1590+
token_program_id: &Pubkey,
1591+
payer: &Pubkey,
1592+
) -> Result<Instruction, ProgramError> {
1593+
check_program_account(token_program_id)?;
1594+
1595+
Ok(Instruction {
1596+
program_id: *token_program_id,
1597+
accounts: vec![
1598+
AccountMeta::new(*payer, true),
1599+
AccountMeta::new(crate::native_mint::id(), false),
1600+
AccountMeta::new_readonly(system_program::id(), false),
1601+
],
1602+
data: TokenInstruction::CreateNativeMint.pack(),
1603+
})
1604+
}
1605+
15711606
/// Utility function that checks index is between MIN_SIGNERS and MAX_SIGNERS
15721607
pub fn is_valid_signer_index(index: usize) -> bool {
15731608
(MIN_SIGNERS..=MAX_SIGNERS).contains(&index)
@@ -1820,5 +1855,12 @@ mod test {
18201855
assert_eq!(packed, expect);
18211856
let unpacked = TokenInstruction::unpack(&expect).unwrap();
18221857
assert_eq!(unpacked, check);
1858+
1859+
let check = TokenInstruction::CreateNativeMint;
1860+
let packed = check.pack();
1861+
let expect = vec![29u8];
1862+
assert_eq!(packed, expect);
1863+
let unpacked = TokenInstruction::unpack(&expect).unwrap();
1864+
assert_eq!(unpacked, check);
18231865
}
18241866
}

token/program-2022/src/native_mint.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44
pub const DECIMALS: u8 = 9;
55

66
// The Mint for native SOL Token accounts
7-
solana_program::declare_id!("So11111111111111111111111111111111111111112");
7+
solana_program::declare_id!("9pan9bMn5HatX4EJdBwg9VgCa7Uz5HL8N1m5D3NdXejP");
8+
9+
/// Seed for the native_mint's program-derived address
10+
pub const PROGRAM_ADDRESS_SEEDS: &[&[u8]] = &["native-mint".as_bytes(), &[255]];
811

912
#[cfg(test)]
1013
mod tests {
11-
use {super::*, solana_program::native_token::*};
14+
use {
15+
super::*,
16+
solana_program::{native_token::*, pubkey::Pubkey},
17+
};
1218

1319
#[test]
1420
fn test_decimals() {
@@ -20,4 +26,11 @@ mod tests {
2026
crate::ui_amount_to_amount(42., DECIMALS)
2127
);
2228
}
29+
30+
#[test]
31+
fn expected_native_mint_id() {
32+
let native_mint_id =
33+
Pubkey::create_program_address(PROGRAM_ADDRESS_SEEDS, &crate::id()).unwrap();
34+
assert_eq!(id(), native_mint_id);
35+
}
2336
}

token/program-2022/src/processor.rs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use {
1515
ExtensionType, StateWithExtensions, StateWithExtensionsMut,
1616
},
1717
instruction::{is_valid_signer_index, AuthorityType, TokenInstruction, MAX_SIGNERS},
18+
native_mint,
1819
state::{Account, AccountState, Mint, Multisig},
1920
},
2021
num_traits::FromPrimitive,
@@ -24,12 +25,13 @@ use {
2425
decode_error::DecodeError,
2526
entrypoint::ProgramResult,
2627
msg,
27-
program::set_return_data,
28+
program::{invoke, invoke_signed, set_return_data},
2829
program_error::{PrintProgramError, ProgramError},
2930
program_memory::sol_memset,
3031
program_option::COption,
3132
program_pack::Pack,
3233
pubkey::Pubkey,
34+
system_instruction,
3335
sysvar::{rent::Rent, Sysvar},
3436
},
3537
std::convert::{TryFrom, TryInto},
@@ -164,7 +166,7 @@ impl Processor {
164166
account.base.delegate = COption::None;
165167
account.base.delegated_amount = 0;
166168
account.base.state = starting_state;
167-
if cmp_pubkeys(mint_info.key, &crate::native_mint::id()) {
169+
if cmp_pubkeys(mint_info.key, &native_mint::id()) {
168170
let rent_exempt_reserve = rent.minimum_balance(new_account_info_data_len);
169171
account.base.is_native = COption::Some(rent_exempt_reserve);
170172
account.base.amount = new_account_info
@@ -1024,6 +1026,51 @@ impl Processor {
10241026
token_account.init_extension::<ImmutableOwner>().map(|_| ())
10251027
}
10261028

1029+
/// Processes a [CreateNativeMint](enum.TokenInstruction.html) instruction
1030+
pub fn process_create_native_mint(accounts: &[AccountInfo]) -> ProgramResult {
1031+
let account_info_iter = &mut accounts.iter();
1032+
let payer_info = next_account_info(account_info_iter)?;
1033+
let native_mint_info = next_account_info(account_info_iter)?;
1034+
let system_program_info = next_account_info(account_info_iter)?;
1035+
1036+
if *native_mint_info.key != native_mint::id() {
1037+
return Err(TokenError::InvalidMint.into());
1038+
}
1039+
1040+
let rent = Rent::get()?;
1041+
let new_minimum_balance = rent.minimum_balance(Mint::get_packed_len());
1042+
let lamports_diff = new_minimum_balance.saturating_sub(native_mint_info.lamports());
1043+
invoke(
1044+
&system_instruction::transfer(payer_info.key, native_mint_info.key, lamports_diff),
1045+
&[
1046+
payer_info.clone(),
1047+
native_mint_info.clone(),
1048+
system_program_info.clone(),
1049+
],
1050+
)?;
1051+
1052+
invoke_signed(
1053+
&system_instruction::allocate(native_mint_info.key, Mint::get_packed_len() as u64),
1054+
&[native_mint_info.clone(), system_program_info.clone()],
1055+
&[native_mint::PROGRAM_ADDRESS_SEEDS],
1056+
)?;
1057+
1058+
invoke_signed(
1059+
&system_instruction::assign(native_mint_info.key, &crate::id()),
1060+
&[native_mint_info.clone(), system_program_info.clone()],
1061+
&[native_mint::PROGRAM_ADDRESS_SEEDS],
1062+
)?;
1063+
1064+
Mint::pack(
1065+
Mint {
1066+
decimals: native_mint::DECIMALS,
1067+
is_initialized: true,
1068+
..Mint::default()
1069+
},
1070+
&mut native_mint_info.data.borrow_mut(),
1071+
)
1072+
}
1073+
10271074
/// Processes an [Instruction](enum.Instruction.html).
10281075
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
10291076
let instruction = TokenInstruction::unpack(input)?;
@@ -1161,6 +1208,10 @@ impl Processor {
11611208
TokenInstruction::MemoTransferExtension => {
11621209
memo_transfer::processor::process_instruction(program_id, accounts, &input[1..])
11631210
}
1211+
TokenInstruction::CreateNativeMint => {
1212+
msg!("Instruction: CreateNativeMint");
1213+
Self::process_create_native_mint(accounts)
1214+
}
11641215
}
11651216
}
11661217

@@ -1443,7 +1494,7 @@ mod tests {
14431494
&crate::native_mint::id(),
14441495
&Pubkey::default(),
14451496
None,
1446-
9,
1497+
crate::native_mint::DECIMALS,
14471498
)
14481499
.unwrap(),
14491500
vec![&mut mint_account, &mut rent_sysvar],

token/rust/src/token.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use spl_token_2022::{
1717
extension::{
1818
default_account_state, memo_transfer, transfer_fee, ExtensionType, StateWithExtensionsOwned,
1919
},
20-
instruction,
20+
instruction, native_mint,
2121
state::{Account, AccountState, Mint},
2222
};
2323
use std::{
@@ -272,6 +272,26 @@ where
272272
Ok(token)
273273
}
274274

275+
/// Create native mint
276+
pub async fn create_native_mint(
277+
client: Arc<dyn ProgramClient<T>>,
278+
program_id: &Pubkey,
279+
payer: S,
280+
) -> TokenResult<Self> {
281+
let token = Self::new(client, program_id, &native_mint::id(), payer);
282+
token
283+
.process_ixs(
284+
&[instruction::create_native_mint(
285+
program_id,
286+
&token.payer.pubkey(),
287+
)?],
288+
&[&token.payer],
289+
)
290+
.await?;
291+
292+
Ok(token)
293+
}
294+
275295
/// Get the address for the associated token account.
276296
pub fn get_associated_token_address(&self, owner: &Pubkey) -> Pubkey {
277297
get_associated_token_address_with_program_id(owner, &self.pubkey, &self.program_id)

0 commit comments

Comments
 (0)