From 8214895a0f9658471d3374fdbc36de922cb9b063 Mon Sep 17 00:00:00 2001 From: OpenSourceSoul Date: Sun, 1 Feb 2026 18:10:25 +0000 Subject: [PATCH] fix: validate negative TAO amounts in StakeParams::new_tao Prevent negative TAO amounts from causing u64 overflow when converted to RAO. Previously, a negative value like -1.0 would produce a massive u64 value (close to u64::MAX) due to how floating point to integer conversion works in Rust. Changes: - Add validation in new_tao() to reject negative amounts - Return Result type instead of Self for error handling - Update docstring examples to use unwrap() - Add tests for negative amount rejection and zero amount handling --- bittensor-rs/src/extrinsics/staking.rs | 68 ++++++++++++++++++-------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/bittensor-rs/src/extrinsics/staking.rs b/bittensor-rs/src/extrinsics/staking.rs index 64eb422..6172380 100644 --- a/bittensor-rs/src/extrinsics/staking.rs +++ b/bittensor-rs/src/extrinsics/staking.rs @@ -29,6 +29,8 @@ pub struct StakeParams { impl StakeParams { /// Create new stake params with amount in TAO /// + /// Returns an error if the amount is negative. + /// /// # Example /// /// ``` @@ -38,15 +40,21 @@ 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 < 0.0 { + return Err(BittensorError::ConfigError { + field: "amount_tao".to_string(), + message: format!("Stake amount cannot be negative: {}", 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 @@ -74,21 +82,21 @@ impl StakeParams { /// An `ExtrinsicResponse` with the staking result /// /// # Example -/// -/// ```rust,ignore -/// use bittensor_rs::extrinsics::{add_stake, StakeParams}; -/// -/// async fn example(client: &subxt::OnlineClient, signer: &impl subxt::tx::Signer) -> Result<(), Box> { -/// let params = StakeParams::new_tao( -/// "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", -/// 1, // netuid -/// 1.0 // TAO amount -/// ); -/// let result = add_stake(client, signer, params).await?; -/// Ok(()) -/// } -/// ``` -pub async fn add_stake( + /// + /// ```rust,ignore + /// use bittensor_rs::extrinsics::{add_stake, StakeParams}; + /// + /// async fn example(client: &subxt::OnlineClient, signer: &impl subxt::tx::Signer) -> Result<(), Box> { + /// let params = StakeParams::new_tao( + /// "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + /// 1, // netuid + /// 1.0 // TAO amount + /// )?; + /// let result = add_stake(client, signer, params).await?; + /// Ok(()) + /// } + /// ``` + pub async fn add_stake( client: &OnlineClient, signer: &S, params: StakeParams, @@ -222,11 +230,31 @@ 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_tao_zero_amount() { + // Zero amount should be allowed (no-op stake) + let params = + StakeParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 1, 0.0) + .unwrap(); + assert_eq!(params.amount_rao, 0); + } + + #[test] + fn test_stake_params_tao_negative_amount_rejected() { + // Negative amounts should be rejected to prevent u64 overflow + let result = + StakeParams::new_tao("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", 1, -1.0); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(err.to_string().contains("cannot be negative")); + } + #[test] fn test_stake_params_rao() { let params =