From 7287dfc2b3544c4bd71cdf7d841d7ba9fb191241 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Wed, 5 Aug 2026 20:55:34 +0530 Subject: [PATCH 01/25] feat(fraud-proofs): Implement InitProtocolConfig --- dlp-api/src/lib.rs | 1 + dlp-api/src/v2/args/init_protocol_config.rs | 25 ++ dlp-api/src/v2/args/mod.rs | 3 + dlp-api/src/v2/instruction.rs | 21 ++ .../init_protocol_config.rs | 34 +++ dlp-api/src/v2/instruction_builder/mod.rs | 3 + dlp-api/src/v2/mod.rs | 12 + dlp-api/src/v2/pda.rs | 12 + dlp-api/src/v2/state/mod.rs | 6 + dlp-api/src/v2/state/protocol_config.rs | 69 +++++ dlp-api/src/v2/state/utils.rs | 38 +++ dlp-api/src/v2/state/verifier_registry.rs | 62 +++++ src/lib.rs | 12 + src/processor/mod.rs | 2 +- src/v2/mod.rs | 3 + src/v2/processor/init_protocol_config.rs | 133 ++++++++++ src/v2/processor/mod.rs | 22 ++ tests/test_v2_init_protocol_config.rs | 249 ++++++++++++++++++ 18 files changed, 706 insertions(+), 1 deletion(-) create mode 100644 dlp-api/src/v2/args/init_protocol_config.rs create mode 100644 dlp-api/src/v2/args/mod.rs create mode 100644 dlp-api/src/v2/instruction.rs create mode 100644 dlp-api/src/v2/instruction_builder/init_protocol_config.rs create mode 100644 dlp-api/src/v2/instruction_builder/mod.rs create mode 100644 dlp-api/src/v2/mod.rs create mode 100644 dlp-api/src/v2/pda.rs create mode 100644 dlp-api/src/v2/state/mod.rs create mode 100644 dlp-api/src/v2/state/protocol_config.rs create mode 100644 dlp-api/src/v2/state/utils.rs create mode 100644 dlp-api/src/v2/state/verifier_registry.rs create mode 100644 src/v2/mod.rs create mode 100644 src/v2/processor/init_protocol_config.rs create mode 100644 src/v2/processor/mod.rs create mode 100644 tests/test_v2_init_protocol_config.rs diff --git a/dlp-api/src/lib.rs b/dlp-api/src/lib.rs index 01a22b04..3a4318d1 100644 --- a/dlp-api/src/lib.rs +++ b/dlp-api/src/lib.rs @@ -17,6 +17,7 @@ pub mod pda; pub mod pod_view; pub mod requires; pub mod state; +pub mod v2; pub use account_size_class::*; diff --git a/dlp-api/src/v2/args/init_protocol_config.rs b/dlp-api/src/v2/args/init_protocol_config.rs new file mode 100644 index 00000000..200ee063 --- /dev/null +++ b/dlp-api/src/v2/args/init_protocol_config.rs @@ -0,0 +1,25 @@ +use crate::compat::{ + borsh::{BorshDeserialize, BorshSerialize}, + Pubkey, +}; + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub struct InitProtocolConfigArgs { + pub vrf_program: Pubkey, + pub vrf_config: Pubkey, + pub resolver: Pubkey, + + pub min_operator_bond: u64, + pub min_verifier_bond: u64, + pub min_challenger_stake: u64, + + pub challenge_window_slots: u64, + pub operator_response_timeout_slots: u64, + pub challenger_reveal_timeout_slots: u64, + pub payout_timelock_slots: u64, + + pub selected_verifier_count: u16, + pub approval_threshold: u16, + pub max_window_extensions: u16, + pub match_penalty_bps: u16, +} diff --git a/dlp-api/src/v2/args/mod.rs b/dlp-api/src/v2/args/mod.rs new file mode 100644 index 00000000..13cc48ad --- /dev/null +++ b/dlp-api/src/v2/args/mod.rs @@ -0,0 +1,3 @@ +mod init_protocol_config; + +pub use init_protocol_config::*; diff --git a/dlp-api/src/v2/instruction.rs b/dlp-api/src/v2/instruction.rs new file mode 100644 index 00000000..d80163c6 --- /dev/null +++ b/dlp-api/src/v2/instruction.rs @@ -0,0 +1,21 @@ +use num_enum::TryFromPrimitive; +use strum::IntoStaticStr; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive, IntoStaticStr)] +#[rustfmt::skip] +pub enum DlpV2Instruction { + /// Creates the global v2 protocol config and verifier registry accounts. + InitProtocolConfig = 100, +} + +impl DlpV2Instruction { + pub fn to_vec(self) -> Vec { + let num = self as u64; + num.to_le_bytes().to_vec() + } + + pub fn name(&self) -> &'static str { + self.into() + } +} diff --git a/dlp-api/src/v2/instruction_builder/init_protocol_config.rs b/dlp-api/src/v2/instruction_builder/init_protocol_config.rs new file mode 100644 index 00000000..9e67b763 --- /dev/null +++ b/dlp-api/src/v2/instruction_builder/init_protocol_config.rs @@ -0,0 +1,34 @@ +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use solana_sdk_ids::system_program; + +use crate::{ + compat::{borsh::to_vec, Modernize}, + v2::{ + pda::{protocol_config_pda, verifier_registry_pda}, + DlpV2Instruction, InitProtocolConfigArgs, + }, +}; + +/// Builds the instruction that creates the global v2 config accounts. +pub fn init_protocol_config( + authority: Pubkey, + args: InitProtocolConfigArgs, +) -> Instruction { + Instruction { + program_id: crate::id().modernize(), + accounts: vec![ + AccountMeta::new(authority, true), + AccountMeta::new(protocol_config_pda().modernize(), false), + AccountMeta::new(verifier_registry_pda().modernize(), false), + AccountMeta::new_readonly(system_program::id(), false), + ], + data: [ + DlpV2Instruction::InitProtocolConfig.to_vec(), + to_vec(&args).unwrap(), + ] + .concat(), + } +} diff --git a/dlp-api/src/v2/instruction_builder/mod.rs b/dlp-api/src/v2/instruction_builder/mod.rs new file mode 100644 index 00000000..13cc48ad --- /dev/null +++ b/dlp-api/src/v2/instruction_builder/mod.rs @@ -0,0 +1,3 @@ +mod init_protocol_config; + +pub use init_protocol_config::*; diff --git a/dlp-api/src/v2/mod.rs b/dlp-api/src/v2/mod.rs new file mode 100644 index 00000000..5a6b3abc --- /dev/null +++ b/dlp-api/src/v2/mod.rs @@ -0,0 +1,12 @@ +pub mod args; +pub mod instruction; +pub mod pda; +pub mod state; + +#[cfg(feature = "instruction")] +pub mod instruction_builder; + +pub use args::*; +pub use instruction::*; +pub use pda::*; +pub use state::*; diff --git a/dlp-api/src/v2/pda.rs b/dlp-api/src/v2/pda.rs new file mode 100644 index 00000000..41d67771 --- /dev/null +++ b/dlp-api/src/v2/pda.rs @@ -0,0 +1,12 @@ +use crate::compat::Pubkey; + +pub const PROTOCOL_CONFIG_SEED: &[u8] = b"protocol-config"; +pub const VERIFIER_REGISTRY_SEED: &[u8] = b"verifier-registry"; + +pub fn protocol_config_pda() -> Pubkey { + Pubkey::find_program_address(&[PROTOCOL_CONFIG_SEED], &crate::id()).0 +} + +pub fn verifier_registry_pda() -> Pubkey { + Pubkey::find_program_address(&[VERIFIER_REGISTRY_SEED], &crate::id()).0 +} diff --git a/dlp-api/src/v2/state/mod.rs b/dlp-api/src/v2/state/mod.rs new file mode 100644 index 00000000..c29e3d8c --- /dev/null +++ b/dlp-api/src/v2/state/mod.rs @@ -0,0 +1,6 @@ +mod protocol_config; +mod utils; +mod verifier_registry; + +pub use protocol_config::*; +pub use verifier_registry::*; diff --git a/dlp-api/src/v2/state/protocol_config.rs b/dlp-api/src/v2/state/protocol_config.rs new file mode 100644 index 00000000..84b687c7 --- /dev/null +++ b/dlp-api/src/v2/state/protocol_config.rs @@ -0,0 +1,69 @@ +use crate::{ + compat::{ + borsh::{BorshDeserialize, BorshSerialize}, + Pubkey, + }, + solana_program::program_error::ProgramError, +}; + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub struct ProtocolConfig { + pub authority: Pubkey, + pub paused: bool, + + pub vrf_program: Pubkey, + pub vrf_config: Pubkey, + pub resolver: Pubkey, + pub protocol_fee_vault: Pubkey, + + pub min_operator_bond: u64, + pub min_verifier_bond: u64, + pub min_challenger_stake: u64, + + pub challenge_window_slots: u64, + pub operator_response_timeout_slots: u64, + pub challenger_reveal_timeout_slots: u64, + pub payout_timelock_slots: u64, + + pub selected_verifier_count: u16, + pub approval_threshold: u16, + pub max_window_extensions: u16, + pub match_penalty_bps: u16, +} + +impl ProtocolConfig { + pub const DISCRIMINATOR: [u8; 8] = *b"v2cfg000"; + pub const SPACE: usize = 8 + + 5 * Self::PUBKEY_SPACE + + Self::BOOL_SPACE + + 7 * Self::U64_SPACE + + 4 * Self::U16_SPACE; + + const PUBKEY_SPACE: usize = 32; + const BOOL_SPACE: usize = 1; + const U64_SPACE: usize = 8; + const U16_SPACE: usize = 2; + + pub fn to_bytes_with_discriminator( + &self, + writer: &mut W, + ) -> Result<(), ProgramError> + where + W: std::io::Write, + { + super::utils::write_with_discriminator( + &Self::DISCRIMINATOR, + self, + writer, + ) + } + + pub fn try_from_bytes_with_discriminator( + data: &[u8], + ) -> Result { + super::utils::try_from_bytes_with_discriminator( + &Self::DISCRIMINATOR, + data, + ) + } +} diff --git a/dlp-api/src/v2/state/utils.rs b/dlp-api/src/v2/state/utils.rs new file mode 100644 index 00000000..6981275f --- /dev/null +++ b/dlp-api/src/v2/state/utils.rs @@ -0,0 +1,38 @@ +use crate::{ + compat::borsh::{BorshDeserialize, BorshSerialize}, + error::DlpError, + solana_program::program_error::ProgramError, +}; + +pub(crate) fn write_with_discriminator( + discriminator: &[u8; 8], + value: &T, + writer: &mut W, +) -> Result<(), ProgramError> +where + T: BorshSerialize, + W: std::io::Write, +{ + writer.write_all(discriminator)?; + value.serialize(writer)?; + Ok(()) +} + +pub(crate) fn try_from_bytes_with_discriminator( + discriminator: &[u8; 8], + data: &[u8], +) -> Result +where + T: BorshDeserialize, +{ + if data.len() < 8 { + return Err(DlpError::InvalidDataLength.into()); + } + + if discriminator.as_slice() != &data[..8] { + return Err(DlpError::InvalidDiscriminator.into()); + } + + T::try_from_slice(&data[8..]) + .or(Err(DlpError::InvalidDelegationRecordData.into())) +} diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs new file mode 100644 index 00000000..bdacf724 --- /dev/null +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -0,0 +1,62 @@ +use crate::{ + compat::{ + borsh::{BorshDeserialize, BorshSerialize}, + Pubkey, + }, + solana_program::program_error::ProgramError, +}; + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub struct VerifierRegistryEntry { + pub verifier_identity: Pubkey, + pub verifier_bond: Pubkey, + pub weight: u64, +} + +impl VerifierRegistryEntry { + pub const SPACE: usize = 32 + 32 + 8; +} + +#[derive( + Clone, Debug, Default, BorshSerialize, BorshDeserialize, PartialEq, Eq, +)] +pub struct VerifierRegistry { + pub registry_revision: u64, + pub entries: Vec, +} + +impl VerifierRegistry { + pub const DISCRIMINATOR: [u8; 8] = *b"v2vreg00"; + pub const EMPTY_SPACE: usize = + 8 + Self::REGISTRY_REVISION_SPACE + Self::VEC_LEN_SPACE; + + const REGISTRY_REVISION_SPACE: usize = 8; + const VEC_LEN_SPACE: usize = 4; + + pub fn size_with_discriminator(&self) -> usize { + Self::EMPTY_SPACE + self.entries.len() * VerifierRegistryEntry::SPACE + } + + pub fn to_bytes_with_discriminator( + &self, + writer: &mut W, + ) -> Result<(), ProgramError> + where + W: std::io::Write, + { + super::utils::write_with_discriminator( + &Self::DISCRIMINATOR, + self, + writer, + ) + } + + pub fn try_from_bytes_with_discriminator( + data: &[u8], + ) -> Result { + super::utils::try_from_bytes_with_discriminator( + &Self::DISCRIMINATOR, + data, + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index b7d3e54c..25fee5ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,9 @@ use { #[cfg(feature = "processor")] mod processor; +#[cfg(feature = "processor")] +mod v2; + #[allow(unused_imports)] pub(crate) use diff::*; @@ -84,6 +87,10 @@ pub fn fast_process_instruction( let (discriminator_bytes, data) = data.split_at(8); + if dlp_api::v2::DlpV2Instruction::try_from(discriminator_bytes[0]).is_ok() { + return None; + } + let discriminator = match DlpDiscriminator::try_from(discriminator_bytes[0]) { Ok(discriminator) => discriminator, @@ -174,6 +181,11 @@ pub fn slow_process_instruction( } let (tag, data) = data.split_at(8); + + if let Ok(ix) = dlp_api::v2::DlpV2Instruction::try_from(tag[0]) { + return v2::process_instruction(program_id, accounts, data, ix); + } + let ix = DlpDiscriminator::try_from(tag[0]) .or(Err(ProgramError::InvalidInstructionData))?; diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 757c25f9..848f38f6 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -9,7 +9,7 @@ mod init_protocol_fees_vault; mod init_validator_fees_vault; mod protocol_claim_fees; mod top_up_ephemeral_balance; -mod utils; +pub(crate) mod utils; mod validator_claim_fees; mod whitelist_validator_for_program; diff --git a/src/v2/mod.rs b/src/v2/mod.rs new file mode 100644 index 00000000..c0725635 --- /dev/null +++ b/src/v2/mod.rs @@ -0,0 +1,3 @@ +mod processor; + +pub use processor::*; diff --git a/src/v2/processor/init_protocol_config.rs b/src/v2/processor/init_protocol_config.rs new file mode 100644 index 00000000..336bcf75 --- /dev/null +++ b/src/v2/processor/init_protocol_config.rs @@ -0,0 +1,133 @@ +use dlp_api::{ + compat::borsh::BorshDeserialize, + pda::fees_vault_pda, + v2::{ + pda::{PROTOCOL_CONFIG_SEED, VERIFIER_REGISTRY_SEED}, + InitProtocolConfigArgs, ProtocolConfig, VerifierRegistry, + }, +}; +use solana_sdk_ids::system_program; + +use crate::{ + processor::utils::{ + loaders::{load_program, load_signer, load_uninitialized_pda}, + pda::create_pda, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, + }, +}; + +/// Initialize the global v2 protocol config accounts. +/// +/// Accounts: +/// 0: `[signer, writable]` authority that pays rent and controls v2 config +/// 1: `[writable]` ProtocolConfig PDA +/// 2: `[writable]` VerifierRegistry PDA +/// 3: `[]` system program +pub fn process_init_protocol_config( + _program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let args = InitProtocolConfigArgs::try_from_slice(data)?; + validate_args(&args)?; + + let [authority, protocol_config, verifier_registry, system_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + load_signer(authority, "authority")?; + load_program(system_program, system_program::id(), "system program")?; + + let protocol_config_bump = load_uninitialized_pda( + protocol_config, + &[PROTOCOL_CONFIG_SEED], + &crate::id(), + true, + "protocol config", + )?; + let verifier_registry_bump = load_uninitialized_pda( + verifier_registry, + &[VERIFIER_REGISTRY_SEED], + &crate::id(), + true, + "verifier registry", + )?; + + create_pda( + protocol_config, + &crate::id(), + ProtocolConfig::SPACE, + &[PROTOCOL_CONFIG_SEED], + protocol_config_bump, + system_program, + authority, + )?; + + let verifier_registry_state = VerifierRegistry::default(); + create_pda( + verifier_registry, + &crate::id(), + verifier_registry_state.size_with_discriminator(), + &[VERIFIER_REGISTRY_SEED], + verifier_registry_bump, + system_program, + authority, + )?; + + let protocol_config_state = ProtocolConfig { + authority: *authority.key, + paused: false, + vrf_program: args.vrf_program, + vrf_config: args.vrf_config, + resolver: args.resolver, + protocol_fee_vault: fees_vault_pda(), + min_operator_bond: args.min_operator_bond, + min_verifier_bond: args.min_verifier_bond, + min_challenger_stake: args.min_challenger_stake, + challenge_window_slots: args.challenge_window_slots, + operator_response_timeout_slots: args.operator_response_timeout_slots, + challenger_reveal_timeout_slots: args.challenger_reveal_timeout_slots, + payout_timelock_slots: args.payout_timelock_slots, + selected_verifier_count: args.selected_verifier_count, + approval_threshold: args.approval_threshold, + max_window_extensions: args.max_window_extensions, + match_penalty_bps: args.match_penalty_bps, + }; + + let mut protocol_config_data = protocol_config.try_borrow_mut_data()?; + protocol_config_state + .to_bytes_with_discriminator(&mut protocol_config_data.as_mut())?; + + let mut verifier_registry_data = verifier_registry.try_borrow_mut_data()?; + verifier_registry_state + .to_bytes_with_discriminator(&mut verifier_registry_data.as_mut())?; + + Ok(()) +} + +fn validate_args(args: &InitProtocolConfigArgs) -> ProgramResult { + if args.vrf_program == Pubkey::default() + || args.vrf_config == Pubkey::default() + || args.resolver == Pubkey::default() + || args.min_operator_bond == 0 + || args.min_verifier_bond == 0 + || args.min_challenger_stake == 0 + || args.challenge_window_slots == 0 + || args.operator_response_timeout_slots == 0 + || args.challenger_reveal_timeout_slots == 0 + || args.payout_timelock_slots == 0 + || args.selected_verifier_count == 0 + || args.approval_threshold == 0 + || args.approval_threshold > args.selected_verifier_count + || args.match_penalty_bps > 10_000 + { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(()) +} diff --git a/src/v2/processor/mod.rs b/src/v2/processor/mod.rs new file mode 100644 index 00000000..300f924d --- /dev/null +++ b/src/v2/processor/mod.rs @@ -0,0 +1,22 @@ +mod init_protocol_config; + +use dlp_api::v2::DlpV2Instruction; + +use crate::solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, +}; + +pub use init_protocol_config::*; + +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], + ix: DlpV2Instruction, +) -> ProgramResult { + match ix { + DlpV2Instruction::InitProtocolConfig => { + process_init_protocol_config(program_id, accounts, data) + } + } +} diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs new file mode 100644 index 00000000..2068c9a2 --- /dev/null +++ b/tests/test_v2_init_protocol_config.rs @@ -0,0 +1,249 @@ +use dlp::solana_program; +use dlp_api::{ + pda::fees_vault_pda, + v2::{ + instruction_builder::init_protocol_config, + pda::{protocol_config_pda, verifier_registry_pda}, + InitProtocolConfigArgs, ProtocolConfig, VerifierRegistry, + }, +}; +use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL}; +use solana_program_test::{BanksClient, ProgramTest}; +use solana_sdk::{ + account::Account, + pubkey::Pubkey, + signature::{Keypair, Signer}, + transaction::Transaction, +}; +use solana_sdk_ids::system_program; + +mod fixtures; + +#[tokio::test] +async fn test_v2_init_protocol_config() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + + let args = valid_args(); + let ix = init_protocol_config(authority.pubkey(), args.clone()); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + let res = banks.process_transaction(tx).await; + assert!(res.is_ok()); + + let protocol_config_account = banks + .get_account(protocol_config_pda()) + .await + .unwrap() + .unwrap(); + let protocol_config = ProtocolConfig::try_from_bytes_with_discriminator( + &protocol_config_account.data, + ) + .unwrap(); + + assert_eq!(protocol_config.authority, authority.pubkey()); + assert!(!protocol_config.paused); + assert_eq!(protocol_config.vrf_program, args.vrf_program); + assert_eq!(protocol_config.vrf_config, args.vrf_config); + assert_eq!(protocol_config.resolver, args.resolver); + assert_eq!(protocol_config.protocol_fee_vault, fees_vault_pda()); + assert_eq!(protocol_config.min_operator_bond, args.min_operator_bond); + assert_eq!(protocol_config.min_verifier_bond, args.min_verifier_bond); + assert_eq!( + protocol_config.min_challenger_stake, + args.min_challenger_stake + ); + assert_eq!( + protocol_config.challenge_window_slots, + args.challenge_window_slots + ); + assert_eq!( + protocol_config.operator_response_timeout_slots, + args.operator_response_timeout_slots + ); + assert_eq!( + protocol_config.challenger_reveal_timeout_slots, + args.challenger_reveal_timeout_slots + ); + assert_eq!( + protocol_config.payout_timelock_slots, + args.payout_timelock_slots + ); + assert_eq!( + protocol_config.selected_verifier_count, + args.selected_verifier_count + ); + assert_eq!(protocol_config.approval_threshold, args.approval_threshold); + assert_eq!( + protocol_config.max_window_extensions, + args.max_window_extensions + ); + assert_eq!(protocol_config.match_penalty_bps, args.match_penalty_bps); + + let verifier_registry_account = banks + .get_account(verifier_registry_pda()) + .await + .unwrap() + .unwrap(); + let verifier_registry = + VerifierRegistry::try_from_bytes_with_discriminator( + &verifier_registry_account.data, + ) + .unwrap(); + + assert_eq!(verifier_registry.registry_revision, 0); + assert!(verifier_registry.entries.is_empty()); +} + +#[tokio::test] +async fn test_v2_init_protocol_config_fails_twice() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + + let ix = init_protocol_config(authority.pubkey(), valid_args()); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + banks.process_transaction(tx).await.unwrap(); + + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = init_protocol_config(authority.pubkey(), valid_args()); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +#[tokio::test] +async fn test_v2_init_protocol_config_fails_with_wrong_protocol_config_pda() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + + let mut ix = init_protocol_config(authority.pubkey(), valid_args()); + ix.accounts[1].pubkey = Pubkey::new_unique(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +#[tokio::test] +async fn test_v2_init_protocol_config_fails_with_wrong_verifier_registry_pda() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + + let mut ix = init_protocol_config(authority.pubkey(), valid_args()); + ix.accounts[2].pubkey = Pubkey::new_unique(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +#[tokio::test] +async fn test_v2_init_protocol_config_fails_without_authority_signature() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + + let mut ix = init_protocol_config(authority.pubkey(), valid_args()); + ix.accounts[0].is_signer = false; + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +#[tokio::test] +async fn test_v2_init_protocol_config_fails_with_invalid_args() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + + let mut args = valid_args(); + args.approval_threshold = args.selected_verifier_count + 1; + + let ix = init_protocol_config(authority.pubkey(), args); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +#[tokio::test] +async fn test_v1_dispatch_still_works_after_v2_routing() { + let (banks, payer, _authority, blockhash) = setup_program_test_env().await; + + let ix = + dlp_api::instruction_builder::init_protocol_fees_vault(payer.pubkey()); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_ok()); +} + +fn valid_args() -> InitProtocolConfigArgs { + InitProtocolConfigArgs { + vrf_program: Pubkey::new_unique(), + vrf_config: Pubkey::new_unique(), + resolver: Pubkey::new_unique(), + min_operator_bond: 1, + min_verifier_bond: 1, + min_challenger_stake: 1, + challenge_window_slots: 10, + operator_response_timeout_slots: 10, + challenger_reveal_timeout_slots: 10, + payout_timelock_slots: 10, + selected_verifier_count: 3, + approval_threshold: 2, + max_window_extensions: 1, + match_penalty_bps: 500, + } +} + +async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { + let mut program_test = ProgramTest::new("dlp", dlp_api::ID, None); + program_test.prefer_bpf(true); + + let authority = Keypair::new(); + program_test.add_account( + authority.pubkey(), + Account { + lamports: LAMPORTS_PER_SOL, + data: vec![], + owner: system_program::id(), + executable: false, + rent_epoch: 0, + }, + ); + + let (banks, payer, blockhash) = program_test.start().await; + (banks, payer, authority, blockhash) +} From 1673c3cae795a7952492c7018f911b74d91ad224 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Thu, 6 Aug 2026 02:07:05 +0530 Subject: [PATCH 02/25] Use wheels --- Cargo.lock | 23 ++++ dlp-api/Cargo.toml | 1 + dlp-api/src/v2/args/init_protocol_config.rs | 36 +++++- .../init_protocol_config.rs | 5 +- dlp-api/src/v2/state/mod.rs | 1 + dlp-api/src/v2/state/protocol_config.rs | 72 ++++++----- dlp-api/src/v2/state/utils.rs | 46 ++++--- dlp-api/src/v2/state/verifier_registry.rs | 113 +++++++++++++----- src/v2/processor/init_protocol_config.rs | 7 +- tests/test_v2_init_protocol_config.rs | 30 +++++ 10 files changed, 241 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00b9d566..8bb9cc39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3204,6 +3204,7 @@ dependencies = [ "static_assertions", "strum 0.28.0", "thiserror 1.0.69", + "wheels", ] [[package]] @@ -9629,6 +9630,28 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wheels" +version = "0.1.0" +source = "git+https://github.com/magicblock-labs/magicblock-wheels.git?rev=5b83b5610ccbd58fb0eec79700c322d09594e951#5b83b5610ccbd58fb0eec79700c322d09594e951" +dependencies = [ + "bytemuck", + "pinocchio 0.10.2", + "pinocchio-log", + "solana-address 2.0.0", + "wheels-macros", +] + +[[package]] +name = "wheels-macros" +version = "0.1.0" +source = "git+https://github.com/magicblock-labs/magicblock-wheels.git?rev=5b83b5610ccbd58fb0eec79700c322d09594e951#5b83b5610ccbd58fb0eec79700c322d09594e951" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/dlp-api/Cargo.toml b/dlp-api/Cargo.toml index b5dd3315..356b3a40 100644 --- a/dlp-api/Cargo.toml +++ b/dlp-api/Cargo.toml @@ -49,6 +49,7 @@ static_assertions = "1.1.0" strum = { version = ">=0.27", features = ["derive"] } thiserror = { version = ">=1" } serde = { version = "1.0.228", default-features = false, features = ["derive"] } +wheels = { git = "https://github.com/magicblock-labs/magicblock-wheels.git", rev = "5b83b5610ccbd58fb0eec79700c322d09594e951", package = "wheels" } solana-pubkey-compat = { package = "solana-pubkey", version = "2.4", features = ["borsh", "bytemuck", "curve25519"] } diff --git a/dlp-api/src/v2/args/init_protocol_config.rs b/dlp-api/src/v2/args/init_protocol_config.rs index 200ee063..504a1a38 100644 --- a/dlp-api/src/v2/args/init_protocol_config.rs +++ b/dlp-api/src/v2/args/init_protocol_config.rs @@ -1,9 +1,9 @@ -use crate::compat::{ - borsh::{BorshDeserialize, BorshSerialize}, - Pubkey, -}; +use wheels::{layout::Decodable, variable_offset_layout}; -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +use crate::{compat::Pubkey, solana_program::program_error::ProgramError}; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[variable_offset_layout(buffer_offset = unaligned)] pub struct InitProtocolConfigArgs { pub vrf_program: Pubkey, pub vrf_config: Pubkey, @@ -23,3 +23,29 @@ pub struct InitProtocolConfigArgs { pub max_window_extensions: u16, pub match_penalty_bps: u16, } + +impl InitProtocolConfigArgs { + pub fn try_from_bytes(data: &[u8]) -> Result { + let view = ::decode(data) + .map_err(super::super::state::layout_error_to_program_error)?; + + Ok(Self { + vrf_program: *view.vrf_program(), + vrf_config: *view.vrf_config(), + resolver: *view.resolver(), + min_operator_bond: view.min_operator_bond(), + min_verifier_bond: view.min_verifier_bond(), + min_challenger_stake: view.min_challenger_stake(), + challenge_window_slots: view.challenge_window_slots(), + operator_response_timeout_slots: view + .operator_response_timeout_slots(), + challenger_reveal_timeout_slots: view + .challenger_reveal_timeout_slots(), + payout_timelock_slots: view.payout_timelock_slots(), + selected_verifier_count: view.selected_verifier_count(), + approval_threshold: view.approval_threshold(), + max_window_extensions: view.max_window_extensions(), + match_penalty_bps: view.match_penalty_bps(), + }) + } +} diff --git a/dlp-api/src/v2/instruction_builder/init_protocol_config.rs b/dlp-api/src/v2/instruction_builder/init_protocol_config.rs index 9e67b763..4f79aa08 100644 --- a/dlp-api/src/v2/instruction_builder/init_protocol_config.rs +++ b/dlp-api/src/v2/instruction_builder/init_protocol_config.rs @@ -3,9 +3,10 @@ use solana_program::{ pubkey::Pubkey, }; use solana_sdk_ids::system_program; +use wheels::layout::Encodable; use crate::{ - compat::{borsh::to_vec, Modernize}, + compat::Modernize, v2::{ pda::{protocol_config_pda, verifier_registry_pda}, DlpV2Instruction, InitProtocolConfigArgs, @@ -27,7 +28,7 @@ pub fn init_protocol_config( ], data: [ DlpV2Instruction::InitProtocolConfig.to_vec(), - to_vec(&args).unwrap(), + args.encode().unwrap(), ] .concat(), } diff --git a/dlp-api/src/v2/state/mod.rs b/dlp-api/src/v2/state/mod.rs index c29e3d8c..45bff348 100644 --- a/dlp-api/src/v2/state/mod.rs +++ b/dlp-api/src/v2/state/mod.rs @@ -3,4 +3,5 @@ mod utils; mod verifier_registry; pub use protocol_config::*; +pub use utils::layout_error_to_program_error; pub use verifier_registry::*; diff --git a/dlp-api/src/v2/state/protocol_config.rs b/dlp-api/src/v2/state/protocol_config.rs index 84b687c7..620119e3 100644 --- a/dlp-api/src/v2/state/protocol_config.rs +++ b/dlp-api/src/v2/state/protocol_config.rs @@ -1,12 +1,12 @@ -use crate::{ - compat::{ - borsh::{BorshDeserialize, BorshSerialize}, - Pubkey, - }, - solana_program::program_error::ProgramError, +use wheels::{ + layout::{Decodable, Encodable}, + variable_offset_layout, }; -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +use crate::{compat::Pubkey, solana_program::program_error::ProgramError}; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[variable_offset_layout(buffer_offset = unaligned)] pub struct ProtocolConfig { pub authority: Pubkey, pub paused: bool, @@ -33,37 +33,51 @@ pub struct ProtocolConfig { impl ProtocolConfig { pub const DISCRIMINATOR: [u8; 8] = *b"v2cfg000"; - pub const SPACE: usize = 8 - + 5 * Self::PUBKEY_SPACE - + Self::BOOL_SPACE - + 7 * Self::U64_SPACE - + 4 * Self::U16_SPACE; - - const PUBKEY_SPACE: usize = 32; - const BOOL_SPACE: usize = 1; - const U64_SPACE: usize = 8; - const U16_SPACE: usize = 2; + pub const SPACE: usize = 8 + Self::DATA_LEN; - pub fn to_bytes_with_discriminator( + pub fn to_bytes_with_discriminator( &self, - writer: &mut W, - ) -> Result<(), ProgramError> - where - W: std::io::Write, - { - super::utils::write_with_discriminator( + data: &mut [u8], + ) -> Result<(), ProgramError> { + let payload = super::utils::payload_with_discriminator_mut( &Self::DISCRIMINATOR, - self, - writer, - ) + data, + )?; + self.encode_to(payload) + .map_err(super::utils::layout_error_to_program_error)?; + Ok(()) } pub fn try_from_bytes_with_discriminator( data: &[u8], ) -> Result { - super::utils::try_from_bytes_with_discriminator( + let payload = super::utils::payload_with_discriminator( &Self::DISCRIMINATOR, data, - ) + )?; + let view = ::decode(payload) + .map_err(super::utils::layout_error_to_program_error)?; + + Ok(Self { + authority: *view.authority(), + paused: view.paused(), + vrf_program: *view.vrf_program(), + vrf_config: *view.vrf_config(), + resolver: *view.resolver(), + protocol_fee_vault: *view.protocol_fee_vault(), + min_operator_bond: view.min_operator_bond(), + min_verifier_bond: view.min_verifier_bond(), + min_challenger_stake: view.min_challenger_stake(), + challenge_window_slots: view.challenge_window_slots(), + operator_response_timeout_slots: view + .operator_response_timeout_slots(), + challenger_reveal_timeout_slots: view + .challenger_reveal_timeout_slots(), + payout_timelock_slots: view.payout_timelock_slots(), + selected_verifier_count: view.selected_verifier_count(), + approval_threshold: view.approval_threshold(), + max_window_extensions: view.max_window_extensions(), + match_penalty_bps: view.match_penalty_bps(), + }) } } diff --git a/dlp-api/src/v2/state/utils.rs b/dlp-api/src/v2/state/utils.rs index 6981275f..1f6129c4 100644 --- a/dlp-api/src/v2/state/utils.rs +++ b/dlp-api/src/v2/state/utils.rs @@ -1,30 +1,15 @@ -use crate::{ - compat::borsh::{BorshDeserialize, BorshSerialize}, - error::DlpError, - solana_program::program_error::ProgramError, -}; +use wheels::DataLayoutError; -pub(crate) fn write_with_discriminator( - discriminator: &[u8; 8], - value: &T, - writer: &mut W, -) -> Result<(), ProgramError> -where - T: BorshSerialize, - W: std::io::Write, -{ - writer.write_all(discriminator)?; - value.serialize(writer)?; - Ok(()) +use crate::{error::DlpError, solana_program::program_error::ProgramError}; + +pub fn layout_error_to_program_error(error: DataLayoutError) -> ProgramError { + ProgramError::Custom(error.code()) } -pub(crate) fn try_from_bytes_with_discriminator( +pub(crate) fn payload_with_discriminator<'a>( discriminator: &[u8; 8], - data: &[u8], -) -> Result -where - T: BorshDeserialize, -{ + data: &'a [u8], +) -> Result<&'a [u8], ProgramError> { if data.len() < 8 { return Err(DlpError::InvalidDataLength.into()); } @@ -33,6 +18,17 @@ where return Err(DlpError::InvalidDiscriminator.into()); } - T::try_from_slice(&data[8..]) - .or(Err(DlpError::InvalidDelegationRecordData.into())) + Ok(&data[8..]) +} + +pub(crate) fn payload_with_discriminator_mut<'a>( + discriminator: &[u8; 8], + data: &'a mut [u8], +) -> Result<&'a mut [u8], ProgramError> { + if data.len() < 8 { + return Err(DlpError::InvalidDataLength.into()); + } + + data[..8].copy_from_slice(discriminator); + Ok(&mut data[8..]) } diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index bdacf724..8c0f774b 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -1,12 +1,11 @@ -use crate::{ - compat::{ - borsh::{BorshDeserialize, BorshSerialize}, - Pubkey, - }, - solana_program::program_error::ProgramError, +use wheels::{ + layout::{Decodable, Encodable}, + variable_offset_layout, }; -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +use crate::{compat::Pubkey, solana_program::program_error::ProgramError}; + +#[derive(Clone, Debug, PartialEq, Eq)] pub struct VerifierRegistryEntry { pub verifier_identity: Pubkey, pub verifier_bond: Pubkey, @@ -15,48 +14,106 @@ pub struct VerifierRegistryEntry { impl VerifierRegistryEntry { pub const SPACE: usize = 32 + 32 + 8; + + fn to_wire(&self) -> [u8; 72] { + let mut bytes = [0; 72]; + bytes[..32].copy_from_slice(self.verifier_identity.as_ref()); + bytes[32..64].copy_from_slice(self.verifier_bond.as_ref()); + bytes[64..72].copy_from_slice(&self.weight.to_le_bytes()); + bytes + } + + fn from_wire(bytes: &[u8; 72]) -> Self { + let mut verifier_identity = [0; 32]; + verifier_identity.copy_from_slice(&bytes[..32]); + + let mut verifier_bond = [0; 32]; + verifier_bond.copy_from_slice(&bytes[32..64]); + + let mut weight = [0; 8]; + weight.copy_from_slice(&bytes[64..72]); + + Self { + verifier_identity: verifier_identity.into(), + verifier_bond: verifier_bond.into(), + weight: u64::from_le_bytes(weight), + } + } } -#[derive( - Clone, Debug, Default, BorshSerialize, BorshDeserialize, PartialEq, Eq, -)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct VerifierRegistry { pub registry_revision: u64, pub entries: Vec, } +#[variable_offset_layout(buffer_offset = unaligned)] +struct VerifierRegistryLayout { + registry_revision: u64, + #[flexible = 4] + entries: Vec, +} + impl VerifierRegistry { pub const DISCRIMINATOR: [u8; 8] = *b"v2vreg00"; - pub const EMPTY_SPACE: usize = - 8 + Self::REGISTRY_REVISION_SPACE + Self::VEC_LEN_SPACE; - - const REGISTRY_REVISION_SPACE: usize = 8; - const VEC_LEN_SPACE: usize = 4; + pub const EMPTY_SPACE: usize = 8 + VerifierRegistryLayout::DATA_LEN_RANGE.0; pub fn size_with_discriminator(&self) -> usize { - Self::EMPTY_SPACE + self.entries.len() * VerifierRegistryEntry::SPACE + Self::EMPTY_SPACE + self.entries.len() * 72 } - pub fn to_bytes_with_discriminator( + pub fn to_bytes_with_discriminator( &self, - writer: &mut W, - ) -> Result<(), ProgramError> - where - W: std::io::Write, - { - super::utils::write_with_discriminator( + data: &mut [u8], + ) -> Result<(), ProgramError> { + let payload = super::utils::payload_with_discriminator_mut( &Self::DISCRIMINATOR, - self, - writer, - ) + data, + )?; + let layout = VerifierRegistryLayout { + registry_revision: self.registry_revision, + entries: self + .entries + .iter() + .flat_map(|entry| entry.to_wire()) + .collect(), + }; + layout + .encode_to(payload) + .map_err(super::utils::layout_error_to_program_error)?; + Ok(()) } pub fn try_from_bytes_with_discriminator( data: &[u8], ) -> Result { - super::utils::try_from_bytes_with_discriminator( + let payload = super::utils::payload_with_discriminator( &Self::DISCRIMINATOR, data, - ) + )?; + let view = ::decode(payload) + .map_err(super::utils::layout_error_to_program_error)?; + + Ok(Self { + registry_revision: view.registry_revision(), + entries: entries_from_wire(view.entries())?, + }) } } + +fn entries_from_wire( + bytes: &[u8], +) -> Result, ProgramError> { + if bytes.len() % VerifierRegistryEntry::SPACE != 0 { + return Err(ProgramError::InvalidAccountData); + } + + Ok(bytes + .chunks_exact(VerifierRegistryEntry::SPACE) + .map(|chunk| { + let mut entry = [0; 72]; + entry.copy_from_slice(chunk); + VerifierRegistryEntry::from_wire(&entry) + }) + .collect()) +} diff --git a/src/v2/processor/init_protocol_config.rs b/src/v2/processor/init_protocol_config.rs index 336bcf75..b12fdc1a 100644 --- a/src/v2/processor/init_protocol_config.rs +++ b/src/v2/processor/init_protocol_config.rs @@ -1,5 +1,4 @@ use dlp_api::{ - compat::borsh::BorshDeserialize, pda::fees_vault_pda, v2::{ pda::{PROTOCOL_CONFIG_SEED, VERIFIER_REGISTRY_SEED}, @@ -31,7 +30,7 @@ pub fn process_init_protocol_config( accounts: &[AccountInfo], data: &[u8], ) -> ProgramResult { - let args = InitProtocolConfigArgs::try_from_slice(data)?; + let args = InitProtocolConfigArgs::try_from_bytes(data)?; validate_args(&args)?; let [authority, protocol_config, verifier_registry, system_program] = @@ -101,11 +100,11 @@ pub fn process_init_protocol_config( let mut protocol_config_data = protocol_config.try_borrow_mut_data()?; protocol_config_state - .to_bytes_with_discriminator(&mut protocol_config_data.as_mut())?; + .to_bytes_with_discriminator(protocol_config_data.as_mut())?; let mut verifier_registry_data = verifier_registry.try_borrow_mut_data()?; verifier_registry_state - .to_bytes_with_discriminator(&mut verifier_registry_data.as_mut())?; + .to_bytes_with_discriminator(verifier_registry_data.as_mut())?; Ok(()) } diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index 2068c9a2..2fa0c698 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -5,6 +5,7 @@ use dlp_api::{ instruction_builder::init_protocol_config, pda::{protocol_config_pda, verifier_registry_pda}, InitProtocolConfigArgs, ProtocolConfig, VerifierRegistry, + VerifierRegistryEntry, }, }; use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL}; @@ -19,6 +20,35 @@ use solana_sdk_ids::system_program; mod fixtures; +#[test] +fn test_v2_verifier_registry_layout_round_trip() { + let registry = VerifierRegistry { + registry_revision: 7, + entries: vec![ + VerifierRegistryEntry { + verifier_identity: Pubkey::new_unique(), + verifier_bond: Pubkey::new_unique(), + weight: 11, + }, + VerifierRegistryEntry { + verifier_identity: Pubkey::new_unique(), + verifier_bond: Pubkey::new_unique(), + weight: 13, + }, + ], + }; + let mut data = vec![0; registry.size_with_discriminator()]; + + registry + .to_bytes_with_discriminator(data.as_mut_slice()) + .unwrap(); + let decoded = + VerifierRegistry::try_from_bytes_with_discriminator(data.as_slice()) + .unwrap(); + + assert_eq!(decoded, registry); +} + #[tokio::test] async fn test_v2_init_protocol_config() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; From 735105a9ade3ebbb0382fc0b5d11b8ccfde1399a Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Fri, 7 Aug 2026 15:59:31 +0530 Subject: [PATCH 03/25] Use Vec in layout --- Cargo.lock | 2 - dlp-api/Cargo.toml | 2 +- dlp-api/src/v2/state/verifier_registry.rs | 68 +++++------------------ 3 files changed, 15 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8bb9cc39..fd28425d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9633,7 +9633,6 @@ dependencies = [ [[package]] name = "wheels" version = "0.1.0" -source = "git+https://github.com/magicblock-labs/magicblock-wheels.git?rev=5b83b5610ccbd58fb0eec79700c322d09594e951#5b83b5610ccbd58fb0eec79700c322d09594e951" dependencies = [ "bytemuck", "pinocchio 0.10.2", @@ -9645,7 +9644,6 @@ dependencies = [ [[package]] name = "wheels-macros" version = "0.1.0" -source = "git+https://github.com/magicblock-labs/magicblock-wheels.git?rev=5b83b5610ccbd58fb0eec79700c322d09594e951#5b83b5610ccbd58fb0eec79700c322d09594e951" dependencies = [ "proc-macro2", "quote", diff --git a/dlp-api/Cargo.toml b/dlp-api/Cargo.toml index 356b3a40..4e13b42e 100644 --- a/dlp-api/Cargo.toml +++ b/dlp-api/Cargo.toml @@ -49,7 +49,7 @@ static_assertions = "1.1.0" strum = { version = ">=0.27", features = ["derive"] } thiserror = { version = ">=1" } serde = { version = "1.0.228", default-features = false, features = ["derive"] } -wheels = { git = "https://github.com/magicblock-labs/magicblock-wheels.git", rev = "5b83b5610ccbd58fb0eec79700c322d09594e951", package = "wheels" } +wheels = { path = "../../magicblock-wheels/rust/wheels" } solana-pubkey-compat = { package = "solana-pubkey", version = "2.4", features = ["borsh", "bytemuck", "curve25519"] } diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index 8c0f774b..65cbaea5 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -1,4 +1,5 @@ use wheels::{ + fixed_offset_layout, layout::{Decodable, Encodable}, variable_offset_layout, }; @@ -6,41 +7,13 @@ use wheels::{ use crate::{compat::Pubkey, solana_program::program_error::ProgramError}; #[derive(Clone, Debug, PartialEq, Eq)] +#[fixed_offset_layout] pub struct VerifierRegistryEntry { pub verifier_identity: Pubkey, pub verifier_bond: Pubkey, pub weight: u64, } -impl VerifierRegistryEntry { - pub const SPACE: usize = 32 + 32 + 8; - - fn to_wire(&self) -> [u8; 72] { - let mut bytes = [0; 72]; - bytes[..32].copy_from_slice(self.verifier_identity.as_ref()); - bytes[32..64].copy_from_slice(self.verifier_bond.as_ref()); - bytes[64..72].copy_from_slice(&self.weight.to_le_bytes()); - bytes - } - - fn from_wire(bytes: &[u8; 72]) -> Self { - let mut verifier_identity = [0; 32]; - verifier_identity.copy_from_slice(&bytes[..32]); - - let mut verifier_bond = [0; 32]; - verifier_bond.copy_from_slice(&bytes[32..64]); - - let mut weight = [0; 8]; - weight.copy_from_slice(&bytes[64..72]); - - Self { - verifier_identity: verifier_identity.into(), - verifier_bond: verifier_bond.into(), - weight: u64::from_le_bytes(weight), - } - } -} - #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct VerifierRegistry { pub registry_revision: u64, @@ -51,7 +24,7 @@ pub struct VerifierRegistry { struct VerifierRegistryLayout { registry_revision: u64, #[flexible = 4] - entries: Vec, + entries: Vec, } impl VerifierRegistry { @@ -59,7 +32,7 @@ impl VerifierRegistry { pub const EMPTY_SPACE: usize = 8 + VerifierRegistryLayout::DATA_LEN_RANGE.0; pub fn size_with_discriminator(&self) -> usize { - Self::EMPTY_SPACE + self.entries.len() * 72 + Self::EMPTY_SPACE + self.entries.len() * VerifierRegistryEntry::DATA_LEN } pub fn to_bytes_with_discriminator( @@ -72,11 +45,7 @@ impl VerifierRegistry { )?; let layout = VerifierRegistryLayout { registry_revision: self.registry_revision, - entries: self - .entries - .iter() - .flat_map(|entry| entry.to_wire()) - .collect(), + entries: self.entries.clone(), }; layout .encode_to(payload) @@ -96,24 +65,15 @@ impl VerifierRegistry { Ok(Self { registry_revision: view.registry_revision(), - entries: entries_from_wire(view.entries())?, + entries: view + .entries() + .iter() + .map(|entry| VerifierRegistryEntry { + verifier_identity: *entry.verifier_identity(), + verifier_bond: *entry.verifier_bond(), + weight: entry.weight(), + }) + .collect(), }) } } - -fn entries_from_wire( - bytes: &[u8], -) -> Result, ProgramError> { - if bytes.len() % VerifierRegistryEntry::SPACE != 0 { - return Err(ProgramError::InvalidAccountData); - } - - Ok(bytes - .chunks_exact(VerifierRegistryEntry::SPACE) - .map(|chunk| { - let mut entry = [0; 72]; - entry.copy_from_slice(chunk); - VerifierRegistryEntry::from_wire(&entry) - }) - .collect()) -} From 70ae4cf46cde9bae7d750b76465a411ac01a37c8 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Sat, 8 Aug 2026 02:24:10 +0530 Subject: [PATCH 04/25] use fixed layout for v2 accounts --- dlp-api/src/v2/state/protocol_config.rs | 4 ++-- dlp-api/src/v2/state/verifier_registry.rs | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/dlp-api/src/v2/state/protocol_config.rs b/dlp-api/src/v2/state/protocol_config.rs index 620119e3..47a288fb 100644 --- a/dlp-api/src/v2/state/protocol_config.rs +++ b/dlp-api/src/v2/state/protocol_config.rs @@ -1,12 +1,12 @@ use wheels::{ + fixed_offset_layout, layout::{Decodable, Encodable}, - variable_offset_layout, }; use crate::{compat::Pubkey, solana_program::program_error::ProgramError}; #[derive(Clone, Debug, PartialEq, Eq)] -#[variable_offset_layout(buffer_offset = unaligned)] +#[fixed_offset_layout(buffer_offset = 0)] pub struct ProtocolConfig { pub authority: Pubkey, pub paused: bool, diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index 65cbaea5..f9a8d207 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -1,13 +1,12 @@ use wheels::{ fixed_offset_layout, layout::{Decodable, Encodable}, - variable_offset_layout, }; use crate::{compat::Pubkey, solana_program::program_error::ProgramError}; #[derive(Clone, Debug, PartialEq, Eq)] -#[fixed_offset_layout] +#[fixed_offset_layout(buffer_offset = 2)] pub struct VerifierRegistryEntry { pub verifier_identity: Pubkey, pub verifier_bond: Pubkey, @@ -20,19 +19,24 @@ pub struct VerifierRegistry { pub entries: Vec, } -#[variable_offset_layout(buffer_offset = unaligned)] +#[fixed_offset_layout(buffer_offset = 0)] struct VerifierRegistryLayout { registry_revision: u64, - #[flexible = 4] + #[flexible = 2] entries: Vec, } impl VerifierRegistry { pub const DISCRIMINATOR: [u8; 8] = *b"v2vreg00"; - pub const EMPTY_SPACE: usize = 8 + VerifierRegistryLayout::DATA_LEN_RANGE.0; + pub const EMPTY_SPACE: usize = 8 + VerifierRegistryLayout::MIN_DATA_LEN; pub fn size_with_discriminator(&self) -> usize { - Self::EMPTY_SPACE + self.entries.len() * VerifierRegistryEntry::DATA_LEN + 8 + VerifierRegistryLayout { + registry_revision: self.registry_revision, + entries: self.entries.clone(), + } + .encoded_len() + .expect("validated verifier registry size") } pub fn to_bytes_with_discriminator( From 402022226338a0453c4223497c32a8b0bed2524c Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Sat, 8 Aug 2026 02:27:11 +0530 Subject: [PATCH 05/25] add v2 fraud proof processor module --- src/v2/processor/fraud_proofs/mod.rs | 1 + src/v2/processor/mod.rs | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/v2/processor/fraud_proofs/mod.rs diff --git a/src/v2/processor/fraud_proofs/mod.rs b/src/v2/processor/fraud_proofs/mod.rs new file mode 100644 index 00000000..c6d8c16b --- /dev/null +++ b/src/v2/processor/fraud_proofs/mod.rs @@ -0,0 +1 @@ +//! Processors for v2 fraud-proof instructions. diff --git a/src/v2/processor/mod.rs b/src/v2/processor/mod.rs index 300f924d..5dd44481 100644 --- a/src/v2/processor/mod.rs +++ b/src/v2/processor/mod.rs @@ -1,3 +1,4 @@ +mod fraud_proofs; mod init_protocol_config; use dlp_api::v2::DlpV2Instruction; From 36942e54a6e6cfd6ac1ecbb424f410452131fb1d Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Sat, 8 Aug 2026 02:30:10 +0530 Subject: [PATCH 06/25] group v2 bootstrap processors --- src/v2/processor/{ => bootstrap}/init_protocol_config.rs | 0 src/v2/processor/bootstrap/mod.rs | 3 +++ src/v2/processor/mod.rs | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) rename src/v2/processor/{ => bootstrap}/init_protocol_config.rs (100%) create mode 100644 src/v2/processor/bootstrap/mod.rs diff --git a/src/v2/processor/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs similarity index 100% rename from src/v2/processor/init_protocol_config.rs rename to src/v2/processor/bootstrap/init_protocol_config.rs diff --git a/src/v2/processor/bootstrap/mod.rs b/src/v2/processor/bootstrap/mod.rs new file mode 100644 index 00000000..13cc48ad --- /dev/null +++ b/src/v2/processor/bootstrap/mod.rs @@ -0,0 +1,3 @@ +mod init_protocol_config; + +pub use init_protocol_config::*; diff --git a/src/v2/processor/mod.rs b/src/v2/processor/mod.rs index 5dd44481..0e0b7d3a 100644 --- a/src/v2/processor/mod.rs +++ b/src/v2/processor/mod.rs @@ -1,5 +1,5 @@ +mod bootstrap; mod fraud_proofs; -mod init_protocol_config; use dlp_api::v2::DlpV2Instruction; @@ -7,7 +7,7 @@ use crate::solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, }; -pub use init_protocol_config::*; +pub use bootstrap::*; pub fn process_instruction( program_id: &Pubkey, From 60787bf9ecf29e580f166f058adf5cf1d9775b82 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 18 Aug 2026 16:24:47 +0530 Subject: [PATCH 07/25] Use fast AccountView path for v2 init --- src/lib.rs | 21 +++- src/processor/fast/mod.rs | 2 +- .../bootstrap/init_protocol_config.rs | 118 +++++++++++++----- src/v2/processor/mod.rs | 9 +- 4 files changed, 106 insertions(+), 44 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 25fee5ee..9e4a8fc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,8 +87,10 @@ pub fn fast_process_instruction( let (discriminator_bytes, data) = data.split_at(8); - if dlp_api::v2::DlpV2Instruction::try_from(discriminator_bytes[0]).is_ok() { - return None; + if let Ok(ix) = + dlp_api::v2::DlpV2Instruction::try_from(discriminator_bytes[0]) + { + return Some(v2::process_instruction(accounts, data, ix)); } let discriminator = match DlpDiscriminator::try_from(discriminator_bytes[0]) @@ -105,6 +107,17 @@ pub fn fast_process_instruction( #[cfg(feature = "logging")] msg!("Processing instruction: {:?}", discriminator); + process_v1_fast_instruction(discriminator, program_id, accounts, data) +} + +#[cfg(feature = "processor")] +#[inline(never)] +fn process_v1_fast_instruction( + discriminator: DlpDiscriminator, + program_id: &pinocchio::Address, + accounts: &[pinocchio::AccountView], + data: &[u8], +) -> Option { match discriminator { DlpDiscriminator::Delegate => Some(processor::fast::process_delegate( program_id, accounts, data, @@ -182,8 +195,8 @@ pub fn slow_process_instruction( let (tag, data) = data.split_at(8); - if let Ok(ix) = dlp_api::v2::DlpV2Instruction::try_from(tag[0]) { - return v2::process_instruction(program_id, accounts, data, ix); + if dlp_api::v2::DlpV2Instruction::try_from(tag[0]).is_ok() { + return Err(ProgramError::InvalidInstructionData); } let ix = DlpDiscriminator::try_from(tag[0]) diff --git a/src/processor/fast/mod.rs b/src/processor/fast/mod.rs index b01c11a5..8d44a38f 100644 --- a/src/processor/fast/mod.rs +++ b/src/processor/fast/mod.rs @@ -11,7 +11,7 @@ mod request_undelegation; mod undelegate; mod undelegate_confined_account; mod undelegate_with_rollback_after_timeout; -mod utils; +pub(crate) mod utils; pub(crate) mod internal; diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index b12fdc1a..494a9052 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -1,23 +1,71 @@ use dlp_api::{ pda::fees_vault_pda, + requires::RequireUninitializedAccountCtx, v2::{ pda::{PROTOCOL_CONFIG_SEED, VERIFIER_REGISTRY_SEED}, InitProtocolConfigArgs, ProtocolConfig, VerifierRegistry, }, }; -use solana_sdk_ids::system_program; +use pinocchio::{ + cpi::{Seed, Signer}, + error::ProgramError, + AccountView, ProgramResult, +}; use crate::{ - processor::utils::{ - loaders::{load_program, load_signer, load_uninitialized_pda}, - pda::create_pda, - }, - solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, - }, + processor::fast::{to_pinocchio_program_error, utils::pda::create_pda}, + requires::{require_program, require_signer, require_uninitialized_pda}, + solana_program::pubkey::Pubkey, }; +struct ProtocolConfigCtx; + +impl RequireUninitializedAccountCtx for ProtocolConfigCtx { + fn label(&self) -> &str { + "protocol config" + } + + fn invalid_seeds(&self) -> ProgramError { + ProgramError::InvalidSeeds + } + + fn invalid_account_owner(&self) -> ProgramError { + ProgramError::InvalidAccountOwner + } + + fn account_already_initialized(&self) -> ProgramError { + ProgramError::AccountAlreadyInitialized + } + + fn immutable(&self) -> ProgramError { + ProgramError::Immutable + } +} + +struct VerifierRegistryCtx; + +impl RequireUninitializedAccountCtx for VerifierRegistryCtx { + fn label(&self) -> &str { + "verifier registry" + } + + fn invalid_seeds(&self) -> ProgramError { + ProgramError::InvalidSeeds + } + + fn invalid_account_owner(&self) -> ProgramError { + ProgramError::InvalidAccountOwner + } + + fn account_already_initialized(&self) -> ProgramError { + ProgramError::AccountAlreadyInitialized + } + + fn immutable(&self) -> ProgramError { + ProgramError::Immutable + } +} + /// Initialize the global v2 protocol config accounts. /// /// Accounts: @@ -26,11 +74,11 @@ use crate::{ /// 2: `[writable]` VerifierRegistry PDA /// 3: `[]` system program pub fn process_init_protocol_config( - _program_id: &Pubkey, - accounts: &[AccountInfo], + accounts: &[AccountView], data: &[u8], ) -> ProgramResult { - let args = InitProtocolConfigArgs::try_from_bytes(data)?; + let args = InitProtocolConfigArgs::try_from_bytes(data) + .map_err(to_pinocchio_program_error)?; validate_args(&args)?; let [authority, protocol_config, verifier_registry, system_program] = @@ -39,47 +87,49 @@ pub fn process_init_protocol_config( return Err(ProgramError::NotEnoughAccountKeys); }; - load_signer(authority, "authority")?; - load_program(system_program, system_program::id(), "system program")?; + require_signer(authority, "authority")?; + require_program(system_program, &pinocchio_system::ID, "system program")?; - let protocol_config_bump = load_uninitialized_pda( + let protocol_config_bump = require_uninitialized_pda( protocol_config, &[PROTOCOL_CONFIG_SEED], - &crate::id(), + &crate::fast::ID, true, - "protocol config", + ProtocolConfigCtx, )?; - let verifier_registry_bump = load_uninitialized_pda( + let verifier_registry_bump = require_uninitialized_pda( verifier_registry, &[VERIFIER_REGISTRY_SEED], - &crate::id(), + &crate::fast::ID, true, - "verifier registry", + VerifierRegistryCtx, )?; create_pda( protocol_config, - &crate::id(), + &crate::fast::ID, ProtocolConfig::SPACE, - &[PROTOCOL_CONFIG_SEED], - protocol_config_bump, - system_program, + &[Signer::from(&[ + Seed::from(PROTOCOL_CONFIG_SEED), + Seed::from(&[protocol_config_bump]), + ])], authority, )?; let verifier_registry_state = VerifierRegistry::default(); create_pda( verifier_registry, - &crate::id(), + &crate::fast::ID, verifier_registry_state.size_with_discriminator(), - &[VERIFIER_REGISTRY_SEED], - verifier_registry_bump, - system_program, + &[Signer::from(&[ + Seed::from(VERIFIER_REGISTRY_SEED), + Seed::from(&[verifier_registry_bump]), + ])], authority, )?; let protocol_config_state = ProtocolConfig { - authority: *authority.key, + authority: authority.address().to_bytes().into(), paused: false, vrf_program: args.vrf_program, vrf_config: args.vrf_config, @@ -98,13 +148,15 @@ pub fn process_init_protocol_config( match_penalty_bps: args.match_penalty_bps, }; - let mut protocol_config_data = protocol_config.try_borrow_mut_data()?; + let mut protocol_config_data = protocol_config.try_borrow_mut()?; protocol_config_state - .to_bytes_with_discriminator(protocol_config_data.as_mut())?; + .to_bytes_with_discriminator(protocol_config_data.as_mut()) + .map_err(to_pinocchio_program_error)?; - let mut verifier_registry_data = verifier_registry.try_borrow_mut_data()?; + let mut verifier_registry_data = verifier_registry.try_borrow_mut()?; verifier_registry_state - .to_bytes_with_discriminator(verifier_registry_data.as_mut())?; + .to_bytes_with_discriminator(verifier_registry_data.as_mut()) + .map_err(to_pinocchio_program_error)?; Ok(()) } diff --git a/src/v2/processor/mod.rs b/src/v2/processor/mod.rs index 0e0b7d3a..666462a0 100644 --- a/src/v2/processor/mod.rs +++ b/src/v2/processor/mod.rs @@ -3,21 +3,18 @@ mod fraud_proofs; use dlp_api::v2::DlpV2Instruction; -use crate::solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, -}; +use pinocchio::{AccountView, ProgramResult}; pub use bootstrap::*; pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], + accounts: &[AccountView], data: &[u8], ix: DlpV2Instruction, ) -> ProgramResult { match ix { DlpV2Instruction::InitProtocolConfig => { - process_init_protocol_config(program_id, accounts, data) + process_init_protocol_config(accounts, data) } } } From 586a3e41f517cb7a0c1a182dc7583ad0fef16010 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 18 Aug 2026 18:29:59 +0530 Subject: [PATCH 08/25] Use wheels require macros --- Cargo.lock | 1 + Cargo.toml | 1 + dlp-api/src/args/delegate_with_actions.rs | 2 +- dlp-api/src/args/types.rs | 4 +- dlp-api/src/diff/algorithm.rs | 4 +- dlp-api/src/diff/types.rs | 4 +- dlp-api/src/requires.rs | 221 +------------------- dlp-api/src/state/delegation_metadata.rs | 3 +- src/lib.rs | 14 +- src/processor/fast/delegate_with_actions.rs | 4 +- src/processor/fast/undelegate.rs | 4 +- tests/test_commit_finalize.rs | 4 +- tests/test_commit_finalize_from_buffer.rs | 2 +- 13 files changed, 34 insertions(+), 234 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd28425d..1e321961 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3170,6 +3170,7 @@ dependencies = [ "strum 0.28.0", "thiserror 2.0.18", "tokio", + "wheels", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 38526224..ad707115 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ solana-security-txt = { version = ">=1.1", optional = true } static_assertions = "1.1.0" strum = { version = ">=0.27", features = ["derive"] } thiserror = { version = ">=1" } +wheels = { path = "../magicblock-wheels/rust/wheels" } pinocchio = { version = "0.10.1", optional = true } pinocchio-log = { version = "0.5.1" , optional = true } diff --git a/dlp-api/src/args/delegate_with_actions.rs b/dlp-api/src/args/delegate_with_actions.rs index 25463c6d..97f7f201 100644 --- a/dlp-api/src/args/delegate_with_actions.rs +++ b/dlp-api/src/args/delegate_with_actions.rs @@ -1,11 +1,11 @@ use borsh::{BorshDeserialize, BorshSerialize}; use pinocchio::error::ProgramError; +use wheels::{require, require_le}; use super::DelegateArgs; use crate::{ compact::{self, MAX_PUBKEYS}, compat::borsh, - require, require_le, }; #[derive(Debug, BorshSerialize, BorshDeserialize)] diff --git a/dlp-api/src/args/types.rs b/dlp-api/src/args/types.rs index d1b3a097..6b247ab8 100644 --- a/dlp-api/src/args/types.rs +++ b/dlp-api/src/args/types.rs @@ -3,7 +3,9 @@ use std::ops::Deref; use bytemuck::{Pod, Zeroable}; use pinocchio::error::ProgramError; -use crate::{pod_view::PodView, require_ge}; +use wheels::require_ge; + +use crate::pod_view::PodView; /// /// Boolean diff --git a/dlp-api/src/diff/algorithm.rs b/dlp-api/src/diff/algorithm.rs index d6529236..82700135 100644 --- a/dlp-api/src/diff/algorithm.rs +++ b/dlp-api/src/diff/algorithm.rs @@ -7,7 +7,9 @@ use super::{ DiffSet, OffsetInData, SizeChanged, SIZE_OF_CHANGED_LEN, SIZE_OF_NUM_OFFSET_PAIRS, SIZE_OF_SINGLE_OFFSET_PAIR, }; -use crate::{error::DlpError, require_eq, require_le}; +use wheels::{require_eq, require_le}; + +use crate::error::DlpError; /// /// Compute diff between original and changed. diff --git a/dlp-api/src/diff/types.rs b/dlp-api/src/diff/types.rs index 9f7d3cfd..f8c6093f 100644 --- a/dlp-api/src/diff/types.rs +++ b/dlp-api/src/diff/types.rs @@ -7,7 +7,9 @@ use std::{cmp::Ordering, ops::Range}; use pinocchio::error::ProgramError; use static_assertions::const_assert; -use crate::{error::DlpError, require_eq, require_ge, require_le, require_lt}; +use wheels::{require_eq, require_ge, require_le, require_lt}; + +use crate::error::DlpError; #[derive(Debug, Clone, Copy)] pub enum SizeChanged { diff --git a/dlp-api/src/requires.rs b/dlp-api/src/requires.rs index a71d2b86..a6ff6521 100644 --- a/dlp-api/src/requires.rs +++ b/dlp-api/src/requires.rs @@ -12,222 +12,7 @@ use crate::{ validator_fees_vault_pda_from_validator, }, }; - -// require true -#[macro_export] -macro_rules! require { - ($cond:expr, $error:expr) => {{ - if !$cond { - let expr = stringify!($cond); - pinocchio_log::log!("require!({}) failed.", expr); - return Err($error.into()); - } - }}; -} - -// require (info.is_signer()) -#[macro_export] -macro_rules! require_signer { - ($info: expr) => {{ - if !$info.is_signer() { - pinocchio_log::log!("require_signer!({}): ", stringify!($info)); - $info.address().log(); - return Err(ProgramError::MissingRequiredSignature); - } - }}; -} - -// require key1 == key2 -#[macro_export] -macro_rules! require_eq_keys { - ( $key1:expr, $key2:expr, $error:expr) => {{ - if !pinocchio::address::address_eq($key1, $key2) { - pinocchio_log::log!( - "require_eq_keys!({}, {}) failed: ", - stringify!($key1), - stringify!($key2) - ); - $key1.log(); - $key2.log(); - return Err($error.into()); - } - }}; -} - -// require a == b -#[macro_export] -macro_rules! require_eq { - ( $val1:expr, $val2:expr, $error:expr) => {{ - if !($val1 == $val2) { - pinocchio_log::log!( - "require_eq!({}, {}) failed: {} == {}", - stringify!($val1), - stringify!($val2), - $val1, - $val2 - ); - return Err($error.into()); - } - }}; -} - -// require a <= b -#[macro_export] -macro_rules! require_le { - ( $val1:expr, $val2:expr, $error:expr) => {{ - if !($val1 <= $val2) { - pinocchio_log::log!( - "require_le!({}, {}) failed: {} <= {}", - stringify!($val1), - stringify!($val2), - $val1, - $val2 - ); - return Err($error.into()); - } - }}; -} - -// require a < b -#[macro_export] -macro_rules! require_lt { - ( $val1:expr, $val2:expr, $error:expr) => {{ - if !($val1 < $val2) { - pinocchio_log::log!( - "require_lt!({}, {}) failed: {} < {}", - stringify!($val1), - stringify!($val2), - $val1, - $val2 - ); - return Err($error.into()); - } - }}; -} - -// require a >= b -#[macro_export] -macro_rules! require_ge { - ( $val1:expr, $val2:expr, $error:expr) => {{ - if !($val1 >= $val2) { - pinocchio_log::log!( - "require_ge!({}, {}) failed: {} >= {}", - stringify!($val1), - stringify!($val2), - $val1, - $val2 - ); - return Err($error.into()); - } - }}; -} - -// require a > b -#[macro_export] -macro_rules! require_gt { - ( $val1:expr, $val2:expr, $error:expr) => {{ - if !($val1 > $val2) { - pinocchio_log::log!( - "require_gt!({}, {}) failed: {} > {}", - stringify!($val1), - stringify!($val2), - $val1, - $val2 - ); - return Err($error.into()); - } - }}; -} - -#[macro_export] -macro_rules! require_n_accounts { - ( $accounts:expr, $n:literal) => {{ - match $accounts.len().cmp(&$n) { - core::cmp::Ordering::Less => { - pinocchio_log::log!( - "Need {} accounts, but got less ({}) accounts", - $n, - $accounts.len() - ); - return Err( - pinocchio::error::ProgramError::NotEnoughAccountKeys, - ); - } - core::cmp::Ordering::Equal => { - TryInto::<&[_; $n]>::try_into($accounts) - .map_err(|_| $crate::error::DlpError::InfallibleError)? - } - core::cmp::Ordering::Greater => { - pinocchio_log::log!( - "Need {} accounts, but got more ({}) accounts", - $n, - $accounts.len() - ); - return Err($crate::error::DlpError::TooManyAccountKeys.into()); - } - } - }}; -} - -#[macro_export] -macro_rules! require_n_accounts_with_optionals { - ( $accounts:expr, $n:literal) => {{ - match $accounts.len().cmp(&$n) { - core::cmp::Ordering::Less => { - pinocchio_log::log!( - "Need {} accounts, but got less ({}) accounts", - $n, - $accounts.len() - ); - return Err( - pinocchio::error::ProgramError::NotEnoughAccountKeys, - ); - } - _ => { - let (exact, optionals) = $accounts.split_at($n); - - ( - TryInto::<&[_; $n]>::try_into(exact).map_err(|_| { - $crate::error::DlpError::InfallibleError - })?, - optionals, - ) - } - } - }}; -} - -#[macro_export] -macro_rules! require_some { - ($option:expr, $error:expr) => {{ - match $option { - Some(val) => val, - None => return Err($error.into()), - } - }}; -} - -/// -/// require_owned_by( -/// info: &AccountView, -/// owner: &Address -/// ) -> Result<(), ProgramError> -/// -#[macro_export] -macro_rules! require_owned_by { - ($info: expr, $owner: expr) => {{ - if !address_eq(unsafe { $info.owner() }, $owner) { - pinocchio_log::log!( - "require_owned_by!({}, {})", - stringify!($info), - stringify!($owner) - ); - $info.address().log(); - $owner.log(); - return Err(ProgramError::InvalidAccountOwner); - } - }}; -} +use wheels::require_eq_keys; /// /// require_initialized_pda( @@ -266,7 +51,7 @@ macro_rules! require_initialized_pda { return Err(ProgramError::InvalidSeeds); } - require_owned_by!($info, $program_id); + wheels::require_owned_by!($info, $program_id); if $is_writable && !$info.is_writable() { pinocchio_log::log!( @@ -297,7 +82,7 @@ macro_rules! require_initialized_pda_fast { return Err(ProgramError::InvalidSeeds); } - require_owned_by!($info, &$crate::fast::ID); + wheels::require_owned_by!($info, &$crate::fast::ID); if $is_writable && !$info.is_writable() { pinocchio_log::log!( diff --git a/dlp-api/src/state/delegation_metadata.rs b/dlp-api/src/state/delegation_metadata.rs index 79d2f077..6160d63c 100644 --- a/dlp-api/src/state/delegation_metadata.rs +++ b/dlp-api/src/state/delegation_metadata.rs @@ -2,12 +2,13 @@ use std::ptr; use borsh::{BorshDeserialize, BorshSerialize}; use pinocchio::{account::RefMut, error::ProgramError, AccountView}; +use wheels::require_ge; use super::discriminator::{AccountDiscriminator, AccountWithDiscriminator}; use crate::{ compat::{borsh, Pubkey}, impl_to_bytes_with_discriminator_borsh, - impl_try_from_bytes_with_discriminator_borsh, require_ge, + impl_try_from_bytes_with_discriminator_borsh, }; /// Identifies who requested that a delegated account should move toward diff --git a/src/lib.rs b/src/lib.rs index 9e4a8fc3..f1d9c8cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,9 @@ extern crate dlp_api; pub use ::solana_program; + +pub type RequireError = dlp_api::error::DlpError; + #[allow(unused_imports)] pub(crate) use dlp_api::diff; #[allow(unused_imports)] @@ -25,13 +28,16 @@ pub(crate) use dlp_api::{ pub use dlp_api::{id, ID}; #[allow(unused_imports)] pub(crate) use dlp_api::{ - require, require_eq, require_eq_keys, require_ge, require_gt, - require_initialized_pda, require_initialized_pda_fast, require_le, - require_lt, require_n_accounts, require_n_accounts_with_optionals, - require_owned_by, require_pda, require_signer, require_some, + require_initialized_pda, require_initialized_pda_fast, require_pda, }; #[cfg(feature = "logging")] use solana_program::msg; +#[allow(unused_imports)] +pub(crate) use wheels::{ + require, require_eq, require_eq_keys, require_ge, require_gt, require_le, + require_lt, require_n_accounts, require_n_accounts_with_optionals, + require_owned_by, require_signer, require_some, +}; #[cfg(feature = "processor")] use { dlp_api::discriminator::DlpDiscriminator, diff --git a/src/processor/fast/delegate_with_actions.rs b/src/processor/fast/delegate_with_actions.rs index d78bfd0d..cac5a298 100644 --- a/src/processor/fast/delegate_with_actions.rs +++ b/src/processor/fast/delegate_with_actions.rs @@ -1,4 +1,4 @@ -use dlp_api::{compat::borsh, require_eq}; +use dlp_api::compat::borsh; use pinocchio::{ address::address_eq, cpi::{Seed, Signer}, @@ -18,7 +18,7 @@ use crate::{ fast::{to_pinocchio_program_error, utils::pda::create_pda}, utils::curve::is_on_curve_fast, }, - require_n_accounts_with_optionals, + require_eq, require_n_accounts_with_optionals, requires::{ require_owned_pda, require_pda, require_signer, require_uninitialized_pda, DelegationMetadataCtx, DelegationRecordCtx, diff --git a/src/processor/fast/undelegate.rs b/src/processor/fast/undelegate.rs index f5dcfdfe..7a67d2d7 100644 --- a/src/processor/fast/undelegate.rs +++ b/src/processor/fast/undelegate.rs @@ -1,4 +1,4 @@ -use dlp_api::{compat::borsh, require_eq_keys}; +use dlp_api::compat::borsh; use pinocchio::{ address::{address_eq, Address}, cpi::{invoke_signed, Signer}, @@ -21,7 +21,7 @@ use crate::{ error::DlpError, pda, processor::fast::utils::pda::{close_pda, close_pda_with_fees, create_pda}, - require_n_accounts_with_optionals, + require_eq_keys, require_n_accounts_with_optionals, requires::{ require_initialized_delegation_metadata, require_initialized_delegation_record, require_initialized_pda, diff --git a/tests/test_commit_finalize.rs b/tests/test_commit_finalize.rs index 3f78ecf8..afb17e62 100644 --- a/tests/test_commit_finalize.rs +++ b/tests/test_commit_finalize.rs @@ -33,12 +33,12 @@ mod fixtures; #[tokio::test] async fn test_commit_finalize_data_perf() { - run_test_commit_finalize(vec![0; 10240], vec![1; 10240], false, 1400).await; + run_test_commit_finalize(vec![0; 10240], vec![1; 10240], false, 1450).await; } #[tokio::test] async fn test_commit_finalize_diff_perf() { - run_test_commit_finalize(vec![0; 10240], vec![1; 10240], true, 1650).await; + run_test_commit_finalize(vec![0; 10240], vec![1; 10240], true, 1700).await; } async fn run_test_commit_finalize( diff --git a/tests/test_commit_finalize_from_buffer.rs b/tests/test_commit_finalize_from_buffer.rs index 5c861190..329e908f 100644 --- a/tests/test_commit_finalize_from_buffer.rs +++ b/tests/test_commit_finalize_from_buffer.rs @@ -68,7 +68,7 @@ async fn test_commit_finalize_from_buffer_perf() { let metadata = metadata.unwrap(); - assertables::assert_lt!(metadata.compute_units_consumed, 1450); + assertables::assert_lt!(metadata.compute_units_consumed, 1480); assert_eq!( metadata.log_messages.len(), From f2423fc7eba09701a085799884b2c55a6e710d3d Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 18 Aug 2026 19:03:51 +0530 Subject: [PATCH 09/25] Use layout views for v2 config accounts --- dlp-api/src/v2/args/init_protocol_config.rs | 32 +------ dlp-api/src/v2/state/protocol_config.rs | 56 +---------- dlp-api/src/v2/state/utils.rs | 29 +----- dlp-api/src/v2/state/verifier_registry.rs | 76 +++------------ .../bootstrap/init_protocol_config.rs | 78 ++++++++------- tests/test_v2_init_protocol_config.rs | 95 ++++++++++++------- 6 files changed, 125 insertions(+), 241 deletions(-) diff --git a/dlp-api/src/v2/args/init_protocol_config.rs b/dlp-api/src/v2/args/init_protocol_config.rs index 504a1a38..422a857a 100644 --- a/dlp-api/src/v2/args/init_protocol_config.rs +++ b/dlp-api/src/v2/args/init_protocol_config.rs @@ -1,9 +1,9 @@ -use wheels::{layout::Decodable, variable_offset_layout}; +use wheels::variable_offset_layout; -use crate::{compat::Pubkey, solana_program::program_error::ProgramError}; +use crate::compat::Pubkey; #[derive(Clone, Debug, PartialEq, Eq)] -#[variable_offset_layout(buffer_offset = unaligned)] +#[variable_offset_layout(buffer_offset = 0)] pub struct InitProtocolConfigArgs { pub vrf_program: Pubkey, pub vrf_config: Pubkey, @@ -23,29 +23,3 @@ pub struct InitProtocolConfigArgs { pub max_window_extensions: u16, pub match_penalty_bps: u16, } - -impl InitProtocolConfigArgs { - pub fn try_from_bytes(data: &[u8]) -> Result { - let view = ::decode(data) - .map_err(super::super::state::layout_error_to_program_error)?; - - Ok(Self { - vrf_program: *view.vrf_program(), - vrf_config: *view.vrf_config(), - resolver: *view.resolver(), - min_operator_bond: view.min_operator_bond(), - min_verifier_bond: view.min_verifier_bond(), - min_challenger_stake: view.min_challenger_stake(), - challenge_window_slots: view.challenge_window_slots(), - operator_response_timeout_slots: view - .operator_response_timeout_slots(), - challenger_reveal_timeout_slots: view - .challenger_reveal_timeout_slots(), - payout_timelock_slots: view.payout_timelock_slots(), - selected_verifier_count: view.selected_verifier_count(), - approval_threshold: view.approval_threshold(), - max_window_extensions: view.max_window_extensions(), - match_penalty_bps: view.match_penalty_bps(), - }) - } -} diff --git a/dlp-api/src/v2/state/protocol_config.rs b/dlp-api/src/v2/state/protocol_config.rs index 47a288fb..3a99ebf4 100644 --- a/dlp-api/src/v2/state/protocol_config.rs +++ b/dlp-api/src/v2/state/protocol_config.rs @@ -1,13 +1,11 @@ -use wheels::{ - fixed_offset_layout, - layout::{Decodable, Encodable}, -}; +use wheels::fixed_offset_layout; -use crate::{compat::Pubkey, solana_program::program_error::ProgramError}; +use crate::compat::Pubkey; #[derive(Clone, Debug, PartialEq, Eq)] #[fixed_offset_layout(buffer_offset = 0)] pub struct ProtocolConfig { + pub discriminator: [u8; 8], pub authority: Pubkey, pub paused: bool, @@ -33,51 +31,5 @@ pub struct ProtocolConfig { impl ProtocolConfig { pub const DISCRIMINATOR: [u8; 8] = *b"v2cfg000"; - pub const SPACE: usize = 8 + Self::DATA_LEN; - - pub fn to_bytes_with_discriminator( - &self, - data: &mut [u8], - ) -> Result<(), ProgramError> { - let payload = super::utils::payload_with_discriminator_mut( - &Self::DISCRIMINATOR, - data, - )?; - self.encode_to(payload) - .map_err(super::utils::layout_error_to_program_error)?; - Ok(()) - } - - pub fn try_from_bytes_with_discriminator( - data: &[u8], - ) -> Result { - let payload = super::utils::payload_with_discriminator( - &Self::DISCRIMINATOR, - data, - )?; - let view = ::decode(payload) - .map_err(super::utils::layout_error_to_program_error)?; - - Ok(Self { - authority: *view.authority(), - paused: view.paused(), - vrf_program: *view.vrf_program(), - vrf_config: *view.vrf_config(), - resolver: *view.resolver(), - protocol_fee_vault: *view.protocol_fee_vault(), - min_operator_bond: view.min_operator_bond(), - min_verifier_bond: view.min_verifier_bond(), - min_challenger_stake: view.min_challenger_stake(), - challenge_window_slots: view.challenge_window_slots(), - operator_response_timeout_slots: view - .operator_response_timeout_slots(), - challenger_reveal_timeout_slots: view - .challenger_reveal_timeout_slots(), - payout_timelock_slots: view.payout_timelock_slots(), - selected_verifier_count: view.selected_verifier_count(), - approval_threshold: view.approval_threshold(), - max_window_extensions: view.max_window_extensions(), - match_penalty_bps: view.match_penalty_bps(), - }) - } + pub const SPACE: usize = Self::DATA_LEN; } diff --git a/dlp-api/src/v2/state/utils.rs b/dlp-api/src/v2/state/utils.rs index 1f6129c4..ce70c333 100644 --- a/dlp-api/src/v2/state/utils.rs +++ b/dlp-api/src/v2/state/utils.rs @@ -1,34 +1,7 @@ use wheels::DataLayoutError; -use crate::{error::DlpError, solana_program::program_error::ProgramError}; +use crate::solana_program::program_error::ProgramError; pub fn layout_error_to_program_error(error: DataLayoutError) -> ProgramError { ProgramError::Custom(error.code()) } - -pub(crate) fn payload_with_discriminator<'a>( - discriminator: &[u8; 8], - data: &'a [u8], -) -> Result<&'a [u8], ProgramError> { - if data.len() < 8 { - return Err(DlpError::InvalidDataLength.into()); - } - - if discriminator.as_slice() != &data[..8] { - return Err(DlpError::InvalidDiscriminator.into()); - } - - Ok(&data[8..]) -} - -pub(crate) fn payload_with_discriminator_mut<'a>( - discriminator: &[u8; 8], - data: &'a mut [u8], -) -> Result<&'a mut [u8], ProgramError> { - if data.len() < 8 { - return Err(DlpError::InvalidDataLength.into()); - } - - data[..8].copy_from_slice(discriminator); - Ok(&mut data[8..]) -} diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index f9a8d207..f2660b0d 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -1,9 +1,6 @@ -use wheels::{ - fixed_offset_layout, - layout::{Decodable, Encodable}, -}; +use wheels::fixed_offset_layout; -use crate::{compat::Pubkey, solana_program::program_error::ProgramError}; +use crate::compat::Pubkey; #[derive(Clone, Debug, PartialEq, Eq)] #[fixed_offset_layout(buffer_offset = 2)] @@ -13,71 +10,26 @@ pub struct VerifierRegistryEntry { pub weight: u64, } -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[fixed_offset_layout(buffer_offset = 0)] pub struct VerifierRegistry { + pub discriminator: [u8; 8], pub registry_revision: u64, - pub entries: Vec, -} - -#[fixed_offset_layout(buffer_offset = 0)] -struct VerifierRegistryLayout { - registry_revision: u64, #[flexible = 2] - entries: Vec, + pub entries: Vec, } impl VerifierRegistry { pub const DISCRIMINATOR: [u8; 8] = *b"v2vreg00"; - pub const EMPTY_SPACE: usize = 8 + VerifierRegistryLayout::MIN_DATA_LEN; + pub const EMPTY_SPACE: usize = Self::MIN_DATA_LEN; +} - pub fn size_with_discriminator(&self) -> usize { - 8 + VerifierRegistryLayout { - registry_revision: self.registry_revision, - entries: self.entries.clone(), +impl Default for VerifierRegistry { + fn default() -> Self { + Self { + discriminator: Self::DISCRIMINATOR, + registry_revision: 0, + entries: Vec::new(), } - .encoded_len() - .expect("validated verifier registry size") - } - - pub fn to_bytes_with_discriminator( - &self, - data: &mut [u8], - ) -> Result<(), ProgramError> { - let payload = super::utils::payload_with_discriminator_mut( - &Self::DISCRIMINATOR, - data, - )?; - let layout = VerifierRegistryLayout { - registry_revision: self.registry_revision, - entries: self.entries.clone(), - }; - layout - .encode_to(payload) - .map_err(super::utils::layout_error_to_program_error)?; - Ok(()) - } - - pub fn try_from_bytes_with_discriminator( - data: &[u8], - ) -> Result { - let payload = super::utils::payload_with_discriminator( - &Self::DISCRIMINATOR, - data, - )?; - let view = ::decode(payload) - .map_err(super::utils::layout_error_to_program_error)?; - - Ok(Self { - registry_revision: view.registry_revision(), - entries: view - .entries() - .iter() - .map(|entry| VerifierRegistryEntry { - verifier_identity: *entry.verifier_identity(), - verifier_bond: *entry.verifier_bond(), - weight: entry.weight(), - }) - .collect(), - }) } } diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index 494a9052..4a283a79 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -2,8 +2,10 @@ use dlp_api::{ pda::fees_vault_pda, requires::RequireUninitializedAccountCtx, v2::{ + layout_error_to_program_error, pda::{PROTOCOL_CONFIG_SEED, VERIFIER_REGISTRY_SEED}, - InitProtocolConfigArgs, ProtocolConfig, VerifierRegistry, + InitProtocolConfigArgs, InitProtocolConfigArgsView, ProtocolConfig, + VerifierRegistry, }, }; use pinocchio::{ @@ -17,6 +19,7 @@ use crate::{ requires::{require_program, require_signer, require_uninitialized_pda}, solana_program::pubkey::Pubkey, }; +use wheels::layout::{Decodable, Encodable}; struct ProtocolConfigCtx; @@ -77,7 +80,8 @@ pub fn process_init_protocol_config( accounts: &[AccountView], data: &[u8], ) -> ProgramResult { - let args = InitProtocolConfigArgs::try_from_bytes(data) + let args = ::decode(data) + .map_err(layout_error_to_program_error) .map_err(to_pinocchio_program_error)?; validate_args(&args)?; @@ -120,7 +124,10 @@ pub fn process_init_protocol_config( create_pda( verifier_registry, &crate::fast::ID, - verifier_registry_state.size_with_discriminator(), + verifier_registry_state + .encoded_len() + .map_err(layout_error_to_program_error) + .map_err(to_pinocchio_program_error)?, &[Signer::from(&[ Seed::from(VERIFIER_REGISTRY_SEED), Seed::from(&[verifier_registry_bump]), @@ -129,53 +136,56 @@ pub fn process_init_protocol_config( )?; let protocol_config_state = ProtocolConfig { + discriminator: ProtocolConfig::DISCRIMINATOR, authority: authority.address().to_bytes().into(), paused: false, - vrf_program: args.vrf_program, - vrf_config: args.vrf_config, - resolver: args.resolver, + vrf_program: *args.vrf_program(), + vrf_config: *args.vrf_config(), + resolver: *args.resolver(), protocol_fee_vault: fees_vault_pda(), - min_operator_bond: args.min_operator_bond, - min_verifier_bond: args.min_verifier_bond, - min_challenger_stake: args.min_challenger_stake, - challenge_window_slots: args.challenge_window_slots, - operator_response_timeout_slots: args.operator_response_timeout_slots, - challenger_reveal_timeout_slots: args.challenger_reveal_timeout_slots, - payout_timelock_slots: args.payout_timelock_slots, - selected_verifier_count: args.selected_verifier_count, - approval_threshold: args.approval_threshold, - max_window_extensions: args.max_window_extensions, - match_penalty_bps: args.match_penalty_bps, + min_operator_bond: args.min_operator_bond(), + min_verifier_bond: args.min_verifier_bond(), + min_challenger_stake: args.min_challenger_stake(), + challenge_window_slots: args.challenge_window_slots(), + operator_response_timeout_slots: args.operator_response_timeout_slots(), + challenger_reveal_timeout_slots: args.challenger_reveal_timeout_slots(), + payout_timelock_slots: args.payout_timelock_slots(), + selected_verifier_count: args.selected_verifier_count(), + approval_threshold: args.approval_threshold(), + max_window_extensions: args.max_window_extensions(), + match_penalty_bps: args.match_penalty_bps(), }; let mut protocol_config_data = protocol_config.try_borrow_mut()?; protocol_config_state - .to_bytes_with_discriminator(protocol_config_data.as_mut()) + .encode_to(protocol_config_data.as_mut()) + .map_err(layout_error_to_program_error) .map_err(to_pinocchio_program_error)?; let mut verifier_registry_data = verifier_registry.try_borrow_mut()?; verifier_registry_state - .to_bytes_with_discriminator(verifier_registry_data.as_mut()) + .encode_to(verifier_registry_data.as_mut()) + .map_err(layout_error_to_program_error) .map_err(to_pinocchio_program_error)?; Ok(()) } -fn validate_args(args: &InitProtocolConfigArgs) -> ProgramResult { - if args.vrf_program == Pubkey::default() - || args.vrf_config == Pubkey::default() - || args.resolver == Pubkey::default() - || args.min_operator_bond == 0 - || args.min_verifier_bond == 0 - || args.min_challenger_stake == 0 - || args.challenge_window_slots == 0 - || args.operator_response_timeout_slots == 0 - || args.challenger_reveal_timeout_slots == 0 - || args.payout_timelock_slots == 0 - || args.selected_verifier_count == 0 - || args.approval_threshold == 0 - || args.approval_threshold > args.selected_verifier_count - || args.match_penalty_bps > 10_000 +fn validate_args(args: &InitProtocolConfigArgsView<'_>) -> ProgramResult { + if args.vrf_program() == &Pubkey::default() + || args.vrf_config() == &Pubkey::default() + || args.resolver() == &Pubkey::default() + || args.min_operator_bond() == 0 + || args.min_verifier_bond() == 0 + || args.min_challenger_stake() == 0 + || args.challenge_window_slots() == 0 + || args.operator_response_timeout_slots() == 0 + || args.challenger_reveal_timeout_slots() == 0 + || args.payout_timelock_slots() == 0 + || args.selected_verifier_count() == 0 + || args.approval_threshold() == 0 + || args.approval_threshold() > args.selected_verifier_count() + || args.match_penalty_bps() > 10_000 { return Err(ProgramError::InvalidInstructionData); } diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index 2fa0c698..5f786cd4 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -17,12 +17,14 @@ use solana_sdk::{ transaction::Transaction, }; use solana_sdk_ids::system_program; +use wheels::layout::{Decodable, Encodable}; mod fixtures; #[test] fn test_v2_verifier_registry_layout_round_trip() { let registry = VerifierRegistry { + discriminator: VerifierRegistry::DISCRIMINATOR, registry_revision: 7, entries: vec![ VerifierRegistryEntry { @@ -37,16 +39,28 @@ fn test_v2_verifier_registry_layout_round_trip() { }, ], }; - let mut data = vec![0; registry.size_with_discriminator()]; + let mut data = vec![0; registry.encoded_len().unwrap()]; - registry - .to_bytes_with_discriminator(data.as_mut_slice()) - .unwrap(); + registry.encode_to(data.as_mut_slice()).unwrap(); let decoded = - VerifierRegistry::try_from_bytes_with_discriminator(data.as_slice()) - .unwrap(); - - assert_eq!(decoded, registry); + ::decode(data.as_slice()).unwrap(); + + assert_eq!(decoded.discriminator(), VerifierRegistry::DISCRIMINATOR); + assert_eq!(decoded.registry_revision(), registry.registry_revision); + assert_eq!(decoded.entries().len(), registry.entries.len()); + for (decoded_entry, registry_entry) in + decoded.entries().iter().zip(registry.entries.iter()) + { + assert_eq!( + *decoded_entry.verifier_identity(), + registry_entry.verifier_identity + ); + assert_eq!( + *decoded_entry.verifier_bond(), + registry_entry.verifier_bond + ); + assert_eq!(decoded_entry.weight(), registry_entry.weight); + } } #[tokio::test] @@ -70,63 +84,72 @@ async fn test_v2_init_protocol_config() { .await .unwrap() .unwrap(); - let protocol_config = ProtocolConfig::try_from_bytes_with_discriminator( - &protocol_config_account.data, - ) - .unwrap(); + let protocol_config = + ::decode(&protocol_config_account.data) + .unwrap(); - assert_eq!(protocol_config.authority, authority.pubkey()); - assert!(!protocol_config.paused); - assert_eq!(protocol_config.vrf_program, args.vrf_program); - assert_eq!(protocol_config.vrf_config, args.vrf_config); - assert_eq!(protocol_config.resolver, args.resolver); - assert_eq!(protocol_config.protocol_fee_vault, fees_vault_pda()); - assert_eq!(protocol_config.min_operator_bond, args.min_operator_bond); - assert_eq!(protocol_config.min_verifier_bond, args.min_verifier_bond); assert_eq!( - protocol_config.min_challenger_stake, + protocol_config.discriminator(), + ProtocolConfig::DISCRIMINATOR + ); + assert_eq!(*protocol_config.authority(), authority.pubkey()); + assert!(!protocol_config.paused()); + assert_eq!(*protocol_config.vrf_program(), args.vrf_program); + assert_eq!(*protocol_config.vrf_config(), args.vrf_config); + assert_eq!(*protocol_config.resolver(), args.resolver); + assert_eq!(*protocol_config.protocol_fee_vault(), fees_vault_pda()); + assert_eq!(protocol_config.min_operator_bond(), args.min_operator_bond); + assert_eq!(protocol_config.min_verifier_bond(), args.min_verifier_bond); + assert_eq!( + protocol_config.min_challenger_stake(), args.min_challenger_stake ); assert_eq!( - protocol_config.challenge_window_slots, + protocol_config.challenge_window_slots(), args.challenge_window_slots ); assert_eq!( - protocol_config.operator_response_timeout_slots, + protocol_config.operator_response_timeout_slots(), args.operator_response_timeout_slots ); assert_eq!( - protocol_config.challenger_reveal_timeout_slots, + protocol_config.challenger_reveal_timeout_slots(), args.challenger_reveal_timeout_slots ); assert_eq!( - protocol_config.payout_timelock_slots, + protocol_config.payout_timelock_slots(), args.payout_timelock_slots ); assert_eq!( - protocol_config.selected_verifier_count, + protocol_config.selected_verifier_count(), args.selected_verifier_count ); - assert_eq!(protocol_config.approval_threshold, args.approval_threshold); assert_eq!( - protocol_config.max_window_extensions, + protocol_config.approval_threshold(), + args.approval_threshold + ); + assert_eq!( + protocol_config.max_window_extensions(), args.max_window_extensions ); - assert_eq!(protocol_config.match_penalty_bps, args.match_penalty_bps); + assert_eq!(protocol_config.match_penalty_bps(), args.match_penalty_bps); let verifier_registry_account = banks .get_account(verifier_registry_pda()) .await .unwrap() .unwrap(); - let verifier_registry = - VerifierRegistry::try_from_bytes_with_discriminator( - &verifier_registry_account.data, - ) - .unwrap(); + let verifier_registry = ::decode( + &verifier_registry_account.data, + ) + .unwrap(); - assert_eq!(verifier_registry.registry_revision, 0); - assert!(verifier_registry.entries.is_empty()); + assert_eq!( + verifier_registry.discriminator(), + VerifierRegistry::DISCRIMINATOR + ); + assert_eq!(verifier_registry.registry_revision(), 0); + assert!(verifier_registry.entries().is_empty()); } #[tokio::test] From 8b3c741f7f8c8cb1e5a8a1b86cd958f7dc87096e Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 18 Aug 2026 19:29:00 +0530 Subject: [PATCH 10/25] Add shared program account context --- dlp-api/src/requires.rs | 33 +++++++++++ .../bootstrap/init_protocol_config.rs | 57 ++----------------- 2 files changed, 38 insertions(+), 52 deletions(-) diff --git a/dlp-api/src/requires.rs b/dlp-api/src/requires.rs index a6ff6521..ec7bd212 100644 --- a/dlp-api/src/requires.rs +++ b/dlp-api/src/requires.rs @@ -488,6 +488,39 @@ pub trait RequireUninitializedAccountCtx { fn immutable(&self) -> ProgramError; } +#[derive(Clone, Copy, Debug)] +pub struct ProgramCtx { + label: &'static str, +} + +impl ProgramCtx { + pub const fn new(label: &'static str) -> Self { + Self { label } + } +} + +impl RequireUninitializedAccountCtx for ProgramCtx { + fn label(&self) -> &str { + self.label + } + + fn invalid_seeds(&self) -> ProgramError { + ProgramError::InvalidSeeds + } + + fn invalid_account_owner(&self) -> ProgramError { + ProgramError::InvalidAccountOwner + } + + fn account_already_initialized(&self) -> ProgramError { + ProgramError::AccountAlreadyInitialized + } + + fn immutable(&self) -> ProgramError { + ProgramError::Immutable + } +} + macro_rules! define_uninitialized_ctx { ( $name:ident, diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index 4a283a79..b15e4ed4 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -1,6 +1,5 @@ use dlp_api::{ pda::fees_vault_pda, - requires::RequireUninitializedAccountCtx, v2::{ layout_error_to_program_error, pda::{PROTOCOL_CONFIG_SEED, VERIFIER_REGISTRY_SEED}, @@ -16,59 +15,13 @@ use pinocchio::{ use crate::{ processor::fast::{to_pinocchio_program_error, utils::pda::create_pda}, - requires::{require_program, require_signer, require_uninitialized_pda}, + requires::{ + require_program, require_signer, require_uninitialized_pda, ProgramCtx, + }, solana_program::pubkey::Pubkey, }; use wheels::layout::{Decodable, Encodable}; -struct ProtocolConfigCtx; - -impl RequireUninitializedAccountCtx for ProtocolConfigCtx { - fn label(&self) -> &str { - "protocol config" - } - - fn invalid_seeds(&self) -> ProgramError { - ProgramError::InvalidSeeds - } - - fn invalid_account_owner(&self) -> ProgramError { - ProgramError::InvalidAccountOwner - } - - fn account_already_initialized(&self) -> ProgramError { - ProgramError::AccountAlreadyInitialized - } - - fn immutable(&self) -> ProgramError { - ProgramError::Immutable - } -} - -struct VerifierRegistryCtx; - -impl RequireUninitializedAccountCtx for VerifierRegistryCtx { - fn label(&self) -> &str { - "verifier registry" - } - - fn invalid_seeds(&self) -> ProgramError { - ProgramError::InvalidSeeds - } - - fn invalid_account_owner(&self) -> ProgramError { - ProgramError::InvalidAccountOwner - } - - fn account_already_initialized(&self) -> ProgramError { - ProgramError::AccountAlreadyInitialized - } - - fn immutable(&self) -> ProgramError { - ProgramError::Immutable - } -} - /// Initialize the global v2 protocol config accounts. /// /// Accounts: @@ -99,14 +52,14 @@ pub fn process_init_protocol_config( &[PROTOCOL_CONFIG_SEED], &crate::fast::ID, true, - ProtocolConfigCtx, + ProgramCtx::new("protocol config"), )?; let verifier_registry_bump = require_uninitialized_pda( verifier_registry, &[VERIFIER_REGISTRY_SEED], &crate::fast::ID, true, - VerifierRegistryCtx, + ProgramCtx::new("verifier registry"), )?; create_pda( From 6f17d1ca371d47f524d2e941bf7b394f6a834f66 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 18 Aug 2026 20:24:26 +0530 Subject: [PATCH 11/25] Use general require macros in v2 init config --- .../bootstrap/init_protocol_config.rs | 122 +++++++++++++----- 1 file changed, 90 insertions(+), 32 deletions(-) diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index b15e4ed4..3ca4cdcc 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -15,37 +15,40 @@ use pinocchio::{ use crate::{ processor::fast::{to_pinocchio_program_error, utils::pda::create_pda}, - requires::{ - require_program, require_signer, require_uninitialized_pda, ProgramCtx, - }, + requires::{require_uninitialized_pda, ProgramCtx}, solana_program::pubkey::Pubkey, }; -use wheels::layout::{Decodable, Encodable}; +use wheels::{ + layout::{Decodable, Encodable}, + require_le, require_n_accounts, require_ne, require_ne_keys, + require_signer, +}; -/// Initialize the global v2 protocol config accounts. +/// Initialize the global protocol config accounts. /// /// Accounts: /// 0: `[signer, writable]` authority that pays rent and controls v2 config /// 1: `[writable]` ProtocolConfig PDA /// 2: `[writable]` VerifierRegistry PDA -/// 3: `[]` system program +/// 3: `[]` system program, required by system CPI pub fn process_init_protocol_config( accounts: &[AccountView], data: &[u8], ) -> ProgramResult { - let args = ::decode(data) + let [ + authority, // force multi-line + protocol_config, + verifier_registry, + _system_program, + ] = require_n_accounts!(accounts, 4); + + let args = InitProtocolConfigArgs::decode(data) .map_err(layout_error_to_program_error) .map_err(to_pinocchio_program_error)?; - validate_args(&args)?; - let [authority, protocol_config, verifier_registry, system_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; + validate_args(&args)?; - require_signer(authority, "authority")?; - require_program(system_program, &pinocchio_system::ID, "system program")?; + require_signer!(authority); let protocol_config_bump = require_uninitialized_pda( protocol_config, @@ -125,23 +128,78 @@ pub fn process_init_protocol_config( } fn validate_args(args: &InitProtocolConfigArgsView<'_>) -> ProgramResult { - if args.vrf_program() == &Pubkey::default() - || args.vrf_config() == &Pubkey::default() - || args.resolver() == &Pubkey::default() - || args.min_operator_bond() == 0 - || args.min_verifier_bond() == 0 - || args.min_challenger_stake() == 0 - || args.challenge_window_slots() == 0 - || args.operator_response_timeout_slots() == 0 - || args.challenger_reveal_timeout_slots() == 0 - || args.payout_timelock_slots() == 0 - || args.selected_verifier_count() == 0 - || args.approval_threshold() == 0 - || args.approval_threshold() > args.selected_verifier_count() - || args.match_penalty_bps() > 10_000 - { - return Err(ProgramError::InvalidInstructionData); - } + let default_pubkey = Pubkey::default(); + + require_ne_keys!( + args.vrf_program(), + &default_pubkey, + ProgramError::InvalidInstructionData + ); + require_ne_keys!( + args.vrf_config(), + &default_pubkey, + ProgramError::InvalidInstructionData + ); + require_ne_keys!( + args.resolver(), + &default_pubkey, + ProgramError::InvalidInstructionData + ); + require_ne!( + args.min_operator_bond(), + 0, + ProgramError::InvalidInstructionData + ); + require_ne!( + args.min_verifier_bond(), + 0, + ProgramError::InvalidInstructionData + ); + require_ne!( + args.min_challenger_stake(), + 0, + ProgramError::InvalidInstructionData + ); + require_ne!( + args.challenge_window_slots(), + 0, + ProgramError::InvalidInstructionData + ); + require_ne!( + args.operator_response_timeout_slots(), + 0, + ProgramError::InvalidInstructionData + ); + require_ne!( + args.challenger_reveal_timeout_slots(), + 0, + ProgramError::InvalidInstructionData + ); + require_ne!( + args.payout_timelock_slots(), + 0, + ProgramError::InvalidInstructionData + ); + require_ne!( + args.selected_verifier_count(), + 0, + ProgramError::InvalidInstructionData + ); + require_ne!( + args.approval_threshold(), + 0, + ProgramError::InvalidInstructionData + ); + require_le!( + args.approval_threshold(), + args.selected_verifier_count(), + ProgramError::InvalidInstructionData + ); + require_le!( + args.match_penalty_bps(), + 10_000, + ProgramError::InvalidInstructionData + ); Ok(()) } From 1e6567782ee217dfcd39f48935cdc9577d01e173 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 18 Aug 2026 20:32:29 +0530 Subject: [PATCH 12/25] Use direct layout error conversion --- dlp-api/src/v2/state/mod.rs | 2 -- dlp-api/src/v2/state/utils.rs | 7 ------ .../bootstrap/init_protocol_config.rs | 22 +++++-------------- 3 files changed, 5 insertions(+), 26 deletions(-) delete mode 100644 dlp-api/src/v2/state/utils.rs diff --git a/dlp-api/src/v2/state/mod.rs b/dlp-api/src/v2/state/mod.rs index 45bff348..31678d9d 100644 --- a/dlp-api/src/v2/state/mod.rs +++ b/dlp-api/src/v2/state/mod.rs @@ -1,7 +1,5 @@ mod protocol_config; -mod utils; mod verifier_registry; pub use protocol_config::*; -pub use utils::layout_error_to_program_error; pub use verifier_registry::*; diff --git a/dlp-api/src/v2/state/utils.rs b/dlp-api/src/v2/state/utils.rs deleted file mode 100644 index ce70c333..00000000 --- a/dlp-api/src/v2/state/utils.rs +++ /dev/null @@ -1,7 +0,0 @@ -use wheels::DataLayoutError; - -use crate::solana_program::program_error::ProgramError; - -pub fn layout_error_to_program_error(error: DataLayoutError) -> ProgramError { - ProgramError::Custom(error.code()) -} diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index 3ca4cdcc..430f7168 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -1,7 +1,6 @@ use dlp_api::{ pda::fees_vault_pda, v2::{ - layout_error_to_program_error, pda::{PROTOCOL_CONFIG_SEED, VERIFIER_REGISTRY_SEED}, InitProtocolConfigArgs, InitProtocolConfigArgsView, ProtocolConfig, VerifierRegistry, @@ -14,7 +13,7 @@ use pinocchio::{ }; use crate::{ - processor::fast::{to_pinocchio_program_error, utils::pda::create_pda}, + processor::fast::utils::pda::create_pda, requires::{require_uninitialized_pda, ProgramCtx}, solana_program::pubkey::Pubkey, }; @@ -42,9 +41,7 @@ pub fn process_init_protocol_config( _system_program, ] = require_n_accounts!(accounts, 4); - let args = InitProtocolConfigArgs::decode(data) - .map_err(layout_error_to_program_error) - .map_err(to_pinocchio_program_error)?; + let args = InitProtocolConfigArgs::decode(data)?; validate_args(&args)?; @@ -80,10 +77,7 @@ pub fn process_init_protocol_config( create_pda( verifier_registry, &crate::fast::ID, - verifier_registry_state - .encoded_len() - .map_err(layout_error_to_program_error) - .map_err(to_pinocchio_program_error)?, + verifier_registry_state.encoded_len()?, &[Signer::from(&[ Seed::from(VERIFIER_REGISTRY_SEED), Seed::from(&[verifier_registry_bump]), @@ -113,16 +107,10 @@ pub fn process_init_protocol_config( }; let mut protocol_config_data = protocol_config.try_borrow_mut()?; - protocol_config_state - .encode_to(protocol_config_data.as_mut()) - .map_err(layout_error_to_program_error) - .map_err(to_pinocchio_program_error)?; + protocol_config_state.encode_to(protocol_config_data.as_mut())?; let mut verifier_registry_data = verifier_registry.try_borrow_mut()?; - verifier_registry_state - .encode_to(verifier_registry_data.as_mut()) - .map_err(layout_error_to_program_error) - .map_err(to_pinocchio_program_error)?; + verifier_registry_state.encode_to(verifier_registry_data.as_mut())?; Ok(()) } From 2e538ea37579faf3eda7ea4407ee4b58edfe25e1 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 18 Aug 2026 20:54:44 +0530 Subject: [PATCH 13/25] Rename standard account context --- dlp-api/src/requires.rs | 6 +++--- src/v2/processor/bootstrap/init_protocol_config.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dlp-api/src/requires.rs b/dlp-api/src/requires.rs index ec7bd212..f481d9d7 100644 --- a/dlp-api/src/requires.rs +++ b/dlp-api/src/requires.rs @@ -489,17 +489,17 @@ pub trait RequireUninitializedAccountCtx { } #[derive(Clone, Copy, Debug)] -pub struct ProgramCtx { +pub struct StandardCtx { label: &'static str, } -impl ProgramCtx { +impl StandardCtx { pub const fn new(label: &'static str) -> Self { Self { label } } } -impl RequireUninitializedAccountCtx for ProgramCtx { +impl RequireUninitializedAccountCtx for StandardCtx { fn label(&self) -> &str { self.label } diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index 430f7168..a0614a5a 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -14,7 +14,7 @@ use pinocchio::{ use crate::{ processor::fast::utils::pda::create_pda, - requires::{require_uninitialized_pda, ProgramCtx}, + requires::{require_uninitialized_pda, StandardCtx}, solana_program::pubkey::Pubkey, }; use wheels::{ @@ -52,14 +52,14 @@ pub fn process_init_protocol_config( &[PROTOCOL_CONFIG_SEED], &crate::fast::ID, true, - ProgramCtx::new("protocol config"), + StandardCtx::new("protocol config"), )?; let verifier_registry_bump = require_uninitialized_pda( verifier_registry, &[VERIFIER_REGISTRY_SEED], &crate::fast::ID, true, - ProgramCtx::new("verifier registry"), + StandardCtx::new("verifier registry"), )?; create_pda( From f725f0034c3d18c1a9d913d8c57bb7195b0e0104 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 18 Aug 2026 21:08:04 +0530 Subject: [PATCH 14/25] Use verifier registry minimum layout size --- dlp-api/src/v2/state/protocol_config.rs | 1 - dlp-api/src/v2/state/verifier_registry.rs | 1 - .../processor/bootstrap/init_protocol_config.rs | 17 +++++++---------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/dlp-api/src/v2/state/protocol_config.rs b/dlp-api/src/v2/state/protocol_config.rs index 3a99ebf4..f9c03de1 100644 --- a/dlp-api/src/v2/state/protocol_config.rs +++ b/dlp-api/src/v2/state/protocol_config.rs @@ -31,5 +31,4 @@ pub struct ProtocolConfig { impl ProtocolConfig { pub const DISCRIMINATOR: [u8; 8] = *b"v2cfg000"; - pub const SPACE: usize = Self::DATA_LEN; } diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index f2660b0d..efd14bd2 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -21,7 +21,6 @@ pub struct VerifierRegistry { impl VerifierRegistry { pub const DISCRIMINATOR: [u8; 8] = *b"v2vreg00"; - pub const EMPTY_SPACE: usize = Self::MIN_DATA_LEN; } impl Default for VerifierRegistry { diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index a0614a5a..7fdf20b3 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -65,7 +65,7 @@ pub fn process_init_protocol_config( create_pda( protocol_config, &crate::fast::ID, - ProtocolConfig::SPACE, + ProtocolConfig::DATA_LEN, &[Signer::from(&[ Seed::from(PROTOCOL_CONFIG_SEED), Seed::from(&[protocol_config_bump]), @@ -73,11 +73,10 @@ pub fn process_init_protocol_config( authority, )?; - let verifier_registry_state = VerifierRegistry::default(); create_pda( verifier_registry, &crate::fast::ID, - verifier_registry_state.encoded_len()?, + VerifierRegistry::MIN_DATA_LEN, &[Signer::from(&[ Seed::from(VERIFIER_REGISTRY_SEED), Seed::from(&[verifier_registry_bump]), @@ -85,7 +84,7 @@ pub fn process_init_protocol_config( authority, )?; - let protocol_config_state = ProtocolConfig { + ProtocolConfig { discriminator: ProtocolConfig::DISCRIMINATOR, authority: authority.address().to_bytes().into(), paused: false, @@ -104,13 +103,11 @@ pub fn process_init_protocol_config( approval_threshold: args.approval_threshold(), max_window_extensions: args.max_window_extensions(), match_penalty_bps: args.match_penalty_bps(), - }; + } + .encode_to(protocol_config.try_borrow_mut()?.as_mut())?; - let mut protocol_config_data = protocol_config.try_borrow_mut()?; - protocol_config_state.encode_to(protocol_config_data.as_mut())?; - - let mut verifier_registry_data = verifier_registry.try_borrow_mut()?; - verifier_registry_state.encode_to(verifier_registry_data.as_mut())?; + VerifierRegistry::default() + .encode_to(verifier_registry.try_borrow_mut()?.as_mut())?; Ok(()) } From 77d6a7e3320a56b533a242922ea401b71823ba78 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 18 Aug 2026 21:15:18 +0530 Subject: [PATCH 15/25] add docs for accounts --- dlp-api/src/v2/state/protocol_config.rs | 33 +++++++++++++++++++++++ dlp-api/src/v2/state/verifier_registry.rs | 32 ++++++++++++++++------ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/dlp-api/src/v2/state/protocol_config.rs b/dlp-api/src/v2/state/protocol_config.rs index f9c03de1..a267928f 100644 --- a/dlp-api/src/v2/state/protocol_config.rs +++ b/dlp-api/src/v2/state/protocol_config.rs @@ -2,30 +2,63 @@ use wheels::fixed_offset_layout; use crate::compat::Pubkey; +/// PDA: `["protocol-config"]`. +/// Created by `InitProtocolConfig`; not normally closed. #[derive(Clone, Debug, PartialEq, Eq)] #[fixed_offset_layout(buffer_offset = 0)] pub struct ProtocolConfig { + /// Account type marker. pub discriminator: [u8; 8], + + /// Signer allowed to update config and permissioned bootstrap state. pub authority: Pubkey, + + /// Emergency stop for new commitments and other non-exit activity. pub paused: bool, + /// VRF program DLP trusts for randomness callbacks. pub vrf_program: Pubkey, + + /// VRF-specific config account. pub vrf_config: Pubkey, + + /// Multisig-controlled signer allowed to resolve disputes. pub resolver: Pubkey, + + /// Vault receiving protocol fees, penalties, or slashed funds. pub protocol_fee_vault: Pubkey, + /// Minimum stake required for an operator to register and stay active. pub min_operator_bond: u64, + + /// Minimum stake required for a verifier to register and stay active. pub min_verifier_bond: u64, + + /// Minimum stake locked by a challenge to prevent cheap spam. pub min_challenger_stake: u64, + /// Slots available for approval/challenge after VRF activation. pub challenge_window_slots: u64, + + /// Slots the operator gets to open state after a challenge. pub operator_response_timeout_slots: u64, + + /// Slots the challenger gets to reveal after operator response or timeout. pub challenger_reveal_timeout_slots: u64, + + /// Delay before a winning challenger can claim payout. pub payout_timelock_slots: u64, + /// Number of verifiers randomly picked from VerifierRegistry. pub selected_verifier_count: u16, + + /// Approvals required for happy-path finalization. pub approval_threshold: u16, + + /// Maximum under-approval extensions before the commitment expires. pub max_window_extensions: u16, + + /// Penalty for a valid reveal that matches the operator state. pub match_penalty_bps: u16, } diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index efd14bd2..a0663f7c 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -2,23 +2,39 @@ use wheels::fixed_offset_layout; use crate::compat::Pubkey; -#[derive(Clone, Debug, PartialEq, Eq)] -#[fixed_offset_layout(buffer_offset = 2)] -pub struct VerifierRegistryEntry { - pub verifier_identity: Pubkey, - pub verifier_bond: Pubkey, - pub weight: u64, -} - +/// PDA: `["verifier-registry"]`. +/// Created by `InitProtocolConfig`; updated by `UpdateVerifierRegistry`. +/// Not normally closed. #[derive(Clone, Debug, PartialEq, Eq)] #[fixed_offset_layout(buffer_offset = 0)] pub struct VerifierRegistry { + /// Account type marker. pub discriminator: [u8; 8], + + /// Increments every time `entries` changes. + /// + /// A pending commitment stores this value before requesting VRF. The VRF + /// callback must see the same value before it can select verifiers. pub registry_revision: u64, + + /// All registered verifiers DLP can select from. #[flexible = 2] pub entries: Vec, } +#[derive(Clone, Debug, PartialEq, Eq)] +#[fixed_offset_layout(buffer_offset = 2)] +pub struct VerifierRegistryEntry { + /// Verifier identity selectable by VRF. + pub verifier_identity: Pubkey, + + /// Bond account proving this verifier has active stake. + pub verifier_bond: Pubkey, + + /// Selection weight. Keep as 1 for equal-weight selection. + pub weight: u64, +} + impl VerifierRegistry { pub const DISCRIMINATOR: [u8; 8] = *b"v2vreg00"; } From ceeefcfd1bfa299eae92aaa36761c3f668787fce Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Wed, 19 Aug 2026 20:07:05 +0530 Subject: [PATCH 16/25] First review --- dlp-api/src/v2/args/init_protocol_config.rs | 3 +-- dlp-api/src/v2/state/protocol_config.rs | 7 ++---- .../bootstrap/init_protocol_config.rs | 10 ++------ tests/test_v2_init_protocol_config.rs | 24 +++++++++++++++---- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/dlp-api/src/v2/args/init_protocol_config.rs b/dlp-api/src/v2/args/init_protocol_config.rs index 422a857a..f487bf8b 100644 --- a/dlp-api/src/v2/args/init_protocol_config.rs +++ b/dlp-api/src/v2/args/init_protocol_config.rs @@ -5,8 +5,7 @@ use crate::compat::Pubkey; #[derive(Clone, Debug, PartialEq, Eq)] #[variable_offset_layout(buffer_offset = 0)] pub struct InitProtocolConfigArgs { - pub vrf_program: Pubkey, - pub vrf_config: Pubkey, + pub vrf_oracle_queue: Pubkey, pub resolver: Pubkey, pub min_operator_bond: u64, diff --git a/dlp-api/src/v2/state/protocol_config.rs b/dlp-api/src/v2/state/protocol_config.rs index a267928f..aaed8094 100644 --- a/dlp-api/src/v2/state/protocol_config.rs +++ b/dlp-api/src/v2/state/protocol_config.rs @@ -16,11 +16,8 @@ pub struct ProtocolConfig { /// Emergency stop for new commitments and other non-exit activity. pub paused: bool, - /// VRF program DLP trusts for randomness callbacks. - pub vrf_program: Pubkey, - - /// VRF-specific config account. - pub vrf_config: Pubkey, + /// Oracle queue DLP uses when requesting randomness. + pub vrf_oracle_queue: Pubkey, /// Multisig-controlled signer allowed to resolve disputes. pub resolver: Pubkey, diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index 7fdf20b3..fc475fea 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -88,8 +88,7 @@ pub fn process_init_protocol_config( discriminator: ProtocolConfig::DISCRIMINATOR, authority: authority.address().to_bytes().into(), paused: false, - vrf_program: *args.vrf_program(), - vrf_config: *args.vrf_config(), + vrf_oracle_queue: *args.vrf_oracle_queue(), resolver: *args.resolver(), protocol_fee_vault: fees_vault_pda(), min_operator_bond: args.min_operator_bond(), @@ -116,12 +115,7 @@ fn validate_args(args: &InitProtocolConfigArgsView<'_>) -> ProgramResult { let default_pubkey = Pubkey::default(); require_ne_keys!( - args.vrf_program(), - &default_pubkey, - ProgramError::InvalidInstructionData - ); - require_ne_keys!( - args.vrf_config(), + args.vrf_oracle_queue(), &default_pubkey, ProgramError::InvalidInstructionData ); diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index 5f786cd4..58cf7756 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -94,8 +94,7 @@ async fn test_v2_init_protocol_config() { ); assert_eq!(*protocol_config.authority(), authority.pubkey()); assert!(!protocol_config.paused()); - assert_eq!(*protocol_config.vrf_program(), args.vrf_program); - assert_eq!(*protocol_config.vrf_config(), args.vrf_config); + assert_eq!(*protocol_config.vrf_oracle_queue(), args.vrf_oracle_queue); assert_eq!(*protocol_config.resolver(), args.resolver); assert_eq!(*protocol_config.protocol_fee_vault(), fees_vault_pda()); assert_eq!(protocol_config.min_operator_bond(), args.min_operator_bond); @@ -246,6 +245,24 @@ async fn test_v2_init_protocol_config_fails_with_invalid_args() { assert!(banks.process_transaction(tx).await.is_err()); } +#[tokio::test] +async fn test_v2_init_protocol_config_fails_with_default_vrf_oracle_queue() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + + let mut args = valid_args(); + args.vrf_oracle_queue = Pubkey::default(); + + let ix = init_protocol_config(authority.pubkey(), args); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + #[tokio::test] async fn test_v1_dispatch_still_works_after_v2_routing() { let (banks, payer, _authority, blockhash) = setup_program_test_env().await; @@ -264,8 +281,7 @@ async fn test_v1_dispatch_still_works_after_v2_routing() { fn valid_args() -> InitProtocolConfigArgs { InitProtocolConfigArgs { - vrf_program: Pubkey::new_unique(), - vrf_config: Pubkey::new_unique(), + vrf_oracle_queue: Pubkey::new_unique(), resolver: Pubkey::new_unique(), min_operator_bond: 1, min_verifier_bond: 1, From 67cf8f7684278c70d05b47158893f23698242726 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Fri, 21 Aug 2026 00:18:37 +0530 Subject: [PATCH 17/25] Drop the idea of using VRF in favor of round-robin strategy --- dlp-api/src/v2/args/init_protocol_config.rs | 3 +- dlp-api/src/v2/state/protocol_config.rs | 9 ++--- dlp-api/src/v2/state/verifier_registry.rs | 12 ++++-- .../bootstrap/init_protocol_config.rs | 14 ++----- tests/test_v2_init_protocol_config.rs | 38 +++++++++++++++---- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/dlp-api/src/v2/args/init_protocol_config.rs b/dlp-api/src/v2/args/init_protocol_config.rs index f487bf8b..8ed2fb62 100644 --- a/dlp-api/src/v2/args/init_protocol_config.rs +++ b/dlp-api/src/v2/args/init_protocol_config.rs @@ -5,7 +5,6 @@ use crate::compat::Pubkey; #[derive(Clone, Debug, PartialEq, Eq)] #[variable_offset_layout(buffer_offset = 0)] pub struct InitProtocolConfigArgs { - pub vrf_oracle_queue: Pubkey, pub resolver: Pubkey, pub min_operator_bond: u64, @@ -17,7 +16,7 @@ pub struct InitProtocolConfigArgs { pub challenger_reveal_timeout_slots: u64, pub payout_timelock_slots: u64, - pub selected_verifier_count: u16, + pub verifiers_per_commitment: u16, pub approval_threshold: u16, pub max_window_extensions: u16, pub match_penalty_bps: u16, diff --git a/dlp-api/src/v2/state/protocol_config.rs b/dlp-api/src/v2/state/protocol_config.rs index aaed8094..e8538980 100644 --- a/dlp-api/src/v2/state/protocol_config.rs +++ b/dlp-api/src/v2/state/protocol_config.rs @@ -16,9 +16,6 @@ pub struct ProtocolConfig { /// Emergency stop for new commitments and other non-exit activity. pub paused: bool, - /// Oracle queue DLP uses when requesting randomness. - pub vrf_oracle_queue: Pubkey, - /// Multisig-controlled signer allowed to resolve disputes. pub resolver: Pubkey, @@ -34,7 +31,7 @@ pub struct ProtocolConfig { /// Minimum stake locked by a challenge to prevent cheap spam. pub min_challenger_stake: u64, - /// Slots available for approval/challenge after VRF activation. + /// Slots available for approval/challenge after commitment post. pub challenge_window_slots: u64, /// Slots the operator gets to open state after a challenge. @@ -46,8 +43,8 @@ pub struct ProtocolConfig { /// Delay before a winning challenger can claim payout. pub payout_timelock_slots: u64, - /// Number of verifiers randomly picked from VerifierRegistry. - pub selected_verifier_count: u16, + /// Maximum number of verifiers selected for one commitment. + pub verifiers_per_commitment: u16, /// Approvals required for happy-path finalization. pub approval_threshold: u16, diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index a0663f7c..efb49452 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -13,10 +13,13 @@ pub struct VerifierRegistry { /// Increments every time `entries` changes. /// - /// A pending commitment stores this value before requesting VRF. The VRF - /// callback must see the same value before it can select verifiers. + /// Pending commitments store this value when selected verifiers are copied + /// from this registry. pub registry_revision: u64, + /// Round-robin start cursor used by the next commitment selection. + pub next_selection_index: u64, + /// All registered verifiers DLP can select from. #[flexible = 2] pub entries: Vec, @@ -25,13 +28,13 @@ pub struct VerifierRegistry { #[derive(Clone, Debug, PartialEq, Eq)] #[fixed_offset_layout(buffer_offset = 2)] pub struct VerifierRegistryEntry { - /// Verifier identity selectable by VRF. + /// Verifier identity selectable by DLP. pub verifier_identity: Pubkey, /// Bond account proving this verifier has active stake. pub verifier_bond: Pubkey, - /// Selection weight. Keep as 1 for equal-weight selection. + /// Selection weight. Keep as 1 until weighted selection is implemented. pub weight: u64, } @@ -44,6 +47,7 @@ impl Default for VerifierRegistry { Self { discriminator: Self::DISCRIMINATOR, registry_revision: 0, + next_selection_index: 0, entries: Vec::new(), } } diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index fc475fea..35633314 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -1,4 +1,5 @@ use dlp_api::{ + compat::Pubkey, pda::fees_vault_pda, v2::{ pda::{PROTOCOL_CONFIG_SEED, VERIFIER_REGISTRY_SEED}, @@ -15,7 +16,6 @@ use pinocchio::{ use crate::{ processor::fast::utils::pda::create_pda, requires::{require_uninitialized_pda, StandardCtx}, - solana_program::pubkey::Pubkey, }; use wheels::{ layout::{Decodable, Encodable}, @@ -88,7 +88,6 @@ pub fn process_init_protocol_config( discriminator: ProtocolConfig::DISCRIMINATOR, authority: authority.address().to_bytes().into(), paused: false, - vrf_oracle_queue: *args.vrf_oracle_queue(), resolver: *args.resolver(), protocol_fee_vault: fees_vault_pda(), min_operator_bond: args.min_operator_bond(), @@ -98,7 +97,7 @@ pub fn process_init_protocol_config( operator_response_timeout_slots: args.operator_response_timeout_slots(), challenger_reveal_timeout_slots: args.challenger_reveal_timeout_slots(), payout_timelock_slots: args.payout_timelock_slots(), - selected_verifier_count: args.selected_verifier_count(), + verifiers_per_commitment: args.verifiers_per_commitment(), approval_threshold: args.approval_threshold(), max_window_extensions: args.max_window_extensions(), match_penalty_bps: args.match_penalty_bps(), @@ -114,11 +113,6 @@ pub fn process_init_protocol_config( fn validate_args(args: &InitProtocolConfigArgsView<'_>) -> ProgramResult { let default_pubkey = Pubkey::default(); - require_ne_keys!( - args.vrf_oracle_queue(), - &default_pubkey, - ProgramError::InvalidInstructionData - ); require_ne_keys!( args.resolver(), &default_pubkey, @@ -160,7 +154,7 @@ fn validate_args(args: &InitProtocolConfigArgsView<'_>) -> ProgramResult { ProgramError::InvalidInstructionData ); require_ne!( - args.selected_verifier_count(), + args.verifiers_per_commitment(), 0, ProgramError::InvalidInstructionData ); @@ -171,7 +165,7 @@ fn validate_args(args: &InitProtocolConfigArgsView<'_>) -> ProgramResult { ); require_le!( args.approval_threshold(), - args.selected_verifier_count(), + args.verifiers_per_commitment(), ProgramError::InvalidInstructionData ); require_le!( diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index 58cf7756..462f9f69 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -26,6 +26,7 @@ fn test_v2_verifier_registry_layout_round_trip() { let registry = VerifierRegistry { discriminator: VerifierRegistry::DISCRIMINATOR, registry_revision: 7, + next_selection_index: 5, entries: vec![ VerifierRegistryEntry { verifier_identity: Pubkey::new_unique(), @@ -47,6 +48,10 @@ fn test_v2_verifier_registry_layout_round_trip() { assert_eq!(decoded.discriminator(), VerifierRegistry::DISCRIMINATOR); assert_eq!(decoded.registry_revision(), registry.registry_revision); + assert_eq!( + decoded.next_selection_index(), + registry.next_selection_index + ); assert_eq!(decoded.entries().len(), registry.entries.len()); for (decoded_entry, registry_entry) in decoded.entries().iter().zip(registry.entries.iter()) @@ -94,7 +99,6 @@ async fn test_v2_init_protocol_config() { ); assert_eq!(*protocol_config.authority(), authority.pubkey()); assert!(!protocol_config.paused()); - assert_eq!(*protocol_config.vrf_oracle_queue(), args.vrf_oracle_queue); assert_eq!(*protocol_config.resolver(), args.resolver); assert_eq!(*protocol_config.protocol_fee_vault(), fees_vault_pda()); assert_eq!(protocol_config.min_operator_bond(), args.min_operator_bond); @@ -120,8 +124,8 @@ async fn test_v2_init_protocol_config() { args.payout_timelock_slots ); assert_eq!( - protocol_config.selected_verifier_count(), - args.selected_verifier_count + protocol_config.verifiers_per_commitment(), + args.verifiers_per_commitment ); assert_eq!( protocol_config.approval_threshold(), @@ -148,6 +152,7 @@ async fn test_v2_init_protocol_config() { VerifierRegistry::DISCRIMINATOR ); assert_eq!(verifier_registry.registry_revision(), 0); + assert_eq!(verifier_registry.next_selection_index(), 0); assert!(verifier_registry.entries().is_empty()); } @@ -232,7 +237,25 @@ async fn test_v2_init_protocol_config_fails_with_invalid_args() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; let mut args = valid_args(); - args.approval_threshold = args.selected_verifier_count + 1; + args.approval_threshold = args.verifiers_per_commitment + 1; + + let ix = init_protocol_config(authority.pubkey(), args); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +#[tokio::test] +async fn test_v2_init_protocol_config_fails_with_zero_verifier_cap() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + + let mut args = valid_args(); + args.verifiers_per_commitment = 0; let ix = init_protocol_config(authority.pubkey(), args); let tx = Transaction::new_signed_with_payer( @@ -246,11 +269,11 @@ async fn test_v2_init_protocol_config_fails_with_invalid_args() { } #[tokio::test] -async fn test_v2_init_protocol_config_fails_with_default_vrf_oracle_queue() { +async fn test_v2_init_protocol_config_fails_with_zero_approval_threshold() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; let mut args = valid_args(); - args.vrf_oracle_queue = Pubkey::default(); + args.approval_threshold = 0; let ix = init_protocol_config(authority.pubkey(), args); let tx = Transaction::new_signed_with_payer( @@ -281,7 +304,6 @@ async fn test_v1_dispatch_still_works_after_v2_routing() { fn valid_args() -> InitProtocolConfigArgs { InitProtocolConfigArgs { - vrf_oracle_queue: Pubkey::new_unique(), resolver: Pubkey::new_unique(), min_operator_bond: 1, min_verifier_bond: 1, @@ -290,7 +312,7 @@ fn valid_args() -> InitProtocolConfigArgs { operator_response_timeout_slots: 10, challenger_reveal_timeout_slots: 10, payout_timelock_slots: 10, - selected_verifier_count: 3, + verifiers_per_commitment: 3, approval_threshold: 2, max_window_extensions: 1, match_penalty_bps: 500, From 58336c66447957be2df45e91bd13a755c1967a99 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Mon, 24 Aug 2026 03:47:02 +0530 Subject: [PATCH 18/25] Share protocol config validation --- src/v2/processor/bootstrap/init_protocol_config.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index 35633314..8f3ac36a 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -30,6 +30,7 @@ use wheels::{ /// 1: `[writable]` ProtocolConfig PDA /// 2: `[writable]` VerifierRegistry PDA /// 3: `[]` system program, required by system CPI +#[inline(never)] pub fn process_init_protocol_config( accounts: &[AccountView], data: &[u8], @@ -43,7 +44,7 @@ pub fn process_init_protocol_config( let args = InitProtocolConfigArgs::decode(data)?; - validate_args(&args)?; + validate_protocol_config_args(&args)?; require_signer!(authority); @@ -110,7 +111,9 @@ pub fn process_init_protocol_config( Ok(()) } -fn validate_args(args: &InitProtocolConfigArgsView<'_>) -> ProgramResult { +pub(super) fn validate_protocol_config_args( + args: &InitProtocolConfigArgsView<'_>, +) -> ProgramResult { let default_pubkey = Pubkey::default(); require_ne_keys!( From becbdb40c7806229eff64e9c684e35a507dd1cba Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 25 Aug 2026 19:33:04 +0530 Subject: [PATCH 19/25] Enforce one verifier per commitment --- src/v2/processor/bootstrap/init_protocol_config.rs | 12 +++++++++++- tests/test_v2_init_protocol_config.rs | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index 8f3ac36a..89f59d34 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -19,7 +19,7 @@ use crate::{ }; use wheels::{ layout::{Decodable, Encodable}, - require_le, require_n_accounts, require_ne, require_ne_keys, + require_eq, require_le, require_n_accounts, require_ne, require_ne_keys, require_signer, }; @@ -171,6 +171,16 @@ pub(super) fn validate_protocol_config_args( args.verifiers_per_commitment(), ProgramError::InvalidInstructionData ); + require_eq!( + args.verifiers_per_commitment(), + 1, + ProgramError::InvalidInstructionData + ); + require_eq!( + args.approval_threshold(), + 1, + ProgramError::InvalidInstructionData + ); require_le!( args.match_penalty_bps(), 10_000, diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index 462f9f69..f06a52a4 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -312,8 +312,8 @@ fn valid_args() -> InitProtocolConfigArgs { operator_response_timeout_slots: 10, challenger_reveal_timeout_slots: 10, payout_timelock_slots: 10, - verifiers_per_commitment: 3, - approval_threshold: 2, + verifiers_per_commitment: 1, + approval_threshold: 1, max_window_extensions: 1, match_penalty_bps: 500, } From 3936e2aae3bfc983d5ad2f7e3d642a4ccd78edcf Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Fri, 28 Aug 2026 04:17:24 +0530 Subject: [PATCH 20/25] Use one-byte v2 instruction tags --- dlp-api/src/v2/args/init_protocol_config.rs | 2 +- dlp-api/src/v2/args/mod.rs | 3 ++ dlp-api/src/v2/instruction.rs | 13 ++++++-- src/lib.rs | 35 ++++++++++++--------- tests/test_v2_init_protocol_config.rs | 21 +++++++++++-- 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/dlp-api/src/v2/args/init_protocol_config.rs b/dlp-api/src/v2/args/init_protocol_config.rs index 8ed2fb62..c2a6f87c 100644 --- a/dlp-api/src/v2/args/init_protocol_config.rs +++ b/dlp-api/src/v2/args/init_protocol_config.rs @@ -3,7 +3,7 @@ use wheels::variable_offset_layout; use crate::compat::Pubkey; #[derive(Clone, Debug, PartialEq, Eq)] -#[variable_offset_layout(buffer_offset = 0)] +#[variable_offset_layout(buffer_offset = 1)] pub struct InitProtocolConfigArgs { pub resolver: Pubkey, diff --git a/dlp-api/src/v2/args/mod.rs b/dlp-api/src/v2/args/mod.rs index 13cc48ad..d399fbcc 100644 --- a/dlp-api/src/v2/args/mod.rs +++ b/dlp-api/src/v2/args/mod.rs @@ -1,3 +1,6 @@ +// V2 processors decode args from `instruction_data[1..]` after the one-byte +// instruction tag, so v2 instruction args use `buffer_offset = 1`. + mod init_protocol_config; pub use init_protocol_config::*; diff --git a/dlp-api/src/v2/instruction.rs b/dlp-api/src/v2/instruction.rs index d80163c6..5c981df9 100644 --- a/dlp-api/src/v2/instruction.rs +++ b/dlp-api/src/v2/instruction.rs @@ -11,11 +11,20 @@ pub enum DlpV2Instruction { impl DlpV2Instruction { pub fn to_vec(self) -> Vec { - let num = self as u64; - num.to_le_bytes().to_vec() + vec![self as u8] } pub fn name(&self) -> &'static str { self.into() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_v2_instruction_tags_are_one_byte() { + assert_eq!(DlpV2Instruction::InitProtocolConfig.to_vec(), vec![100]); + } +} diff --git a/src/lib.rs b/src/lib.rs index f1d9c8cb..a29441b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,22 +85,25 @@ pub fn fast_process_instruction( accounts: &[pinocchio::AccountView], data: &[u8], ) -> Option { - if data.len() < 8 { + let Some((&tag, v2_data)) = data.split_first() else { return Some(Err( pinocchio::error::ProgramError::InvalidInstructionData, )); - } + }; - let (discriminator_bytes, data) = data.split_at(8); + if let Ok(ix) = dlp_api::v2::DlpV2Instruction::try_from(tag) { + return Some(v2::process_instruction(accounts, v2_data, ix)); + } - if let Ok(ix) = - dlp_api::v2::DlpV2Instruction::try_from(discriminator_bytes[0]) - { - return Some(v2::process_instruction(accounts, data, ix)); + if data.len() < 8 { + return Some(Err( + pinocchio::error::ProgramError::InvalidInstructionData, + )); } - let discriminator = match DlpDiscriminator::try_from(discriminator_bytes[0]) - { + let (_, data) = data.split_at(8); + + let discriminator = match DlpDiscriminator::try_from(tag) { Ok(discriminator) => discriminator, Err(_) => { pinocchio_log::log!("Failed to read and parse discriminator"); @@ -195,17 +198,21 @@ pub fn slow_process_instruction( accounts: &[AccountInfo], data: &[u8], ) -> ProgramResult { - if data.len() < 8 { + let Some((&tag, _)) = data.split_first() else { return Err(ProgramError::InvalidInstructionData); - } + }; - let (tag, data) = data.split_at(8); + if dlp_api::v2::DlpV2Instruction::try_from(tag).is_ok() { + return Err(ProgramError::InvalidInstructionData); + } - if dlp_api::v2::DlpV2Instruction::try_from(tag[0]).is_ok() { + if data.len() < 8 { return Err(ProgramError::InvalidInstructionData); } - let ix = DlpDiscriminator::try_from(tag[0]) + let (_, data) = data.split_at(8); + + let ix = DlpDiscriminator::try_from(tag) .or(Err(ProgramError::InvalidInstructionData))?; match ix { diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index f06a52a4..577d5899 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -4,8 +4,8 @@ use dlp_api::{ v2::{ instruction_builder::init_protocol_config, pda::{protocol_config_pda, verifier_registry_pda}, - InitProtocolConfigArgs, ProtocolConfig, VerifierRegistry, - VerifierRegistryEntry, + DlpV2Instruction, InitProtocolConfigArgs, ProtocolConfig, + VerifierRegistry, VerifierRegistryEntry, }, }; use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL}; @@ -68,6 +68,23 @@ fn test_v2_verifier_registry_layout_round_trip() { } } +#[test] +fn test_v2_init_protocol_config_instruction_data_uses_one_byte_tag() { + let args = valid_args(); + let ix = init_protocol_config(Pubkey::new_unique(), args.clone()); + let encoded_args = args.encode().unwrap(); + + assert_eq!(ix.data[0], DlpV2Instruction::InitProtocolConfig as u8); + assert_eq!(ix.data.len(), 1 + encoded_args.len()); + assert_eq!(&ix.data[1..], encoded_args.as_slice()); + + let decoded = + ::decode(&ix.data[1..]).unwrap(); + assert_eq!(*decoded.resolver(), args.resolver); + assert_eq!(decoded.min_operator_bond(), args.min_operator_bond); + assert_eq!(decoded.approval_threshold(), args.approval_threshold); +} + #[tokio::test] async fn test_v2_init_protocol_config() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; From 7fa66c5f3acd8697a7e5e299b4c4066a44067cf0 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Sun, 30 Aug 2026 21:53:06 +0530 Subject: [PATCH 21/25] Use extendable verifier registry entries --- dlp-api/src/v2/state/verifier_registry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index efb49452..be42b472 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -21,7 +21,7 @@ pub struct VerifierRegistry { pub next_selection_index: u64, /// All registered verifiers DLP can select from. - #[flexible = 2] + #[extendable = 2] pub entries: Vec, } From 77a62b5be0eb744070176ac26c55ce72c79b1020 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Mon, 31 Aug 2026 12:56:58 +0530 Subject: [PATCH 22/25] Drop trivial InitProtocolConfig instruction data test --- tests/test_v2_init_protocol_config.rs | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index 577d5899..f06a52a4 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -4,8 +4,8 @@ use dlp_api::{ v2::{ instruction_builder::init_protocol_config, pda::{protocol_config_pda, verifier_registry_pda}, - DlpV2Instruction, InitProtocolConfigArgs, ProtocolConfig, - VerifierRegistry, VerifierRegistryEntry, + InitProtocolConfigArgs, ProtocolConfig, VerifierRegistry, + VerifierRegistryEntry, }, }; use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL}; @@ -68,23 +68,6 @@ fn test_v2_verifier_registry_layout_round_trip() { } } -#[test] -fn test_v2_init_protocol_config_instruction_data_uses_one_byte_tag() { - let args = valid_args(); - let ix = init_protocol_config(Pubkey::new_unique(), args.clone()); - let encoded_args = args.encode().unwrap(); - - assert_eq!(ix.data[0], DlpV2Instruction::InitProtocolConfig as u8); - assert_eq!(ix.data.len(), 1 + encoded_args.len()); - assert_eq!(&ix.data[1..], encoded_args.as_slice()); - - let decoded = - ::decode(&ix.data[1..]).unwrap(); - assert_eq!(*decoded.resolver(), args.resolver); - assert_eq!(decoded.min_operator_bond(), args.min_operator_bond); - assert_eq!(decoded.approval_threshold(), args.approval_threshold); -} - #[tokio::test] async fn test_v2_init_protocol_config() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; From d922dc45356a4a247bbea2614d4670061380ea4e Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 1 Sep 2026 05:09:20 +0530 Subject: [PATCH 23/25] Rename init protocol config tests --- dlp-api/src/v2/instruction.rs | 2 +- tests/test_v2_init_protocol_config.rs | 43 +++++++++++++++------------ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/dlp-api/src/v2/instruction.rs b/dlp-api/src/v2/instruction.rs index 5c981df9..c21c22ed 100644 --- a/dlp-api/src/v2/instruction.rs +++ b/dlp-api/src/v2/instruction.rs @@ -24,7 +24,7 @@ mod tests { use super::*; #[test] - fn test_v2_instruction_tags_are_one_byte() { + fn test_instruction_tags_are_one_byte() { assert_eq!(DlpV2Instruction::InitProtocolConfig.to_vec(), vec![100]); } } diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index f06a52a4..8d17a9b4 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -22,7 +22,7 @@ use wheels::layout::{Decodable, Encodable}; mod fixtures; #[test] -fn test_v2_verifier_registry_layout_round_trip() { +fn test_verifier_registry_layout_round_trip() { let registry = VerifierRegistry { discriminator: VerifierRegistry::DISCRIMINATOR, registry_revision: 7, @@ -69,10 +69,10 @@ fn test_v2_verifier_registry_layout_round_trip() { } #[tokio::test] -async fn test_v2_init_protocol_config() { +async fn test_init_protocol_config() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let args = valid_args(); + let args = valid_protocol_config_args(); let ix = init_protocol_config(authority.pubkey(), args.clone()); let tx = Transaction::new_signed_with_payer( &[ix], @@ -157,10 +157,11 @@ async fn test_v2_init_protocol_config() { } #[tokio::test] -async fn test_v2_init_protocol_config_fails_twice() { +async fn test_init_protocol_config_fails_twice() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let ix = init_protocol_config(authority.pubkey(), valid_args()); + let ix = + init_protocol_config(authority.pubkey(), valid_protocol_config_args()); let tx = Transaction::new_signed_with_payer( &[ix], Some(&payer.pubkey()), @@ -170,7 +171,8 @@ async fn test_v2_init_protocol_config_fails_twice() { banks.process_transaction(tx).await.unwrap(); let blockhash = banks.get_latest_blockhash().await.unwrap(); - let ix = init_protocol_config(authority.pubkey(), valid_args()); + let ix = + init_protocol_config(authority.pubkey(), valid_protocol_config_args()); let tx = Transaction::new_signed_with_payer( &[ix], Some(&payer.pubkey()), @@ -182,10 +184,11 @@ async fn test_v2_init_protocol_config_fails_twice() { } #[tokio::test] -async fn test_v2_init_protocol_config_fails_with_wrong_protocol_config_pda() { +async fn test_init_protocol_config_fails_with_wrong_protocol_config_pda() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let mut ix = init_protocol_config(authority.pubkey(), valid_args()); + let mut ix = + init_protocol_config(authority.pubkey(), valid_protocol_config_args()); ix.accounts[1].pubkey = Pubkey::new_unique(); let tx = Transaction::new_signed_with_payer( @@ -199,10 +202,11 @@ async fn test_v2_init_protocol_config_fails_with_wrong_protocol_config_pda() { } #[tokio::test] -async fn test_v2_init_protocol_config_fails_with_wrong_verifier_registry_pda() { +async fn test_init_protocol_config_fails_with_wrong_verifier_registry_pda() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let mut ix = init_protocol_config(authority.pubkey(), valid_args()); + let mut ix = + init_protocol_config(authority.pubkey(), valid_protocol_config_args()); ix.accounts[2].pubkey = Pubkey::new_unique(); let tx = Transaction::new_signed_with_payer( @@ -216,10 +220,11 @@ async fn test_v2_init_protocol_config_fails_with_wrong_verifier_registry_pda() { } #[tokio::test] -async fn test_v2_init_protocol_config_fails_without_authority_signature() { +async fn test_init_protocol_config_fails_without_authority_signature() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let mut ix = init_protocol_config(authority.pubkey(), valid_args()); + let mut ix = + init_protocol_config(authority.pubkey(), valid_protocol_config_args()); ix.accounts[0].is_signer = false; let tx = Transaction::new_signed_with_payer( @@ -233,10 +238,10 @@ async fn test_v2_init_protocol_config_fails_without_authority_signature() { } #[tokio::test] -async fn test_v2_init_protocol_config_fails_with_invalid_args() { +async fn test_init_protocol_config_fails_with_invalid_args() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let mut args = valid_args(); + let mut args = valid_protocol_config_args(); args.approval_threshold = args.verifiers_per_commitment + 1; let ix = init_protocol_config(authority.pubkey(), args); @@ -251,10 +256,10 @@ async fn test_v2_init_protocol_config_fails_with_invalid_args() { } #[tokio::test] -async fn test_v2_init_protocol_config_fails_with_zero_verifier_cap() { +async fn test_init_protocol_config_fails_with_zero_verifier_cap() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let mut args = valid_args(); + let mut args = valid_protocol_config_args(); args.verifiers_per_commitment = 0; let ix = init_protocol_config(authority.pubkey(), args); @@ -269,10 +274,10 @@ async fn test_v2_init_protocol_config_fails_with_zero_verifier_cap() { } #[tokio::test] -async fn test_v2_init_protocol_config_fails_with_zero_approval_threshold() { +async fn test_init_protocol_config_fails_with_zero_approval_threshold() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let mut args = valid_args(); + let mut args = valid_protocol_config_args(); args.approval_threshold = 0; let ix = init_protocol_config(authority.pubkey(), args); @@ -302,7 +307,7 @@ async fn test_v1_dispatch_still_works_after_v2_routing() { assert!(banks.process_transaction(tx).await.is_ok()); } -fn valid_args() -> InitProtocolConfigArgs { +fn valid_protocol_config_args() -> InitProtocolConfigArgs { InitProtocolConfigArgs { resolver: Pubkey::new_unique(), min_operator_bond: 1, From fab9eb6603995606847551341ccfd477339cfee4 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 1 Sep 2026 22:58:51 +0530 Subject: [PATCH 24/25] dev review --- dlp-api/src/v2/instruction.rs | 10 -- dlp-api/src/v2/pda.rs | 2 + dlp-api/src/v2/state/protocol_config.rs | 6 ++ dlp-api/src/v2/state/verifier_registry.rs | 16 +--- .../bootstrap/init_protocol_config.rs | 32 +++---- tests/test_v2_init_protocol_config.rs | 95 ++++--------------- 6 files changed, 45 insertions(+), 116 deletions(-) diff --git a/dlp-api/src/v2/instruction.rs b/dlp-api/src/v2/instruction.rs index c21c22ed..fc1751ac 100644 --- a/dlp-api/src/v2/instruction.rs +++ b/dlp-api/src/v2/instruction.rs @@ -18,13 +18,3 @@ impl DlpV2Instruction { self.into() } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_instruction_tags_are_one_byte() { - assert_eq!(DlpV2Instruction::InitProtocolConfig.to_vec(), vec![100]); - } -} diff --git a/dlp-api/src/v2/pda.rs b/dlp-api/src/v2/pda.rs index 41d67771..b1f2ad9f 100644 --- a/dlp-api/src/v2/pda.rs +++ b/dlp-api/src/v2/pda.rs @@ -3,6 +3,8 @@ use crate::compat::Pubkey; pub const PROTOCOL_CONFIG_SEED: &[u8] = b"protocol-config"; pub const VERIFIER_REGISTRY_SEED: &[u8] = b"verifier-registry"; +// TODO (snawaz): Precompute these addresses if PDA derivation becomes const-safe. + pub fn protocol_config_pda() -> Pubkey { Pubkey::find_program_address(&[PROTOCOL_CONFIG_SEED], &crate::id()).0 } diff --git a/dlp-api/src/v2/state/protocol_config.rs b/dlp-api/src/v2/state/protocol_config.rs index e8538980..9b740949 100644 --- a/dlp-api/src/v2/state/protocol_config.rs +++ b/dlp-api/src/v2/state/protocol_config.rs @@ -10,6 +10,9 @@ pub struct ProtocolConfig { /// Account type marker. pub discriminator: [u8; 8], + /// Canonical PDA bump for this account. + pub bump: u8, + /// Signer allowed to update config and permissioned bootstrap state. pub authority: Pubkey, @@ -58,4 +61,7 @@ pub struct ProtocolConfig { impl ProtocolConfig { pub const DISCRIMINATOR: [u8; 8] = *b"v2cfg000"; + + /// Maximum value for basis-points config fields. + pub const MAX_MATCH_PENALTY_BPS: u16 = 10_000; } diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index be42b472..25dd1419 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -11,6 +11,9 @@ pub struct VerifierRegistry { /// Account type marker. pub discriminator: [u8; 8], + /// Canonical PDA bump for this account. + pub bump: u8, + /// Increments every time `entries` changes. /// /// Pending commitments store this value when selected verifiers are copied @@ -26,7 +29,7 @@ pub struct VerifierRegistry { } #[derive(Clone, Debug, PartialEq, Eq)] -#[fixed_offset_layout(buffer_offset = 2)] +#[fixed_offset_layout(buffer_offset = 3)] pub struct VerifierRegistryEntry { /// Verifier identity selectable by DLP. pub verifier_identity: Pubkey, @@ -41,14 +44,3 @@ pub struct VerifierRegistryEntry { impl VerifierRegistry { pub const DISCRIMINATOR: [u8; 8] = *b"v2vreg00"; } - -impl Default for VerifierRegistry { - fn default() -> Self { - Self { - discriminator: Self::DISCRIMINATOR, - registry_revision: 0, - next_selection_index: 0, - entries: Vec::new(), - } - } -} diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index 89f59d34..218e9f61 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -42,12 +42,12 @@ pub fn process_init_protocol_config( _system_program, ] = require_n_accounts!(accounts, 4); + require_signer!(authority); + let args = InitProtocolConfigArgs::decode(data)?; validate_protocol_config_args(&args)?; - require_signer!(authority); - let protocol_config_bump = require_uninitialized_pda( protocol_config, &[PROTOCOL_CONFIG_SEED], @@ -87,6 +87,7 @@ pub fn process_init_protocol_config( ProtocolConfig { discriminator: ProtocolConfig::DISCRIMINATOR, + bump: protocol_config_bump, authority: authority.address().to_bytes().into(), paused: false, resolver: *args.resolver(), @@ -105,8 +106,14 @@ pub fn process_init_protocol_config( } .encode_to(protocol_config.try_borrow_mut()?.as_mut())?; - VerifierRegistry::default() - .encode_to(verifier_registry.try_borrow_mut()?.as_mut())?; + VerifierRegistry { + discriminator: VerifierRegistry::DISCRIMINATOR, + bump: verifier_registry_bump, + registry_revision: 0, + next_selection_index: 0, + entries: Vec::new(), + } + .encode_to(verifier_registry.try_borrow_mut()?.as_mut())?; Ok(()) } @@ -156,21 +163,6 @@ pub(super) fn validate_protocol_config_args( 0, ProgramError::InvalidInstructionData ); - require_ne!( - args.verifiers_per_commitment(), - 0, - ProgramError::InvalidInstructionData - ); - require_ne!( - args.approval_threshold(), - 0, - ProgramError::InvalidInstructionData - ); - require_le!( - args.approval_threshold(), - args.verifiers_per_commitment(), - ProgramError::InvalidInstructionData - ); require_eq!( args.verifiers_per_commitment(), 1, @@ -183,7 +175,7 @@ pub(super) fn validate_protocol_config_args( ); require_le!( args.match_penalty_bps(), - 10_000, + ProtocolConfig::MAX_MATCH_PENALTY_BPS, ProgramError::InvalidInstructionData ); diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index 8d17a9b4..981de73e 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -3,9 +3,11 @@ use dlp_api::{ pda::fees_vault_pda, v2::{ instruction_builder::init_protocol_config, - pda::{protocol_config_pda, verifier_registry_pda}, + pda::{ + protocol_config_pda, verifier_registry_pda, PROTOCOL_CONFIG_SEED, + VERIFIER_REGISTRY_SEED, + }, InitProtocolConfigArgs, ProtocolConfig, VerifierRegistry, - VerifierRegistryEntry, }, }; use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL}; @@ -17,57 +19,10 @@ use solana_sdk::{ transaction::Transaction, }; use solana_sdk_ids::system_program; -use wheels::layout::{Decodable, Encodable}; +use wheels::layout::Decodable; mod fixtures; -#[test] -fn test_verifier_registry_layout_round_trip() { - let registry = VerifierRegistry { - discriminator: VerifierRegistry::DISCRIMINATOR, - registry_revision: 7, - next_selection_index: 5, - entries: vec![ - VerifierRegistryEntry { - verifier_identity: Pubkey::new_unique(), - verifier_bond: Pubkey::new_unique(), - weight: 11, - }, - VerifierRegistryEntry { - verifier_identity: Pubkey::new_unique(), - verifier_bond: Pubkey::new_unique(), - weight: 13, - }, - ], - }; - let mut data = vec![0; registry.encoded_len().unwrap()]; - - registry.encode_to(data.as_mut_slice()).unwrap(); - let decoded = - ::decode(data.as_slice()).unwrap(); - - assert_eq!(decoded.discriminator(), VerifierRegistry::DISCRIMINATOR); - assert_eq!(decoded.registry_revision(), registry.registry_revision); - assert_eq!( - decoded.next_selection_index(), - registry.next_selection_index - ); - assert_eq!(decoded.entries().len(), registry.entries.len()); - for (decoded_entry, registry_entry) in - decoded.entries().iter().zip(registry.entries.iter()) - { - assert_eq!( - *decoded_entry.verifier_identity(), - registry_entry.verifier_identity - ); - assert_eq!( - *decoded_entry.verifier_bond(), - registry_entry.verifier_bond - ); - assert_eq!(decoded_entry.weight(), registry_entry.weight); - } -} - #[tokio::test] async fn test_init_protocol_config() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; @@ -81,8 +36,7 @@ async fn test_init_protocol_config() { blockhash, ); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); + banks.process_transaction(tx).await.unwrap(); let protocol_config_account = banks .get_account(protocol_config_pda()) @@ -90,13 +44,15 @@ async fn test_init_protocol_config() { .unwrap() .unwrap(); let protocol_config = - ::decode(&protocol_config_account.data) - .unwrap(); + ProtocolConfig::decode(&protocol_config_account.data).unwrap(); + let (_, expected_protocol_config_bump) = + Pubkey::find_program_address(&[PROTOCOL_CONFIG_SEED], &dlp_api::id()); assert_eq!( protocol_config.discriminator(), ProtocolConfig::DISCRIMINATOR ); + assert_eq!(protocol_config.bump(), expected_protocol_config_bump); assert_eq!(*protocol_config.authority(), authority.pubkey()); assert!(!protocol_config.paused()); assert_eq!(*protocol_config.resolver(), args.resolver); @@ -142,15 +98,16 @@ async fn test_init_protocol_config() { .await .unwrap() .unwrap(); - let verifier_registry = ::decode( - &verifier_registry_account.data, - ) - .unwrap(); + let verifier_registry = + VerifierRegistry::decode(&verifier_registry_account.data).unwrap(); + let (_, expected_verifier_registry_bump) = + Pubkey::find_program_address(&[VERIFIER_REGISTRY_SEED], &dlp_api::id()); assert_eq!( verifier_registry.discriminator(), VerifierRegistry::DISCRIMINATOR ); + assert_eq!(verifier_registry.bump(), expected_verifier_registry_bump); assert_eq!(verifier_registry.registry_revision(), 0); assert_eq!(verifier_registry.next_selection_index(), 0); assert!(verifier_registry.entries().is_empty()); @@ -189,6 +146,7 @@ async fn test_init_protocol_config_fails_with_wrong_protocol_config_pda() { let mut ix = init_protocol_config(authority.pubkey(), valid_protocol_config_args()); + ix.accounts[1].pubkey = Pubkey::new_unique(); let tx = Transaction::new_signed_with_payer( @@ -207,6 +165,7 @@ async fn test_init_protocol_config_fails_with_wrong_verifier_registry_pda() { let mut ix = init_protocol_config(authority.pubkey(), valid_protocol_config_args()); + ix.accounts[2].pubkey = Pubkey::new_unique(); let tx = Transaction::new_signed_with_payer( @@ -225,6 +184,7 @@ async fn test_init_protocol_config_fails_without_authority_signature() { let mut ix = init_protocol_config(authority.pubkey(), valid_protocol_config_args()); + ix.accounts[0].is_signer = false; let tx = Transaction::new_signed_with_payer( @@ -242,6 +202,7 @@ async fn test_init_protocol_config_fails_with_invalid_args() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; let mut args = valid_protocol_config_args(); + args.approval_threshold = args.verifiers_per_commitment + 1; let ix = init_protocol_config(authority.pubkey(), args); @@ -260,6 +221,7 @@ async fn test_init_protocol_config_fails_with_zero_verifier_cap() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; let mut args = valid_protocol_config_args(); + args.verifiers_per_commitment = 0; let ix = init_protocol_config(authority.pubkey(), args); @@ -278,6 +240,7 @@ async fn test_init_protocol_config_fails_with_zero_approval_threshold() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; let mut args = valid_protocol_config_args(); + args.approval_threshold = 0; let ix = init_protocol_config(authority.pubkey(), args); @@ -291,22 +254,6 @@ async fn test_init_protocol_config_fails_with_zero_approval_threshold() { assert!(banks.process_transaction(tx).await.is_err()); } -#[tokio::test] -async fn test_v1_dispatch_still_works_after_v2_routing() { - let (banks, payer, _authority, blockhash) = setup_program_test_env().await; - - let ix = - dlp_api::instruction_builder::init_protocol_fees_vault(payer.pubkey()); - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&payer.pubkey()), - &[&payer], - blockhash, - ); - - assert!(banks.process_transaction(tx).await.is_ok()); -} - fn valid_protocol_config_args() -> InitProtocolConfigArgs { InitProtocolConfigArgs { resolver: Pubkey::new_unique(), From 03282f80d7994d4ee83a3d41338c27237ac30c28 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Wed, 2 Sep 2026 15:47:57 +0530 Subject: [PATCH 25/25] Remove verifier registry revision --- dlp-api/src/v2/state/verifier_registry.rs | 6 ------ src/v2/processor/bootstrap/init_protocol_config.rs | 1 - tests/test_v2_init_protocol_config.rs | 1 - 3 files changed, 8 deletions(-) diff --git a/dlp-api/src/v2/state/verifier_registry.rs b/dlp-api/src/v2/state/verifier_registry.rs index 25dd1419..0a60151a 100644 --- a/dlp-api/src/v2/state/verifier_registry.rs +++ b/dlp-api/src/v2/state/verifier_registry.rs @@ -14,12 +14,6 @@ pub struct VerifierRegistry { /// Canonical PDA bump for this account. pub bump: u8, - /// Increments every time `entries` changes. - /// - /// Pending commitments store this value when selected verifiers are copied - /// from this registry. - pub registry_revision: u64, - /// Round-robin start cursor used by the next commitment selection. pub next_selection_index: u64, diff --git a/src/v2/processor/bootstrap/init_protocol_config.rs b/src/v2/processor/bootstrap/init_protocol_config.rs index 218e9f61..5a9f0e44 100644 --- a/src/v2/processor/bootstrap/init_protocol_config.rs +++ b/src/v2/processor/bootstrap/init_protocol_config.rs @@ -109,7 +109,6 @@ pub fn process_init_protocol_config( VerifierRegistry { discriminator: VerifierRegistry::DISCRIMINATOR, bump: verifier_registry_bump, - registry_revision: 0, next_selection_index: 0, entries: Vec::new(), } diff --git a/tests/test_v2_init_protocol_config.rs b/tests/test_v2_init_protocol_config.rs index 981de73e..10787960 100644 --- a/tests/test_v2_init_protocol_config.rs +++ b/tests/test_v2_init_protocol_config.rs @@ -108,7 +108,6 @@ async fn test_init_protocol_config() { VerifierRegistry::DISCRIMINATOR ); assert_eq!(verifier_registry.bump(), expected_verifier_registry_bump); - assert_eq!(verifier_registry.registry_revision(), 0); assert_eq!(verifier_registry.next_selection_index(), 0); assert!(verifier_registry.entries().is_empty()); }