Merge pull request #426 from logos-blockchain/moudy/feat-self-program-id

add self_program_id
This commit is contained in:
Moudy
2026-04-02 21:15:32 +02:00
committed by GitHub
55 changed files with 191 additions and 37 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -19,6 +19,7 @@ fn main() {
// Read inputs
let (
ProgramInput {
self_program_id,
pre_states,
instruction: greeting,
},
@@ -50,5 +51,11 @@ fn main() {
// with the NSSA program rules.
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
// called to commit the output.
ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state]).write();
ProgramOutput::new(
self_program_id,
instruction_data,
vec![pre_state],
vec![post_state],
)
.write();
}
@@ -19,6 +19,7 @@ fn main() {
// Read inputs
let (
ProgramInput {
self_program_id,
pre_states,
instruction: greeting,
},
@@ -57,5 +58,11 @@ fn main() {
// with the NSSA program rules.
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
// called to commit the output.
ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state]).write();
ProgramOutput::new(
self_program_id,
instruction_data,
vec![pre_state],
vec![post_state],
)
.write();
}
@@ -66,6 +66,7 @@ fn main() {
// Read input accounts.
let (
ProgramInput {
self_program_id,
pre_states,
instruction: (function_id, data),
},
@@ -85,5 +86,5 @@ fn main() {
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
// called to commit the output.
ProgramOutput::new(instruction_words, pre_states, post_states).write();
ProgramOutput::new(self_program_id, instruction_words, pre_states, post_states).write();
}
@@ -27,6 +27,7 @@ fn main() {
// Read inputs
let (
ProgramInput {
self_program_id,
pre_states,
instruction: (),
},
@@ -55,7 +56,12 @@ fn main() {
// Write the outputs.
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
// called to commit the output.
ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state])
.with_chained_calls(vec![chained_call])
.write();
ProgramOutput::new(
self_program_id,
instruction_data,
vec![pre_state],
vec![post_state],
)
.with_chained_calls(vec![chained_call])
.write();
}
@@ -33,6 +33,7 @@ fn main() {
// Read inputs
let (
ProgramInput {
self_program_id,
pre_states,
instruction: (),
},
@@ -68,7 +69,12 @@ fn main() {
// Write the outputs.
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
// called to commit the output.
ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state])
.with_chained_calls(vec![chained_call])
.write();
ProgramOutput::new(
self_program_id,
instruction_data,
vec![pre_state],
vec![post_state],
)
.with_chained_calls(vec![chained_call])
.write();
}
+14 -7
View File
@@ -16,6 +16,7 @@ pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
pub type ProgramId = [u32; 8];
pub type InstructionData = Vec<u32>;
pub struct ProgramInput<T> {
pub self_program_id: ProgramId,
pub pre_states: Vec<AccountWithMetadata>,
pub instruction: T,
}
@@ -281,6 +282,8 @@ pub struct InvalidWindow;
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
#[must_use = "ProgramOutput does nothing unless written"]
pub struct ProgramOutput {
/// The program ID of the program that produced this output.
pub self_program_id: ProgramId,
/// The instruction data the program received to produce this output.
pub instruction_data: InstructionData,
/// The account pre states the program received to produce this output.
@@ -297,11 +300,13 @@ pub struct ProgramOutput {
impl ProgramOutput {
pub const fn new(
self_program_id: ProgramId,
instruction_data: InstructionData,
pre_states: Vec<AccountWithMetadata>,
post_states: Vec<AccountPostState>,
) -> Self {
Self {
self_program_id,
instruction_data,
pre_states,
post_states,
@@ -415,11 +420,13 @@ pub fn compute_authorized_pdas(
/// Reads the NSSA inputs from the guest environment.
#[must_use]
pub fn read_nssa_inputs<T: DeserializeOwned>() -> (ProgramInput<T>, InstructionData) {
let self_program_id: ProgramId = env::read();
let pre_states: Vec<AccountWithMetadata> = env::read();
let instruction_words: InstructionData = env::read();
let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap();
(
ProgramInput {
self_program_id,
pre_states,
instruction,
},
@@ -620,7 +627,7 @@ mod tests {
#[test]
fn program_output_try_with_block_validity_window_range() {
let output = ProgramOutput::new(vec![], vec![], vec![])
let output = ProgramOutput::new(DEFAULT_PROGRAM_ID, vec![], vec![], vec![])
.try_with_block_validity_window(10_u64..100)
.unwrap();
assert_eq!(output.block_validity_window.start(), Some(10));
@@ -629,24 +636,24 @@ mod tests {
#[test]
fn program_output_with_block_validity_window_range_from() {
let output =
ProgramOutput::new(vec![], vec![], vec![]).with_block_validity_window(10_u64..);
let output = ProgramOutput::new(DEFAULT_PROGRAM_ID, vec![], vec![], vec![])
.with_block_validity_window(10_u64..);
assert_eq!(output.block_validity_window.start(), Some(10));
assert_eq!(output.block_validity_window.end(), None);
}
#[test]
fn program_output_with_block_validity_window_range_to() {
let output =
ProgramOutput::new(vec![], vec![], vec![]).with_block_validity_window(..100_u64);
let output = ProgramOutput::new(DEFAULT_PROGRAM_ID, vec![], vec![], vec![])
.with_block_validity_window(..100_u64);
assert_eq!(output.block_validity_window.start(), None);
assert_eq!(output.block_validity_window.end(), Some(100));
}
#[test]
fn program_output_try_with_block_validity_window_empty_range_fails() {
let result =
ProgramOutput::new(vec![], vec![], vec![]).try_with_block_validity_window(5_u64..5);
let result = ProgramOutput::new(DEFAULT_PROGRAM_ID, vec![], vec![], vec![])
.try_with_block_validity_window(5_u64..5);
assert!(result.is_err());
}
@@ -158,7 +158,7 @@ fn execute_and_prove_program(
) -> Result<Receipt, NssaError> {
// Write inputs to the program
let mut env_builder = ExecutorEnv::builder();
Program::write_inputs(pre_states, instruction_data, &mut env_builder)?;
Program::write_inputs(program.id(), pre_states, instruction_data, &mut env_builder)?;
let env = env_builder.build().unwrap();
// Prove the program
+9 -2
View File
@@ -58,7 +58,7 @@ impl Program {
// Write inputs to the program
let mut env_builder = ExecutorEnv::builder();
env_builder.session_limit(Some(MAX_NUM_CYCLES_PUBLIC_EXECUTION));
Self::write_inputs(pre_states, instruction_data, &mut env_builder)?;
Self::write_inputs(self.id, pre_states, instruction_data, &mut env_builder)?;
let env = env_builder.build().unwrap();
// Execute the program (without proving)
@@ -78,13 +78,20 @@ impl Program {
/// Writes inputs to `env_builder` in the order expected by the programs.
pub(crate) fn write_inputs(
program_id: ProgramId,
pre_states: &[AccountWithMetadata],
instruction_data: &[u32],
env_builder: &mut ExecutorEnvBuilder,
) -> Result<(), NssaError> {
env_builder
.write(&program_id)
.map_err(|e| NssaError::ProgramWriteInputFailed(e.to_string()))?;
let pre_states = pre_states.to_vec();
env_builder
.write(&(pre_states, instruction_data))
.write(&pre_states)
.map_err(|e| NssaError::ProgramWriteInputFailed(e.to_string()))?;
env_builder
.write(&instruction_data)
.map_err(|e| NssaError::ProgramWriteInputFailed(e.to_string()))?;
Ok(())
}
@@ -184,6 +184,12 @@ impl PublicTransaction {
);
}
// Verify that the program output's self_program_id matches the expected program ID.
ensure!(
program_output.self_program_id == chained_call.program_id,
NssaError::InvalidProgramBehavior
);
// Verify execution corresponds to a well-behaved program.
// See the # Programs section for the definition of the `validate_execution` method.
ensure!(
+9 -3
View File
@@ -14,6 +14,7 @@ use nssa_core::program::{ProgramInput, ProgramOutput, read_nssa_inputs};
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction,
},
@@ -152,7 +153,12 @@ fn main() {
}
};
ProgramOutput::new(instruction_words, pre_states_clone, post_states)
.with_chained_calls(chained_calls)
.write();
ProgramOutput::new(
self_program_id,
instruction_words,
pre_states_clone,
post_states,
)
.with_chained_calls(chained_calls)
.write();
}
@@ -4,6 +4,7 @@ use nssa_core::program::{ProgramInput, ProgramOutput, read_nssa_inputs};
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction,
},
@@ -56,7 +57,12 @@ fn main() {
}
};
ProgramOutput::new(instruction_words, pre_states_clone, post_states)
.with_chained_calls(chained_calls)
.write();
ProgramOutput::new(
self_program_id,
instruction_words,
pre_states_clone,
post_states,
)
.with_chained_calls(chained_calls)
.write();
}
@@ -67,6 +67,7 @@ fn main() {
// Read input accounts.
let (
ProgramInput {
self_program_id,
pre_states,
instruction: balance_to_move,
},
@@ -84,5 +85,5 @@ fn main() {
_ => panic!("invalid params"),
};
ProgramOutput::new(instruction_words, pre_states, post_states).write();
ProgramOutput::new(self_program_id, instruction_words, pre_states, post_states).write();
}
+2
View File
@@ -46,6 +46,7 @@ fn main() {
// It is expected to receive only two accounts: [pinata_account, winner_account]
let (
ProgramInput {
self_program_id,
pre_states,
instruction: solution,
},
@@ -79,6 +80,7 @@ fn main() {
.expect("Overflow when adding prize to winner");
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pinata, winner],
vec![
@@ -52,6 +52,7 @@ fn main() {
// winner_token_holding]
let (
ProgramInput {
self_program_id,
pre_states,
instruction: solution,
},
@@ -97,6 +98,7 @@ fn main() {
.with_pda_seeds(vec![PdaSeed::new([0; 32])]);
ProgramOutput::new(
self_program_id,
instruction_words,
vec![
pinata_definition,
@@ -107,6 +107,13 @@ impl ExecutionState {
|_: Infallible| unreachable!("Infallible error is never constructed"),
);
// Verify that the program output's self_program_id matches the expected program ID.
// This ensures the proof commits to which program produced the output.
assert_eq!(
program_output.self_program_id, chained_call.program_id,
"Program output self_program_id does not match chained call program_id"
);
// Check that the program is well behaved.
// See the # Programs section for the definition of the `validate_execution` method.
let execution_valid = validate_execution(
+8 -1
View File
@@ -12,6 +12,7 @@ use token_program::core::Instruction;
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction,
},
@@ -81,5 +82,11 @@ fn main() {
}
};
ProgramOutput::new(instruction_words, pre_states_clone, post_states).write();
ProgramOutput::new(
self_program_id,
instruction_words,
pre_states_clone,
post_states,
)
.write();
}
@@ -5,6 +5,7 @@ type Instruction = u128;
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction: balance_to_burn,
},
@@ -20,6 +21,7 @@ fn main() {
account_post.balance = account_post.balance.saturating_sub(balance_to_burn);
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![AccountPostState::new(account_post)],
@@ -13,6 +13,7 @@ type Instruction = (u128, ProgramId, u32, Option<PdaSeed>);
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction: (balance, auth_transfer_id, num_chain_calls, pda_seed),
},
@@ -55,6 +56,7 @@ fn main() {
}
ProgramOutput::new(
self_program_id,
instruction_words,
vec![sender_pre.clone(), recipient_pre.clone()],
vec![
@@ -6,6 +6,7 @@ type Instruction = (Option<Vec<u8>>, bool);
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction: (data_opt, should_claim),
},
@@ -33,5 +34,11 @@ fn main() {
AccountPostState::new(account_post)
};
ProgramOutput::new(instruction_words, vec![pre], vec![post_state]).write();
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![post_state],
)
.write();
}
@@ -5,6 +5,7 @@ type Instruction = ();
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction: (),
},
@@ -17,5 +18,11 @@ fn main() {
let account_post = AccountPostState::new_claimed(pre.account.clone(), Claim::Authorized);
ProgramOutput::new(instruction_words, vec![pre], vec![account_post]).write();
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![account_post],
)
.write();
}
@@ -6,6 +6,7 @@ type Instruction = Vec<u8>;
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction: data,
},
@@ -23,6 +24,7 @@ fn main() {
.expect("provided data should fit into data limit");
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![AccountPostState::new_claimed(
@@ -6,7 +6,14 @@ use nssa_core::{
type Instruction = ();
fn main() {
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
let (
ProgramInput {
self_program_id,
pre_states,
..
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
let Ok([pre]) = <[_; 1]>::try_from(pre_states) else {
return;
@@ -15,6 +22,7 @@ fn main() {
let account_pre = pre.account.clone();
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![
@@ -14,6 +14,7 @@ type Instruction = (u128, ProgramId);
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction: (balance, transfer_program_id),
},
@@ -40,6 +41,7 @@ fn main() {
};
ProgramOutput::new(
self_program_id,
instruction_words,
vec![sender.clone(), receiver.clone()],
vec![
+9 -1
View File
@@ -3,7 +3,14 @@ use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nss
type Instruction = ();
fn main() {
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
let (
ProgramInput {
self_program_id,
pre_states,
..
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
let Ok([pre]) = <[_; 1]>::try_from(pre_states) else {
return;
@@ -17,6 +24,7 @@ fn main() {
.expect("Balance overflow");
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![AccountPostState::new(account_post)],
@@ -3,7 +3,14 @@ use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nss
type Instruction = ();
fn main() {
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
let (
ProgramInput {
self_program_id,
pre_states,
..
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
let Ok([pre1, pre2]) = <[_; 2]>::try_from(pre_states) else {
return;
@@ -12,6 +19,7 @@ fn main() {
let account_pre1 = pre1.account.clone();
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre1, pre2],
vec![AccountPostState::new(account_pre1)],
@@ -64,6 +64,7 @@ fn main() {
// Read input accounts.
let (
ProgramInput {
self_program_id,
pre_states,
instruction: balance_to_move,
},
@@ -80,5 +81,5 @@ fn main() {
}
_ => panic!("invalid params"),
};
ProgramOutput::new(instruction_data, pre_states, post_states).write();
ProgramOutput::new(self_program_id, instruction_data, pre_states, post_states).write();
}
@@ -3,7 +3,14 @@ use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nss
type Instruction = ();
fn main() {
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
let (
ProgramInput {
self_program_id,
pre_states,
..
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
let Ok([pre]) = <[_; 1]>::try_from(pre_states) else {
return;
@@ -14,6 +21,7 @@ fn main() {
account_post.nonce.public_account_nonce_increment();
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![AccountPostState::new(account_post)],
+9 -2
View File
@@ -3,11 +3,18 @@ use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nss
type Instruction = ();
fn main() {
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
let (
ProgramInput {
self_program_id,
pre_states,
..
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
let post_states = pre_states
.iter()
.map(|account| AccountPostState::new(account.account.clone()))
.collect();
ProgramOutput::new(instruction_words, pre_states, post_states).write();
ProgramOutput::new(self_program_id, instruction_words, pre_states, post_states).write();
}
@@ -3,7 +3,14 @@ use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nss
type Instruction = ();
fn main() {
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
let (
ProgramInput {
self_program_id,
pre_states,
..
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
let Ok([pre]) = <[_; 1]>::try_from(pre_states) else {
return;
@@ -14,6 +21,7 @@ fn main() {
account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7];
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![AccountPostState::new(account_post)],
@@ -5,6 +5,7 @@ type Instruction = u128;
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction: balance,
},
@@ -27,6 +28,7 @@ fn main() {
.expect("Overflow when adding balance");
ProgramOutput::new(
self_program_id,
instruction_words,
vec![sender_pre, receiver_pre],
vec![
@@ -8,6 +8,7 @@ type Instruction = (BlockValidityWindow, TimestampValidityWindow);
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction: (block_validity_window, timestamp_validity_window),
},
@@ -21,6 +22,7 @@ fn main() {
let post = pre.account.clone();
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![AccountPostState::new(post)],
@@ -16,6 +16,7 @@ type Instruction = (BlockValidityWindow, ProgramId, BlockValidityWindow);
fn main() {
let (
ProgramInput {
self_program_id,
pre_states,
instruction: (block_validity_window, chained_program_id, chained_block_validity_window),
},
@@ -38,6 +39,7 @@ fn main() {
};
ProgramOutput::new(
self_program_id,
instruction_words,
vec![pre],
vec![AccountPostState::new(post)],