use c32address::*;
use std::fs;
use rayon::prelude::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Instant;
use env_logger;
fn setup() {
let _ = env_logger::builder()
.is_test(true)
.try_init();
}
const TEST_HEX_STRINGS: &[&str] = &[
"a46ff88886c2ef9762d970b4d2c63678835bd39d",
"0000000000000000000000000000000000000000",
"0000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000000",
];
const TEST_VERSIONS: &[u8] = &[22, 0, 31, 20, 26, 21];
const TEST_C32_ADDRESSES: &[&[&str]] = &[
&[
"SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7",
"SP000000000000000000002Q6VF78",
"SP00000000000000000005JA84HQ",
"SP80000000000000000000000000000004R0CMNV",
"SP800000000000000000000000000000033H8YKK",
],
&[
"S02J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKPVKG2CE",
"S0000000000000000000002AA028H",
"S000000000000000000006EKBDDS",
"S080000000000000000000000000000007R1QC00",
"S080000000000000000000000000000003ENTGCQ",
],
&[
"SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR",
"SZ000000000000000000002ZE1VMN",
"SZ00000000000000000005HZ3DVN",
"SZ80000000000000000000000000000004XBV6MS",
"SZ800000000000000000000000000000007VF5G0",
],
&[
"SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G",
"SM0000000000000000000062QV6X",
"SM00000000000000000005VR75B2",
"SM80000000000000000000000000000004WBEWKC",
"SM80000000000000000000000000000000JGSYGV",
],
&[
"ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0RQ",
"ST000000000000000000002AMW42H",
"ST000000000000000000042DB08Y",
"ST80000000000000000000000000000006BYJ4R4",
"ST80000000000000000000000000000002YBNPV3",
],
&[
"SN2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKP6D2ZK9",
"SN000000000000000000003YDHWKJ",
"SN00000000000000000005341MC8",
"SN800000000000000000000000000000066KZWY0",
"SN800000000000000000000000000000006H75AK",
],
];
#[test]
fn test_c32address_encode_decode() {
setup();
for hex_str in TEST_HEX_STRINGS {
if hex_str.is_empty() {
continue;
}
let bytes = hex::decode(hex_str).unwrap();
let encoded = c32::encode(&bytes);
let decoded = c32::decode(&encoded).expect("Failed to decode c32 string");
assert_eq!(
decoded, bytes,
"c32_decode(c32_encode({})): expected {}, got {}",
hex_str,
hex::encode(&bytes),
hex::encode(&decoded)
);
}
}
#[test]
fn test_c32check_encode_decode() {
setup();
for (_i, hex_str) in TEST_HEX_STRINGS.iter().enumerate() {
if hex_str.is_empty() {
continue;
}
for &version in TEST_VERSIONS {
let bytes = hex::decode(hex_str).unwrap();
let encoded = c32::encode_check(&bytes, version)
.expect("Failed to encode");
let (decoded_version, decoded_bytes) = c32::decode_check(&encoded)
.expect("Failed to decode");
assert_eq!(
decoded_version, version,
"c32::encode_check version: expected {}, got {}",
version, decoded_version
);
assert_eq!(
decoded_bytes, bytes,
"c32::decode_check bytes: expected {}, got {}",
hex::encode(&bytes),
hex::encode(&decoded_bytes)
);
}
}
}
#[test]
fn test_encode_address() {
setup();
for (i, hex_str) in TEST_HEX_STRINGS.iter().enumerate() {
if hex_str.is_empty() {
continue;
}
for (j, &version) in TEST_VERSIONS.iter().enumerate() {
let bytes = hex::decode(hex_str).unwrap();
let address = encode_address(version, &bytes).expect("Failed to encode address");
assert_eq!(
address,
TEST_C32_ADDRESSES[j][i],
"encode_address version={} {}: expected {}, got {}",
version,
hex_str,
TEST_C32_ADDRESSES[j][i],
address
);
let (decoded_version, decoded_bytes) = decode_address(&address)
.expect("Failed to decode address");
assert_eq!(
decoded_version, version,
"decode_address {}: expected version {}, got {}",
address, version, decoded_version
);
let expected_hex = if hex_str.len() % 2 != 0 {
format!("0{}", hex_str)
} else {
hex_str.to_string()
};
let decoded_hex = hex::encode(&decoded_bytes);
assert_eq!(
decoded_hex,
expected_hex,
"decode_address {}: expected hex {}, got {}",
address,
expected_hex,
decoded_hex
);
}
}
}
#[test]
fn test_invalid_version() {
setup();
let hex_str = "a46ff88886c2ef9762d970b4d2c63678835bd39d";
let bytes = hex::decode(hex_str).unwrap();
let invalid_versions = [-1i8 as u8, 32];
for &version in &invalid_versions {
let result = encode_address(version, &bytes);
assert!(
matches!(result, Err(AddressError::InvalidVersion(_))),
"encode_address with invalid version {} should return InvalidVersion error",
version
);
}
}
#[test]
fn test_invalid_length() {
setup();
let invalid_hex = [
"a46ff88886c2ef9762d970b4d2c63678835bd39d00", "a46ff88886c2ef9762d970b4d2c63678835bd3", ];
for hex_str in &invalid_hex {
let bytes = hex::decode(hex_str).unwrap();
let result = encode_address(22, &bytes);
assert!(
matches!(result, Err(AddressError::InvalidLength(_))),
"encode_address with invalid length should return InvalidLength error"
);
}
}
#[test]
fn test_decode_invalid_address() {
setup();
let invalid_addresses = [
"ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0RQ0", "ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0R", "ST2J", "XP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7", ];
for address in &invalid_addresses {
let result = decode_address(address);
assert!(
matches!(result,
Err(AddressError::InvalidFormat(_))
),
"decode_address with invalid address {} should return InvalidFormat, got {:?}",
address,
result
);
}
}
#[test]
fn test_simple() {
setup();
let hex_strings = &[
"a46ff88886c2ef9762d970b4d2c63678835bd39d",
"",
"0000000000000000000000000000000000000000",
"0000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000000",
"01",
"22",
"0001",
"000001",
"00000001",
"10",
"0100",
"1000",
"010000",
"100000",
"01000000",
"10000000",
"0100000000",
];
let c32_strs = [
"MHQZH246RBQSERPSE2TD5HHPF21NQMWX",
"",
"00000000000000000000",
"00000000000000000001",
"20000000000000000000000000000001",
"20000000000000000000000000000000",
"1",
"12",
"01",
"001",
"0001",
"G",
"80",
"400",
"2000",
"10000",
"G0000",
"800000",
"4000000",
];
let results: Vec<_> = hex_strings
.iter()
.zip(c32_strs.iter())
.map(|(hex_str, expected)| {
let bytes = hex::decode(hex_str).unwrap();
let c32_encoded = c32::encode(&bytes);
let decoded_bytes = c32::decode(&c32_encoded).unwrap();
let result = (bytes, c32_encoded, decoded_bytes, expected);
println!("{:?}", result);
result
})
.collect();
for (bytes, c32_encoded, decoded_bytes, expected_c32) in results.iter() {
assert_eq!(bytes, decoded_bytes);
assert_eq!(c32_encoded, *expected_c32);
}
}
#[test]
fn test_addresses() {
setup();
let hex_strs = [
"a46ff88886c2ef9762d970b4d2c63678835bd39d",
"0000000000000000000000000000000000000000",
"0000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000000",
];
let versions = [22, 0, 31, 20, 26, 21];
let c32_addrs = [
[
"SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7",
"SP000000000000000000002Q6VF78",
"SP00000000000000000005JA84HQ",
"SP80000000000000000000000000000004R0CMNV",
"SP800000000000000000000000000000033H8YKK",
],
[
"S02J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKPVKG2CE",
"S0000000000000000000002AA028H",
"S000000000000000000006EKBDDS",
"S080000000000000000000000000000007R1QC00",
"S080000000000000000000000000000003ENTGCQ",
],
[
"SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR",
"SZ000000000000000000002ZE1VMN",
"SZ00000000000000000005HZ3DVN",
"SZ80000000000000000000000000000004XBV6MS",
"SZ800000000000000000000000000000007VF5G0",
],
[
"SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G",
"SM0000000000000000000062QV6X",
"SM00000000000000000005VR75B2",
"SM80000000000000000000000000000004WBEWKC",
"SM80000000000000000000000000000000JGSYGV",
],
[
"ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0RQ",
"ST000000000000000000002AMW42H",
"ST000000000000000000042DB08Y",
"ST80000000000000000000000000000006BYJ4R4",
"ST80000000000000000000000000000002YBNPV3",
],
[
"SN2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKP6D2ZK9",
"SN000000000000000000003YDHWKJ",
"SN00000000000000000005341MC8",
"SN800000000000000000000000000000066KZWY0",
"SN800000000000000000000000000000006H75AK",
],
];
for (i, h) in hex_strs.iter().enumerate() {
for (j, v) in versions.iter().enumerate() {
let b = hex::decode(h).unwrap();
let z = encode_address(*v, &b).unwrap();
assert_eq!(z, c32_addrs[j][i]);
let (decoded_version, decoded_bytes) = decode_address(&z).unwrap();
assert_eq!(decoded_version, *v);
assert_eq!(decoded_bytes, b);
}
}
}
#[test]
fn test_random_c32_encoding() {
setup();
let run_full_test = std::env::var("FULL_TEST").is_ok();
println!("\nRunning c32 encoding/decoding verification test");
println!("----------------------------------------------");
let data = fs::read_to_string("tests/random-data.json")
.expect("Failed to read random-data.json");
let test_data: Vec<serde_json::Value> = serde_json::from_str(&data)
.expect("Failed to parse random-data.json");
let num_cases = if run_full_test {
println!("FULL_TEST flag detected - running all available test cases");
println!("Note: Full test takes ~90-100 seconds on a MacBook Pro M3 Max using parallel execution");
test_data.len()
} else {
println!("Running first 3000 test cases in parallel (fast mode, ~1 second on MacBook Pro M3 Max)");
println!("To run all test cases, use:");
println!(" FULL_TEST=1 cargo test test_random_c32_encoding");
std::cmp::min(3000, test_data.len())
};
let test_subset: Vec<_> = test_data.iter().take(num_cases).collect();
println!("\nTesting {} out of {} total test cases", test_subset.len(), test_data.len());
let processed = Arc::new(AtomicUsize::new(0));
let total_cases = test_subset.len();
let start_time = Instant::now();
let processed_clone = Arc::clone(&processed);
let progress_thread = std::thread::spawn(move || {
while processed_clone.load(Ordering::Relaxed) < total_cases {
let current = processed_clone.load(Ordering::Relaxed);
let elapsed = start_time.elapsed();
println!(
"Progress: {}/{} ({:.1}%) - {:.2}s elapsed",
current,
total_cases,
(current as f64 / total_cases as f64) * 100.0,
elapsed.as_secs_f64()
);
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
test_subset.par_iter().try_for_each(|test_case| -> Result<(), String> {
let expected_hex = test_case["hex"].as_str()
.ok_or("Test case missing hex field")?;
let expected_c32 = test_case["c32"].as_str()
.ok_or("Test case missing c32 field")?;
let bytes = hex::decode(expected_hex).map_err(|e| format!("Failed to decode hex: {}", e))?;
let actual_c32 = c32::encode(&bytes);
if actual_c32.len() == expected_c32.len() + 1 && actual_c32.starts_with('0') {
let actual_without_zero = &actual_c32[1..];
if actual_without_zero != expected_c32 {
return Err(format!(
"c32encode failed - hex {} - expected {}, got {} (without leading zero: {})",
expected_hex, expected_c32, actual_c32, actual_without_zero
));
}
} else if actual_c32 != expected_c32 {
return Err(format!(
"c32encode failed - hex {} - expected {}, got {}",
expected_hex, expected_c32, actual_c32
));
}
let expected_bytes_len = expected_hex.len() / 2;
let decoded_bytes = match c32::decode(expected_c32) {
Ok(mut bytes) => {
while bytes.len() < expected_bytes_len {
bytes.insert(0, 0);
}
bytes
},
Err(e) => return Err(format!("Failed to decode c32: {}", e))
};
let actual_hex = hex::encode(&decoded_bytes);
if actual_hex != expected_hex {
return Err(format!(
"c32decode failed - c32 {} - expected {}, got {} (decoded bytes len: {}, expected len: {})",
expected_c32, expected_hex, actual_hex, decoded_bytes.len(), expected_bytes_len
));
}
processed.fetch_add(1, Ordering::Relaxed);
Ok(())
}).expect("Test failed");
progress_thread.join().unwrap();
let total_time = start_time.elapsed();
println!("\nAll {} test cases passed in {:.2}s!", total_cases, total_time.as_secs_f64());
if !run_full_test {
println!("\nNote: Only ran the first 3000 test cases");
println!("To run all test cases (~90-100 seconds on MacBook Pro M3 Max), use:");
println!(" FULL_TEST=1 cargo test test_random_c32_encoding");
}
}
#[test]
fn test_random_c32_encoding_info() {
setup();
println!("\nNote: The random c32 encoding test runs 3000 test cases by default in parallel (~1 second)");
println!("To run all test cases (~90-100 seconds on MacBook Pro M3 Max), use:");
println!(" FULL_TEST=1 cargo test test_random_c32_encoding\n");
}