# 淺入淺出 Solana Program
### Program 如何運作
LLVM -> ELF (include BPF)

----
## 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}]"}