1use solana_program::account_info::AccountInfo;
3use solana_program::pubkey::Pubkey;
4
5pub use borsh;
9pub use solana_program;
10
11pub mod account_data;
12pub use account_data::{AccountData, ProgramId, System};
13
14pub mod account;
15pub use account::{Account, Signer, Program, SystemAccount, UncheckedAccount};
16
17pub mod context;
18pub use context::Context;
19
20pub mod prelude;
21
22pub use verified_anchor_macros::VerifiedAccounts;
23pub use verified_anchor_macros::AccountData as AccountData;
24pub use verified_anchor_macros::account;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum VAError {
29 MissingSigner { field: &'static str },
30 NotWritable { field: &'static str },
31 WrongOwner { field: &'static str },
32 NotEnoughAccounts { expected: usize, got: usize },
34 WrongHasOne { field: &'static str, target: &'static str },
35 InitFailed { field: &'static str },
36 CloseFailed { field: &'static str },
37 WrongPda { field: &'static str },
38 WrongBump { field: &'static str },
39 WrongDiscriminator { field: &'static str },
40 BorshFailed { field: &'static str },
41}
42
43impl core::fmt::Display for VAError {
44 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45 match self {
46 VAError::MissingSigner { field } => write!(f, "account `{field}` must be a signer"),
47 VAError::NotWritable { field } => write!(f, "account `{field}` must be writable"),
48 VAError::WrongOwner { field } => write!(f, "account `{field}` has the wrong owner"),
49 VAError::NotEnoughAccounts { expected, got } =>
50 write!(f, "expected {expected} accounts, got {got}"),
51 VAError::WrongHasOne { field, target } =>
52 write!(f, "account `{field}` field does not match `{target}`"),
53 VAError::InitFailed { field } => write!(f, "init failed for `{field}`"),
54 VAError::CloseFailed { field } => write!(f, "close failed for `{field}`"),
55 VAError::WrongPda { field } => write!(f, "account `{field}` is not the expected PDA"),
56 VAError::WrongBump { field } => write!(f, "account `{field}` has a non-canonical bump"),
57 VAError::WrongDiscriminator { field } => write!(f, "account `{field}` has the wrong 8-byte discriminator"),
58 VAError::BorshFailed { field } => write!(f, "Borsh deserialization failed for `{field}`"),
59 }
60 }
61}
62
63impl std::error::Error for VAError {}
64
65impl From<VAError> for solana_program::program_error::ProgramError {
68 fn from(e: VAError) -> Self {
69 let code: u32 = match e {
70 VAError::MissingSigner { .. } => 1,
71 VAError::NotWritable { .. } => 2,
72 VAError::WrongOwner { .. } => 3,
73 VAError::NotEnoughAccounts { .. } => 4,
74 VAError::WrongHasOne { .. } => 5,
75 VAError::InitFailed { .. } => 6,
76 VAError::CloseFailed { .. } => 7,
77 VAError::WrongPda { .. } => 8,
78 VAError::WrongBump { .. } => 9,
79 VAError::WrongDiscriminator { .. } => 10,
80 VAError::BorshFailed { .. } => 11,
81 };
82 solana_program::program_error::ProgramError::Custom(code)
83 }
84}
85
86pub trait Validate {
89 fn validate(
90 accounts: &[AccountInfo],
91 instr_data: &[u8],
92 program_id: &Pubkey,
93 ) -> Result<(), VAError>;
94}
95
96pub trait Accounts<'info>: Sized {
99 type Bumps;
100 fn try_accounts(
101 program_id: &Pubkey,
102 accounts: &'info [AccountInfo<'info>],
103 instr_data: &[u8],
104 ) -> Result<(Self, Self::Bumps), VAError>;
105}
106
107#[cfg(not(target_os = "solana"))]
115pub use inventory;
116
117#[cfg(not(target_os = "solana"))]
119pub struct SpecEntry {
120 pub name: &'static str,
121 pub lean_spec: fn() -> String,
123 pub has_lifecycle: bool,
125}
126
127#[cfg(not(target_os = "solana"))]
128inventory::collect!(SpecEntry);
129
130#[cfg(not(target_os = "solana"))]
132pub fn collect_specs() -> Vec<&'static SpecEntry> {
133 inventory::iter::<SpecEntry>.into_iter().collect()
134}
135
136#[cfg(not(target_os = "solana"))]
140pub fn write_spec_files(dir: &std::path::Path) -> std::io::Result<()> {
141 std::fs::create_dir_all(dir)?;
142 for e in collect_specs() {
143 let kind = if e.has_lifecycle { "lifecycle" } else { "validation" };
144 std::fs::write(dir.join(format!("{}.{}", e.name, kind)), (e.lean_spec)())?;
145 }
146 Ok(())
147}
148
149#[macro_export]
154macro_rules! emit_specs {
155 () => {
156 #[cfg(test)]
157 #[test]
158 fn __verified_anchor_emit_specs() {
159 if let Ok(dir) = ::std::env::var("VERIFIED_ANCHOR_SPEC_DIR") {
160 ::verified_anchor::write_spec_files(::std::path::Path::new(&dir)).unwrap();
161 }
162 }
163 };
164}
165
166#[cfg(test)]
167mod spec_collection_tests {
168 use super::*;
169
170 inventory::submit! { SpecEntry { name: "FakeStruct", lean_spec: || "FAKE-SPEC".to_string(), has_lifecycle: false } }
172
173 #[test]
174 fn write_spec_files_emits_one_file_per_entry() {
175 let dir = std::env::temp_dir().join("va-m1-spec-test");
176 let _ = std::fs::remove_dir_all(&dir);
177 write_spec_files(&dir).unwrap();
178 let f = dir.join("FakeStruct.validation");
179 assert!(f.exists(), "expected {f:?}");
180 assert_eq!(std::fs::read_to_string(&f).unwrap(), "FAKE-SPEC");
181 }
182}