I want to build a Solana program (smart contract) ...
Creato il: 7 dicembre 2024
Creato il: 7 dicembre 2024
I want to build a Solana program (smart contract) that allows users to interact with a custom cryptocurrency. Users can send tokens to others, query their balance, and mint new tokens (admin-only functionality). If a user attempts to transfer tokens but doesn't have enough balance, the program should return an error. Design a plan for the directory structure needed for this project, then provide the complete code for each file. Only supply your reasoning at the beginning and end, not throughout the code.
To build a Solana program (smart contract) that supports a custom cryptocurrency, we need to structure the project in a way that allows for efficient development, easy testing, and a clear separation of concerns. Here's an outline of the directory structure:
textsolana-token-program/ ├── Cargo.toml # Project dependencies and metadata ├── README.md # Documentation of the program ├── src/ │ ├── entrypoint.rs # Entry point for the Solana program │ ├── lib.rs # Main logic for the token program │ └── instruction.rs # Definitions of program instructions (e.g., transfer, mint) ├── tests/ │ └── test_token_program.rs # Tests for the smart contract └── target/ # Compilation target files (generated during build)
Cargo.toml: Will define dependencies like solana-program and serde, and set up the project for building with Rust.README.md: Provides basic documentation and how-to for running and deploying the program.src/: This folder contains the core logic:
entrypoint.rs: Contains the main entry point function for the Solana program.lib.rs: Implements the logic for token transfers, balance checking, and minting functionality.instruction.rs: Defines various instructions like transferring tokens, minting, and others.tests/: Contains unit tests for the program, ensuring that the contract behaves as expected.Now, I will proceed with the complete code for each of these files. Here's how the Solana token program can be implemented.
Cargo.tomltoml[package] name = "solana-token-program" version = "0.1.0" edition = "2021" [dependencies] solana-program = "1.17.0" borsh = "0.9" serde = "1.0" serde_derive = "1.0"
README.mdmarkdown# Solana Token Program This Solana smart contract allows users to interact with a custom cryptocurrency. Users can: - Transfer tokens to other users - Query their token balance - Mint new tokens (admin-only functionality) ## Instructions 1. Clone the repository. 2. Set up the Solana CLI and network. 3. Build the project: `cargo build-bpf` 4. Deploy to the Solana devnet: `solana program deploy <path-to-program>` 5. Interact with the contract through Solana CLI or using a client program.
src/entrypoint.rsrustuse solana_program::{ entrypoint::{ProgramResult, EntryPoint}, pubkey::Pubkey, msg, }; use crate::processor::process_instruction; #[cfg(not(feature = "no-entrypoint"))] solana_program::entrypoint!(process_instruction); #[cfg(feature = "no-entrypoint")] pub fn process_instruction( program_id: &Pubkey, accounts: &[solana_program::account_info::AccountInfo], data: &[u8], ) -> ProgramResult { process_instruction(program_id, accounts, data) }
src/lib.rsrustpub mod instruction; pub mod processor; pub mod state; pub mod error;
src/instruction.rsrustuse solana_program::program_error::ProgramError; use solana_program::instruction::{AccountMeta, Instruction}; use solana_program::pubkey::Pubkey; use solana_program::sysvar; #[derive(Clone, Debug, PartialEq)] pub enum TokenInstruction { Transfer { amount: u64 }, Mint { amount: u64 }, } impl TokenInstruction { pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> { let (tag, rest) = input.split_at(1); match tag { [0] => { let amount = rest .get(0..8) .and_then(|bytes| Some(u64::from_le_bytes(bytes.try_into().unwrap()))) .ok_or(ProgramError::InvalidInstructionData)?; Ok(TokenInstruction::Transfer { amount }) } [1] => { let amount = rest .get(0..8) .and_then(|bytes| Some(u64::from_le_bytes(bytes.try_into().unwrap()))) .ok_or(ProgramError::InvalidInstructionData)?; Ok(TokenInstruction::Mint { amount }) } _ => Err(ProgramError::InvalidInstructionData), } } pub fn pack(&self) -> Vec<u8> { match *self { TokenInstruction::Transfer { amount } => { let mut data = vec![0]; data.extend_from_slice(&amount.to_le_bytes()); data } TokenInstruction::Mint { amount } => { let mut data = vec![1]; data.extend_from_slice(&amount.to_le_bytes()); data } } } }
src/processor.rsrustuse solana_program::{ account_info::{AccountInfo, next_account_info}, entrypoint::ProgramResult, msg, pubkey::Pubkey, system_instruction, }; use crate::{instruction::TokenInstruction, state::TokenState, error::TokenError}; use solana_program::program::invoke_signed; use std::convert::TryInto; pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8], ) -> ProgramResult { let instruction = TokenInstruction::unpack(data)?; match instruction { TokenInstruction::Transfer { amount } => { process_transfer(accounts, amount, program_id) } TokenInstruction::Mint { amount } => { process_mint(accounts, amount, program_id) } } } fn process_transfer(accounts: &[AccountInfo], amount: u64, program_id: &Pubkey) -> ProgramResult { let accounts_iter = &mut accounts.iter(); let sender_account = next_account_info(accounts_iter)?; let recipient_account = next_account_info(accounts_iter)?; let mut sender_balance = TokenState::from_account(sender_account)?; let mut recipient_balance = TokenState::from_account(recipient_account)?; if sender_balance.balance < amount { return Err(TokenError::InsufficientFunds.into()); } sender_balance.balance -= amount; recipient_balance.balance += amount; sender_balance.save(sender_account)?; recipient_balance.save(recipient_account)?; msg!("Transferred {} tokens from {} to {}", amount, sender_account.key, recipient_account.key); Ok(()) } fn process_mint(accounts: &[AccountInfo], amount: u64, program_id: &Pubkey) -> ProgramResult { let accounts_iter = &mut accounts.iter(); let admin_account = next_account_info(accounts_iter)?; if !admin_account.is_signer { return Err(TokenError::Unauthorized.into()); } let mint_account = next_account_info(accounts_iter)?; let mut mint_balance = TokenState::from_account(mint_account)?; mint_balance.balance += amount; mint_balance.save(mint_account)?; msg!("Minted {} tokens to {}", amount, mint_account.key); Ok(()) }
src/state.rsrustuse solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; use solana_program::program_error::ProgramError; use solana_program::borsh::BorshDeserialize; #[derive(Clone, Debug, BorshDeserialize)] pub struct TokenState { pub balance: u64, } impl TokenState { pub fn from_account(account_info: &AccountInfo) -> Result<Self, ProgramError> { let data = &account_info.data.borrow(); let state: TokenState = TokenState::try_from_slice(data)?; Ok(state) } pub fn save(&self, account_info: &AccountInfo) -> Result<(), ProgramError> { let mut data = account_info.data.borrow_mut(); self.try_to_slice_mut(&mut data)?; Ok(()) } }
src/error.rsrustuse solana_program::program_error::ProgramError; #[derive(Debug)] pub enum TokenError { InsufficientFunds, Unauthorized, } impl From<TokenError> for ProgramError { fn from(e: TokenError) -> Self { match e { TokenError::InsufficientFunds => ProgramError::Custom(0), TokenError::Unauthorized => ProgramError::Custom(1), } } }
tests/test_token_program.rsrustuse solana_program::{program_pack::Pack, pubkey::Pubkey}; use solana_sdk::{ account::Account, transaction::Transaction, signer::Signer, transport::TransportError, system_program, }; #[cfg(test)] mod tests { use super::*; #[test] fn test_transfer_tokens() { let from_balance = 100; let to_balance = 50; let mut from_account = Account::new(from_balance, 0, &Pubkey::new_unique()); let mut to_account = Account::new(to_balance, 0, &Pubkey::new_unique()); let amount_to_transfer = 30; // Transfer tokens logic transfer_tokens(&mut from_account, &mut to_account, amount_to_transfer).unwrap(); assert_eq!(from_account.lamports, from_balance - amount_to_transfer); assert_eq!(to_account.lamports, to_balance + amount