use std::str::FromStr;
use rialo_s_sdk::pubkey::Pubkey;
use thiserror::Error;
use validator::ValidationErrors;
use crate::constants::*;
#[derive(Debug, Error, Clone)]
pub enum ValidationError {
#[error("Invalid format: {0}")]
InvalidFormat(String),
#[error("Value out of range: {0}")]
OutOfRange(String),
#[error("Missing required field: {0}")]
MissingField(String),
#[error("Invalid signature: {0}")]
InvalidSignature(String),
#[error("Invalid encoding: {0}. Supported encodings: base64, base58")]
InvalidEncoding(String),
#[error("Invalid public key: {0}")]
InvalidPublicKey(String),
#[error("Invalid transaction: {0}")]
InvalidTransaction(String),
#[error("Multiple validation errors: {0}")]
Multiple(String),
}
impl From<ValidationErrors> for ValidationError {
fn from(errors: ValidationErrors) -> Self {
let error_messages: Vec<String> = errors
.field_errors()
.iter()
.flat_map(|(field, errors)| {
errors.iter().map(move |error| {
format!(
"{}: {}",
field,
error
.message
.as_ref()
.unwrap_or(&"validation failed".into())
)
})
})
.collect();
if error_messages.len() == 1 {
ValidationError::InvalidFormat(error_messages[0].clone())
} else {
ValidationError::Multiple(error_messages.join(", "))
}
}
}
pub type ValidationResult<T> = Result<T, ValidationError>;
pub fn validate_pubkey(pubkey: &str) -> Result<(), validator::ValidationError> {
Pubkey::from_str(pubkey).map_err(|_| validator::ValidationError::new("invalid_pubkey"))?;
Ok(())
}
pub fn validate_base64(data: &str) -> Result<(), validator::ValidationError> {
use fastcrypto::encoding::{Base64, Encoding};
Base64::decode(data).map_err(|_| validator::ValidationError::new("invalid_base64"))?;
Ok(())
}
pub fn validate_base58(data: &str) -> Result<(), validator::ValidationError> {
use fastcrypto::encoding::{Base58, Encoding};
Base58::decode(data).map_err(|_| validator::ValidationError::new("invalid_base58"))?;
Ok(())
}
pub fn validate_signature(signature: &str) -> Result<(), validator::ValidationError> {
if signature.len() < MIN_SIGNATURE_LENGTH || signature.len() > MAX_SIGNATURE_LENGTH {
return Err(validator::ValidationError::new("invalid_signature_length"));
}
validate_base58(signature)
}
pub fn validate_nonce(nonce: &str) -> Result<(), validator::ValidationError> {
if nonce.is_empty() {
return Err(validator::ValidationError::new("empty_nonce"));
}
if nonce.len() > MAX_NONCE_LENGTH {
return Err(validator::ValidationError::new("nonce_too_long"));
}
Ok(())
}
pub fn validate_lamports(lamports: u64) -> Result<(), validator::ValidationError> {
if lamports > MAX_LAMPORTS {
return Err(validator::ValidationError::new("lamports_too_large"));
}
Ok(())
}
pub fn validate_limit(limit: &u64) -> Result<(), validator::ValidationError> {
if *limit == 0 {
return Err(validator::ValidationError::new("limit_zero"));
}
if *limit > MAX_PAGINATION_LIMIT {
return Err(validator::ValidationError::new("limit_too_large"));
}
Ok(())
}
pub fn validate_pubkey_array(pubkeys: &[String]) -> Result<(), validator::ValidationError> {
for pubkey in pubkeys {
validate_pubkey(pubkey)?;
}
Ok(())
}
pub fn validate_signatures_array(signatures: &[String]) -> Result<(), validator::ValidationError> {
for signature in signatures {
validate_signature(signature)?;
}
Ok(())
}
pub fn validate_airdrop_amount(lamports: u64) -> Result<(), validator::ValidationError> {
validate_lamports(lamports)?;
if lamports > MAX_AIRDROP_AMOUNT {
return Err(validator::ValidationError::new("airdrop_amount_too_large"));
}
if lamports == 0 {
return Err(validator::ValidationError::new("airdrop_amount_zero"));
}
Ok(())
}
pub fn validate_airdrop_amount_i64(lamports: i64) -> Result<(), validator::ValidationError> {
if lamports < 0 {
return Err(validator::ValidationError::new("airdrop_amount_negative"));
}
if lamports == 0 {
return Err(validator::ValidationError::new("airdrop_amount_zero"));
}
let lamports_u64 = lamports as u64;
validate_lamports(lamports_u64)?;
if lamports_u64 > MAX_AIRDROP_AMOUNT {
return Err(validator::ValidationError::new("airdrop_amount_too_large"));
}
Ok(())
}
pub fn validate_signature_limit(limit: &u16) -> Result<(), validator::ValidationError> {
if *limit == 0 {
return Err(validator::ValidationError::new("limit_must_be_positive"));
}
if *limit > MAX_PAGINATION_LIMIT as u16 {
return Err(validator::ValidationError::new("limit_exceeds_maximum"));
}
Ok(())
}
pub fn validate_transaction_data(transaction: &str) -> Result<(), validator::ValidationError> {
if transaction.is_empty() {
return Ok(());
}
if validate_base64(transaction).is_ok() {
return validate_transaction_structure_base64(transaction);
}
if validate_base58(transaction).is_ok() {
return validate_transaction_structure_base58(transaction);
}
Err(validator::ValidationError::new(
"invalid_transaction_encoding",
))
}
fn validate_transaction_structure_base64(
transaction: &str,
) -> Result<(), validator::ValidationError> {
use fastcrypto::encoding::{Base64, Encoding};
let decoded = Base64::decode(transaction)
.map_err(|_| validator::ValidationError::new("invalid_base64_transaction"))?;
validate_transaction_bytes(&decoded)
}
fn validate_transaction_structure_base58(
transaction: &str,
) -> Result<(), validator::ValidationError> {
use fastcrypto::encoding::{Base58, Encoding};
let decoded = Base58::decode(transaction)
.map_err(|_| validator::ValidationError::new("invalid_base58_transaction"))?;
validate_transaction_bytes(&decoded)
}
fn validate_transaction_bytes(transaction_bytes: &[u8]) -> Result<(), validator::ValidationError> {
if transaction_bytes.len() < MIN_TRANSACTION_SIZE {
return Err(validator::ValidationError::new("transaction_too_small"));
}
if transaction_bytes.len() > MAX_TRANSACTION_SIZE {
return Err(validator::ValidationError::new("transaction_too_large"));
}
match bincode::deserialize::<rialo_s_sdk::transaction::VersionedTransaction>(transaction_bytes)
{
Ok(_) => Ok(()),
Err(_) => {
match bincode::deserialize::<rialo_s_sdk::transaction::Transaction>(transaction_bytes) {
Ok(_) => Ok(()),
Err(_) => Err(validator::ValidationError::new(
"invalid_transaction_structure",
)),
}
}
}
}
pub fn validate_limit_string(limit: &str) -> Result<(), validator::ValidationError> {
let limit_val: u64 = limit
.parse()
.map_err(|_| validator::ValidationError::new("invalid_limit_format"))?;
validate_limit(&limit_val)?;
Ok(())
}
pub fn validate_blockhash(blockhash: &str) -> Result<(), validator::ValidationError> {
if blockhash.len() < MIN_BLOCKHASH_LENGTH || blockhash.len() > MAX_BLOCKHASH_LENGTH {
return Err(validator::ValidationError::new("invalid_blockhash_length"));
}
validate_base58(blockhash)
}
pub fn validate_addresses(addresses: &[String]) -> Result<(), validator::ValidationError> {
for address in addresses {
validate_pubkey(address)?;
}
Ok(())
}
pub fn validate_signatures(signatures: &[String]) -> Result<(), validator::ValidationError> {
for signature in signatures {
validate_signature(signature)?;
}
Ok(())
}
pub fn validate_encoding(encoding: &str) -> Result<(), validator::ValidationError> {
match encoding {
"json" | "jsonParsed" | "base58" | "base64" => Ok(()),
_ => Err(validator::ValidationError::new("invalid_encoding_format")),
}
}
pub fn validate_max_transaction_version(version: &u8) -> Result<(), validator::ValidationError> {
if *version <= 1 {
Ok(())
} else {
Err(validator::ValidationError::new(
"invalid_max_transaction_version",
))
}
}
pub fn validate_request<T>(request: T) -> ValidationResult<T>
where
T: validator::Validate,
{
request.validate().map_err(ValidationError::from)?;
Ok(request)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_limit() {
assert!(validate_limit(&1).is_ok());
assert!(validate_limit(&MAX_PAGINATION_LIMIT).is_ok());
assert!(validate_limit(&0).is_err());
assert!(validate_limit(&(MAX_PAGINATION_LIMIT + 1)).is_err());
}
#[test]
fn test_validate_nonce() {
assert!(validate_nonce("valid_nonce").is_ok());
assert!(validate_nonce("").is_err());
let long_nonce = "x".repeat(65);
assert!(validate_nonce(&long_nonce).is_err());
}
#[test]
fn test_validate_encoding() {
assert!(validate_encoding("json").is_ok());
assert!(validate_encoding("jsonParsed").is_ok());
assert!(validate_encoding("base58").is_ok());
assert!(validate_encoding("base64").is_ok());
assert!(validate_encoding("invalid").is_err());
}
#[test]
fn test_validate_max_transaction_version() {
assert!(validate_max_transaction_version(&0).is_ok());
assert!(validate_max_transaction_version(&1).is_ok());
assert!(validate_max_transaction_version(&2).is_err());
}
}