diff --git a/Cargo.lock b/Cargo.lock index c101608..731ec21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1506,6 +1506,7 @@ dependencies = [ "solana-program-runtime", "solana-sdk", "solana-system-program", + "thiserror", ] [[package]] @@ -2691,18 +2692,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 08d6976..fdea035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,4 @@ solana-program = "2.0" solana-program-runtime = "2.0" solana-system-program = "2.0" solana-sdk = "2.0" +thiserror = "1.0.64" diff --git a/harness/Cargo.toml b/harness/Cargo.toml index 9df61ec..0a0af35 100644 --- a/harness/Cargo.toml +++ b/harness/Cargo.toml @@ -17,6 +17,7 @@ solana-program-runtime = { workspace = true } solana-system-program = { workspace = true } solana-sdk = { workspace = true } solana-logger = { workspace = true } +thiserror = { workspace = true } [[bench]] name = "ips" diff --git a/harness/src/error.rs b/harness/src/error.rs new file mode 100644 index 0000000..7d7a823 --- /dev/null +++ b/harness/src/error.rs @@ -0,0 +1,46 @@ +//! Mollusk errors. These errors will throw a panic. They represent +//! misconfiguration of test inputs or the test environment. + +use { + solana_sdk::pubkey::Pubkey, + std::{fmt::Display, path::Path}, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum MolluskError<'a> { + /// Failed to open file. + #[error(" [MOLLUSK]: Failed to open file: {0}")] + FileOpenError(&'a Path), + /// Failed to read file. + #[error(" [MOLLUSK]: Failed to read file: {0}")] + FileReadError(&'a Path), + /// Program file not found. + #[error(" [MOLLUSK]: Program file not found: {0}")] + FileNotFound(&'a str), + /// An account required by the instruction was not provided. + #[error(" [MOLLUSK]: An account required by the instruction was not provided: {0}")] + AccountMissing(&'a Pubkey), + /// Program targeted by the instruction is missing from the cache. + #[error(" [MOLLUSK]: Program targeted by the instruction is missing from the cache: {0}")] + ProgramNotCached(&'a Pubkey), +} + +pub trait MolluskPanic { + fn or_panic_with(self, error: MolluskError) -> T; +} + +impl MolluskPanic for Result +where + E: Display, +{ + fn or_panic_with(self, mollusk_err: MolluskError) -> T { + self.unwrap_or_else(|err| panic!("{}: {}", mollusk_err, err)) + } +} + +impl MolluskPanic for Option { + fn or_panic_with(self, mollusk_err: MolluskError) -> T { + self.unwrap_or_else(|| panic!("{}", mollusk_err)) + } +} diff --git a/harness/src/file.rs b/harness/src/file.rs index a76585e..33cd72a 100644 --- a/harness/src/file.rs +++ b/harness/src/file.rs @@ -18,10 +18,13 @@ //! purposes, most of them will panic if the file is not found or if there is an //! error reading the file. -use std::{ - fs::File, - io::Read, - path::{Path, PathBuf}, +use { + crate::error::{MolluskError, MolluskPanic}, + std::{ + fs::File, + io::Read, + path::{Path, PathBuf}, + }, }; fn default_shared_object_dirs() -> Vec { @@ -55,12 +58,11 @@ fn find_file(filename: &str) -> Option { /// Read the contents of a file into a `Vec`. pub fn read_file>(path: P) -> Vec { let path = path.as_ref(); - let mut file = File::open(path) - .unwrap_or_else(|err| panic!("Failed to open \"{}\": {}", path.display(), err)); + let mut file = File::open(path).or_panic_with(MolluskError::FileOpenError(path)); let mut file_data = Vec::new(); file.read_to_end(&mut file_data) - .unwrap_or_else(|err| panic!("Failed to read \"{}\": {}", path.display(), err)); + .or_panic_with(MolluskError::FileReadError(path)); file_data } @@ -77,7 +79,6 @@ pub fn read_file>(path: P) -> Vec { /// The name of the program ELF file is expected to be `{program_name}.so`. pub fn load_program_elf(program_name: &str) -> Vec { let file_name = format!("{program_name}.so"); - let program_file = find_file(&file_name) - .unwrap_or_else(|| panic!("Program file data not available for \"{}\"", file_name,)); + let program_file = find_file(&file_name).or_panic_with(MolluskError::FileNotFound(&file_name)); read_file(program_file) } diff --git a/harness/src/keys.rs b/harness/src/keys.rs index 94cd2d5..89e0f09 100644 --- a/harness/src/keys.rs +++ b/harness/src/keys.rs @@ -20,6 +20,7 @@ //! . use { + crate::error::{MolluskError, MolluskPanic}, solana_sdk::{ account::{AccountSharedData, WritableAccount}, instruction::Instruction, @@ -141,13 +142,7 @@ pub fn compile_accounts( .iter() .find(|(k, _)| k == *key) .map(|(_, account)| account.clone()) - .unwrap_or_else(|| { - panic!( - " [mollusk]: An account required by the instruction was not \ - provided: {:?}", - key, - ) - }); + .or_panic_with(MolluskError::AccountMissing(key)); (**key, account) } }) diff --git a/harness/src/lib.rs b/harness/src/lib.rs index 7a150f8..466b226 100644 --- a/harness/src/lib.rs +++ b/harness/src/lib.rs @@ -27,6 +27,7 @@ //! * `process_and_validate_instruction`: Process an instruction and perform a //! series of checks on the result, panicking if any checks fail. +mod error; pub mod file; mod keys; pub mod program; @@ -35,6 +36,7 @@ pub mod sysvar; use { crate::{ + error::{MolluskError, MolluskPanic}, program::ProgramCache, result::{Check, InstructionResult}, sysvar::Sysvars, @@ -145,12 +147,7 @@ impl Mollusk { let loader_key = self .program_cache .load_program(&instruction.program_id) - .unwrap_or_else(|| { - panic!( - " [mollusk]: Program targeted by instruction is missing from cache: {:?}", - instruction.program_id, - ) - }) + .or_panic_with(MolluskError::ProgramNotCached(&instruction.program_id)) .account_owner(); let CompiledAccounts {