Skip to content

Commit 58de8b9

Browse files
feat: add Quasar variants for repository-layout, rent, and cross-program-invocation
1 parent 4962ffe commit 58de8b9

File tree

38 files changed

+1384
-0
lines changed

38 files changed

+1384
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Cross-Program Invocation — Quasar
2+
3+
This example contains **two separate Quasar programs** that work together:
4+
5+
- **`lever/`** — A program with on-chain `PowerStatus` state and a `switch_power` instruction that toggles a boolean.
6+
- **`hand/`** — A program that calls the lever program's `switch_power` via CPI.
7+
8+
## Building
9+
10+
Each program is a separate Quasar workspace. Build them independently:
11+
12+
```bash
13+
cd lever && quasar build
14+
cd hand && quasar build
15+
```
16+
17+
The hand program must be built **after** the lever, since its tests load the lever's compiled `.so` file.
18+
19+
## Testing
20+
21+
```bash
22+
cd lever && cargo test
23+
cd hand && cargo test
24+
```
25+
26+
The hand tests load **both** programs into `QuasarSvm` and verify that the CPI correctly toggles the lever's power state.
27+
28+
## CPI Pattern
29+
30+
Quasar doesn't have a `declare_program!` equivalent for importing arbitrary program instruction types (unlike Anchor). Instead, the hand program:
31+
32+
1. Defines a **marker type** (`LeverProgram`) that implements the `Id` trait with the lever's program address
33+
2. Uses `Program<LeverProgram>` in the accounts struct for compile-time address + executable validation
34+
3. Builds the CPI instruction data **manually** using `BufCpiCall`, constructing the lever's wire format directly
35+
36+
This is lower-level than Anchor's CPI pattern but gives full control and works with any program.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Build artifacts
2+
/target
3+
4+
# Dependencies
5+
node_modules
6+
7+
# Environment
8+
.env
9+
.env.*
10+
11+
# OS
12+
.DS_Store
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
name = "quasar-hand"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# Standalone workspace — not part of the root program-examples workspace.
7+
[workspace]
8+
9+
[lints.rust.unexpected_cfgs]
10+
level = "warn"
11+
check-cfg = [
12+
'cfg(target_os, values("solana"))',
13+
]
14+
15+
[lib]
16+
crate-type = ["cdylib"]
17+
18+
[features]
19+
alloc = []
20+
client = []
21+
debug = []
22+
23+
[dependencies]
24+
quasar-lang = "0.0"
25+
solana-instruction = { version = "3.2.0" }
26+
27+
[dev-dependencies]
28+
quasar-svm = { version = "0.1" }
29+
solana-account = { version = "3.4.0" }
30+
solana-address = { version = "2.2.0", features = ["decode"] }
31+
solana-instruction = { version = "3.2.0", features = ["bincode"] }
32+
solana-pubkey = { version = "4.1.0" }
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[project]
2+
name = "quasar_hand"
3+
4+
[toolchain]
5+
type = "solana"
6+
7+
[testing]
8+
language = "rust"
9+
10+
[testing.rust]
11+
framework = "quasar-svm"
12+
13+
[testing.rust.test]
14+
program = "cargo"
15+
args = [
16+
"test",
17+
"tests::",
18+
]
19+
20+
[clients]
21+
languages = ["rust"]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod pull_lever;
2+
pub use pull_lever::*;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use quasar_lang::{
2+
cpi::{BufCpiCall, InstructionAccount},
3+
prelude::*,
4+
};
5+
6+
/// Accounts for the hand program's pull_lever instruction.
7+
/// The lever_program uses `Program<LeverProgram>` with a custom marker type
8+
/// that implements `Id` — this lets Quasar verify the program address and
9+
/// the executable flag during account parsing.
10+
#[derive(Accounts)]
11+
pub struct PullLever<'info> {
12+
#[account(mut)]
13+
pub power: &'info UncheckedAccount,
14+
pub lever_program: &'info Program<crate::LeverProgram>,
15+
}
16+
17+
impl<'info> PullLever<'info> {
18+
#[inline(always)]
19+
pub fn pull_lever(&self, name: &str) -> Result<(), ProgramError> {
20+
log("Hand is pulling the lever!");
21+
22+
// Build the switch_power instruction data for the lever program:
23+
// [disc=1] [name: u32 len + bytes]
24+
// 128 bytes is enough for any reasonable name.
25+
let mut data = [0u8; 128];
26+
let name_bytes = name.as_bytes();
27+
let data_len = 1 + 4 + name_bytes.len();
28+
29+
// Discriminator = 1 (switch_power)
30+
data[0] = 1;
31+
32+
// Name string: u32 little-endian length prefix + bytes
33+
let len_bytes = (name_bytes.len() as u32).to_le_bytes();
34+
data[1] = len_bytes[0];
35+
data[2] = len_bytes[1];
36+
data[3] = len_bytes[2];
37+
data[4] = len_bytes[3];
38+
39+
// Copy name bytes
40+
let mut i = 0;
41+
while i < name_bytes.len() {
42+
data[5 + i] = name_bytes[i];
43+
i += 1;
44+
}
45+
46+
let power_view = self.power.to_account_view();
47+
let lever_view = self.lever_program.to_account_view();
48+
49+
// Build CPI call with 1 account (power, writable, not a signer).
50+
let cpi = BufCpiCall::<1, 128>::new(
51+
lever_view.address(),
52+
[InstructionAccount::writable(power_view.address())],
53+
[power_view],
54+
data,
55+
data_len,
56+
);
57+
58+
cpi.invoke()
59+
}
60+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![cfg_attr(not(test), no_std)]
2+
3+
use quasar_lang::prelude::*;
4+
5+
mod instructions;
6+
use instructions::*;
7+
#[cfg(test)]
8+
mod tests;
9+
10+
declare_id!("Bi5N7SUQhpGknVcqPTzdFFVueQoxoUu8YTLz75J6fT8A");
11+
12+
/// The lever program's ID — used to verify the correct program is passed.
13+
pub const LEVER_PROGRAM_ID: Address = address!("E64FVeubGC4NPNF2UBJYX4AkrVowf74fRJD9q6YhwstN");
14+
15+
/// Marker type for the lever program, implementing `Id` so it can be used
16+
/// with `Program<LeverProgram>` in the accounts struct.
17+
pub struct LeverProgram;
18+
19+
impl Id for LeverProgram {
20+
const ID: Address = LEVER_PROGRAM_ID;
21+
}
22+
23+
#[program]
24+
mod quasar_hand {
25+
use super::*;
26+
27+
/// Pull the lever by invoking the lever program's switch_power via CPI.
28+
#[instruction(discriminator = 0)]
29+
pub fn pull_lever(ctx: Ctx<PullLever>, name: String) -> Result<(), ProgramError> {
30+
ctx.accounts.pull_lever(name)
31+
}
32+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use quasar_svm::{Account, Instruction, Pubkey, QuasarSvm};
2+
use solana_address::Address;
3+
4+
/// Lever program's program ID — must match the lever's declare_id!().
5+
fn lever_program_id() -> Pubkey {
6+
Pubkey::from(crate::LEVER_PROGRAM_ID)
7+
}
8+
9+
/// PowerStatus discriminator from the lever program.
10+
const POWER_STATUS_DISCRIMINATOR: u8 = 1;
11+
12+
fn setup() -> QuasarSvm {
13+
let hand_elf = include_bytes!("../target/deploy/quasar_hand.so");
14+
let lever_elf = include_bytes!("../../lever/target/deploy/quasar_lever.so");
15+
QuasarSvm::new()
16+
.with_program(&Pubkey::from(crate::ID), hand_elf)
17+
.with_program(&lever_program_id(), lever_elf)
18+
}
19+
20+
fn power_account(address: Pubkey, is_on: bool) -> Account {
21+
// Account data: [discriminator: u8] [is_on: u8]
22+
let data = vec![POWER_STATUS_DISCRIMINATOR, if is_on { 1 } else { 0 }];
23+
Account {
24+
address,
25+
lamports: 1_000_000_000,
26+
data,
27+
owner: lever_program_id(),
28+
executable: false,
29+
}
30+
}
31+
32+
/// Build pull_lever instruction data (discriminator = 0).
33+
/// Wire format: [disc=0] [name: String]
34+
fn build_pull_lever(name: &str) -> Vec<u8> {
35+
let mut data = vec![0u8]; // discriminator = 0
36+
data.extend_from_slice(&(name.len() as u32).to_le_bytes());
37+
data.extend_from_slice(name.as_bytes());
38+
data
39+
}
40+
41+
#[test]
42+
fn test_pull_lever_turns_on() {
43+
let mut svm = setup();
44+
45+
let (power_addr, _bump) = Pubkey::find_program_address(&[b"power"], &lever_program_id());
46+
47+
let ix = Instruction {
48+
program_id: Pubkey::from(crate::ID),
49+
accounts: vec![
50+
solana_instruction::AccountMeta::new(
51+
Address::from(power_addr.to_bytes()),
52+
false,
53+
),
54+
solana_instruction::AccountMeta::new_readonly(
55+
Address::from(lever_program_id().to_bytes()),
56+
false,
57+
),
58+
],
59+
data: build_pull_lever("Alice"),
60+
};
61+
62+
// The lever program account is provided by the SVM (loaded via with_program).
63+
// Only the power data account needs to be passed explicitly.
64+
let result = svm.process_instruction(
65+
&ix,
66+
&[power_account(power_addr, false)],
67+
);
68+
69+
result.assert_success();
70+
71+
let logs = result.logs.join("\n");
72+
assert!(logs.contains("Hand is pulling"), "hand should log");
73+
assert!(logs.contains("pulling the power switch"), "lever should log");
74+
assert!(logs.contains("now on"), "power should turn on");
75+
76+
let account = result.account(&power_addr).unwrap();
77+
assert_eq!(account.data[1], 1, "power should be on");
78+
}
79+
80+
#[test]
81+
fn test_pull_lever_turns_off() {
82+
let mut svm = setup();
83+
84+
let (power_addr, _bump) = Pubkey::find_program_address(&[b"power"], &lever_program_id());
85+
86+
let ix = Instruction {
87+
program_id: Pubkey::from(crate::ID),
88+
accounts: vec![
89+
solana_instruction::AccountMeta::new(
90+
Address::from(power_addr.to_bytes()),
91+
false,
92+
),
93+
solana_instruction::AccountMeta::new_readonly(
94+
Address::from(lever_program_id().to_bytes()),
95+
false,
96+
),
97+
],
98+
data: build_pull_lever("Bob"),
99+
};
100+
101+
let result = svm.process_instruction(
102+
&ix,
103+
&[power_account(power_addr, true)],
104+
);
105+
106+
result.assert_success();
107+
108+
let logs = result.logs.join("\n");
109+
assert!(logs.contains("now off"), "power should turn off");
110+
111+
let account = result.account(&power_addr).unwrap();
112+
assert_eq!(account.data[1], 0, "power should be off");
113+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Build artifacts
2+
/target
3+
4+
# Dependencies
5+
node_modules
6+
7+
# Environment
8+
.env
9+
.env.*
10+
11+
# OS
12+
.DS_Store
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
name = "quasar-lever"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# Standalone workspace — not part of the root program-examples workspace.
7+
[workspace]
8+
9+
[lints.rust.unexpected_cfgs]
10+
level = "warn"
11+
check-cfg = [
12+
'cfg(target_os, values("solana"))',
13+
]
14+
15+
[lib]
16+
crate-type = ["cdylib"]
17+
18+
[features]
19+
alloc = []
20+
client = []
21+
debug = []
22+
23+
[dependencies]
24+
quasar-lang = "0.0"
25+
solana-instruction = { version = "3.2.0" }
26+
27+
[dev-dependencies]
28+
quasar-svm = { version = "0.1" }
29+
solana-account = { version = "3.4.0" }
30+
solana-address = { version = "2.2.0", features = ["decode"] }
31+
solana-instruction = { version = "3.2.0", features = ["bincode"] }
32+
solana-pubkey = { version = "4.1.0" }

0 commit comments

Comments
 (0)