Skip to content
2 changes: 2 additions & 0 deletions dlp-api/src/v2/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
mod init_protocol_config;
mod register_operator;
mod register_verifier;
mod update_verifier_registry;

pub use init_protocol_config::*;
pub use register_operator::*;
pub use register_verifier::*;
pub use update_verifier_registry::*;
21 changes: 21 additions & 0 deletions dlp-api/src/v2/args/update_verifier_registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use wheels::variable_offset_layout;

#[derive(Clone, Debug, PartialEq, Eq)]
#[variable_offset_layout(buffer_offset = 1)]
pub struct UpdateVerifierRegistryArgs {
pub action: u8,
pub weight: u64,
}

#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VerifierRegistryAction {
Add = 1,
Remove = 2,
}

impl VerifierRegistryAction {
pub const fn value(self) -> u8 {
self as u8
}
}
2 changes: 2 additions & 0 deletions dlp-api/src/v2/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub enum DlpV2Instruction {
RegisterOperator = 101,
/// Registers one verifier and deposits its initial stake.
RegisterVerifier = 102,
/// Updates the set of verifiers that can be selected.
UpdateVerifierRegistry = 103,
}

impl DlpV2Instruction {
Expand Down
2 changes: 2 additions & 0 deletions dlp-api/src/v2/instruction_builder/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod init_protocol_config;
mod register_operator;
mod register_verifier;
mod update_verifier_registry;

pub use init_protocol_config::*;
pub use register_operator::*;
pub use register_verifier::*;
pub use update_verifier_registry::*;
40 changes: 40 additions & 0 deletions dlp-api/src/v2/instruction_builder/update_verifier_registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
use solana_sdk_ids::system_program;
use wheels::layout::Encodable;

use crate::{
compat::{Compatize, Modernize},
v2::{
pda::{protocol_config_pda, verifier_bond_pda, verifier_registry_pda},
DlpV2Instruction, UpdateVerifierRegistryArgs,
},
};

/// Builds the instruction that updates the verifier selection registry.
pub fn update_verifier_registry(
authority: Pubkey,
verifier: Pubkey,
args: UpdateVerifierRegistryArgs,
) -> Instruction {
Instruction {
program_id: crate::id().modernize(),
accounts: vec![
AccountMeta::new(authority, true),
AccountMeta::new_readonly(protocol_config_pda().modernize(), false),
AccountMeta::new(verifier_registry_pda().modernize(), false),
AccountMeta::new_readonly(
verifier_bond_pda(&verifier.compatize()).modernize(),
false,
),
AccountMeta::new_readonly(system_program::id(), false),
],
data: [
DlpV2Instruction::UpdateVerifierRegistry.to_vec(),
args.encode().unwrap(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'fn update_verifier_registry|args\.encode\(\)|trait Encodable|impl Encodable' dlp-api

Repository: magicblock-labs/delegation-program

Length of output: 4119


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file outline =="
ast-grep outline dlp-api/src/v2/instruction_builder/update_verifier_registry.rs || true
echo
echo "== file contents =="
cat -n dlp-api/src/v2/instruction_builder/update_verifier_registry.rs
echo
echo "== search encode implementation =="
rg -n -C 5 'trait Encodable|impl.*Encodable|fn encode|UpdateVerifierRegistryArgs' src dlp-api || true

Repository: magicblock-labs/delegation-program

Length of output: 7911


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path

# Inspect the macro-derived encoding path as data
macro_files = list(Path(".").rglob("variable_offset_layout*"))
print("candidate macro files:")
for p in macro_files:
    print(f"  {p}")

for p in Path(".").rglob("*.rs"):
    text = p.read_text(errors="ignore")
    if "pub trait Encodable" in text or "fn encode" in text or "variable_offset_layout" in text:
        # print concise hits, not the whole file
        for i,line in enumerate(text.splitlines(),1):
            if "pub trait Encodable" in line or "fn encode" in line or "variable_offset_layout" in line:
                print(f"\n== {p}:{i} ==")
                print(line.strip())
                break

encodable_macro_path = next((p for p in list(Path(".").rglob("*.rs")) if "variable_offset" in p.name), None)
if encodable_macro_path and encodable_macro_path.name != "update_verifier_registry.rs":
    print("\n== full macro file excerpt ==")
    lines = encodable_macro_path.read_text(errors="ignore").splitlines()
    for i in range(1, min(len(lines),240)+1):
        print(f"{i:4}: {lines[i-1]}")
PY

Repository: magicblock-labs/delegation-program

Length of output: 633


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== dependency manifests for wheels =="
fd 'Cargo.toml' . -t f -x sh -c 'echo "-- $1"; sed -n "s/wheels[^[:space:]].*/line: /p" "$1" || true' sh {}
echo
echo "== lockfile references =="
if [ -f Cargo.lock ]; then rg -n -C 3 "name = \"wheels\"|source = \".*wheels|wheels" Cargo.lock; fi
echo
echo "== all encode/decode implementation snippets =="
python3 - <<'PY'
from pathlib import Path
for p in sorted(Path(".").rglob("*.rs")):
    txt = p.read_text(errors="ignore")
    hits = []
    for i,line in enumerate(txt.splitlines(),1):
        if any(s in line for s in ["impl Encodable for", "impl Decodable for", "fn encode", "fn decode", "variable_offset_layout", "encodable", "decodable"]):
            hits.append((i,line.strip()))
    if hits and "wheels" in txt:
        print(f"\n== {p} ==")
        for i,l in hits[:40]:
            print(f"{i}: {l}")
PY

Repository: magicblock-labs/delegation-program

Length of output: 1639


🌐 Web query:

wheels crate crates.io variable_offset_layout Encodable encode

💡 Result:

There is no widely known Rust crate named "wheels" that features "variable_offset_layout" or the "Encodable::encode" pattern as its core definition. The terminology you are inquiring about appears to stem from separate, unrelated Rust libraries: 1. Encoding and Serialization: The "Encodable" trait and its "encode" method are core components of the "encode" crate [1][2][3]. This crate is a toolbox for building encoders and serializers, drawing inspiration from parsing libraries like "nom" and "winnow" [2][3]. Its Encodable trait is defined as: fn encode(&self, encoder: &mut E) -> Result<, Self::Error>; 2. Memory and Data Layout: The term "variable_offset_layout" is not a standard crate name, but the concept of explicit memory and field layout in Rust is addressed by several specialized crates, including: - "memory-layout" [4]: Allows defining struct layouts similar to C#'s [StructLayout(LayoutKind.Explicit)] [4]. - "struct_layout" [5]: Provides a similar mechanism with a focus on internal arrays and specific offset annotations [5]. - "binary_layout" [6]: Provides a type-safe, zero-copy way to access binary data structures [6]. 3. Crates named "wheel": Several unrelated crates exist with "wheel" in their name, such as "wheel-rs" (a general utility library) [7][8], "uwheel" (for stream aggregation) [9], "logs-wheel" (for rolling log files) [10], and "bitwheel" (for high-performance timers) [11]. None of these are primarily associated with the encoding or layout features mentioned. If you are following a specific tutorial or project, you may be conflating these distinct libraries. To use the encoding functionality, you should refer to the "encode" crate documentation [2], and for explicit memory layout, you should investigate "memory-layout" or "binary_layout" [4][6].

Citations:


Replace the fallible encoding unwrap.

args.encode() can return an error, and update_verifier_registry() currently panics by using .unwrap(). Return a Result<Instruction, ...> from this builder, or document and enforce an invariant that makes encoding failure impossible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dlp-api/src/v2/instruction_builder/update_verifier_registry.rs` at line 36,
Replace the unwrap on args.encode() in update_verifier_registry() with explicit
error propagation by changing the builder to return Result<Instruction, ...> and
forwarding the encoding error; only preserve a non-fallible return if a concrete
invariant is enforced that guarantees encoding cannot fail.

Source: Path instructions

]
.concat(),
}
}
24 changes: 24 additions & 0 deletions src/processor/fast/utils/pda.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,30 @@ pub(crate) fn close_pda(
target_account.resize(0)
}

/// Tops up a PDA to the rent-exempt balance for `space`.
#[inline(always)]
pub(crate) fn top_up_pda_rent(
payer: &AccountView,
target_account: &AccountView,
space: usize,
) -> ProgramResult {
let rent = Rent::get()?;
let rent_exempt_balance = rent
.try_minimum_balance(space)?
.saturating_sub(target_account.lamports());

if rent_exempt_balance > 0 {
system::Transfer {
from: payer,
to: target_account,
lamports: rent_exempt_balance,
}
.invoke()?;
}

Ok(())
}

/// Close PDA with fees, distributing the fees to the specified addresses in sequence
/// The total fees are calculated as `fee_percentage` of the total lamports in the PDA
/// Each fee address receives fee_percentage % of the previous fee address's amount
Expand Down
2 changes: 2 additions & 0 deletions src/v2/processor/bootstrap/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod init_protocol_config;
mod register_operator;
mod register_verifier;
mod update_verifier_registry;

pub use init_protocol_config::*;
pub use register_operator::*;
pub use register_verifier::*;
pub use update_verifier_registry::*;
204 changes: 204 additions & 0 deletions src/v2/processor/bootstrap/update_verifier_registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
use dlp_api::{
error::DlpError,
v2::{
pda::{
PROTOCOL_CONFIG_SEED, VERIFIER_BOND_SEED, VERIFIER_REGISTRY_SEED,
},
ProtocolConfig, ProtocolConfigView, UpdateVerifierRegistryArgs,
UpdateVerifierRegistryArgsView, VerifierBond, VerifierBondView,
VerifierRegistry, VerifierRegistryAction, VerifierRegistryEntry,
VerifierStatus,
},
};
use pinocchio::{error::ProgramError, AccountView, ProgramResult};
use wheels::{
layout::Decodable, require, require_eq, require_eq_keys, require_ge,
require_n_accounts, require_signer,
};

use crate::{
processor::fast::utils::pda::top_up_pda_rent,
requires::{require_initialized_pda, require_owned_pda, require_pda},
};

/// Update the verifier registry used by v2 verifier selection.
///
/// Accounts:
/// 0: `[signer, writable]` protocol authority and registry rent payer
/// 1: `[]` ProtocolConfig PDA
/// 2: `[writable]` VerifierRegistry PDA
/// 3: `[]` VerifierBond PDA
/// 4: `[]` system program, required by system CPI
#[inline(never)]
pub fn process_update_verifier_registry(
accounts: &[AccountView],
data: &[u8],
) -> ProgramResult {
let [
authority, // force multi-line
protocol_config,
verifier_registry,
verifier_bond,
_system_program,
] = require_n_accounts!(accounts, 5);

require_signer!(authority);

let args = UpdateVerifierRegistryArgs::decode(data)?;
validate_update_args(&args)?;

require_initialized_pda(
protocol_config,
&[PROTOCOL_CONFIG_SEED],
&crate::fast::ID,
false,
"protocol config",
)?;
require_initialized_pda(
verifier_registry,
&[VERIFIER_REGISTRY_SEED],
&crate::fast::ID,
true,
"verifier registry",
)?;
require_owned_pda(verifier_bond, &crate::fast::ID, "verifier bond")?;

let verifier_bond_data = verifier_bond.try_borrow()?;
let verifier_bond_state =
VerifierBond::decode(verifier_bond_data.as_ref())?;
validate_verifier_bond(&verifier_bond_state, verifier_bond)?;

{
let protocol_config_data = protocol_config.try_borrow()?;
let protocol_config_state =
ProtocolConfig::decode(protocol_config_data.as_ref())?;
validate_protocol_config(&protocol_config_state, authority)?;
validate_verifier_can_be_added(
&protocol_config_state,
&verifier_bond_state,
)?;
}

let verifier_identity = verifier_bond_state.verifier_identity();
let verifier_bond_key = verifier_bond.address();

{
let verifier_registry_data = verifier_registry.try_borrow()?;
let verifier_registry_view =
VerifierRegistry::decode(verifier_registry_data.as_ref())?;
require!(
verifier_registry_view.discriminator()
== VerifierRegistry::DISCRIMINATOR,
ProgramError::InvalidAccountData
);
// CHECKPOINT: this treats verifier identity and verifier bond as
// separate unique registry keys. Revisit if bond rotation should keep
// the same identity entry instead of rejecting either duplicate.
require!(
!verifier_registry_view.entries().iter().any(|entry| {
entry.verifier_identity() == verifier_identity
|| entry.verifier_bond() == verifier_bond_key
}),
ProgramError::AccountAlreadyInitialized
);
}

// CHECKPOINT: this single-PDA Vec is only suitable while the verifier set
// is small. Before allowing unbounded growth, cap the entry count or
// replace this with paged storage / Merkle-root based membership.
let old_registry_len = verifier_registry.data_len();
let mut verifier_registry_state =
VerifierRegistry::decode_mut(verifier_registry)?;

verifier_registry_state
.entries_mut()?
.push(&VerifierRegistryEntry {
verifier_identity: *verifier_identity,
verifier_bond: *verifier_bond_key,
weight: args.weight(),
})?;

let new_registry_len = verifier_registry.data_len();
if new_registry_len > old_registry_len {
top_up_pda_rent(authority, verifier_registry, new_registry_len)?;
}

Ok(())
}

fn validate_update_args(
args: &UpdateVerifierRegistryArgsView<'_>,
) -> ProgramResult {
// CHECKPOINT: implement `VerifierRegistryAction::Remove` when
// withdrawal/removal rules are finalized.
require!(
args.action() == VerifierRegistryAction::Add.value(),
ProgramError::InvalidInstructionData
);
// MVP verifier selection is equal-weight round-robin, so the only
// meaningful weight until weighted selection exists is 1.
require_eq!(args.weight(), 1_u64, ProgramError::InvalidInstructionData);

Ok(())
}

fn validate_protocol_config(
protocol_config: &ProtocolConfigView<'_>,
authority: &AccountView,
) -> ProgramResult {
require!(
protocol_config.discriminator() == ProtocolConfig::DISCRIMINATOR,
ProgramError::InvalidAccountData
);
require_eq_keys!(
protocol_config.authority(),
authority.address(),
DlpError::InvalidAuthority
);

Ok(())
}

fn validate_verifier_bond(
verifier_bond: &VerifierBondView<'_>,
verifier_bond_account: &AccountView,
) -> ProgramResult {
require!(
verifier_bond.discriminator() == VerifierBond::DISCRIMINATOR,
ProgramError::InvalidAccountData
);
require_pda(
verifier_bond_account,
&[
VERIFIER_BOND_SEED,
verifier_bond.verifier_identity().as_ref(),
],
&crate::fast::ID,
false,
"verifier bond",
)?;

Ok(())
}

fn validate_verifier_can_be_added(
protocol_config: &ProtocolConfigView<'_>,
verifier_bond: &VerifierBondView<'_>,
) -> ProgramResult {
require_eq!(
verifier_bond.status(),
VerifierStatus::Active.value(),
ProgramError::InvalidInstructionData
);
require_ge!(
verifier_bond.stake_lamports(),
protocol_config.min_verifier_bond(),
ProgramError::InvalidInstructionData
);
require!(
verifier_bond.withdraw_requested_slot().is_none(),
ProgramError::InvalidInstructionData
);

Ok(())
}
3 changes: 3 additions & 0 deletions src/v2/processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ pub fn process_instruction(
DlpV2Instruction::RegisterVerifier => {
process_register_verifier(accounts, data)
}
DlpV2Instruction::UpdateVerifierRegistry => {
process_update_verifier_registry(accounts, data)
}
}
}
Loading
Loading