mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-08-25 06:41:13 +00:00
add program deployment examples
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "test-program-methods"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[build-dependencies]
|
||||
risc0-build = { version = "3.0.3" }
|
||||
|
||||
[package.metadata.risc0]
|
||||
methods = ["guest"]
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
risc0_build::embed_methods();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "programs"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
risc0-zkvm = { version = "3.0.3", features = ['std'] }
|
||||
nssa-core = { path = "../../../../nssa/core" }
|
||||
serde = { version = "1.0.219", default-features = false }
|
||||
hex = "0.4.3"
|
||||
bytemuck = "1.24.0"
|
||||
@@ -0,0 +1,60 @@
|
||||
use nssa_core::program::{
|
||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
};
|
||||
|
||||
// Hello-world example program.
|
||||
//
|
||||
// This program reads an arbitrary sequence of bytes as its instruction
|
||||
// and appends those bytes to the `data` field of the single input account.
|
||||
//
|
||||
// Execution succeeds only if the input account is either:
|
||||
// - uninitialized, or
|
||||
// - already owned by this program.
|
||||
//
|
||||
// In case the input account is uninitialized, the program claims it.
|
||||
//
|
||||
// The updated account is emitted as the sole post-state.
|
||||
|
||||
type Instruction = Vec<u8>;
|
||||
|
||||
fn main() {
|
||||
// Read inputs
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: greeting,
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
// Unpack the input account pre state
|
||||
let [pre_state] = pre_states
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
|
||||
|
||||
// Construct the post state account values
|
||||
let post_account = {
|
||||
let mut this = pre_state.account.clone();
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&greeting);
|
||||
this.data = bytes
|
||||
.try_into()
|
||||
.expect("Data should fit within the allowed limits");
|
||||
this
|
||||
};
|
||||
|
||||
// Wrap the post state account values inside a `AccountPostState` instance.
|
||||
// This is used to forward the account claiming request if any
|
||||
let post_state = if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||
// This produces a claim request
|
||||
AccountPostState::new_claimed(post_account)
|
||||
} else {
|
||||
// This doesn't produce a claim request
|
||||
AccountPostState::new(post_account)
|
||||
};
|
||||
|
||||
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
||||
// with the previous values of the accounts, and the transition to the post states conforms
|
||||
// with the NSSA program rules.
|
||||
write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
use nssa_core::program::{
|
||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
};
|
||||
|
||||
// Hello-world with authorization example program.
|
||||
//
|
||||
// This program reads an arbitrary sequence of bytes as its instruction
|
||||
// and appends those bytes to the `data` field of the single input account.
|
||||
//
|
||||
// Execution succeeds only if the input account **is authorized** and is either:
|
||||
// - uninitialized, or
|
||||
// - already owned by this program.
|
||||
//
|
||||
// In case the input account is uninitialized, the program claims it.
|
||||
//
|
||||
// The updated account is emitted as the sole post-state.
|
||||
|
||||
type Instruction = Vec<u8>;
|
||||
|
||||
fn main() {
|
||||
// Read inputs
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: greeting,
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
// Unpack the input account pre state
|
||||
let [pre_state] = pre_states
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
|
||||
|
||||
// #### Difference with `hello_world` example here:
|
||||
// Fail if the input account is not authorized
|
||||
// The `is_authorized` field will be correctly populated or verified by the system if
|
||||
// authorization is provided.
|
||||
if !pre_state.is_authorized {
|
||||
panic!("Missing required authorization");
|
||||
}
|
||||
// ####
|
||||
|
||||
// Construct the post state account values
|
||||
let post_account = {
|
||||
let mut this = pre_state.account.clone();
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&greeting);
|
||||
this.data = bytes
|
||||
.try_into()
|
||||
.expect("Data should fit within the allowed limits");
|
||||
this
|
||||
};
|
||||
|
||||
// Wrap the post state account values inside a `AccountPostState` instance.
|
||||
// This is used to forward the account claiming request if any
|
||||
let post_state = if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||
// This produces a claim request
|
||||
AccountPostState::new_claimed(post_account)
|
||||
} else {
|
||||
// This doesn't produce a claim request
|
||||
AccountPostState::new(post_account)
|
||||
};
|
||||
|
||||
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
||||
// with the previous values of the accounts, and the transition to the post states conforms
|
||||
// with the NSSA program rules.
|
||||
write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata},
|
||||
program::{
|
||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
},
|
||||
};
|
||||
|
||||
// Hello-world with write + move_data example program.
|
||||
//
|
||||
// This program reads an instruction of the form `(function_id, data)` and
|
||||
// dispatches to either:
|
||||
//
|
||||
// - `write`: appends `data` to the `data` field of a single input account.
|
||||
// - `move_data`: moves all bytes from one account to another. The source account is cleared and the
|
||||
// destination account receives the appended bytes.
|
||||
//
|
||||
// Execution succeeds only if:
|
||||
// - the accounts involved are either uninitialized, or
|
||||
// - already owned by this program.
|
||||
//
|
||||
// In case an input account is uninitialized, the program will claim it when
|
||||
// producing the post-state.
|
||||
|
||||
type Instruction = (u8, Vec<u8>);
|
||||
const WRITE_FUNCTION_ID: u8 = 0;
|
||||
const MOVE_DATA_FUNCTION_ID: u8 = 1;
|
||||
|
||||
fn build_post_state(post_account: Account) -> AccountPostState {
|
||||
if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||
// This produces a claim request
|
||||
AccountPostState::new_claimed(post_account)
|
||||
} else {
|
||||
// This doesn't produce a claim request
|
||||
AccountPostState::new(post_account)
|
||||
}
|
||||
}
|
||||
|
||||
fn write(pre_state: AccountWithMetadata, greeting: Vec<u8>) -> AccountPostState {
|
||||
// Construct the post state account values
|
||||
let post_account = {
|
||||
let mut this = pre_state.account.clone();
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&greeting);
|
||||
this.data = bytes
|
||||
.try_into()
|
||||
.expect("Data should fit within the allowed limits");
|
||||
this
|
||||
};
|
||||
|
||||
build_post_state(post_account)
|
||||
}
|
||||
|
||||
fn move_data(
|
||||
from_pre: &AccountWithMetadata,
|
||||
to_pre: &AccountWithMetadata,
|
||||
) -> Vec<AccountPostState> {
|
||||
// Construct the post state account values
|
||||
let from_data: Vec<u8> = from_pre.account.data.clone().into();
|
||||
|
||||
let from_post = {
|
||||
let mut this = from_pre.account.clone();
|
||||
this.data = Default::default();
|
||||
build_post_state(this)
|
||||
};
|
||||
|
||||
let to_post = {
|
||||
let mut this = to_pre.account.clone();
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&from_data);
|
||||
this.data = bytes
|
||||
.try_into()
|
||||
.expect("Data should fit within the allowed limits");
|
||||
build_post_state(this)
|
||||
};
|
||||
|
||||
vec![from_post, to_post]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: (function_id, data),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let post_states = match (pre_states.as_slice(), function_id, data.len()) {
|
||||
([account_pre], WRITE_FUNCTION_ID, _) => {
|
||||
let post = write(account_pre.clone(), data);
|
||||
vec![post]
|
||||
}
|
||||
([account_from_pre, account_to_pre], MOVE_DATA_FUNCTION_ID, 0) => {
|
||||
move_data(account_from_pre, account_to_pre)
|
||||
}
|
||||
_ => panic!("invalid params"),
|
||||
};
|
||||
|
||||
write_nssa_outputs(instruction_words, pre_states, post_states);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use nssa_core::program::{
|
||||
AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs,
|
||||
write_nssa_outputs_with_chained_call,
|
||||
};
|
||||
|
||||
// Tail Call example program.
|
||||
//
|
||||
// This program shows how to chain execution to another program using `ChainedCall`.
|
||||
// It reads a single account, emits it unchanged, and then triggers a tail call
|
||||
// to the Hello World program with a fixed greeting.
|
||||
|
||||
|
||||
const HELLO_WORLD_PROGRAM_ID_HEX: &str =
|
||||
"7e99d6e2d158f4dea59597011da5d1c2eef17beed6667657f515b387035b935a";
|
||||
|
||||
fn hello_world_program_id() -> ProgramId {
|
||||
let hello_world_program_id_bytes: [u8; 32] = hex::decode(HELLO_WORLD_PROGRAM_ID_HEX)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
bytemuck::cast(hello_world_program_id_bytes)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Read inputs
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: _,
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<()>();
|
||||
|
||||
// Unpack the input account pre state
|
||||
let [pre_state] = pre_states
|
||||
.clone()
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
|
||||
|
||||
// Create the (unchanged) post state
|
||||
let post_state = AccountPostState::new(pre_state.account.clone());
|
||||
|
||||
// Create the chained call
|
||||
let chained_call_greeting: Vec<u8> = b"Hello from tail call".to_vec();
|
||||
let chained_call_instruction_data = risc0_zkvm::serde::to_vec(&chained_call_greeting).unwrap();
|
||||
let chained_call = ChainedCall {
|
||||
program_id: hello_world_program_id(),
|
||||
instruction_data: chained_call_instruction_data,
|
||||
pre_states,
|
||||
pda_seeds: vec![],
|
||||
};
|
||||
|
||||
// Write the outputs
|
||||
write_nssa_outputs_with_chained_call(
|
||||
instruction_data,
|
||||
vec![pre_state],
|
||||
vec![post_state],
|
||||
vec![chained_call],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
||||
Reference in New Issue
Block a user