diff --git a/Cargo.lock b/Cargo.lock index 3a0dc4d..f4ff739 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4956,6 +4956,14 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "system" +version = "0.1.0" +dependencies = [ + "solana-nostd-entrypoint", + "solana-program", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -5194,6 +5202,14 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "token" +version = "0.1.0" +dependencies = [ + "solana-nostd-entrypoint", + "solana-program", +] + [[package]] name = "tokio" version = "1.40.0" diff --git a/Cargo.toml b/Cargo.toml index 3954b60..e93ea17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["solana-nostd-entrypoint", "example-program"] +members = ["solana-nostd-entrypoint", "example-program", "system", "token"] resolver = "2" diff --git a/system/Cargo.toml b/system/Cargo.toml new file mode 100644 index 0000000..842a7b6 --- /dev/null +++ b/system/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "system" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +solana-program = { workspace = true } +solana-nostd-entrypoint = { workspace = true } diff --git a/system/src/instructions/advance_nonce_account.rs b/system/src/instructions/advance_nonce_account.rs new file mode 100644 index 0000000..bacaaea --- /dev/null +++ b/system/src/instructions/advance_nonce_account.rs @@ -0,0 +1,48 @@ +use solana_nostd_entrypoint::{InstructionC, NoStdAccountInfo}; + +use crate::{invoke_signed::invoke_signed, ProgramResult}; + +pub struct AdvanceNonceAccount<'a> { + /// Nonce account. + pub account: &'a NoStdAccountInfo, + + /// RecentBlockhashes sysvar. + pub recent_blockhashes_sysvar: &'a NoStdAccountInfo, + + /// Nonce authority. + pub authority: &'a NoStdAccountInfo, +} + +impl<'a> AdvanceNonceAccount<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let account_metas = [ + self.account.to_meta_c(), + self.recent_blockhashes_sysvar + .to_meta_c(), + self.authority.to_meta_c_signer(), + ]; + + let account_infos = [ + self.account.to_info_c(), + self.recent_blockhashes_sysvar + .to_info_c(), + self.authority.to_info_c(), + ]; + + let mut instruction_data = [0; 4]; + instruction_data[0] = 4; + + let instruction = InstructionC { + program_id: &crate::ID, + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/system/src/instructions/allocate.rs b/system/src/instructions/allocate.rs new file mode 100644 index 0000000..66aa600 --- /dev/null +++ b/system/src/instructions/allocate.rs @@ -0,0 +1,33 @@ +use solana_nostd_entrypoint::{InstructionC, NoStdAccountInfo}; + +use crate::{invoke_signed::invoke_signed, ProgramResult}; + +pub struct Allocate<'a> { + pub account: &'a NoStdAccountInfo, + pub space: u64, +} + +impl<'a> Allocate<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let account_metas = [self.account.to_meta_c_signer()]; + let account_infos = [self.account.to_info_c()]; + + let mut instruction_data = [0; 12]; + instruction_data[0] = 8; + instruction_data[4..12] + .copy_from_slice(&self.space.to_le_bytes()); + + let instruction = InstructionC { + program_id: &crate::ID, + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/system/src/instructions/assign.rs b/system/src/instructions/assign.rs new file mode 100644 index 0000000..05221b2 --- /dev/null +++ b/system/src/instructions/assign.rs @@ -0,0 +1,33 @@ +use solana_nostd_entrypoint::{InstructionC, NoStdAccountInfo}; +use solana_program::pubkey::Pubkey; + +use crate::{invoke_signed::invoke_signed, ProgramResult}; + +pub struct Assign<'a> { + pub account: &'a NoStdAccountInfo, + pub owner: &'a Pubkey, +} + +impl<'a> Assign<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let account_metas = [self.account.to_meta_c_signer()]; + let account_infos = [self.account.to_info_c()]; + + let mut instruction_data = [0; 36]; + instruction_data[0] = 1; + instruction_data[4..36].copy_from_slice(self.owner.as_ref()); + + let instruction = InstructionC { + program_id: &crate::ID, + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/system/src/instructions/create_account.rs b/system/src/instructions/create_account.rs new file mode 100644 index 0000000..5bf3799 --- /dev/null +++ b/system/src/instructions/create_account.rs @@ -0,0 +1,43 @@ +use solana_nostd_entrypoint::{ + solana_program::pubkey::Pubkey, InstructionC, NoStdAccountInfo, +}; + +use crate::{invoke_signed::invoke_signed, ProgramResult}; + +pub struct CreateAccount<'a> { + pub from: &'a NoStdAccountInfo, + pub to: &'a NoStdAccountInfo, + pub lamports: u64, + pub space: u64, + pub owner: &'a Pubkey, +} + +impl<'a> CreateAccount<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let account_metas = + [self.from.to_meta_c_signer(), self.to.to_meta_c_signer()]; + let account_infos = + [self.from.to_info_c(), self.to.to_info_c()]; + + let mut instruction_data = [0; 52]; + // create account instruction has a '0' discriminator + instruction_data[4..12] + .copy_from_slice(&self.lamports.to_le_bytes()); + instruction_data[12..20] + .copy_from_slice(&self.space.to_le_bytes()); + instruction_data[20..52].copy_from_slice(self.owner.as_ref()); + + let instruction = InstructionC { + program_id: &crate::ID, + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_slice().as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/system/src/instructions/mod.rs b/system/src/instructions/mod.rs new file mode 100644 index 0000000..4544858 --- /dev/null +++ b/system/src/instructions/mod.rs @@ -0,0 +1,5 @@ +pub mod allocate; +pub mod assign; +pub mod create_account; +pub mod transfer; +pub mod advance_nonce_account; diff --git a/system/src/instructions/transfer.rs b/system/src/instructions/transfer.rs new file mode 100644 index 0000000..deb7622 --- /dev/null +++ b/system/src/instructions/transfer.rs @@ -0,0 +1,36 @@ +use solana_nostd_entrypoint::{InstructionC, NoStdAccountInfo}; + +use crate::{invoke_signed::invoke_signed, ProgramResult}; + +pub struct Transfer<'a> { + pub from: &'a NoStdAccountInfo, + pub to: &'a NoStdAccountInfo, + pub lamports: u64, +} + +impl<'a> Transfer<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let account_metas = + [self.from.to_meta_c_signer(), self.to.to_meta_c()]; + let account_infos = + [self.from.to_info_c(), self.to.to_info_c()]; + + let mut instruction_data = [0; 12]; + instruction_data[0] = 2; + instruction_data[4..12] + .copy_from_slice(&self.lamports.to_le_bytes()); + + let instruction = InstructionC { + program_id: &crate::ID, + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_slice().as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/system/src/invoke_signed.rs b/system/src/invoke_signed.rs new file mode 100644 index 0000000..5db37be --- /dev/null +++ b/system/src/invoke_signed.rs @@ -0,0 +1,26 @@ +use solana_nostd_entrypoint::{AccountInfoC, InstructionC}; + +use crate::ProgramResult; + +pub fn invoke_signed( + instruction: &InstructionC, + infos: &[AccountInfoC], + seeds: &[&[&[u8]]], +) -> ProgramResult { + #[cfg(target_os = "solana")] + unsafe { + solana_program::syscalls::sol_invoke_signed_c( + instruction as *const InstructionC as *const u8, + infos.as_ptr() as *const u8, + infos.len() as u64, + seeds.as_ptr() as *const u8, + seeds.len() as u64, + ); + } + + // For clippy + #[cfg(not(target_os = "solana"))] + core::hint::black_box(&(&instruction, &infos, &seeds)); + + Ok(()) +} diff --git a/system/src/lib.rs b/system/src/lib.rs new file mode 100644 index 0000000..554a8d5 --- /dev/null +++ b/system/src/lib.rs @@ -0,0 +1,11 @@ +use solana_nostd_entrypoint::solana_program::pubkey::Pubkey; +use solana_program::program_error::ProgramError; + +pub mod instructions; +pub mod invoke_signed; + +pub const ID: Pubkey = solana_nostd_entrypoint::solana_program::pubkey!( + "11111111111111111111111111111111" +); + +pub type ProgramResult = Result<(), ProgramError>; \ No newline at end of file diff --git a/token/Cargo.toml b/token/Cargo.toml new file mode 100644 index 0000000..f4b10de --- /dev/null +++ b/token/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "token" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +solana-program = { workspace = true } +solana-nostd-entrypoint = { workspace = true } diff --git a/token/src/instructions/close_account.rs b/token/src/instructions/close_account.rs new file mode 100644 index 0000000..d493955 --- /dev/null +++ b/token/src/instructions/close_account.rs @@ -0,0 +1,44 @@ +use solana_nostd_entrypoint::{ + solana_program::entrypoint::ProgramResult, InstructionC, + NoStdAccountInfo, +}; + +use crate::invoke_signed::invoke_signed; + +pub struct CloseAccount<'a> { + pub token_program: &'a NoStdAccountInfo, + pub account: &'a NoStdAccountInfo, + pub destination: &'a NoStdAccountInfo, + pub authority: &'a NoStdAccountInfo, +} + +impl<'a> CloseAccount<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let instruction_data = &[9]; + + let account_metas = [ + self.account.to_meta_c(), + self.destination.to_meta_c(), + self.authority.to_meta_c_signer(), + ]; + + let account_infos = [ + self.account.to_info_c(), + self.destination.to_info_c(), + self.authority.to_info_c(), + ]; + + let instruction = InstructionC { + program_id: self.token_program.key(), + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/token/src/instructions/initialize_account_3.rs b/token/src/instructions/initialize_account_3.rs new file mode 100644 index 0000000..00f1541 --- /dev/null +++ b/token/src/instructions/initialize_account_3.rs @@ -0,0 +1,40 @@ +use solana_nostd_entrypoint::{ + solana_program::entrypoint::ProgramResult, + solana_program::pubkey::Pubkey, InstructionC, NoStdAccountInfo, +}; + +use crate::invoke_signed::invoke_signed; + +pub struct InitializeAccount3<'a> { + pub token_program: &'a NoStdAccountInfo, + pub token: &'a NoStdAccountInfo, + pub mint: &'a NoStdAccountInfo, + pub owner: &'a Pubkey, +} + +impl<'a> InitializeAccount3<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let account_metas = + [self.token.to_meta_c(), self.mint.to_meta_c()]; + let account_infos = + [self.token.to_info_c(), self.mint.to_info_c()]; + + // Preallocate vec with exact size: 1 (discriminator) + 32 (pubkey) + let mut instruction_data = Vec::with_capacity(33); + instruction_data.push(18); + instruction_data.extend_from_slice(self.owner.as_ref()); + + let instruction = &InstructionC { + program_id: self.token_program.key(), + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_slice().as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/token/src/instructions/mod.rs b/token/src/instructions/mod.rs new file mode 100644 index 0000000..8afaff8 --- /dev/null +++ b/token/src/instructions/mod.rs @@ -0,0 +1,21 @@ +pub mod close_account; +pub mod initialize_account_3; +pub mod sync_native; +pub mod transfer; +pub mod transfer_checked; + +pub mod token_program { + use solana_nostd_entrypoint::solana_program::pubkey::Pubkey; + + pub const ID: Pubkey = solana_nostd_entrypoint::solana_program::pubkey!( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + ); +} + +pub mod token_2022 { + use solana_nostd_entrypoint::solana_program::pubkey::Pubkey; + + pub const ID: Pubkey = solana_nostd_entrypoint::solana_program::pubkey!( + "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + ); +} diff --git a/token/src/instructions/sync_native.rs b/token/src/instructions/sync_native.rs new file mode 100644 index 0000000..d68ff88 --- /dev/null +++ b/token/src/instructions/sync_native.rs @@ -0,0 +1,33 @@ +use solana_nostd_entrypoint::{ + solana_program::entrypoint::ProgramResult, InstructionC, + NoStdAccountInfo, +}; + +use crate::invoke_signed::invoke_signed; + +pub struct SyncNative<'a> { + pub token_program: &'a NoStdAccountInfo, + pub token: &'a NoStdAccountInfo, +} + +impl<'a> SyncNative<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let account_metas = [self.token.to_meta_c()]; + let account_infos = [self.token.to_info_c()]; + + let instruction_data = &[17]; + + let instruction = InstructionC { + program_id: self.token_program.key(), + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/token/src/instructions/transfer.rs b/token/src/instructions/transfer.rs new file mode 100644 index 0000000..9b793a6 --- /dev/null +++ b/token/src/instructions/transfer.rs @@ -0,0 +1,48 @@ +use solana_nostd_entrypoint::{ + solana_program::entrypoint::ProgramResult, InstructionC, + NoStdAccountInfo, +}; + +use crate::invoke_signed::invoke_signed; + +pub struct Transfer<'a> { + pub token_program: &'a NoStdAccountInfo, + pub from: &'a NoStdAccountInfo, + pub to: &'a NoStdAccountInfo, + pub authority: &'a NoStdAccountInfo, + pub amount: u64, +} + +impl<'a> Transfer<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let account_metas = [ + self.from.to_meta_c(), + self.to.to_meta_c(), + self.authority.to_meta_c_signer(), + ]; + let account_infos = [ + self.from.to_info_c(), + self.to.to_info_c(), + self.authority.to_info_c(), + self.token_program.to_info_c(), + ]; + + let mut instruction_data = [0u8; 9]; + instruction_data[0] = 3; // Transfer instruction discriminator + instruction_data[1..9] + .copy_from_slice(&self.amount.to_le_bytes()); + + let instruction = InstructionC { + program_id: self.token_program.key(), + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/token/src/instructions/transfer_checked.rs b/token/src/instructions/transfer_checked.rs new file mode 100644 index 0000000..56bfef5 --- /dev/null +++ b/token/src/instructions/transfer_checked.rs @@ -0,0 +1,51 @@ +use crate::invoke_signed::invoke_signed; +use solana_nostd_entrypoint::{ + solana_program::entrypoint::ProgramResult, InstructionC, + NoStdAccountInfo, +}; +pub struct TransferChecked<'a> { + pub token_program: &'a NoStdAccountInfo, + pub from: &'a NoStdAccountInfo, + pub mint: &'a NoStdAccountInfo, + pub to: &'a NoStdAccountInfo, + pub authority: &'a NoStdAccountInfo, + pub amount: u64, + pub decimals: u8, +} + +impl<'a> TransferChecked<'a> { + pub fn invoke_signed( + &self, + signer_seeds: &[&[&[u8]]], + ) -> ProgramResult { + let account_metas = [ + self.from.to_meta_c(), + self.mint.to_meta_c(), + self.to.to_meta_c(), + self.authority.to_meta_c_signer(), + ]; + + let account_infos = [ + self.from.to_info_c(), + self.mint.to_info_c(), + self.to.to_info_c(), + self.authority.to_info_c(), + ]; + + let mut instruction_data = [0u8; 10]; + instruction_data[0] = 12; // TransferChecked instruction discriminator + instruction_data[1..9] + .copy_from_slice(&self.amount.to_le_bytes()); + instruction_data[9] = self.decimals; + + let instruction = InstructionC { + program_id: self.token_program.key(), + accounts: account_metas.as_ptr(), + accounts_len: account_metas.len() as u64, + data: instruction_data.as_ptr(), + data_len: instruction_data.len() as u64, + }; + + invoke_signed(&instruction, &account_infos, signer_seeds) + } +} diff --git a/token/src/invoke_signed.rs b/token/src/invoke_signed.rs new file mode 100644 index 0000000..5db37be --- /dev/null +++ b/token/src/invoke_signed.rs @@ -0,0 +1,26 @@ +use solana_nostd_entrypoint::{AccountInfoC, InstructionC}; + +use crate::ProgramResult; + +pub fn invoke_signed( + instruction: &InstructionC, + infos: &[AccountInfoC], + seeds: &[&[&[u8]]], +) -> ProgramResult { + #[cfg(target_os = "solana")] + unsafe { + solana_program::syscalls::sol_invoke_signed_c( + instruction as *const InstructionC as *const u8, + infos.as_ptr() as *const u8, + infos.len() as u64, + seeds.as_ptr() as *const u8, + seeds.len() as u64, + ); + } + + // For clippy + #[cfg(not(target_os = "solana"))] + core::hint::black_box(&(&instruction, &infos, &seeds)); + + Ok(()) +} diff --git a/token/src/lib.rs b/token/src/lib.rs new file mode 100644 index 0000000..1345c54 --- /dev/null +++ b/token/src/lib.rs @@ -0,0 +1,11 @@ +use solana_nostd_entrypoint::solana_program::pubkey::Pubkey; +use solana_program::program_error::ProgramError; + +pub mod instructions; +pub mod invoke_signed; + +pub const ID: Pubkey = solana_nostd_entrypoint::solana_program::pubkey!( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" +); + +pub type ProgramResult = Result<(), ProgramError>;