From e5bb97228e595e5d4500dcef28961b88ebf40748 Mon Sep 17 00:00:00 2001 From: KIMURA Yu <33382781+kimurayu45z@users.noreply.github.com> Date: Sun, 25 May 2025 11:32:45 +0900 Subject: [PATCH 1/6] test --- src/types/address.rs | 33 ++++ src/types/decimal.rs | 198 +++++++++++++++++++- src/types/environment.rs | 74 ++++++++ src/types/token.rs | 178 ++++++++++++++++++ src/types/tokens.rs | 215 ++++++++++++++++++++++ src/types/uint.rs | 261 +++++++++++++++++++++++++- src/utils/indexed_map.rs | 385 +++++++++++++++++++++++++++++++++++++++ src/utils/item.rs | 161 ++++++++++++++++ src/utils/key.rs | 8 + src/utils/map.rs | 215 ++++++++++++++++++++++ 10 files changed, 1722 insertions(+), 6 deletions(-) diff --git a/src/types/address.rs b/src/types/address.rs index 9e13290..845f7e8 100644 --- a/src/types/address.rs +++ b/src/types/address.rs @@ -1,3 +1,36 @@ /// Represents a 32-byte address used throughout the InterLiquid SDK. /// This is typically used to identify accounts, contracts, or other entities. pub type Address = [u8; 32]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_address_creation() { + let addr: Address = [0u8; 32]; + assert_eq!(addr.len(), 32); + assert!(addr.iter().all(|&b| b == 0)); + } + + #[test] + fn test_address_with_values() { + let mut addr: Address = [0u8; 32]; + addr[0] = 1; + addr[31] = 255; + + assert_eq!(addr[0], 1); + assert_eq!(addr[31], 255); + assert_eq!(addr[1], 0); + } + + #[test] + fn test_address_comparison() { + let addr1: Address = [1u8; 32]; + let addr2: Address = [1u8; 32]; + let addr3: Address = [2u8; 32]; + + assert_eq!(addr1, addr2); + assert_ne!(addr1, addr3); + } +} diff --git a/src/types/decimal.rs b/src/types/decimal.rs index a33f5a3..9886a4d 100644 --- a/src/types/decimal.rs +++ b/src/types/decimal.rs @@ -52,13 +52,17 @@ impl Decimal { if lhs.precision < max_precision { let diff = max_precision - lhs.precision; let ten: U256 = 10u64.into(); - lhs = lhs.checked_mul(&ten.powi(diff)?)?; + let multiplier = ten.powi(diff)?; + lhs.value = lhs.value.checked_mul(&multiplier)?; + lhs.precision = max_precision; } if rhs.precision < max_precision { let diff = max_precision - rhs.precision; let ten: U256 = 10u64.into(); - rhs = rhs.checked_mul(&ten.powi(diff)?)?; + let multiplier = ten.powi(diff)?; + rhs.value = rhs.value.checked_mul(&multiplier)?; + rhs.precision = max_precision; } Ok((lhs, rhs)) @@ -74,7 +78,7 @@ impl Decimal { /// /// # Errors /// Returns an error if the addition overflows - pub fn checked_add(mut self, mut rhs: Decimal) -> Result { + pub fn checked_add(self, rhs: Decimal) -> Result { let (lhs, rhs) = Self::align_precision(self, rhs)?; Ok(Self::new(lhs.value.checked_add(&rhs.value)?, lhs.precision)) @@ -90,7 +94,7 @@ impl Decimal { /// /// # Errors /// Returns an error if the subtraction underflows - pub fn checked_sub(mut self, mut rhs: Decimal) -> Result { + pub fn checked_sub(self, rhs: Decimal) -> Result { let (lhs, rhs) = Self::align_precision(self, rhs)?; Ok(Self::new(lhs.value.checked_sub(&rhs.value)?, lhs.precision)) @@ -130,8 +134,194 @@ impl Decimal { if new_precision < 0 { let ten: U256 = 10u64.into(); new_value = new_value.checked_mul(&ten.powi(new_precision.abs() as u8)?)?; + return Ok(Self::new(new_value, 0)); } return Ok(Self::new(new_value, new_precision as u8)); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_decimal() { + let decimal = Decimal::new(1000u64.into(), 2); + assert_eq!(decimal.value, 1000u64.into()); + assert_eq!(decimal.precision, 2); + } + + #[test] + fn test_is_zero() { + let zero_decimal = Decimal::new(0u64.into(), 2); + assert!(zero_decimal.is_zero()); + + let non_zero_decimal = Decimal::new(100u64.into(), 2); + assert!(!non_zero_decimal.is_zero()); + } + + #[test] + fn test_align_precision_no_change() { + let lhs = Decimal::new(100u64.into(), 2); + let rhs = Decimal::new(200u64.into(), 2); + + let (aligned_lhs, aligned_rhs) = Decimal::align_precision(lhs, rhs).unwrap(); + + assert_eq!(aligned_lhs.value, 100u64.into()); + assert_eq!(aligned_rhs.value, 200u64.into()); + assert_eq!(aligned_lhs.precision, 2); + assert_eq!(aligned_rhs.precision, 2); + } + + #[test] + fn test_align_precision_different() { + let lhs = Decimal::new(100u64.into(), 2); // 1.00 + let rhs = Decimal::new(50u64.into(), 4); // 0.0050 + + let (aligned_lhs, aligned_rhs) = Decimal::align_precision(lhs, rhs).unwrap(); + + assert_eq!(aligned_lhs.value, 10000u64.into()); // 100 * 100 = 10000 + assert_eq!(aligned_rhs.value, 50u64.into()); + assert_eq!(aligned_lhs.precision, 4); + assert_eq!(aligned_rhs.precision, 4); + } + + #[test] + fn test_checked_add_same_precision() { + let a = Decimal::new(100u64.into(), 2); // 1.00 + let b = Decimal::new(200u64.into(), 2); // 2.00 + + let result = a.checked_add(b).unwrap(); + assert_eq!(result.value, 300u64.into()); + assert_eq!(result.precision, 2); + } + + #[test] + fn test_checked_add_different_precision() { + let a = Decimal::new(100u64.into(), 2); // 1.00 + let b = Decimal::new(50u64.into(), 4); // 0.0050 + + let result = a.checked_add(b).unwrap(); + assert_eq!(result.value, 10050u64.into()); // 10000 + 50 + assert_eq!(result.precision, 4); + } + + #[test] + fn test_checked_sub_same_precision() { + let a = Decimal::new(300u64.into(), 2); // 3.00 + let b = Decimal::new(100u64.into(), 2); // 1.00 + + let result = a.checked_sub(b).unwrap(); + assert_eq!(result.value, 200u64.into()); + assert_eq!(result.precision, 2); + } + + #[test] + fn test_checked_sub_underflow() { + let a = Decimal::new(100u64.into(), 2); // 1.00 + let b = Decimal::new(200u64.into(), 2); // 2.00 + + let result = a.checked_sub(b); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::Underflow) => (), + _ => panic!("Expected Underflow error"), + } + } + + #[test] + fn test_checked_mul() { + let a = Decimal::new(100u64.into(), 2); // 1.00 + let b = Decimal::new(200u64.into(), 2); // 2.00 + + let result = a.checked_mul(&b).unwrap(); + assert_eq!(result.value, 20000u64.into()); // 100 * 200 + assert_eq!(result.precision, 4); // 2 + 2 + } + + #[test] + fn test_checked_mul_zero() { + let a = Decimal::new(100u64.into(), 2); // 1.00 + let b = Decimal::new(0u64.into(), 2); // 0.00 + + let result = a.checked_mul(&b).unwrap(); + assert_eq!(result.value, 0u64.into()); + assert_eq!(result.precision, 4); + } + + #[test] + fn test_display() { + // Skip Display test since Display trait is not implemented + // The Decimal type uses Debug for formatting instead + } + + #[test] + fn test_debug() { + let decimal = Decimal::new(12345u64.into(), 3); + let debug_str = format!("{:?}", decimal); + assert!(debug_str.contains("Decimal")); + // Debug output will contain the U256 value representation, not the plain number + assert!(debug_str.contains("value")); + assert!(debug_str.contains("precision")); + } + + #[test] + fn test_align_precision_overflow() { + // Create a large value that would overflow when multiplied + // Create a large value that would overflow when multiplied + let bytes = vec![0xFF; 32]; + let max_inner = crate::crypto_bigint::U256::from_be_slice(&bytes); + let max_val = U256::new(max_inner); + let lhs = Decimal::new(max_val, 0); + let rhs = Decimal::new(1u64.into(), 100); // Very high precision difference + + let result = Decimal::align_precision(lhs, rhs); + assert!(result.is_err()); + } + + + #[test] + fn test_checked_div() { + let d1 = Decimal::new(1000u64.into(), 3); // 1.000 + let d2 = Decimal::new(250u64.into(), 2); // 2.50 + + let result = d1.checked_div(&d2).unwrap(); + + assert_eq!(result.value, 4u64.into()); // 1.000 / 2.50 = 0.4 + assert_eq!(result.precision, 1); // 3 - 2 + } + + #[test] + fn test_checked_div_by_zero() { + let d1 = Decimal::new(100u64.into(), 2); + let d2 = Decimal::new(0u64.into(), 2); + + let result = d1.checked_div(&d2); + + assert!(result.is_err()); + } + + #[test] + fn test_checked_div_negative_precision() { + let d1 = Decimal::new(100u64.into(), 2); // 1.00 + let d2 = Decimal::new(10u64.into(), 4); // 0.0010 + + let result = d1.checked_div(&d2).unwrap(); + + // 1.00 / 0.0010 = 1000, but precision is 2-4=-2 + // 100 / 10 = 10, then multiply by 10^2 = 100, giving us 1000 + assert_eq!(result.value, 1000u64.into()); + assert_eq!(result.precision, 0); + } + + #[test] + fn test_decimal_equality() { + let d1 = Decimal::new(100u64.into(), 2); + let d2 = Decimal::new(100u64.into(), 2); + let d3 = Decimal::new(100u64.into(), 3); + + assert_eq!(d1, d2); + assert_ne!(d1, d3); // Different precision means different decimal + } +} diff --git a/src/types/environment.rs b/src/types/environment.rs index ddd2160..69c8393 100644 --- a/src/types/environment.rs +++ b/src/types/environment.rs @@ -29,3 +29,77 @@ impl Environment { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_environment() { + let env = Environment::new("testnet-1".to_string(), 12345, 1640000000); + + assert_eq!(env.chain_id, "testnet-1"); + assert_eq!(env.block_height, 12345); + assert_eq!(env.block_time, 1640000000); + } + + #[test] + fn test_environment_clone() { + let env1 = Environment::new("mainnet".to_string(), 1000, 1234567890); + let env2 = env1.clone(); + + assert_eq!(env1, env2); + assert_eq!(env2.chain_id, "mainnet"); + assert_eq!(env2.block_height, 1000); + assert_eq!(env2.block_time, 1234567890); + } + + #[test] + fn test_environment_debug() { + let env = Environment::new("test".to_string(), 1, 1); + let debug_str = format!("{:?}", env); + + assert!(debug_str.contains("Environment")); + assert!(debug_str.contains("test")); + assert!(debug_str.contains("1")); + } + + #[test] + fn test_environment_equality() { + let env1 = Environment::new("chain1".to_string(), 100, 1000); + let env2 = Environment::new("chain1".to_string(), 100, 1000); + let env3 = Environment::new("chain2".to_string(), 100, 1000); + let env4 = Environment::new("chain1".to_string(), 101, 1000); + let env5 = Environment::new("chain1".to_string(), 100, 1001); + + assert_eq!(env1, env2); + assert_ne!(env1, env3); // Different chain_id + assert_ne!(env1, env4); // Different block_height + assert_ne!(env1, env5); // Different block_time + } + + #[test] + fn test_environment_edge_cases() { + // Test with empty chain_id + let env1 = Environment::new("".to_string(), 0, 0); + assert_eq!(env1.chain_id, ""); + assert_eq!(env1.block_height, 0); + assert_eq!(env1.block_time, 0); + + // Test with max values + let env2 = Environment::new("max".to_string(), u64::MAX, u64::MAX); + assert_eq!(env2.block_height, u64::MAX); + assert_eq!(env2.block_time, u64::MAX); + } + + #[test] + fn test_environment_serialization() { + let env = Environment::new("test-chain".to_string(), 42, 1234567890); + + // Test Borsh serialization roundtrip + let serialized = borsh::to_vec(&env).unwrap(); + let deserialized: Environment = borsh::from_slice(&serialized).unwrap(); + + assert_eq!(env, deserialized); + } +} diff --git a/src/types/token.rs b/src/types/token.rs index 1e9d386..3ea3159 100644 --- a/src/types/token.rs +++ b/src/types/token.rs @@ -101,3 +101,181 @@ impl Token { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_token() { + let token = Token::new("uatom".to_string(), 1000u64.into()); + + assert_eq!(token.denom, "uatom"); + assert_eq!(token.amount, 1000u64.into()); + } + + #[test] + fn test_validate_denom_valid() { + assert!(Token::validate_denom("uatom").is_ok()); + assert!(Token::validate_denom("usdc").is_ok()); + assert!(Token::validate_denom("ibc/ABC123").is_ok()); + } + + #[test] + fn test_validate_denom_invalid() { + let result = Token::validate_denom(""); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::InvalidDenom) => (), + _ => panic!("Expected InvalidDenom error"), + } + } + + #[test] + fn test_validate_amount_valid() { + assert!(Token::validate_amount(&100u64.into()).is_ok()); + assert!(Token::validate_amount(&1u64.into()).is_ok()); + assert!(Token::validate_amount(&U256::from(u64::MAX)).is_ok()); + } + + #[test] + fn test_validate_amount_invalid() { + let result = Token::validate_amount(&0u64.into()); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::ZeroAmount) => (), + _ => panic!("Expected ZeroAmount error"), + } + } + + #[test] + fn test_validate_valid_token() { + let token = Token::new("uatom".to_string(), 1000u64.into()); + assert!(token.validate().is_ok()); + } + + #[test] + fn test_validate_invalid_denom() { + let token = Token::new("".to_string(), 1000u64.into()); + let result = token.validate(); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::InvalidDenom) => (), + _ => panic!("Expected InvalidDenom error"), + } + } + + #[test] + fn test_validate_zero_amount() { + let token = Token::new("uatom".to_string(), 0u64.into()); + let result = token.validate(); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::ZeroAmount) => (), + _ => panic!("Expected ZeroAmount error"), + } + } + + #[test] + fn test_checked_add_same_denom() { + let token1 = Token::new("uatom".to_string(), 100u64.into()); + let token2 = Token::new("uatom".to_string(), 200u64.into()); + + let result = token1.checked_add(&token2).unwrap(); + + assert_eq!(result.denom, "uatom"); + assert_eq!(result.amount, 300u64.into()); + } + + #[test] + fn test_checked_add_different_denom() { + let token1 = Token::new("uatom".to_string(), 100u64.into()); + let token2 = Token::new("usdc".to_string(), 200u64.into()); + + let result = token1.checked_add(&token2); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::DenomMismatch) => (), + _ => panic!("Expected DenomMismatch error"), + } + } + + #[test] + fn test_checked_add_overflow() { + // Create a very large value that will overflow when added to itself + let bytes = vec![0xFF; 32]; + let max_inner = crate::crypto_bigint::U256::from_be_slice(&bytes); + let max_value = U256::new(max_inner); + + let token1 = Token::new("uatom".to_string(), max_value.clone()); + let token2 = Token::new("uatom".to_string(), max_value); + + let result = token1.checked_add(&token2); + assert!(result.is_err()); // Should overflow + } + + #[test] + fn test_checked_sub_same_denom() { + let token1 = Token::new("uatom".to_string(), 300u64.into()); + let token2 = Token::new("uatom".to_string(), 100u64.into()); + + let result = token1.checked_sub(&token2).unwrap(); + + assert_eq!(result.denom, "uatom"); + assert_eq!(result.amount, 200u64.into()); + } + + #[test] + fn test_checked_sub_different_denom() { + let token1 = Token::new("uatom".to_string(), 300u64.into()); + let token2 = Token::new("usdc".to_string(), 100u64.into()); + + let result = token1.checked_sub(&token2); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::DenomMismatch) => (), + _ => panic!("Expected DenomMismatch error"), + } + } + + #[test] + fn test_checked_sub_underflow() { + let token1 = Token::new("uatom".to_string(), 100u64.into()); + let token2 = Token::new("uatom".to_string(), 200u64.into()); + + let result = token1.checked_sub(&token2); + assert!(result.is_err()); // Should underflow + } + + #[test] + fn test_token_equality() { + let token1 = Token::new("uatom".to_string(), 100u64.into()); + let token2 = Token::new("uatom".to_string(), 100u64.into()); + let token3 = Token::new("usdc".to_string(), 100u64.into()); + let token4 = Token::new("uatom".to_string(), 200u64.into()); + + assert_eq!(token1, token2); + assert_ne!(token1, token3); // Different denom + assert_ne!(token1, token4); // Different amount + } + + #[test] + fn test_token_clone() { + let token1 = Token::new("uatom".to_string(), 100u64.into()); + let token2 = token1.clone(); + + assert_eq!(token1, token2); + assert_eq!(token2.denom, "uatom"); + assert_eq!(token2.amount, 100u64.into()); + } + + #[test] + fn test_token_debug() { + let token = Token::new("uatom".to_string(), 100u64.into()); + let debug_str = format!("{:?}", token); + + assert!(debug_str.contains("Token")); + assert!(debug_str.contains("uatom")); + // The amount is displayed as U256 struct, not a plain number + } +} diff --git a/src/types/tokens.rs b/src/types/tokens.rs index b7c28d7..de581cd 100644 --- a/src/types/tokens.rs +++ b/src/types/tokens.rs @@ -99,3 +99,218 @@ impl TokensI for Tokens { Ok(lhs) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tokens_validate_empty() { + let tokens = Tokens::new(); + assert!(tokens.validate().is_ok()); + } + + #[test] + fn test_tokens_validate_valid() { + let mut tokens = Tokens::new(); + tokens.insert("uatom".to_string(), 100u64.into()); + tokens.insert("usdc".to_string(), 200u64.into()); + + assert!(tokens.validate().is_ok()); + } + + #[test] + fn test_tokens_validate_invalid_denom() { + let mut tokens = Tokens::new(); + tokens.insert("".to_string(), 100u64.into()); + + let result = tokens.validate(); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::InvalidDenom) => (), + _ => panic!("Expected InvalidDenom error"), + } + } + + #[test] + fn test_tokens_validate_zero_amount() { + let mut tokens = Tokens::new(); + tokens.insert("uatom".to_string(), 0u64.into()); + + let result = tokens.validate(); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::ZeroAmount) => (), + _ => panic!("Expected ZeroAmount error"), + } + } + + #[test] + fn test_tokens_checked_add_both_empty() { + let tokens1 = Tokens::new(); + let tokens2 = Tokens::new(); + + let result = tokens1.checked_add(&tokens2).unwrap(); + assert!(result.is_empty()); + } + + #[test] + fn test_tokens_checked_add_one_empty() { + let mut tokens1 = Tokens::new(); + tokens1.insert("uatom".to_string(), 100u64.into()); + + let tokens2 = Tokens::new(); + + let result = tokens1.clone().checked_add(&tokens2).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result.get("uatom"), Some(&100u64.into())); + + // Test reverse + let result2 = tokens2.checked_add(&tokens1).unwrap(); + assert_eq!(result2.len(), 1); + assert_eq!(result2.get("uatom"), Some(&100u64.into())); + } + + #[test] + fn test_tokens_checked_add_same_denoms() { + let mut tokens1 = Tokens::new(); + tokens1.insert("uatom".to_string(), 100u64.into()); + tokens1.insert("usdc".to_string(), 200u64.into()); + + let mut tokens2 = Tokens::new(); + tokens2.insert("uatom".to_string(), 50u64.into()); + tokens2.insert("usdc".to_string(), 150u64.into()); + + let result = tokens1.checked_add(&tokens2).unwrap(); + + assert_eq!(result.len(), 2); + assert_eq!(result.get("uatom"), Some(&150u64.into())); + assert_eq!(result.get("usdc"), Some(&350u64.into())); + } + + #[test] + fn test_tokens_checked_add_different_denoms() { + let mut tokens1 = Tokens::new(); + tokens1.insert("uatom".to_string(), 100u64.into()); + + let mut tokens2 = Tokens::new(); + tokens2.insert("usdc".to_string(), 200u64.into()); + + let result = tokens1.checked_add(&tokens2).unwrap(); + + assert_eq!(result.len(), 2); + assert_eq!(result.get("uatom"), Some(&100u64.into())); + assert_eq!(result.get("usdc"), Some(&200u64.into())); + } + + #[test] + fn test_tokens_checked_add_mixed_denoms() { + let mut tokens1 = Tokens::new(); + tokens1.insert("uatom".to_string(), 100u64.into()); + tokens1.insert("usdc".to_string(), 200u64.into()); + + let mut tokens2 = Tokens::new(); + tokens2.insert("usdc".to_string(), 50u64.into()); + tokens2.insert("ueth".to_string(), 300u64.into()); + + let result = tokens1.checked_add(&tokens2).unwrap(); + + assert_eq!(result.len(), 3); + assert_eq!(result.get("uatom"), Some(&100u64.into())); + assert_eq!(result.get("usdc"), Some(&250u64.into())); + assert_eq!(result.get("ueth"), Some(&300u64.into())); + } + + #[test] + fn test_tokens_checked_sub_same_amounts() { + let mut tokens1 = Tokens::new(); + tokens1.insert("uatom".to_string(), 100u64.into()); + + let mut tokens2 = Tokens::new(); + tokens2.insert("uatom".to_string(), 100u64.into()); + + let result = tokens1.checked_sub(&tokens2).unwrap(); + + assert_eq!(result.len(), 1); + assert_eq!(result.get("uatom"), Some(&0u64.into())); + } + + #[test] + fn test_tokens_checked_sub_partial() { + let mut tokens1 = Tokens::new(); + tokens1.insert("uatom".to_string(), 100u64.into()); + tokens1.insert("usdc".to_string(), 200u64.into()); + + let mut tokens2 = Tokens::new(); + tokens2.insert("uatom".to_string(), 50u64.into()); + tokens2.insert("usdc".to_string(), 150u64.into()); + + let result = tokens1.checked_sub(&tokens2).unwrap(); + + assert_eq!(result.len(), 2); + assert_eq!(result.get("uatom"), Some(&50u64.into())); + assert_eq!(result.get("usdc"), Some(&50u64.into())); + } + + #[test] + fn test_tokens_checked_sub_underflow() { + let mut tokens1 = Tokens::new(); + tokens1.insert("uatom".to_string(), 50u64.into()); + + let mut tokens2 = Tokens::new(); + tokens2.insert("uatom".to_string(), 100u64.into()); + + let result = tokens1.checked_sub(&tokens2); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::Underflow) => (), + _ => panic!("Expected Underflow error"), + } + } + + #[test] + fn test_tokens_checked_sub_missing_denom() { + let mut tokens1 = Tokens::new(); + tokens1.insert("uatom".to_string(), 100u64.into()); + + let mut tokens2 = Tokens::new(); + tokens2.insert("usdc".to_string(), 50u64.into()); + + let result = tokens1.checked_sub(&tokens2); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::Underflow) => (), + _ => panic!("Expected Underflow error"), + } + } + + #[test] + fn test_tokens_btree_order() { + let mut tokens = Tokens::new(); + tokens.insert("usdc".to_string(), 100u64.into()); + tokens.insert("uatom".to_string(), 200u64.into()); + tokens.insert("ueth".to_string(), 300u64.into()); + + // BTreeMap maintains sorted order + let keys: Vec = tokens.keys().cloned().collect(); + assert_eq!(keys, vec!["uatom", "ueth", "usdc"]); + } + + #[test] + fn test_tokens_edge_cases() { + // Test with max values + let bytes = vec![0xFF; 32]; + let max_inner = crate::crypto_bigint::U256::from_be_slice(&bytes); + let max_value = U256::new(max_inner); + + let mut tokens1 = Tokens::new(); + tokens1.insert("uatom".to_string(), max_value.clone()); + + let mut tokens2 = Tokens::new(); + tokens2.insert("uatom".to_string(), max_value); + + // This should overflow + let result = tokens1.checked_add(&tokens2); + assert!(result.is_err()); + } +} diff --git a/src/types/uint.rs b/src/types/uint.rs index 91c2e4a..072f52e 100644 --- a/src/types/uint.rs +++ b/src/types/uint.rs @@ -122,9 +122,9 @@ impl U256 { } else if exponent == 1 { return Ok(self.clone()); } else { - let mut val = self.checked_mul(self)?; + let mut val = self.clone(); - for _ in 2..exponent { + for _ in 1..exponent { val = val.checked_mul(self)?; } @@ -172,3 +172,260 @@ impl From for U256 { U256(U256Lib::from_u64(value)) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_u256() { + let value = U256Lib::from_u64(12345); + let u256 = U256::new(value); + assert_eq!(*u256.inner_value(), value); + } + + #[test] + fn test_is_zero() { + let zero = U256::from(0u64); + assert!(zero.is_zero()); + + let non_zero = U256::from(1u64); + assert!(!non_zero.is_zero()); + } + + #[test] + fn test_checked_add_success() { + let a = U256::from(100u64); + let b = U256::from(200u64); + + let result = a.checked_add(&b).unwrap(); + assert_eq!(result, U256::from(300u64)); + } + + #[test] + fn test_checked_add_overflow() { + // Create a value close to max + let max = U256::new(U256Lib::MAX); + let one = U256::from(1u64); + + let result = max.checked_add(&one); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::Overflow) => (), + _ => panic!("Expected Overflow error"), + } + } + + #[test] + fn test_checked_sub_success() { + let a = U256::from(300u64); + let b = U256::from(100u64); + + let result = a.checked_sub(&b).unwrap(); + assert_eq!(result, U256::from(200u64)); + } + + #[test] + fn test_checked_sub_underflow() { + let a = U256::from(100u64); + let b = U256::from(200u64); + + let result = a.checked_sub(&b); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::Underflow) => (), + _ => panic!("Expected Underflow error"), + } + } + + #[test] + fn test_checked_mul_success() { + let a = U256::from(50u64); + let b = U256::from(100u64); + + let result = a.checked_mul(&b).unwrap(); + assert_eq!(result, U256::from(5000u64)); + } + + #[test] + fn test_checked_mul_overflow() { + // Create large values that will overflow when multiplied + // Use a very large value that will overflow when multiplied by 3 + let large_inner = U256Lib::from_be_slice(&[0xFF; 32]); + let large = U256::new(large_inner); + let three = U256::from(3u64); + + let result = large.checked_mul(&three); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::Overflow) => (), + _ => panic!("Expected Overflow error"), + } + } + + #[test] + fn test_checked_div_success() { + let a = U256::from(1000u64); + let b = U256::from(10u64); + + let result = a.checked_div(&b).unwrap(); + assert_eq!(result, U256::from(100u64)); + } + + #[test] + fn test_checked_div_by_zero() { + let a = U256::from(100u64); + let zero = U256::from(0u64); + + let result = a.checked_div(&zero); + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::DivisionByZero) => (), + _ => panic!("Expected DivisionByZero error"), + } + } + + #[test] + fn test_powi_zero_exponent() { + let base = U256::from(123u64); + let result = base.powi(0).unwrap(); + assert_eq!(result, U256::from(1u64)); + } + + #[test] + fn test_powi_one_exponent() { + let base = U256::from(123u64); + let result = base.powi(1).unwrap(); + assert_eq!(result, U256::from(123u64)); + } + + #[test] + fn test_powi_small_exponents() { + let base = U256::from(2u64); + + let result2 = base.powi(2).unwrap(); + assert_eq!(result2, U256::from(4u64)); + + let result3 = base.powi(3).unwrap(); + assert_eq!(result3, U256::from(8u64)); + + let result4 = base.powi(4).unwrap(); + assert_eq!(result4, U256::from(16u64)); + + let result8 = base.powi(8).unwrap(); + assert_eq!(result8, U256::from(256u64)); + } + + #[test] + fn test_powi_base_10() { + let base = U256::from(10u64); + + let result2 = base.powi(2).unwrap(); + assert_eq!(result2, U256::from(100u64)); + + let result3 = base.powi(3).unwrap(); + assert_eq!(result3, U256::from(1000u64)); + + let result6 = base.powi(6).unwrap(); + assert_eq!(result6, U256::from(1_000_000u64)); + } + + #[test] + fn test_powi_overflow() { + // Create a large base that will overflow when raised to a high power + let base = U256::from(u64::MAX); + let result = base.powi(100); // This should definitely overflow + assert!(result.is_err()); + match result { + Err(InterLiquidSdkError::Overflow) => (), + _ => panic!("Expected Overflow error"), + } + } + + #[test] + fn test_from_u256lib() { + let lib_value = U256Lib::from_u64(999); + let u256: U256 = lib_value.into(); + assert_eq!(*u256.inner_value(), lib_value); + } + + #[test] + fn test_into_u256lib() { + let u256 = U256::from(888u64); + let lib_value: U256Lib = u256.into(); + assert_eq!(lib_value, U256Lib::from_u64(888)); + } + + #[test] + fn test_from_u64() { + let u256 = U256::from(12345u64); + assert_eq!(*u256.inner_value(), U256Lib::from_u64(12345)); + + // Test edge cases + let zero = U256::from(0u64); + assert!(zero.is_zero()); + + let max = U256::from(u64::MAX); + assert_eq!(*max.inner_value(), U256Lib::from_u64(u64::MAX)); + } + + #[test] + fn test_clone() { + let original = U256::from(100u64); + let cloned = original.clone(); + + assert_eq!(original, cloned); + assert_eq!(*original.inner_value(), *cloned.inner_value()); + } + + #[test] + fn test_debug() { + let u256 = U256::from(123u64); + let debug_str = format!("{:?}", u256); + + // The debug format will show U256 struct with the inner U256Lib value + assert!(debug_str.contains("U256")); + } + + #[test] + fn test_equality() { + let a = U256::from(100u64); + let b = U256::from(100u64); + let c = U256::from(200u64); + + assert_eq!(a, b); + assert_ne!(a, c); + } + + #[test] + fn test_borsh_serialization() { + let original = U256::from(123456789u64); + + // Serialize + let serialized = borsh::to_vec(&original).unwrap(); + + // Deserialize + let deserialized: U256 = borsh::from_slice(&serialized).unwrap(); + + assert_eq!(original, deserialized); + } + + #[test] + fn test_edge_case_arithmetic() { + // Test with zero + let zero = U256::from(0u64); + let one = U256::from(1u64); + + assert_eq!(zero.checked_add(&one).unwrap(), one); + assert_eq!(one.checked_sub(&one).unwrap(), zero); + assert_eq!(zero.checked_mul(&one).unwrap(), zero); + assert_eq!(zero.checked_div(&one).unwrap(), zero); + + // Test identity operations + let value = U256::from(42u64); + assert_eq!(value.checked_add(&zero).unwrap(), value); + assert_eq!(value.checked_sub(&zero).unwrap(), value); + assert_eq!(value.checked_mul(&one).unwrap(), value); + assert_eq!(value.checked_div(&one).unwrap(), value); + } +} diff --git a/src/utils/indexed_map.rs b/src/utils/indexed_map.rs index 1300550..636cf02 100644 --- a/src/utils/indexed_map.rs +++ b/src/utils/indexed_map.rs @@ -300,3 +300,388 @@ impl<'a, IK: KeyDeclaration, PK: KeyDeclaration, V: Value> IndexerI Ok(buf) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::state::RelatedState; + use crate::utils::key::KeyDeclaration; + use borsh_derive::{BorshSerialize, BorshDeserialize}; + use std::collections::BTreeMap; + + // Test types + #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] + struct User { + id: u32, + name: String, + email: String, + age: u32, + } + + #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] + struct UserId(u32); + + impl KeyDeclaration for UserId { + type KeyReference<'a> = &'a UserId; + + fn to_key_bytes(key: &UserId) -> Vec { + let mut buf = Vec::new(); + key.serialize(&mut buf).unwrap(); + buf + } + } + + #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] + struct Email(String); + + impl KeyDeclaration for Email { + type KeyReference<'a> = &'a Email; + + fn to_key_bytes(key: &Email) -> Vec { + let mut buf = Vec::new(); + key.serialize(&mut buf).unwrap(); + buf + } + } + + #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] + struct Age(u32); + + impl KeyDeclaration for Age { + type KeyReference<'a> = &'a Age; + + fn to_key_bytes(key: &Age) -> Vec { + let mut buf = Vec::new(); + key.serialize(&mut buf).unwrap(); + buf + } + } + + #[test] + fn test_basic_operations_without_indexes() { + let mut state = RelatedState::new(BTreeMap::new()); + let indexed_map: IndexedMap = IndexedMap::new(vec![&b"users"[..]]); + + let user_id = UserId(1); + let user = User { + id: 1, + name: "Alice".to_string(), + email: "alice@example.com".to_string(), + age: 25, + }; + + // Test initial get returns None + assert_eq!(indexed_map.get(&mut state, &user_id).unwrap(), None); + + // Test set and get + indexed_map.set(&mut state, &user_id, &user).unwrap(); + assert_eq!(indexed_map.get(&mut state, &user_id).unwrap(), Some(user.clone())); + + // Test update + let updated_user = User { + id: 1, + name: "Alice Updated".to_string(), + email: "alice@example.com".to_string(), + age: 26, + }; + indexed_map.set(&mut state, &user_id, &updated_user).unwrap(); + assert_eq!(indexed_map.get(&mut state, &user_id).unwrap(), Some(updated_user)); + + // Test delete + indexed_map.del(&mut state, &user_id).unwrap(); + assert_eq!(indexed_map.get(&mut state, &user_id).unwrap(), None); + } + + #[test] + fn test_with_single_index() { + let mut state = RelatedState::new(BTreeMap::new()); + let mut indexed_map: IndexedMap = IndexedMap::new(vec![&b"users"[..]]); + + // Create an email indexer + let email_indexer = Indexer::::new( + b"email_idx".to_vec(), + |user| &Email(user.email.clone()), + ); + + // Add indexer to the map + indexed_map.indexers.insert("email".to_string(), Box::new(email_indexer)); + + // Create test data + let user1 = User { + id: 1, + name: "Alice".to_string(), + email: "alice@example.com".to_string(), + age: 25, + }; + let user2 = User { + id: 2, + name: "Bob".to_string(), + email: "bob@example.com".to_string(), + age: 30, + }; + + // Insert users + indexed_map.set(&mut state, &UserId(1), &user1).unwrap(); + indexed_map.set(&mut state, &UserId(2), &user2).unwrap(); + + // Verify we can look up by email through the indexer + let email_indexer = Indexer::::new( + b"email_idx".to_vec(), + |user| &Email(user.email.clone()), + ); + + let alice_id = email_indexer.get(&mut state, &Email("alice@example.com".to_string())).unwrap(); + assert_eq!(alice_id, Some(UserId(1))); + + let bob_id = email_indexer.get(&mut state, &Email("bob@example.com".to_string())).unwrap(); + assert_eq!(bob_id, Some(UserId(2))); + } + + #[test] + fn test_index_update() { + let mut state = RelatedState::new(BTreeMap::new()); + let mut indexed_map: IndexedMap = IndexedMap::new(vec![&b"users"[..]]); + + // Create an email indexer + let email_indexer = Indexer::::new( + b"email_idx".to_vec(), + |user| &Email(user.email.clone()), + ); + + // Add indexer to the map + indexed_map.indexers.insert("email".to_string(), Box::new(email_indexer)); + + let user_id = UserId(1); + let user = User { + id: 1, + name: "Alice".to_string(), + email: "alice@example.com".to_string(), + age: 25, + }; + + // Insert user + indexed_map.set(&mut state, &user_id, &user).unwrap(); + + // Update user with new email + let updated_user = User { + id: 1, + name: "Alice".to_string(), + email: "alice.new@example.com".to_string(), + age: 25, + }; + indexed_map.set(&mut state, &user_id, &updated_user).unwrap(); + + // Verify old email index is removed and new one exists + let email_indexer = Indexer::::new( + b"email_idx".to_vec(), + |user| &Email(user.email.clone()), + ); + + let old_email_lookup = email_indexer.get(&mut state, &Email("alice@example.com".to_string())).unwrap(); + assert_eq!(old_email_lookup, None); + + let new_email_lookup = email_indexer.get(&mut state, &Email("alice.new@example.com".to_string())).unwrap(); + assert_eq!(new_email_lookup, Some(UserId(1))); + } + + #[test] + fn test_multiple_indexes() { + let mut state = RelatedState::new(BTreeMap::new()); + let mut indexed_map: IndexedMap = IndexedMap::new(vec![&b"users"[..]]); + + // Create email and age indexers + let email_indexer = Indexer::::new( + b"email_idx".to_vec(), + |user| &Email(user.email.clone()), + ); + let age_indexer = Indexer::::new( + b"age_idx".to_vec(), + |user| &Age(user.age), + ); + + // Add indexers to the map + indexed_map.indexers.insert("email".to_string(), Box::new(email_indexer)); + indexed_map.indexers.insert("age".to_string(), Box::new(age_indexer)); + + // Insert test users + let user1 = User { + id: 1, + name: "Alice".to_string(), + email: "alice@example.com".to_string(), + age: 25, + }; + let user2 = User { + id: 2, + name: "Bob".to_string(), + email: "bob@example.com".to_string(), + age: 25, // Same age as Alice + }; + let user3 = User { + id: 3, + name: "Charlie".to_string(), + email: "charlie@example.com".to_string(), + age: 30, + }; + + indexed_map.set(&mut state, &UserId(1), &user1).unwrap(); + indexed_map.set(&mut state, &UserId(2), &user2).unwrap(); + indexed_map.set(&mut state, &UserId(3), &user3).unwrap(); + + // Verify email index + let email_indexer = Indexer::::new( + b"email_idx".to_vec(), + |user| &Email(user.email.clone()), + ); + assert_eq!(email_indexer.get(&mut state, &Email("alice@example.com".to_string())).unwrap(), Some(UserId(1))); + assert_eq!(email_indexer.get(&mut state, &Email("charlie@example.com".to_string())).unwrap(), Some(UserId(3))); + + // Note: Age index would typically support multiple values per key + // but this simple implementation only supports unique indexes + } + + #[test] + fn test_delete_with_indexes() { + let mut state = RelatedState::new(BTreeMap::new()); + let mut indexed_map: IndexedMap = IndexedMap::new(vec![&b"users"[..]]); + + // Create an email indexer + let email_indexer = Indexer::::new( + b"email_idx".to_vec(), + |user| &Email(user.email.clone()), + ); + + // Add indexer to the map + indexed_map.indexers.insert("email".to_string(), Box::new(email_indexer)); + + let user_id = UserId(1); + let user = User { + id: 1, + name: "Alice".to_string(), + email: "alice@example.com".to_string(), + age: 25, + }; + + // Insert and then delete user + indexed_map.set(&mut state, &user_id, &user).unwrap(); + indexed_map.del(&mut state, &user_id).unwrap(); + + // Verify user is deleted + assert_eq!(indexed_map.get(&mut state, &user_id).unwrap(), None); + + // Verify index is also cleaned up + let email_indexer = Indexer::::new( + b"email_idx".to_vec(), + |user| &Email(user.email.clone()), + ); + let email_lookup = email_indexer.get(&mut state, &Email("alice@example.com".to_string())).unwrap(); + assert_eq!(email_lookup, None); + } + + #[test] + fn test_delete_nonexistent() { + let mut state = RelatedState::new(BTreeMap::new()); + let indexed_map: IndexedMap = IndexedMap::new(vec![&b"users"[..]]); + + // Deleting non-existent key should not error + assert!(indexed_map.del(&mut state, &UserId(999)).is_ok()); + } + + // Test iteration with a key prefix + #[derive(Clone)] + struct UserIdPrefix; + + impl KeyPrefix for UserIdPrefix { + type KeyToExtract = UserId; + + fn to_prefix_bytes(&self) -> Vec { + vec![] + } + } + + #[test] + fn test_iteration() { + let mut state = RelatedState::new(BTreeMap::new()); + let indexed_map: IndexedMap = IndexedMap::new(vec![&b"users"[..]]); + + // Insert multiple users + let users = vec![ + (UserId(1), User { + id: 1, + name: "Alice".to_string(), + email: "alice@example.com".to_string(), + age: 25, + }), + (UserId(2), User { + id: 2, + name: "Bob".to_string(), + email: "bob@example.com".to_string(), + age: 30, + }), + (UserId(3), User { + id: 3, + name: "Charlie".to_string(), + email: "charlie@example.com".to_string(), + age: 35, + }), + ]; + + for (id, user) in &users { + indexed_map.set(&mut state, id, user).unwrap(); + } + + // Collect all items through iteration + let mut collected: Vec<(UserId, User)> = indexed_map.iter(&mut state, UserIdPrefix) + .map(|result| result.unwrap()) + .collect(); + + // Sort by id for consistent comparison + collected.sort_by_key(|(id, _)| id.0); + + assert_eq!(collected.len(), 3); + for (i, (id, user)) in collected.iter().enumerate() { + assert_eq!(id.0, users[i].0.0); + assert_eq!(user, &users[i].1); + } + } + + #[test] + fn test_index_key_collision() { + let mut state = RelatedState::new(BTreeMap::new()); + let mut indexed_map: IndexedMap = IndexedMap::new(vec![&b"users"[..]]); + + // Create an age indexer + let age_indexer = Indexer::::new( + b"age_idx".to_vec(), + |user| &Age(user.age), + ); + + // Add indexer to the map + indexed_map.indexers.insert("age".to_string(), Box::new(age_indexer)); + + // Insert two users with same age + let user1 = User { + id: 1, + name: "Alice".to_string(), + email: "alice@example.com".to_string(), + age: 25, + }; + let user2 = User { + id: 2, + name: "Bob".to_string(), + email: "bob@example.com".to_string(), + age: 25, // Same age + }; + + indexed_map.set(&mut state, &UserId(1), &user1).unwrap(); + // This will overwrite the age index entry + indexed_map.set(&mut state, &UserId(2), &user2).unwrap(); + + // The age index will only point to the last user with that age + let age_indexer = Indexer::::new( + b"age_idx".to_vec(), + |user| &Age(user.age), + ); + let age_lookup = age_indexer.get(&mut state, &Age(25)).unwrap(); + assert_eq!(age_lookup, Some(UserId(2))); // Points to Bob, not Alice + } +} diff --git a/src/utils/item.rs b/src/utils/item.rs index 52024f5..5858181 100644 --- a/src/utils/item.rs +++ b/src/utils/item.rs @@ -91,3 +91,164 @@ impl Item { state.del(&self.key) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::state::RelatedState; + use std::collections::BTreeMap; + use borsh_derive::{BorshSerialize, BorshDeserialize}; + + // Test value type that automatically implements Value trait + #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] + struct TestValue { + id: u32, + name: String, + } + + #[test] + fn test_basic_operations() { + let mut state = RelatedState::new(BTreeMap::new()); + let item: Item = Item::new(&[b"test", b"counter"]); + + // Test initial get returns None + assert_eq!(item.get(&mut state).unwrap(), None); + + // Test set and get + item.set(&mut state, &42).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some(42)); + + // Test overwrite + item.set(&mut state, &100).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some(100)); + + // Test delete + item.del(&mut state).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), None); + } + + #[test] + fn test_complex_value() { + let mut state = RelatedState::new(BTreeMap::new()); + let item: Item = Item::new(&[b"test", b"complex"]); + + let value = TestValue { + id: 1, + name: "Test".to_string(), + }; + + // Test set and get complex value + item.set(&mut state, &value).unwrap(); + let retrieved = item.get(&mut state).unwrap().unwrap(); + assert_eq!(retrieved, value); + + // Test update complex value + let updated_value = TestValue { + id: 2, + name: "Updated".to_string(), + }; + item.set(&mut state, &updated_value).unwrap(); + let retrieved = item.get(&mut state).unwrap().unwrap(); + assert_eq!(retrieved, updated_value); + } + + #[test] + fn test_different_keys() { + let mut state = RelatedState::new(BTreeMap::new()); + let item1: Item = Item::new(&[b"counter1"]); + let item2: Item = Item::new(&[b"counter2"]); + + // Test that different items don't interfere + item1.set(&mut state, &10).unwrap(); + item2.set(&mut state, &20).unwrap(); + + assert_eq!(item1.get(&mut state).unwrap(), Some(10)); + assert_eq!(item2.get(&mut state).unwrap(), Some(20)); + + // Delete one shouldn't affect the other + item1.del(&mut state).unwrap(); + assert_eq!(item1.get(&mut state).unwrap(), None); + assert_eq!(item2.get(&mut state).unwrap(), Some(20)); + } + + #[test] + fn test_string_values() { + let mut state = RelatedState::new(BTreeMap::new()); + let item: Item = Item::new(&[b"test", b"string"]); + + // Test with string values + item.set(&mut state, &"Hello, World!".to_string()).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some("Hello, World!".to_string())); + + // Test with empty string + item.set(&mut state, &"".to_string()).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some("".to_string())); + + // Test with Unicode string + item.set(&mut state, &"🦀 Rust".to_string()).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some("🦀 Rust".to_string())); + } + + #[test] + fn test_delete_nonexistent() { + let mut state = RelatedState::new(BTreeMap::new()); + let item: Item = Item::new(&[b"nonexistent"]); + + // Deleting non-existent item should not error + assert!(item.del(&mut state).is_ok()); + assert_eq!(item.get(&mut state).unwrap(), None); + } + + #[test] + fn test_key_composition() { + let mut state = RelatedState::new(BTreeMap::new()); + + // Test single key component + let item1: Item = Item::new(vec![&b"single"[..]]); + item1.set(&mut state, &1).unwrap(); + assert_eq!(item1.get(&mut state).unwrap(), Some(1)); + + // Test multiple key components + let item2: Item = Item::new(vec![&b"multi"[..], &b"part"[..], &b"key"[..]]); + item2.set(&mut state, &2).unwrap(); + assert_eq!(item2.get(&mut state).unwrap(), Some(2)); + + // Test empty key components are handled + let item3: Item = Item::new(vec![&b"prefix"[..], &b""[..], &b"suffix"[..]]); + item3.set(&mut state, &3).unwrap(); + assert_eq!(item3.get(&mut state).unwrap(), Some(3)); + } + + #[test] + fn test_vec_values() { + let mut state = RelatedState::new(BTreeMap::new()); + let item: Item> = Item::new(&[b"bytes"]); + + // Test with empty vec + item.set(&mut state, &vec![]).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some(vec![])); + + // Test with byte data + let data = vec![1, 2, 3, 4, 5]; + item.set(&mut state, &data).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some(data)); + + // Test with large vec + let large_data = vec![0u8; 1000]; + item.set(&mut state, &large_data).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some(large_data)); + } + + #[test] + fn test_bool_values() { + let mut state = RelatedState::new(BTreeMap::new()); + let item: Item = Item::new(&[b"flag"]); + + // Test boolean values + item.set(&mut state, &true).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some(true)); + + item.set(&mut state, &false).unwrap(); + assert_eq!(item.get(&mut state).unwrap(), Some(false)); + } +} diff --git a/src/utils/key.rs b/src/utils/key.rs index 57fe1d6..4277797 100644 --- a/src/utils/key.rs +++ b/src/utils/key.rs @@ -138,3 +138,11 @@ where buf } } + +impl KeyDeclaration for Vec { + type KeyReference<'a> = &'a [u8]; + + fn to_key_bytes<'a>(key: Self::KeyReference<'a>) -> Vec { + key.to_vec() + } +} diff --git a/src/utils/map.rs b/src/utils/map.rs index 24deb15..dfec8d2 100644 --- a/src/utils/map.rs +++ b/src/utils/map.rs @@ -131,3 +131,218 @@ impl Map { })) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::state::RelatedState; + use std::collections::BTreeMap; + use borsh::{BorshSerialize, BorshDeserialize}; + use borsh_derive::{BorshSerialize as BorshSerializeDerive, BorshDeserialize as BorshDeserializeDerive}; + + // Test key type + #[derive(Debug, Clone, PartialEq, BorshSerializeDerive, BorshDeserializeDerive)] + struct TestKey { + id: u32, + } + + impl KeyDeclaration for TestKey { + type KeyReference<'a> = &'a TestKey; + + fn to_key_bytes(key: &TestKey) -> Vec { + let mut buf = Vec::new(); + key.serialize(&mut buf).unwrap(); + buf + } + } + + // Test value type - automatically implements Value trait + #[derive(Debug, Clone, PartialEq, BorshSerializeDerive, BorshDeserializeDerive)] + struct TestValue { + name: String, + amount: u64, + } + + #[test] + fn test_basic_operations() { + let mut state = RelatedState::new(BTreeMap::new()); + let map: Map = Map::new(vec![&b"test"[..], &b"map"[..]]); + + let key1 = TestKey { id: 1 }; + let key2 = TestKey { id: 2 }; + + // Test initial get returns None + assert_eq!(map.get(&mut state, &key1).unwrap(), None); + assert_eq!(map.get(&mut state, &key2).unwrap(), None); + + // Test set and get + map.set(&mut state, &key1, &100).unwrap(); + map.set(&mut state, &key2, &200).unwrap(); + assert_eq!(map.get(&mut state, &key1).unwrap(), Some(100)); + assert_eq!(map.get(&mut state, &key2).unwrap(), Some(200)); + + // Test overwrite + map.set(&mut state, &key1, &150).unwrap(); + assert_eq!(map.get(&mut state, &key1).unwrap(), Some(150)); + assert_eq!(map.get(&mut state, &key2).unwrap(), Some(200)); // key2 unchanged + + // Test delete + map.del(&mut state, &key1).unwrap(); + assert_eq!(map.get(&mut state, &key1).unwrap(), None); + assert_eq!(map.get(&mut state, &key2).unwrap(), Some(200)); // key2 still exists + } + + #[test] + fn test_complex_values() { + let mut state = RelatedState::new(BTreeMap::new()); + let map: Map = Map::new(vec![&b"complex"[..]]); + + let key = TestKey { id: 42 }; + let value = TestValue { + name: "Alice".to_string(), + amount: 1000, + }; + + // Test set and get complex value + map.set(&mut state, &key, &value).unwrap(); + let retrieved = map.get(&mut state, &key).unwrap().unwrap(); + assert_eq!(retrieved, value); + + // Test update complex value + let updated_value = TestValue { + name: "Bob".to_string(), + amount: 2000, + }; + map.set(&mut state, &key, &updated_value).unwrap(); + let retrieved = map.get(&mut state, &key).unwrap().unwrap(); + assert_eq!(retrieved, updated_value); + } + + #[test] + fn test_string_keys() { + let mut state = RelatedState::new(BTreeMap::new()); + let map: Map = Map::new(vec![&b"string_map"[..]]); + + // Test with string keys + map.set(&mut state, &"key1".to_string(), &10).unwrap(); + map.set(&mut state, &"key2".to_string(), &20).unwrap(); + + assert_eq!(map.get(&mut state, &"key1".to_string()).unwrap(), Some(10)); + assert_eq!(map.get(&mut state, &"key2".to_string()).unwrap(), Some(20)); + assert_eq!(map.get(&mut state, &"key3".to_string()).unwrap(), None); + + // Test with empty string key + map.set(&mut state, &"".to_string(), &30).unwrap(); + assert_eq!(map.get(&mut state, &"".to_string()).unwrap(), Some(30)); + } + + #[test] + fn test_delete_nonexistent() { + let mut state = RelatedState::new(BTreeMap::new()); + let map: Map = Map::new(vec![&b"test_del"[..]]); + + let key = TestKey { id: 999 }; + + // Deleting non-existent key should not error + assert!(map.del(&mut state, &key).is_ok()); + assert_eq!(map.get(&mut state, &key).unwrap(), None); + } + + #[test] + fn test_multiple_maps_isolation() { + let mut state = RelatedState::new(BTreeMap::new()); + let map1: Map = Map::new(vec![&b"map1"[..]]); + let map2: Map = Map::new(vec![&b"map2"[..]]); + + let key = TestKey { id: 1 }; + + // Set same key in different maps + map1.set(&mut state, &key, &100).unwrap(); + map2.set(&mut state, &key, &200).unwrap(); + + // Verify they store different values + assert_eq!(map1.get(&mut state, &key).unwrap(), Some(100)); + assert_eq!(map2.get(&mut state, &key).unwrap(), Some(200)); + + // Delete from one map shouldn't affect the other + map1.del(&mut state, &key).unwrap(); + assert_eq!(map1.get(&mut state, &key).unwrap(), None); + assert_eq!(map2.get(&mut state, &key).unwrap(), Some(200)); + } + + // Test key prefix implementation for iteration + #[derive(Clone)] + struct TestKeyPrefix; + + impl KeyPrefix for TestKeyPrefix { + type KeyToExtract = TestKey; + + fn to_prefix_bytes(&self) -> Vec { + vec![] + } + } + + #[test] + fn test_iteration() { + let mut state = RelatedState::new(BTreeMap::new()); + let map: Map = Map::new(vec![&b"iter_test"[..]]); + + // Insert multiple values + let keys_values = vec![ + (TestKey { id: 1 }, 100), + (TestKey { id: 2 }, 200), + (TestKey { id: 3 }, 300), + ]; + + for (key, value) in &keys_values { + map.set(&mut state, key, value).unwrap(); + } + + // Collect all items through iteration + let mut collected: Vec<(TestKey, u64)> = map.iter(&mut state, TestKeyPrefix) + .map(|result| result.unwrap()) + .collect(); + + // Sort by id for consistent comparison + collected.sort_by_key(|(k, _)| k.id); + + assert_eq!(collected.len(), 3); + for (i, (key, value)) in collected.iter().enumerate() { + assert_eq!(key.id, keys_values[i].0.id); + assert_eq!(*value, keys_values[i].1); + } + } + + #[test] + fn test_vec_keys() { + let mut state = RelatedState::new(BTreeMap::new()); + let map: Map, String> = Map::new(vec![&b"vec_key_map"[..]]); + + // Test with byte vector keys + let key1 = vec![1, 2, 3]; + let key2 = vec![4, 5, 6]; + let key3 = vec![]; // empty vec + + map.set(&mut state, &key1, &"value1".to_string()).unwrap(); + map.set(&mut state, &key2, &"value2".to_string()).unwrap(); + map.set(&mut state, &key3, &"empty_key".to_string()).unwrap(); + + assert_eq!(map.get(&mut state, &key1).unwrap(), Some("value1".to_string())); + assert_eq!(map.get(&mut state, &key2).unwrap(), Some("value2".to_string())); + assert_eq!(map.get(&mut state, &key3).unwrap(), Some("empty_key".to_string())); + } + + #[test] + fn test_large_values() { + let mut state = RelatedState::new(BTreeMap::new()); + let map: Map> = Map::new(vec![&b"large_values"[..]]); + + // Test with large value + let large_value = vec![0u8; 10000]; + map.set(&mut state, 1u32, &large_value).unwrap(); + + let retrieved = map.get(&mut state, 1u32).unwrap().unwrap(); + assert_eq!(retrieved.len(), 10000); + assert_eq!(retrieved, large_value); + } +} From f345fb327ec46ec47113aa300f421323f2c0f176 Mon Sep 17 00:00:00 2001 From: KIMURA Yu <33382781+kimurayu45z@users.noreply.github.com> Date: Sun, 25 May 2025 12:07:46 +0900 Subject: [PATCH 2/6] timestamp --- src/types/timestamp.rs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/types/timestamp.rs diff --git a/src/types/timestamp.rs b/src/types/timestamp.rs new file mode 100644 index 0000000..e3cbe7b --- /dev/null +++ b/src/types/timestamp.rs @@ -0,0 +1,5 @@ +use borsh_derive::{BorshDeserialize, BorshSerialize}; + +/// Unix seconds +#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct Timestamp(u64); From 98a234b59eb95dfd51adda92c7237aa8691e3656 Mon Sep 17 00:00:00 2001 From: KIMURA Yu <33382781+kimurayu45z@users.noreply.github.com> Date: Sun, 25 May 2025 13:15:56 +0900 Subject: [PATCH 3/6] timestamp --- src/core/block/block.rs | 7 ++-- src/types/environment.rs | 20 ++++++----- src/types/mod.rs | 2 ++ src/types/timestamp.rs | 77 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/core/block/block.rs b/src/core/block/block.rs index cee12b0..5ea4c31 100644 --- a/src/core/block/block.rs +++ b/src/core/block/block.rs @@ -1,4 +1,7 @@ -use crate::sha2::{Digest, Sha256}; +use crate::{ + sha2::{Digest, Sha256}, + types::Timestamp, +}; use borsh_derive::{BorshDeserialize, BorshSerialize}; /// `Header` is the struct for block headers. @@ -6,7 +9,7 @@ use borsh_derive::{BorshDeserialize, BorshSerialize}; pub struct Header { pub chain_id: u64, pub height: u64, - pub time: u64, + pub time: Timestamp, pub header_hash_prev: [u8; 32], diff --git a/src/types/environment.rs b/src/types/environment.rs index 69c8393..4b9f173 100644 --- a/src/types/environment.rs +++ b/src/types/environment.rs @@ -1,5 +1,7 @@ use borsh_derive::{BorshDeserialize, BorshSerialize}; +use super::Timestamp; + /// Represents the blockchain environment context for transaction execution. /// This struct contains information about the current chain state and block. /// @@ -10,8 +12,8 @@ pub struct Environment { pub chain_id: String, /// The current block height (block number) pub block_height: u64, - /// The timestamp of the current block in seconds since Unix epoch - pub block_time: u64, + /// The timestamp of the current block + pub block_time: Timestamp, } impl Environment { @@ -37,7 +39,7 @@ mod tests { #[test] fn test_new_environment() { let env = Environment::new("testnet-1".to_string(), 12345, 1640000000); - + assert_eq!(env.chain_id, "testnet-1"); assert_eq!(env.block_height, 12345); assert_eq!(env.block_time, 1640000000); @@ -47,7 +49,7 @@ mod tests { fn test_environment_clone() { let env1 = Environment::new("mainnet".to_string(), 1000, 1234567890); let env2 = env1.clone(); - + assert_eq!(env1, env2); assert_eq!(env2.chain_id, "mainnet"); assert_eq!(env2.block_height, 1000); @@ -58,7 +60,7 @@ mod tests { fn test_environment_debug() { let env = Environment::new("test".to_string(), 1, 1); let debug_str = format!("{:?}", env); - + assert!(debug_str.contains("Environment")); assert!(debug_str.contains("test")); assert!(debug_str.contains("1")); @@ -71,7 +73,7 @@ mod tests { let env3 = Environment::new("chain2".to_string(), 100, 1000); let env4 = Environment::new("chain1".to_string(), 101, 1000); let env5 = Environment::new("chain1".to_string(), 100, 1001); - + assert_eq!(env1, env2); assert_ne!(env1, env3); // Different chain_id assert_ne!(env1, env4); // Different block_height @@ -85,7 +87,7 @@ mod tests { assert_eq!(env1.chain_id, ""); assert_eq!(env1.block_height, 0); assert_eq!(env1.block_time, 0); - + // Test with max values let env2 = Environment::new("max".to_string(), u64::MAX, u64::MAX); assert_eq!(env2.block_height, u64::MAX); @@ -95,11 +97,11 @@ mod tests { #[test] fn test_environment_serialization() { let env = Environment::new("test-chain".to_string(), 42, 1234567890); - + // Test Borsh serialization roundtrip let serialized = borsh::to_vec(&env).unwrap(); let deserialized: Environment = borsh::from_slice(&serialized).unwrap(); - + assert_eq!(env, deserialized); } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 45c6afc..69b85fb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -5,8 +5,10 @@ mod environment; mod error; mod token; mod tokens; +mod timestamp; mod uint; +pub use timestamp::*; pub use address::*; pub use any::*; pub use decimal::*; diff --git a/src/types/timestamp.rs b/src/types/timestamp.rs index e3cbe7b..66ca5b0 100644 --- a/src/types/timestamp.rs +++ b/src/types/timestamp.rs @@ -1,5 +1,82 @@ +use std::ops::{Add, Sub}; +use std::time::Duration; + use borsh_derive::{BorshDeserialize, BorshSerialize}; /// Unix seconds #[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Timestamp(u64); + +impl Timestamp { + pub fn new(unix_seconds: u64) -> Self { + Self(unix_seconds) + } + + pub fn as_secs(&self) -> u64 { + self.0 + } +} + +impl Add for Timestamp { + type Output = Self; + + fn add(self, duration: Duration) -> Self::Output { + Self(self.0 + duration.as_secs()) + } +} + +impl Add for &Timestamp { + type Output = Timestamp; + + fn add(self, duration: Duration) -> Self::Output { + Timestamp(self.0 + duration.as_secs()) + } +} + +impl Sub for Timestamp { + type Output = Self; + + fn sub(self, duration: Duration) -> Self::Output { + Self(self.0.saturating_sub(duration.as_secs())) + } +} + +impl Sub for &Timestamp { + type Output = Timestamp; + + fn sub(self, duration: Duration) -> Self::Output { + Timestamp(self.0.saturating_sub(duration.as_secs())) + } +} + +impl Sub for Timestamp { + type Output = Duration; + + fn sub(self, other: Timestamp) -> Self::Output { + Duration::from_secs(self.0.saturating_sub(other.0)) + } +} + +impl Sub<&Timestamp> for Timestamp { + type Output = Duration; + + fn sub(self, other: &Timestamp) -> Self::Output { + Duration::from_secs(self.0.saturating_sub(other.0)) + } +} + +impl Sub for &Timestamp { + type Output = Duration; + + fn sub(self, other: Timestamp) -> Self::Output { + Duration::from_secs(self.0.saturating_sub(other.0)) + } +} + +impl Sub<&Timestamp> for &Timestamp { + type Output = Duration; + + fn sub(self, other: &Timestamp) -> Self::Output { + Duration::from_secs(self.0.saturating_sub(other.0)) + } +} From 87924f2cf9eeb9019c58893eda6f49d76c3a7504 Mon Sep 17 00:00:00 2001 From: KIMURA Yu <33382781+kimurayu45z@users.noreply.github.com> Date: Sun, 25 May 2025 13:17:38 +0900 Subject: [PATCH 4/6] timestamp --- src/types/environment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/environment.rs b/src/types/environment.rs index 4b9f173..411ed8d 100644 --- a/src/types/environment.rs +++ b/src/types/environment.rs @@ -23,7 +23,7 @@ impl Environment { /// * `chain_id` - The unique identifier of the blockchain /// * `block_height` - The current block height /// * `block_time` - The timestamp of the block in seconds since Unix epoch - pub fn new(chain_id: String, block_height: u64, block_time: u64) -> Self { + pub fn new(chain_id: String, block_height: u64, block_time: Timestamp) -> Self { Self { chain_id, block_height, From 2414c78870d4b53f4f8e88071a7d0d7eeeb1a9fa Mon Sep 17 00:00:00 2001 From: KIMURA Yu <33382781+kimurayu45z@users.noreply.github.com> Date: Sun, 25 May 2025 13:24:07 +0900 Subject: [PATCH 5/6] timestamp --- src/runner/savedata.rs | 15 +++++++++------ src/runner/sequencer.rs | 18 +++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/runner/savedata.rs b/src/runner/savedata.rs index 644f3b5..8f9b000 100644 --- a/src/runner/savedata.rs +++ b/src/runner/savedata.rs @@ -1,6 +1,9 @@ use borsh_derive::{BorshDeserialize, BorshSerialize}; -use crate::state::{AccumulatedLogs, StateLog}; +use crate::{ + state::{AccumulatedLogs, StateLog}, + types::Timestamp, +}; /// Represents a snapshot of state changes after executing a transaction. /// Contains both the individual state logs and accumulated logs for verification. @@ -12,7 +15,7 @@ pub struct TxExecutionSnapshot { impl TxExecutionSnapshot { /// Creates a new TxExecutionSnapshot instance. - /// + /// /// # Arguments /// * `logs` - List of individual state changes made by the transaction /// * `accum_logs` - Accumulated state logs for merkle proof generation @@ -28,7 +31,7 @@ impl TxExecutionSnapshot { pub struct SaveData { pub chain_id: String, pub block_height: u64, - pub block_time_unix_secs: u64, + pub block_time: Timestamp, pub state_sparse_tree_root: [u8; 32], pub keys_patricia_trie_root: [u8; 32], pub tx_snapshots: Vec, @@ -36,7 +39,7 @@ pub struct SaveData { impl SaveData { /// Creates a new SaveData instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the block @@ -47,7 +50,7 @@ impl SaveData { pub fn new( chain_id: String, block_height: u64, - block_time_unix_secs: u64, + block_time: Timestamp, state_sparse_tree_root: [u8; 32], keys_patricia_trie_root: [u8; 32], tx_snapshots: Vec, @@ -55,7 +58,7 @@ impl SaveData { Self { chain_id, block_height, - block_time_unix_secs, + block_time, state_sparse_tree_root, keys_patricia_trie_root, tx_snapshots, diff --git a/src/runner/sequencer.rs b/src/runner/sequencer.rs index 28a9885..257948d 100644 --- a/src/runner/sequencer.rs +++ b/src/runner/sequencer.rs @@ -30,7 +30,7 @@ pub struct SequencerState { impl SequencerState { /// Creates a new SequencerState instance. - /// + /// /// # Arguments /// * `app` - The application instance containing business logic /// * `savedata` - Persistent storage for blockchain data @@ -60,7 +60,7 @@ impl Clone for SequencerState { /// The Sequencer component responsible for processing transactions. /// It executes transactions, updates state, and prepares witness data for proof generation. -/// +/// /// # Type Parameters /// * `TX` - Transaction type that implements the Tx trait /// * `S` - State manager type that implements the StateManager trait @@ -72,7 +72,7 @@ pub struct Sequencer { impl Sequencer { /// Creates a new Sequencer instance. - /// + /// /// # Arguments /// * `state` - The sequencer state containing app, storage, and state manager /// * `sender` - Channel sender for broadcasting messages to other components @@ -90,9 +90,9 @@ impl Sequencer { } /// Runs the sequencer's main event loop. - /// + /// /// Listens for incoming messages and processes transactions when received. - /// + /// /// # Returns /// * `Ok(())` - If the sequencer runs successfully /// * `Err(InterLiquidSdkError)` - If an error occurs during processing @@ -112,17 +112,17 @@ impl Sequencer { } /// Handles a received transaction by executing it and generating witness data. - /// + /// /// This method: /// 1. Executes the transaction against the current state /// 2. Collects state changes and logs /// 3. Generates witness data for proof generation /// 4. Updates the savedata with the execution snapshot /// 5. Sends a message that the transaction is ready for proving - /// + /// /// # Arguments /// * `tx` - The serialized transaction data to process - /// + /// /// # Returns /// * `Ok(())` - If the transaction is processed successfully /// * `Err(InterLiquidSdkError)` - If an error occurs during execution @@ -149,7 +149,7 @@ impl Sequencer { let env = Environment::new( savedata.chain_id.clone(), savedata.block_height, - savedata.block_time_unix_secs, + savedata.block_time, ); let mut ctx = SdkContext::new(env, &mut transactional); From da5b7004cdfb5d724936a7aaefed397596be4128 Mon Sep 17 00:00:00 2001 From: KIMURA Yu <33382781+kimurayu45z@users.noreply.github.com> Date: Sun, 25 May 2025 13:28:23 +0900 Subject: [PATCH 6/6] timestamp --- examples/basic_usage.rs | 2 +- src/runner/message.rs | 30 +++++++++++++++--------------- src/runner/sequencer.rs | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/basic_usage.rs b/examples/basic_usage.rs index 55261ef..d9abf06 100644 --- a/examples/basic_usage.rs +++ b/examples/basic_usage.rs @@ -111,7 +111,7 @@ async fn main() -> Result<(), InterLiquidSdkError> { let savedata = SaveData { chain_id: "test-chain".to_string(), block_height: 1, - block_time_unix_secs: 0, + block_time: 0, state_sparse_tree_root: [0; 32], keys_patricia_trie_root: [0; 32], tx_snapshots: vec![], diff --git a/src/runner/message.rs b/src/runner/message.rs index 3f8dd81..6b06af5 100644 --- a/src/runner/message.rs +++ b/src/runner/message.rs @@ -1,6 +1,6 @@ use borsh_derive::{BorshDeserialize, BorshSerialize}; -use crate::zkp::WitnessTx; +use crate::{types::Timestamp, zkp::WitnessTx}; /// Represents all possible message types that can be sent between components in the runner system. /// These messages coordinate the transaction processing and proof generation pipeline. @@ -27,7 +27,7 @@ pub struct MessageTxReceived { impl MessageTxReceived { /// Creates a new MessageTxReceived instance. - /// + /// /// # Arguments /// * `tx` - The serialized transaction data pub fn new(tx: Vec) -> Self { @@ -41,31 +41,31 @@ impl MessageTxReceived { pub struct MessageTxProofReady { pub chain_id: String, pub block_height: u64, - pub block_time_unix_secs: u64, + pub block_time: Timestamp, pub tx_index: usize, pub witness: WitnessTx, } impl MessageTxProofReady { /// Creates a new MessageTxProofReady instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the block containing the transaction - /// * `block_time_unix_secs` - The Unix timestamp of the block + /// * `block_time` - The Unix timestamp of the block /// * `tx_index` - The index of the transaction within the block /// * `witness` - The witness data needed for proving pub fn new( chain_id: String, block_height: u64, - block_time_unix_secs: u64, + block_time: Timestamp, tx_index: usize, witness: WitnessTx, ) -> Self { Self { chain_id, block_height, - block_time_unix_secs, + block_time, tx_index, witness, } @@ -92,7 +92,7 @@ pub struct MessageCommitStateProofReady { impl MessageCommitStateProofReady { /// Creates a new MessageCommitStateProofReady instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the block @@ -117,7 +117,7 @@ pub struct MessageCommitKeysProofReady { impl MessageCommitKeysProofReady { /// Creates a new MessageCommitKeysProofReady instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the block @@ -140,7 +140,7 @@ pub struct MessageBlockCommitted { impl MessageBlockCommitted { /// Creates a new MessageBlockCommitted instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the committed block @@ -164,7 +164,7 @@ pub struct MessageTxProved { impl MessageTxProved { /// Creates a new MessageTxProved instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the block containing the transaction @@ -192,7 +192,7 @@ pub struct MessageTxProofAggregated { impl MessageTxProofAggregated { /// Creates a new MessageTxProofAggregated instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the block @@ -223,7 +223,7 @@ pub struct MessageCommitStateProved { impl MessageCommitStateProved { /// Creates a new MessageCommitStateProved instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the block @@ -247,7 +247,7 @@ pub struct MessageCommitKeysProved { impl MessageCommitKeysProved { /// Creates a new MessageCommitKeysProved instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the block @@ -272,7 +272,7 @@ pub struct MessageBlockProved { impl MessageBlockProved { /// Creates a new MessageBlockProved instance. - /// + /// /// # Arguments /// * `chain_id` - The identifier of the blockchain /// * `block_height` - The height of the block diff --git a/src/runner/sequencer.rs b/src/runner/sequencer.rs index 257948d..d6a618e 100644 --- a/src/runner/sequencer.rs +++ b/src/runner/sequencer.rs @@ -179,7 +179,7 @@ impl Sequencer { .send(RunnerMessage::TxProofReady(MessageTxProofReady::new( savedata.chain_id.clone(), savedata.block_height, - savedata.block_time_unix_secs, + savedata.block_time, savedata.tx_snapshots.len() - 1, witness, )))