use sp_runtime::traits::UniqueSaturatedInto; macro_rules! generate_perfect_slot_mapper { ( struct_name: $struct_name:ident, inner_type: $inner_type:ty, init_rng: $init_rng:expr, golden_ratio: $golden_ratio:expr, lcg_multiplier: $lcg_multiplier:expr, lcg_increment: $lcg_increment:expr, ) => { pub struct $struct_name; impl $struct_name { const TOTAL_BITS: usize = core::mem::size_of::<$inner_type>() * 8; fn get_exact_iterations(items_count: usize) -> Option { let table_size = Self::TOTAL_BITS; if items_count <= 1 { return Some(1); } if items_count > table_size { return None; } let density_pct = (items_count * 100) / table_size; match table_size { 16 => match density_pct { 0 => Some(1), 1..=5 => Some(1), // max 0 items 6..=10 => Some(1), // max 1 items 11..=15 => Some(2), // max 2 items 16..=20 => Some(2), // max 3 items 21..=25 => Some(3), // max 4 items 26..=30 => Some(3), // max 4 items 31..=35 => Some(5), // max 5 items 36..=40 => Some(8), // max 6 items 41..=45 => Some(13), // max 7 items 46..=50 => Some(24), // max 8 items 51..=55 => Some(24), // max 8 items 56..=60 => Some(49), // max 9 items 61..=65 => Some(112), // max 10 items 66..=70 => Some(301), // max 11 items 71..=75 => Some(966), // max 12 items 76..=80 => Some(966), // max 12 items 81..=85 => Some(3868), // max 13 items 86..=90 => Some(20633), // max 14 items 91..=95 => Some(165075), // max 15 items _ => None, }, 32 => match density_pct { 0 => Some(1), 1..=5 => Some(1), // max 1 items 6..=10 => Some(2), // max 3 items 11..=15 => Some(2), // max 4 items 16..=20 => Some(4), // max 6 items 21..=25 => Some(7), // max 8 items 26..=30 => Some(9), // max 9 items 31..=35 => Some(20), // max 11 items 36..=40 => Some(31), // max 12 items 41..=45 => Some(85), // max 14 items 46..=50 => Some(287), // max 16 items 51..=55 => Some(575), // max 17 items 56..=60 => Some(2807), // max 19 items 61..=65 => Some(6912), // max 20 items 66..=70 => Some(53627), // max 22 items 71..=75 => Some(610170), // max 24 items _ => None, }, 64 => match density_pct { 0 => Some(1), 1..=5 => Some(1), // max 3 items 6..=10 => Some(2), // max 6 items 11..=15 => Some(4), // max 9 items 16..=20 => Some(8), // max 12 items 21..=25 => Some(22), // max 16 items 26..=30 => Some(58), // max 19 items 31..=35 => Some(180), // max 22 items 36..=40 => Some(686), // max 25 items 41..=45 => Some(3285), // max 28 items 46..=50 => Some(38995), // max 32 items 51..=55 => Some(343497), // max 35 items _ => None, }, 128 => match density_pct { 0 => Some(1), 1..=5 => Some(2), // max 6 items 6..=10 => Some(4), // max 12 items 11..=15 => Some(11), // max 19 items 16..=20 => Some(36), // max 25 items 21..=25 => Some(207), // max 32 items 26..=30 => Some(1368), // max 38 items 31..=35 => Some(13426), // max 44 items 36..=40 => Some(331105), // max 51 items _ => None, }, _ => None, } } pub fn get_bits_metadata() -> Option<($inner_type, $inner_type)> { let table_size = Self::TOTAL_BITS; let bits_needed = table_size.trailing_zeros() as usize; let bit_shift_usize = table_size.checked_sub(bits_needed)?; let bit_shift = bit_shift_usize as $inner_type; let bit_mask = (table_size - 1) as $inner_type; Some((bit_shift, bit_mask)) } pub fn get_lcg_xor_mask(values: &[V]) -> Option<$inner_type> where V: UniqueSaturatedInto<$inner_type> + Copy, { let table_size = Self::TOTAL_BITS; if values.len() > table_size { return None; } let max_iterations = Self::get_exact_iterations(values.len())?; let (bit_shift, bit_mask) = Self::get_bits_metadata()?; let mut lcg_rng_state: $inner_type = $init_rng; let mut xor_key: $inner_type = lcg_rng_state; let mut slots_taken = [false; Self::TOTAL_BITS]; for _ in 0..max_iterations { slots_taken.fill(false); let mut collision_found = false; for value in values { let slot_index = Self::get_lcg_based_slot_index( bit_mask, bit_shift, xor_key, value ); if slot_index >= table_size || slots_taken[slot_index] { collision_found = true; break; } slots_taken[slot_index] = true; } if !collision_found { return Some(xor_key); } lcg_rng_state = lcg_rng_state .wrapping_mul($lcg_multiplier) .wrapping_add($lcg_increment); xor_key = lcg_rng_state; } None } #[inline(always)] pub fn get_lcg_based_slot_index( bit_mask: $inner_type, bit_shift: $inner_type, xor_key: $inner_type, value: &V ) -> usize where V: UniqueSaturatedInto<$inner_type> + Copy, { let converted_value: $inner_type = (*value).unique_saturated_into(); let knuth_hash = (converted_value ^ xor_key).wrapping_mul($golden_ratio); let shifted_hash = knuth_hash.checked_shr(bit_shift as u32).unwrap_or(knuth_hash); let slot_index_converted = shifted_hash & bit_mask; slot_index_converted as usize } } } } generate_perfect_slot_mapper!( struct_name: PerfectSlotMapperU16, inner_type: u16, init_rng: 0x1234, golden_ratio: 0x9E37, // Computed with: 2^16 * ( (sqrt(5) - 1) / 2) lcg_multiplier: 25173, // Reference: Peter Grogono, Programming in Pascal, Section 4.5. lcg_increment: 13849, // http://engr.case.edu/merat_francis/eeap282f97/labs/Random_Lab.pdf ); generate_perfect_slot_mapper!( struct_name: PerfectSlotMapperU32, inner_type: u32, init_rng: 0x12345678, golden_ratio: 0x9E3779B9, // Computd with: 2^32 * ( (sqrt(5) - 1) / 2) lcg_multiplier: 1664525, // Numerical Recipes `ranqd1`, Chapter 7.1, lcg_increment: 1013904223, // An Even Quicker Generator, Eq. 7.1.6 ); generate_perfect_slot_mapper!( struct_name: PerfectSlotMapperU64, inner_type: u64, init_rng: 0x123456789ABCDEF, golden_ratio: 0x9E3779B97F4A7C15, // Taken from `splitmix64`, this generator is specifically used in PostgreSQL lcg_multiplier: 6364136223846793005, // Newlib / Musl libc 64-bit LCG multiplier lcg_increment: 1, // https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdlib/rand.c ); generate_perfect_slot_mapper!( struct_name: PerfectSlotMapperU128, inner_type: u128, init_rng: 0x123456789ABCDEF0123456789ABCDEF0, golden_ratio: 0x9E3779B97F4A7C15F39CC0605CEDC834, // Computd with: 2^128 * ( (sqrt(5) - 1) / 2) with python lcg_multiplier: 0x2360ED051FC65DA44385DF649FCCF645, // 2549297995355413924ULL,4865540595714422341ULL lcg_increment: 0x5851F42D4C957F2D14057B7EF767814F, // 6364136223846793005ULL,1442695040888963407ULL // https://github.com/imneme/pcg-cpp/blob/master/include/pcg_random.hpp ); #[cfg(test)] mod perfect_mapper_tests { use super::*; macro_rules! generate_mapper_test { ($mod_name:ident, $struct_name:ident, $init_rng:expr, $inner_type:ty) => { mod $mod_name { use super::*; #[test] fn test_standard_pack_20_percentages() { const TABLE_SIZE: usize = $struct_name::TOTAL_BITS; const ITEMS_COUNT: usize = (TABLE_SIZE * 20) / 100; let mut values = [0 as $inner_type; TABLE_SIZE]; for i in 0..ITEMS_COUNT { values[i] = ((i as $inner_type).wrapping_mul(999)) ^ 0xABC; } let filled_values = &values[0..ITEMS_COUNT]; let mask = $struct_name::get_lcg_xor_mask(filled_values); assert!(mask.is_some()); let xor_key = mask.unwrap(); let bits_needed = TABLE_SIZE.trailing_zeros() as $inner_type; let bit_mask = (TABLE_SIZE - 1) as $inner_type; let bit_shift = (TABLE_SIZE as $inner_type) - bits_needed; let mut slots = vec![false; TABLE_SIZE]; for val in filled_values { let idx = $struct_name::get_lcg_based_slot_index( bit_mask, bit_shift, xor_key, val ); assert!(idx < TABLE_SIZE); assert!(!slots[idx]); slots[idx] = true; } } #[test] fn test_heavy_math() { const TABLE_SIZE: usize = $struct_name::TOTAL_BITS; let max_val = <$inner_type>::MAX; let one_val = 1 as $inner_type; let mut block_pattern = 0 as $inner_type; let mut noise_pattern = 0 as $inner_type; let total_bytes = core::mem::size_of::<$inner_type>(); let mut i = 0; while i < total_bytes { let byte_val = if i % 2 == 0 { 0x55 } else { 0xAA }; noise_pattern = (noise_pattern << 8) | (byte_val as $inner_type); let block_val = if i < total_bytes / 3 { 0xEE } else if i < (total_bytes * 2) / 3 { 0xFF } else { 0xAA }; block_pattern = (block_pattern << 8) | (block_val as $inner_type); i += 1; } let center_bit_shift = (TABLE_SIZE / 2) as u32; let center_bit_pattern = (1 as $inner_type) .checked_shl(center_bit_shift) .unwrap_or(1); let values: [$inner_type; 6] = [ $init_rng, noise_pattern, max_val, one_val, center_bit_pattern, block_pattern ]; let mask = $struct_name::get_lcg_xor_mask(&values); assert!(mask.is_some()); } #[test] fn test_single_element() { const TABLE_SIZE: usize = $struct_name::TOTAL_BITS; let single_value = [420 as $inner_type]; let mask = $struct_name::get_lcg_xor_mask(&single_value); assert!(mask.is_some()); let xor_key = mask.unwrap(); let bits_needed = TABLE_SIZE.trailing_zeros() as $inner_type; let bit_mask = (TABLE_SIZE - 1) as $inner_type; let bit_shift = (TABLE_SIZE as $inner_type) - bits_needed; let idx = $struct_name::get_lcg_based_slot_index( bit_mask, bit_shift, xor_key, &single_value[0] ); assert!(idx < TABLE_SIZE); } #[test] fn test_invalid_bounds_returns_none() { const TABLE_SIZE: usize = $struct_name::TOTAL_BITS; let oversized_values = [1 as $inner_type; TABLE_SIZE]; let mask = $struct_name::get_lcg_xor_mask(&oversized_values); assert!(mask.is_none()); } #[test] fn test_top_25_evm_chains_by_tvl() { const TABLE_SIZE: usize = $struct_name::TOTAL_BITS; let evm_network_ids: [u64; 25] = [ 1, // Ethereum Mainnet 8453, // Base 56, // BNB Smart Chain 42161, // Arbitrum One 10143, // Monad 137, // Polygon PoS 43114, // Avalanche C-Chain 10, // OP Mainnet 57073, // Ink 14, // Flare Network 196, // X Layer 25, // Cronos 5000, // Mantle 100, // Gnosis Chain 59144, // Linea 534352, // Scroll 324, // zkSync Era 146, // Sonic (Fantom) 1116, // Core 42220, // Celo 1088, // Metis 592, // Astar 1284, // Moonbeam 98865, // Plume Network 60808, // BOB (Build on Bitcoin) ]; let mask = $struct_name::get_lcg_xor_mask(&evm_network_ids); if let Some(xor_key) = mask { let mut slots_check = [false; TABLE_SIZE]; let bits_needed = TABLE_SIZE.trailing_zeros() as $inner_type; let bit_mask = (TABLE_SIZE - 1) as $inner_type; let bit_shift = (TABLE_SIZE as $inner_type) - bits_needed; for chain_id in &evm_network_ids { let slot_idx = $struct_name::get_lcg_based_slot_index( bit_mask, bit_shift, xor_key, chain_id ); assert!(slot_idx < TABLE_SIZE); assert!(!slots_check[slot_idx]); slots_check[slot_idx] = true; } } else { assert!(TABLE_SIZE <= 32); } } #[test] fn test_top_25_evm_testnet_chains() { const TABLE_SIZE: usize = $struct_name::TOTAL_BITS; let evm_network_ids: [u64; 10] = [ 63, // Mordor (Ethereum Classic Testnet) 11155111, // Ethereum Sepolia 80002, // Polygon Amoy 421614, // Arbitrum Sepolia 84532, // Base Sepolia 11155420, // Optimism Sepolia 59141, // Linea Sepolia 534351, // Scroll Sepolia 80084, // Berachain Bartio 98864, // Plume Testnet ]; let mask = $struct_name::get_lcg_xor_mask(&evm_network_ids); if let Some(xor_key) = mask { let mut slots_check = [false; TABLE_SIZE]; let bits_needed = TABLE_SIZE.trailing_zeros() as $inner_type; let bit_mask = (TABLE_SIZE - 1) as $inner_type; let bit_shift = (TABLE_SIZE as $inner_type) - bits_needed; for chain_id in &evm_network_ids { let slot_idx = $struct_name::get_lcg_based_slot_index( bit_mask, bit_shift, xor_key, chain_id ); assert!(slot_idx < TABLE_SIZE); assert!(!slots_check[slot_idx]); slots_check[slot_idx] = true; } } else { assert!(TABLE_SIZE == 16); } } } } } generate_mapper_test!(u16_mapper, PerfectSlotMapperU16, 0x1234, u16); generate_mapper_test!(u32_mapper, PerfectSlotMapperU32, 0x12345678, u32); generate_mapper_test!(u64_mapper, PerfectSlotMapperU64, 0x123456789ABCDEF, u64); generate_mapper_test!(u128_mapper, PerfectSlotMapperU128, 0x123456789ABCDEF0123456789ABCDEF0, u128); }