diff --git a/bittensor-rs/src/error.rs b/bittensor-rs/src/error.rs index 595d923..9fe6e43 100644 --- a/bittensor-rs/src/error.rs +++ b/bittensor-rs/src/error.rs @@ -148,6 +148,9 @@ pub enum BittensorError { #[error("Insufficient balance: {available} < {required}")] InsufficientBalance { available: u64, required: u64 }, + + #[error("Invalid amount: {reason}")] + InvalidAmount { reason: String }, } /// Classification of errors for retry logic @@ -443,7 +446,8 @@ impl BittensorError { | BittensorError::MaxRetriesExceeded { .. } | BittensorError::BackoffTimeoutReached { .. } | BittensorError::BlockNotFound { .. } - | BittensorError::InvalidBlockNumber { .. } => ErrorCategory::Permanent, + | BittensorError::InvalidBlockNumber { .. } + | BittensorError::InvalidAmount { .. } => ErrorCategory::Permanent, // Legacy errors - categorize based on content BittensorError::RpcError { message } diff --git a/bittensor-rs/src/extrinsics/staking.rs b/bittensor-rs/src/extrinsics/staking.rs index 64eb422..ebdf206 100644 --- a/bittensor-rs/src/extrinsics/staking.rs +++ b/bittensor-rs/src/extrinsics/staking.rs @@ -38,15 +38,20 @@ impl StakeParams { /// "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", /// 1, // netuid /// 1.5 // TAO - /// ); + /// ).unwrap(); /// assert_eq!(params.amount_rao, 1_500_000_000); /// ``` - pub fn new_tao(hotkey: &str, netuid: u16, amount_tao: f64) -> Self { - Self { + pub fn new_tao(hotkey: &str, netuid: u16, amount_tao: f64) -> Result { + if !amount_tao.is_finite() || amount_tao < 0.0 { + return Err(BittensorError::InvalidAmount { + reason: format!("TAO amount must be a non-negative finite number, got {}", amount_tao), + }); + } + Ok(Self { hotkey: hotkey.to_string(), netuid, amount_rao: (amount_tao * 1_000_000_000.0) as u64, - } + }) } /// Create new stake params with amount in RAO @@ -83,7 +88,7 @@ impl StakeParams { /// "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", /// 1, // netuid /// 1.0 // TAO amount -/// ); +/// )?; /// let result = add_stake(client, signer, params).await?; /// Ok(()) /// } @@ -222,11 +227,36 @@ mod tests { #[test] fn test_stake_params_tao() { let params = - StakeParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 1, 1.5); + StakeParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 1, 1.5) + .unwrap(); assert_eq!(params.amount_rao, 1_500_000_000); assert_eq!(params.netuid, 1); } + #[test] + fn test_stake_params_negative_tao() { + let result = + StakeParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 1, -1.0); + assert!(result.is_err()); + } + + #[test] + fn test_stake_params_nan_tao() { + let result = + StakeParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 1, f64::NAN); + assert!(result.is_err()); + } + + #[test] + fn test_stake_params_infinity_tao() { + let result = StakeParams::new_tao( + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + 1, + f64::INFINITY, + ); + assert!(result.is_err()); + } + #[test] fn test_stake_params_rao() { let params = diff --git a/bittensor-rs/src/extrinsics/transfer.rs b/bittensor-rs/src/extrinsics/transfer.rs index a18d62f..ea8c01c 100644 --- a/bittensor-rs/src/extrinsics/transfer.rs +++ b/bittensor-rs/src/extrinsics/transfer.rs @@ -36,15 +36,20 @@ impl TransferParams { /// let params = TransferParams::new_tao( /// "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", /// 1.5 - /// ); + /// ).unwrap(); /// assert_eq!(params.amount_rao, 1_500_000_000); /// ``` - pub fn new_tao(dest: &str, amount_tao: f64) -> Self { - Self { + pub fn new_tao(dest: &str, amount_tao: f64) -> Result { + if !amount_tao.is_finite() || amount_tao < 0.0 { + return Err(BittensorError::InvalidAmount { + reason: format!("TAO amount must be a non-negative finite number, got {}", amount_tao), + }); + } + Ok(Self { dest: dest.to_string(), amount_rao: (amount_tao * 1_000_000_000.0) as u64, keep_alive: true, - } + }) } /// Create new transfer params with amount in RAO @@ -84,7 +89,7 @@ impl TransferParams { /// let params = TransferParams::new_tao( /// "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", /// 1.0 -/// ); +/// )?; /// let result = transfer(client, signer, params).await?; /// Ok(()) /// } @@ -218,11 +223,35 @@ mod tests { #[test] fn test_transfer_params_tao() { let params = - TransferParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 1.5); + TransferParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 1.5) + .unwrap(); assert_eq!(params.amount_rao, 1_500_000_000); assert!(params.keep_alive); } + #[test] + fn test_transfer_params_negative_tao() { + let result = + TransferParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", -1.0); + assert!(result.is_err()); + } + + #[test] + fn test_transfer_params_nan_tao() { + let result = + TransferParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", f64::NAN); + assert!(result.is_err()); + } + + #[test] + fn test_transfer_params_infinity_tao() { + let result = TransferParams::new_tao( + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + f64::INFINITY, + ); + assert!(result.is_err()); + } + #[test] fn test_transfer_params_rao() { let params = @@ -234,6 +263,7 @@ mod tests { fn test_transfer_params_builder() { let params = TransferParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 1.0) + .unwrap() .keep_alive(false); assert!(!params.keep_alive);