Pārlūkot izejas kodu

add counter program to examples

bidhan-a 1 mēnesi atpakaļ
vecāks
revīzija
e42cff98e7

+ 11 - 0
examples/sbpf-asm-counter/.gitignore

@@ -0,0 +1,11 @@
+build/**/*
+deploy/**/*
+node_modules
+.sbpf
+.DS_Store
+.vscode
+keypair.json
+package-lock.json
+test-ledger
+yarn.lock
+target

+ 13 - 0
examples/sbpf-asm-counter/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "sbpf-asm-counter"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+
+[dev-dependencies]
+mollusk-svm = "0.4.1"
+solana-sdk = "2.2.1"
+
+[features]
+test-sbf = []

+ 33 - 0
examples/sbpf-asm-counter/README.md

@@ -0,0 +1,33 @@
+# sbpf-asm-counter
+
+A Solana program written in sBPF Assembly that allows users to create and increment an on-chain counter. 
+
+Its main purpose is to demonstrate how to create an account and manage account data directly using sBPF.
+
+It utilizes the following syscalls:
+
+- `sol_create_program_address`
+- `sol_memcmp_`
+- `sol_get_rent_sysvar`
+- `sol_memcpy_`
+- `sol_invoke_signed_c`
+
+## Build
+
+To build the program, run the following command:
+
+```bash
+sbpf build
+```
+
+## Test
+
+To test the program, run the following command:
+
+```bash
+sbpf test
+```
+
+---
+
+Created with [sbpf](https://github.com/blueshift-gg/sbpf)

+ 124 - 0
examples/sbpf-asm-counter/src/lib.rs

@@ -0,0 +1,124 @@
+#[cfg(test)]
+mod tests {
+    use mollusk_svm::program;
+    use mollusk_svm::{result::Check, Mollusk};
+    use solana_sdk::account::Account;
+    use solana_sdk::instruction::{AccountMeta, Instruction};
+    use solana_sdk::native_token::LAMPORTS_PER_SOL;
+    use solana_sdk::pubkey::Pubkey;
+
+    const BASE_LAMPORTS: u64 = 10 * LAMPORTS_PER_SOL;
+    const COUNTER_SIZE: usize = 9;
+
+    pub fn get_program_id() -> Pubkey {
+        let program_id_keypair_bytes = std::fs::read("deploy/sbpf-asm-counter-keypair.json")
+            .unwrap()[..32]
+            .try_into()
+            .expect("slice with incorrect length");
+        Pubkey::new_from_array(program_id_keypair_bytes)
+    }
+
+    #[test]
+    fn test_initialize() {
+        let program_id = get_program_id();
+        let mollusk = Mollusk::new(&program_id, "deploy/sbpf-asm-counter");
+        let (system_program, system_account) = program::keyed_account_for_system_program();
+
+        let owner_pubkey = Pubkey::new_unique();
+        let owner_account = Account::new(BASE_LAMPORTS, 0, &system_program);
+
+        let (counter_pda, counter_bump) =
+            Pubkey::find_program_address(&[b"counter", &owner_pubkey.to_bytes()], &program_id);
+        let counter_account = Account::new(0, 0, &system_program);
+
+        let mut instruction_data = vec![0]; // 0 -> Initialize
+        instruction_data.extend_from_slice(&counter_bump.to_le_bytes());
+
+        let instruction = Instruction::new_with_bytes(
+            program_id,
+            &instruction_data,
+            vec![
+                AccountMeta::new(owner_pubkey, true),
+                AccountMeta::new(counter_pda, false),
+                AccountMeta::new_readonly(system_program, false),
+            ],
+        );
+
+        let mut expected_data = Vec::with_capacity(9);
+        expected_data.push(counter_bump);
+        expected_data.extend_from_slice(&0u64.to_le_bytes());
+
+        let expected_lamports = mollusk.sysvars.rent.minimum_balance(COUNTER_SIZE);
+
+        mollusk.process_and_validate_instruction(
+            &instruction,
+            &[
+                (owner_pubkey, owner_account),
+                (counter_pda, counter_account),
+                (system_program, system_account.clone()),
+            ],
+            &[
+                Check::success(),
+                // Check if account was initialized with minimum balance for rent exemption.
+                Check::account(&counter_pda)
+                    .lamports(expected_lamports)
+                    .build(),
+                // Check if account was initialized with expected data.
+                Check::account(&counter_pda).data(&expected_data).build(),
+            ],
+        );
+    }
+
+    #[test]
+    fn test_increment() {
+        let program_id = get_program_id();
+        let mollusk = Mollusk::new(&program_id, "deploy/sbpf-asm-counter");
+        let (system_program, system_account) = program::keyed_account_for_system_program();
+
+        let owner_pubkey = Pubkey::new_unique();
+        let owner_account = Account::new(BASE_LAMPORTS, 0, &system_program);
+
+        let (counter_pda, counter_bump) =
+            Pubkey::find_program_address(&[b"counter", &owner_pubkey.to_bytes()], &program_id);
+        let mut counter_account = Account::new(
+            mollusk.sysvars.rent.minimum_balance(COUNTER_SIZE),
+            COUNTER_SIZE,
+            &&program_id.into(),
+        );
+
+        let mut counter_data = Vec::with_capacity(9);
+        counter_data.push(counter_bump);
+        counter_data.extend_from_slice(&0u64.to_le_bytes()); // Initial count -> 0
+        counter_account.data = counter_data;
+
+        let mut instruction_data = vec![1]; // 1 -> Increment
+        instruction_data.extend_from_slice(&counter_bump.to_le_bytes());
+
+        let instruction = Instruction::new_with_bytes(
+            program_id,
+            &instruction_data,
+            vec![
+                AccountMeta::new(owner_pubkey, true),
+                AccountMeta::new(counter_pda, false),
+                AccountMeta::new_readonly(system_program, false),
+            ],
+        );
+
+        let mut expected_data = Vec::with_capacity(9);
+        expected_data.push(counter_bump);
+        expected_data.extend_from_slice(&1u64.to_le_bytes()); // Expected count -> 1
+
+        mollusk.process_and_validate_instruction(
+            &instruction,
+            &[
+                (owner_pubkey, owner_account),
+                (counter_pda, counter_account),
+                (system_program, system_account.clone()),
+            ],
+            &[
+                Check::success(),
+                Check::account(&counter_pda).data(&expected_data).build(),
+            ],
+        );
+    }
+}

+ 324 - 0
examples/sbpf-asm-counter/src/sbpf-asm-counter/sbpf-asm-counter.s

@@ -0,0 +1,324 @@
+
+.equ NUM_ACCOUNTS, 0x0000
+
+.equ OWNER_HEADER, 0x0008
+.equ OWNER_KEY, 0x0010
+.equ OWNER_OWNER, 0x0030
+.equ OWNER_LAMPORTS, 0x0050
+.equ OWNER_DATA_LEN, 0x0058
+.equ OWNER_DATA, 0x0060
+.equ OWNER_RENT_EPOCH, 0x2860
+
+.equ COUNTER_HEADER, 0x2868
+.equ COUNTER_KEY, 0x2870
+.equ COUNTER_OWNER, 0x2890
+.equ COUNTER_LAMPORTS, 0x28b0
+.equ COUNTER_DATA_LEN, 0x28b8
+.equ COUNTER_DATA, 0x28c0
+.equ COUNTER_RENT_EPOCH, 0x50c0
+
+.equ SYSTEM_PROGRAM_HEADER, 0x50c8
+.equ SYSTEM_PROGRAM_KEY, 0x50d0
+.equ SYSTEM_PROGRAM_OWNER, 0x50f0
+.equ SYSTEM_PROGRAM_LAMPORTS, 0x5110
+.equ SYSTEM_PROGRAM_DATA_LEN, 0x5118
+.equ SYSTEM_PROGRAM_DATA, 0x5120
+.equ SYSTEM_PROGRAM_RENT_EPOCH, 0x7930
+
+.equ INSTRUCTION_DATA_LEN, 0x7938
+.equ INSTRUCTION_DATA, 0x7940
+.equ PROGRAM_ID, 0x7942
+
+.equ COUNTER_SEED, 0x7265746e756f63
+.equ COUNTER_DATA_SIZE, 0x9                                       # 9 (u8 bump + u64 counter)
+
+.equ ACCOUNT_STORAGE_OVERHEAD, 0x80                               # 128
+
+
+# NOTE: We are using a DEFAULT_RENT_EXEMPTION_THRESHOLD value of 2 to avoid floating-point math.
+# Check here for more details: https://github.com/solana-foundation/solana-improvement-documents/pull/194
+.equ DEFAULT_RENT_EXEMPTION_THRESHOLD, 0x2
+
+
+.globl entrypoint
+
+
+entrypoint:
+  ldxdw r3, [r1 + COUNTER_DATA_LEN]
+  mov64 r6, r1
+  jeq r3, 0, check_instruction
+  add64 r6, 16                                                    # COUNTER_DATA_SIZE + round up to 8
+
+check_instruction:
+  ldxdw r4, [r6 + INSTRUCTION_DATA_LEN]
+  jne r4, 2, error_invalid_instruction
+
+  ##########################
+  ##     Prepare seeds    ##
+  ##########################
+
+  mov64 r9, r10
+  sub64 r9, 8
+  lddw r2, COUNTER_SEED
+  stxdw [r9 + 0], r2
+
+  mov64 r8, r9
+  sub64 r8, 8
+  ldxb r2, [r6 + INSTRUCTION_DATA + 1]
+  stxdw [r8 + 0], r2
+
+  mov64 r5, r8
+  sub64 r5, 48
+
+  # First seed ("counter")
+  mov64 r2, r5                 
+  stxdw [r2 + 0], r9          
+  lddw r3, 7
+  stxdw [r2 + 8], r3           
+
+  # Second seed (owner key)
+  add64 r2, 16
+  mov64 r4, r1
+  add64 r4, OWNER_KEY
+  stxdw [r2 + 0], r4
+  lddw r3, 32
+  stxdw [r2 + 8], r3
+
+  # bump
+  add64 r2, 16
+  stxdw [r2 + 0], r8
+  lddw r3, 1
+  stxdw [r2 + 8], r3
+
+  ##########################
+  ##      Validate PDA    ##
+  ##########################
+
+  mov64 r7, r1
+  mov64 r1, r5 
+  lddw r2, 3 
+  mov64 r3, r6
+  add64 r3, PROGRAM_ID 
+  mov64 r4, r5 
+  sub64 r4, 32
+  call sol_create_program_address
+  
+  mov64 r1, r4
+  mov64 r2, r7
+  add64 r2, COUNTER_KEY
+  lddw r3, 32
+  mov64 r4, r5 
+  sub64 r4, 4
+  call sol_memcmp_
+
+  ldxw r1, [r4 + 0]
+  jne r1, 0x0, error_invalid_pda
+
+  # Branch based on instruction type.
+  mov64 r1, r7
+  ldxb r4, [r6 + INSTRUCTION_DATA + 0]
+  jeq r4, 0x0, initialize
+  jeq r4, 0x1, increment
+  ja error_invalid_instruction
+
+
+initialize:
+
+  ##########################
+  ## Set up account metas ##
+  ##########################
+
+  mov64 r9, r5
+  sub64 r9, 32
+
+  # Owner
+  mov64 r2, r9
+  mov64 r3, r1
+  add64 r3, OWNER_KEY
+  stxdw [r2 + 0], r3                                              # pubkey
+  ldxb r3, [r1 + OWNER_HEADER + 2]
+  stxb [r2 + 8], r3                                               # is_writable
+  ldxb r3, [r1 + OWNER_HEADER + 1]
+  stxb [r2 + 9], r3                                               # is_signer
+
+  # Counter
+  add64 r2, 16
+  mov64 r3, r1
+  add64 r3, COUNTER_KEY
+  stxdw [r2 + 0], r3                                              # pubkey
+  ldxb r3, [r1 + COUNTER_HEADER + 2]
+  stxb [r2 + 8], r3                                               # is_writable
+  lddw r3, 1
+  stxb [r2 + 9], r3                                               # is_signer
+
+  #############################
+  ## Set up instruction data ##
+  #############################
+
+  mov64 r7, r1
+  mov64 r6, r9
+  sub64 r6, 48
+  
+  # Find the minimum balance for rent exemption
+  mov64 r1, r6
+  call sol_get_rent_sysvar
+  ldxdw r3, [r1 + 0]                                              # lamports_per_byte_year
+  lddw r4, ACCOUNT_STORAGE_OVERHEAD
+  add64 r4, COUNTER_DATA_SIZE
+  mul64 r4, r3
+  mul64 r4, DEFAULT_RENT_EXEMPTION_THRESHOLD
+
+  mov64 r8, r6
+  sub64 r8, 56
+
+  # Build instruction to create an account
+  mov64 r2, r8
+  lddw r3, 0                                                      # Instruction discriminator (0 = CreateAccount)
+  stxw [r2 + 0], r3                
+  stxdw [r2 + 4], r4                                              # Lamports for rent exemption
+  lddw r3, COUNTER_DATA_SIZE
+  stxdw [r2 + 12], r3                                             # Account space
+
+  mov64 r1, r2                                                    # Owner (32 byte program ID)
+  add64 r1, 20
+  mov64 r2, r7
+  add64 r2, PROGRAM_ID
+  lddw r3, 32
+  call sol_memcpy_
+
+
+  ############################
+  ## Set up the instruction ##
+  ############################
+
+  mov64 r1, r7
+  mov64 r7, r8
+  sub64 r7, 40
+
+  mov64 r2, r7
+  mov64 r3, r1
+  add64 r3, SYSTEM_PROGRAM_KEY
+  stxdw [r2 + 0], r3                                              # program_id
+  mov64 r3, r9
+  stxdw [r2 + 8], r3                                              # accounts      
+  lddw r3, 2
+  stxdw [r2 + 16], r3                                             # account_len
+  mov64 r3, r8
+  stxdw [r2 + 24], r3                                             # data                   
+  lddw r3, 52
+  stxdw [r2 + 32], r3                                             # data_len
+
+  ##########################
+  ## Set up account infos ##
+  ##########################
+
+  mov64 r6, r7
+  sub64 r6, 112
+
+  # Owner
+  mov64 r2, r6
+  mov64 r3, r1 
+  add64 r3, OWNER_KEY
+  stxdw [r2 + 0], r3                                              # key
+  mov64 r3, r1 
+  add64 r3, OWNER_LAMPORTS
+  stxdw [r2 + 8], r3                                              # lamports
+  ldxdw r3, [r1 + OWNER_DATA_LEN]
+  stxdw [r2 + 16], r3                                             # data_len
+  mov64 r3, r1 
+  add64 r3, OWNER_DATA
+  stxdw [r2 + 24], r3                                             # data
+  mov64 r3, r1 
+  add64 r3, OWNER_OWNER
+  stxdw [r2 + 32], r3                                             # owner
+  ldxdw r3, [r1 + OWNER_RENT_EPOCH]
+  stxdw [r2 + 40], r3                                             # rent_epoch
+  ldxb r3, [r1 + OWNER_HEADER + 1]
+  stxb [r2 + 48], r3                                              # is_signer
+  ldxb r3, [r1 + OWNER_HEADER + 2]
+  stxb [r2 + 49], r3                                              # is_writable
+  ldxb r3, [r1 + OWNER_HEADER + 3]
+  stxb [r2 + 50], r3                                              # is_executable
+
+  # Counter
+  add64 r2, 56
+  mov64 r3, r1
+  add64 r3, COUNTER_KEY
+  stxdw [r2 + 0], r3
+  mov64 r3, r1                                                    # key
+  add64 r3, COUNTER_LAMPORTS
+  stxdw [r2 + 8], r3                                              # lamports
+  ldxdw r3, [r1 + COUNTER_DATA_LEN]
+  stxdw [r2 + 16], r3                                             # data_len
+  mov64 r3, r1
+  add64 r3, COUNTER_DATA
+  stxdw [r2 + 24], r3                                             # data
+  mov64 r3, r1
+  add64 r3, COUNTER_OWNER
+  stxdw [r2 + 32], r3                                             # owner
+  ldxdw r3, [r1 + COUNTER_RENT_EPOCH]
+  stxdw [r2 + 40], r3                                             # rent_epoch
+  ldxb r3, [r1 + COUNTER_HEADER + 1]
+  stxb [r2 + 48], r3                                              # is_signer
+  ldxb r3, [r1 + COUNTER_HEADER + 2]
+  stxb [r2 + 49], r3                                              # is_writable
+  ldxb r3, [r1 + COUNTER_HEADER + 3]
+  stxb [r2 + 50], r3                                              # is_executable
+
+  ##########################
+  ##  Set up signer seeds ##
+  ##########################
+
+  mov64 r9, r6
+  sub64 r9, 16
+
+  mov64 r2, r9
+  stxdw [r2 + 0], r5
+  lddw r3, 3                                                      
+  stxdw [r2 + 8], r3     
+
+
+  ####################
+  ## Invoke the CPI ##
+  ####################
+  
+  mov64 r8, r1
+  mov64 r1, r7                                                    # Instruction
+  mov64 r2, r6                                                    # Account infos
+  lddw r3, 2                                                      # Number of account infos
+  mov64 r4, r9                                                    # Seeds
+  lddw r5, 1                                                      # Seeds count
+  call sol_invoke_signed_c
+
+  # Write data to the newly created account
+  ldxb r2, [r8 + INSTRUCTION_DATA + 1]
+  stxb [r8 + COUNTER_DATA], r2                                    # Bump
+  lddw r2, 0
+  stxdw [r8 + COUNTER_DATA + 1], r2                               # Initial counter value (0)
+
+  exit
+
+increment:
+
+  # Check if owner is the signer
+  ldxb r3, [r1 + OWNER_HEADER + 1]
+  jeq r3, 0, error_invalid_signature
+
+  # Increment count by 1
+  ldxdw r2, [r1 + COUNTER_DATA + 1]
+  add64 r2, 1
+  stxdw [r1 + COUNTER_DATA + 1], r2
+
+  exit
+
+error_invalid_instruction:
+  lddw r0, 0xa
+  exit
+
+error_invalid_pda:
+  lddw r0, 0xb
+  exit
+
+error_invalid_signature:
+  lddw r0, 0xc
+  exit