use solana_program::account_info::AccountInfo;
use solana_program::pubkey::Pubkey;
pub use borsh;
pub use solana_program;
pub mod account_data;
pub use account_data::{AccountData, ProgramId, System};
pub mod account;
pub use account::{Account, Signer, Program, SystemAccount, UncheckedAccount};
pub mod context;
pub use context::Context;
pub mod prelude;
pub use verified_anchor_macros::VerifiedAccounts;
pub use verified_anchor_macros::AccountData as AccountData;
pub use verified_anchor_macros::account;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VAError {
MissingSigner { field: &'static str },
NotWritable { field: &'static str },
WrongOwner { field: &'static str },
NotEnoughAccounts { expected: usize, got: usize },
WrongHasOne { field: &'static str, target: &'static str },
InitFailed { field: &'static str },
CloseFailed { field: &'static str },
WrongPda { field: &'static str },
WrongBump { field: &'static str },
WrongDiscriminator { field: &'static str },
BorshFailed { field: &'static str },
}
impl core::fmt::Display for VAError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
VAError::MissingSigner { field } => write!(f, "account `{field}` must be a signer"),
VAError::NotWritable { field } => write!(f, "account `{field}` must be writable"),
VAError::WrongOwner { field } => write!(f, "account `{field}` has the wrong owner"),
VAError::NotEnoughAccounts { expected, got } =>
write!(f, "expected {expected} accounts, got {got}"),
VAError::WrongHasOne { field, target } =>
write!(f, "account `{field}` field does not match `{target}`"),
VAError::InitFailed { field } => write!(f, "init failed for `{field}`"),
VAError::CloseFailed { field } => write!(f, "close failed for `{field}`"),
VAError::WrongPda { field } => write!(f, "account `{field}` is not the expected PDA"),
VAError::WrongBump { field } => write!(f, "account `{field}` has a non-canonical bump"),
VAError::WrongDiscriminator { field } => write!(f, "account `{field}` has the wrong 8-byte discriminator"),
VAError::BorshFailed { field } => write!(f, "Borsh deserialization failed for `{field}`"),
}
}
}
impl std::error::Error for VAError {}
impl From<VAError> for solana_program::program_error::ProgramError {
fn from(e: VAError) -> Self {
let code: u32 = match e {
VAError::MissingSigner { .. } => 1,
VAError::NotWritable { .. } => 2,
VAError::WrongOwner { .. } => 3,
VAError::NotEnoughAccounts { .. } => 4,
VAError::WrongHasOne { .. } => 5,
VAError::InitFailed { .. } => 6,
VAError::CloseFailed { .. } => 7,
VAError::WrongPda { .. } => 8,
VAError::WrongBump { .. } => 9,
VAError::WrongDiscriminator { .. } => 10,
VAError::BorshFailed { .. } => 11,
};
solana_program::program_error::ProgramError::Custom(code)
}
}
pub trait Validate {
fn validate(
accounts: &[AccountInfo],
instr_data: &[u8],
program_id: &Pubkey,
) -> Result<(), VAError>;
}
pub trait Accounts<'info>: Sized {
type Bumps;
fn try_accounts(
program_id: &Pubkey,
accounts: &'info [AccountInfo<'info>],
instr_data: &[u8],
) -> Result<(Self, Self::Bumps), VAError>;
}
#[cfg(not(target_os = "solana"))]
pub use inventory;
#[cfg(not(target_os = "solana"))]
pub struct SpecEntry {
pub name: &'static str,
pub lean_spec: fn() -> String,
pub has_lifecycle: bool,
}
#[cfg(not(target_os = "solana"))]
inventory::collect!(SpecEntry);
#[cfg(not(target_os = "solana"))]
pub fn collect_specs() -> Vec<&'static SpecEntry> {
inventory::iter::<SpecEntry>.into_iter().collect()
}
#[cfg(not(target_os = "solana"))]
pub fn write_spec_files(dir: &std::path::Path) -> std::io::Result<()> {
std::fs::create_dir_all(dir)?;
for e in collect_specs() {
let kind = if e.has_lifecycle { "lifecycle" } else { "validation" };
std::fs::write(dir.join(format!("{}.{}", e.name, kind)), (e.lean_spec)())?;
}
Ok(())
}
#[macro_export]
macro_rules! emit_specs {
() => {
#[cfg(test)]
#[test]
fn __verified_anchor_emit_specs() {
if let Ok(dir) = ::std::env::var("VERIFIED_ANCHOR_SPEC_DIR") {
::verified_anchor::write_spec_files(::std::path::Path::new(&dir)).unwrap();
}
}
};
}
#[cfg(test)]
mod spec_collection_tests {
use super::*;
inventory::submit! { SpecEntry { name: "FakeStruct", lean_spec: || "FAKE-SPEC".to_string(), has_lifecycle: false } }
#[test]
fn write_spec_files_emits_one_file_per_entry() {
let dir = std::env::temp_dir().join("va-m1-spec-test");
let _ = std::fs::remove_dir_all(&dir);
write_spec_files(&dir).unwrap();
let f = dir.join("FakeStruct.validation");
assert!(f.exists(), "expected {f:?}");
assert_eq!(std::fs::read_to_string(&f).unwrap(), "FAKE-SPEC");
}
}