# 淺入淺出 Solana Program ### Program 如何運作 LLVM -> ELF (include BPF) ![llvm arch](https://miro.medium.com/max/1116/1*KmC_EtMxS5ttRKGi8VYwgg.png) ---- ## Program 如何運作 ### 行為 & 資料 ```solidity= // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.16 <0.9.0; contract SimpleStorage { uint storedData; function set(uint x) public { storedData = x; } function get() public view returns (uint) { return storedData; } } ``` ---- ## Program 如何運作 ### 行為 (i.e instruction) ```rust= entrypoint!(process_greeting_account); pub fn process_greeting_account( program_id: &Pubkey, // Public key of the account the hello world program was loaded into accounts: &[AccountInfo], // The account to say hello to _instruction_data: &[u8], // Ignored, all helloworld instructions are hellos ) -> ProgramResult { msg!("Hello World Rust program entrypoint"); // Iterating accounts is safer then indexing let accounts_iter = &mut accounts.iter(); // Get the account to say hello to let account = next_account_info(accounts_iter)?; // The account must be owned by the program in order to modify its data if account.owner != program_id { msg!("Greeted account does not have the correct program id"); return Err(ProgramError::IncorrectProgramId); } // Increment and store the number of times the account has been greeted let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?; greeting_account.counter += 1; greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?; msg!("Greeted {} time(s)!", greeting_account.counter); Ok(()) } ``` ---- ## Program 如何運作 ### 資料 ```rust= /// Define the type of state stored in accounts #[derive(BorshSerialize, BorshDeserialize, Debug)] pub struct GreetingAccount { /// number of greetings pub counter: u32, } ``` --- ## Client 如何與 Program 互動 ### Program-Owned Account ```rust= system_instruction::create_account_with_seed( &pub_key, &program_account, &pub_key, seed, min_rent_exemption, space, program_id, ); ``` [create account transaction](https://explorer.solana.com/tx/an5QER5UAcF83MJH4yy3CR7mGx8YXb5jpSFaPsPws4TtGXyC3zBr27o9K4mRCKyBWSEbsgKsaGsC84PJwK7F9Ee?cluster=devnet) ---- ### Instruction ```rust= pub struct Instruction { /// Pubkey of the instruction processor that executes this instruction pub program_id: Pubkey, /// Metadata for what accounts should be passed to the instruction processor pub accounts: Vec<AccountMeta>, /// Opaque data passed to the instruction processor pub data: Vec<u8>, } pub struct AccountMeta { /// An account's public key pub pubkey: Pubkey, /// True if an Instruction requires a Transaction signature matching `pubkey`. pub is_signer: bool, /// True if the `pubkey` can be loaded as a read-write account. pub is_writable: bool, } pub struct AccountInfo<'a> { /// Public key of the account pub key: &'a Pubkey, /// Was the transaction signed by this account's public key? pub is_signer: bool, /// Is the account writable? pub is_writable: bool, /// The lamports in the account. Modifiable by programs. pub lamports: Rc<RefCell<&'a mut u64>>, /// The data held in this account. Modifiable by programs. pub data: Rc<RefCell<&'a mut [u8]>>, /// Program that owns this account pub owner: &'a Pubkey, /// This account's data contains a loaded program (and is now read-only) pub executable: bool, /// The epoch at which this account will next owe rent pub rent_epoch: Epoch, } ``` ---- ### Transaction - 一個 Message 含有多個 Instructions - 多個 Signatures ```rust= pub struct Message { /// The message header, identifying signed and read-only `account_keys` /// NOTE: Serialization-related changes must be paired with the direct read at sigverify. pub header: MessageHeader, /// All the account keys used by this transaction #[serde(with = "short_vec")] pub account_keys: Vec<Pubkey>, /// The id of a recent ledger entry. pub recent_blockhash: Hash, /// Programs that will be executed in sequence and committed in one atomic transaction if all /// succeed. #[serde(with = "short_vec")] pub instructions: Vec<CompiledInstruction>, } pub struct Transaction { pub signatures: Vec<Signature>, /// The message to sign. pub message: Message, } ``` ---- ### 如何產生 Transaction Signature 1. 找出 instructions 裡面所有 is_sign(true) 的 accounts 2. 給多對 key_pair(i.e private,public) 3. 找出對應 is_sign account 的 private key 對 message 加密產生 sign ```rust= pub fn create_account_with_seed( from_pubkey: &Pubkey, to_pubkey: &Pubkey, base: &Pubkey, ... ) -> Instruction { let account_metas = vec![ AccountMeta::new(*from_pubkey, true), AccountMeta::new(*to_pubkey, false), AccountMeta::new_readonly(*base, true), ]; Instruction::new_with_bincode(...,account_metas) } ``` --- ## 如何寫一個 Solana Program ### 環境準備 ```shell= // rust 安裝 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustc --version cargo --version // solana cli 安裝 sh -c "$(curl -sSfL https://release.solana.com/v1.8.0/install)" solana --version ``` ---- ### hello-world project [project](https://github.com/solana-labs/example-helloworld) ```shell= solana config set --url https://api.devnet.solana.com solana-keygen new --force cat ~/.config/solana/id.json cat ~/.config/solana/cli/config.yml solana airdrop 5 npm run build:program-rust solana program deploy dist/program/helloworld.so npm install npm run start ``` ---- ### bank account program [project](https://github.com/vx416/solana_play) ```shell= cargo build-bpf solana program deploy ./target/deploy/program.so cargo run --bin bank ``` ---- ### solana token (spl) ```shell= cargo install spl-token-cli spl-token create-token spl-token create-account {program_id} spl-token accounts spl-token mint {program_id} 100 spl-token transfer {program_id} 50 {to_account} ``` [ref](https://spl.solana.com/token) ---- ### how does spl work [solana-program lib](https://github.com/solana-labs/solana-program-library) ```rust= pub struct Account { /// The mint associated with this account pub mint: Pubkey, /// The owner of this account. pub owner: Pubkey, /// The amount of tokens this account holds. pub amount: u64, /// If `delegate` is `Some` then `delegated_amount` represents /// the amount authorized by the delegate pub delegate: COption<Pubkey>, /// The account's state pub state: AccountState, /// If is_some, this is a native token, and the value logs the rent-exempt reserve. An Account /// is required to be rent-exempt, so the value is used by the Processor to ensure that wrapped /// SOL accounts do not drop below this threshold. pub is_native: COption<u64>, /// The amount delegated pub delegated_amount: u64, /// Optional authority to close the account. pub close_authority: COption<Pubkey>, } ``` --- ## SPL token 運作 ### standard solana program layout ``` |- state.rs (資料結構) |- instruction.rs (client 打 program 的指令) |- processor.rs (處理 instruction 的邏輯) |- error.rs (error message 定義) |- entrypoint.rs (宣告 program 進入點) |- lib.rs (初始化進入點) ``` ---- ### SPL token state 解析 #### mint 代表 token ```rust= pub struct Mint { pub mint_authority: COption<Pubkey>, pub supply: u64, pub decimals: u8, pub is_initialized: bool, pub freeze_authority: COption<Pubkey>, } ``` ---- ### SPL token state 解析 #### account 代表 不同 user 在不同 token 下的帳戶 ```rust= pub struct Account { pub mint: Pubkey, pub owner: Pubkey, pub amount: u64, pub delegate: COption<Pubkey>, pub state: AccountState, pub is_native: COption<u64>, pub delegated_amount: u64, pub close_authority: COption<Pubkey>, } ``` ---- ### SPL token 初始化 instruction ```rust= pub enum TokenInstruction { InitializeMint { decimals: u8, mint_authority: Pubkey, freeze_authority: COption<Pubkey>, }, InitializeAccount, ... } ``` ---- ### SPL token 轉錢 instruction ```rust= pub enum TokenInstruction { MintTo { amount: u64, }, Burn { amount: u64, }, Transfer { amount: u64, }, ... } ``` ---- ### SPL 序列化方式 #### rust : bytes 解析 ```rust= pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> { use TokenError::InvalidInstruction; let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?; Ok(match tag { 0 => { let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?; let (mint_authority, rest) = Self::unpack_pubkey(rest)?; let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?; Self::InitializeMint { mint_authority, freeze_authority, decimals, } } ... ``` ---- ### SPL 序列化方式 - javascript : BufferLayout library ```javascript= static createInitMintInstruction( programId: PublicKey, mint: PublicKey, decimals: number, mintAuthority: PublicKey, freezeAuthority: PublicKey | null, ): TransactionInstruction { let keys = [ {pubkey: mint, isSigner: false, isWritable: true}, {pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false}, ]; const commandDataLayout = BufferLayout.struct([ BufferLayout.u8('instruction'), BufferLayout.u8('decimals'), Layout.publicKey('mintAuthority'), BufferLayout.u8('option'), Layout.publicKey('freezeAuthority'), ]); let data = Buffer.alloc(1024); { const encodeLength = commandDataLayout.encode( { instruction: 0, // InitializeMint instruction decimals, mintAuthority: pubkeyToBuffer(mintAuthority), option: freezeAuthority === null ? 0 : 1, freezeAuthority: pubkeyToBuffer(freezeAuthority || new PublicKey(0)), }, data, ); data = data.slice(0, encodeLength); } return new TransactionInstruction({ keys, programId, data, }); } ``` --- ## References - [spl-token](https://spl.solana.com/token) - [how-to-deploy-solana-program](https://blog.chain.link/how-to-build-and-deploy-a-solana-smart-contract/) - [install-solana-toolkit](https://docs.solana.com/cli/install-solana-cli-tools) - [solana program overview](https://docs.solana.com/developing/programming-model/overview) - [solana-transaction-in-depth](https://medium.com/@asmiller1989/solana-transactions-in-depth-1f7f7fe06ac2) - [solana-program-library](https://github.com/solana-labs/solana-program-library) - [solana-example-helloworld](https://github.com/solana-labs/example-helloworld) - [solana-web3js](https://docs.solana.com/developing/clients/javascript-api) - [solana-rust-client](https://docs.rs/solana-client/1.8.0/solana_client/) - [solana-develop-with-rust](https://docs.solana.com/developing/on-chain-programs/developing-rust)
{"metaMigratedAt":"2023-06-16T12:17:57.702Z","metaMigratedFrom":"Content","title":"淺入淺出 Solana Program","breaks":true,"contributors":"[{\"id\":\"4b479e8b-6efd-4468-9f9c-7de538e8e994\",\"add\":10083,\"del\":774}]"}
    522 views