use crate::shim::econ::TokenAmount;
use crate::utils::rand::forest_rng;
use num::Zero as _;
use rand::Rng;
pub fn weighted_quick_select(
mut premiums: Vec<TokenAmount>,
mut limits: Vec<u64>,
mut target_index: u64,
) -> TokenAmount {
loop {
match (premiums.as_slice(), limits.as_slice()) {
([], _) => return TokenAmount::zero(),
([premium], [limit]) => {
return if *limit > target_index {
premium.clone()
} else {
TokenAmount::zero()
};
}
_ => {}
}
let pivot_idx = forest_rng().gen_range(0..premiums.len());
let pivot = premiums
.get(pivot_idx)
.expect("pivot_idx is in range")
.clone();
let (mut more_premiums, mut more_weights, mut more_total_weight) =
(Vec::new(), Vec::new(), 0u64);
let mut equal_total_weight = 0u64;
let (mut less_premiums, mut less_weights, mut less_total_weight) =
(Vec::new(), Vec::new(), 0u64);
for (premium, limit) in premiums.into_iter().zip(limits) {
match premium.cmp(&pivot) {
std::cmp::Ordering::Greater => {
more_total_weight = more_total_weight.saturating_add(limit);
more_premiums.push(premium);
more_weights.push(limit);
}
std::cmp::Ordering::Equal => {
equal_total_weight = equal_total_weight.saturating_add(limit);
}
std::cmp::Ordering::Less => {
less_total_weight = less_total_weight.saturating_add(limit);
less_premiums.push(premium);
less_weights.push(limit);
}
}
}
if target_index < more_total_weight {
premiums = more_premiums;
limits = more_weights;
} else if target_index < more_total_weight.saturating_add(equal_total_weight) {
return pivot;
} else {
target_index -= more_total_weight.saturating_add(equal_total_weight);
if target_index < less_total_weight {
premiums = less_premiums;
limits = less_weights;
} else {
return TokenAmount::zero();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_weighted_quick_select_basic() {
assert_eq!(
weighted_quick_select(vec![], vec![], 0),
TokenAmount::zero()
);
assert_eq!(
weighted_quick_select(
vec![TokenAmount::from_atto(123)],
vec![5_999_999_999],
8_000_000_000
),
TokenAmount::zero()
);
assert_eq!(
weighted_quick_select(
vec![TokenAmount::from_atto(123)],
vec![8_000_000_001],
8_000_000_000
),
TokenAmount::from_atto(123)
);
}
#[test]
fn test_weighted_quick_select_deterministic() {
const TARGET_INDEX: u64 = 7_999_999_999;
for _ in 0..10 {
assert_eq!(
weighted_quick_select(
vec![TokenAmount::from_atto(123), TokenAmount::from_atto(100)],
vec![5_999_999_999, 2_000_000_000],
TARGET_INDEX
),
TokenAmount::zero()
);
assert_eq!(
weighted_quick_select(
vec![TokenAmount::from_atto(123), TokenAmount::from_atto(0)],
vec![5_999_999_999, 2_000_000_001],
TARGET_INDEX
),
TokenAmount::from_atto(0)
);
assert_eq!(
weighted_quick_select(
vec![TokenAmount::from_atto(123), TokenAmount::from_atto(100)],
vec![5_999_999_999, 2_000_000_001],
TARGET_INDEX
),
TokenAmount::from_atto(100)
);
assert_eq!(
weighted_quick_select(
vec![TokenAmount::from_atto(123), TokenAmount::from_atto(100)],
vec![7_999_999_999, 2_000_000_001],
TARGET_INDEX
),
TokenAmount::from_atto(100)
);
assert_eq!(
weighted_quick_select(
vec![TokenAmount::from_atto(123), TokenAmount::from_atto(100)],
vec![8_000_000_000, 2_000_000_000],
TARGET_INDEX
),
TokenAmount::from_atto(123)
);
assert_eq!(
weighted_quick_select(
vec![TokenAmount::from_atto(123), TokenAmount::from_atto(100)],
vec![8_000_000_000, 9_000_000_000],
TARGET_INDEX
),
TokenAmount::from_atto(123)
);
assert_eq!(
weighted_quick_select(
vec![
TokenAmount::from_atto(100),
TokenAmount::from_atto(200),
TokenAmount::from_atto(300),
TokenAmount::from_atto(400),
TokenAmount::from_atto(500),
TokenAmount::from_atto(600),
TokenAmount::from_atto(700),
],
vec![
4_000_000_000,
1_000_000_000,
2_000_000_000,
1_000_000_000,
2_000_000_000,
2_000_000_000,
3_000_000_000,
],
TARGET_INDEX
),
TokenAmount::from_atto(400)
);
}
}
mod quickcheck_tests {
use super::*;
use crate::blocks::BLOCK_MESSAGE_LIMIT;
use crate::shim::econ::BLOCK_GAS_LIMIT;
use quickcheck::{Arbitrary, Gen};
use quickcheck_macros::quickcheck;
#[derive(Clone, Debug)]
struct RealisticGasLimits(Vec<u64>);
impl Arbitrary for RealisticGasLimits {
fn arbitrary(g: &mut Gen) -> Self {
let size = usize::arbitrary(g) % BLOCK_MESSAGE_LIMIT;
let limits: Vec<u64> = (0..size)
.map(|_| u64::arbitrary(g) % BLOCK_GAS_LIMIT) .collect();
RealisticGasLimits(limits)
}
}
#[derive(Clone, Debug)]
struct MatchedPremiumsAndLimits {
premiums: Vec<u64>,
limits: Vec<u64>,
}
impl Arbitrary for MatchedPremiumsAndLimits {
fn arbitrary(g: &mut Gen) -> Self {
let limits = RealisticGasLimits::arbitrary(g).0;
let premiums: Vec<u64> = (0..limits.len()).map(|_| u64::arbitrary(g)).collect();
MatchedPremiumsAndLimits { premiums, limits }
}
}
#[quickcheck]
fn prop_result_is_from_input_or_zero(input: MatchedPremiumsAndLimits, target: u64) -> bool {
let premium_amounts: Vec<TokenAmount> = input
.premiums
.iter()
.map(|&p| TokenAmount::from_atto(p))
.collect();
let result =
weighted_quick_select(premium_amounts.clone(), input.limits.clone(), target);
result.is_zero() || premium_amounts.iter().any(|p| p == &result)
}
#[quickcheck]
fn prop_empty_input_returns_zero(target: u64) -> bool {
let result = weighted_quick_select(vec![], vec![], target);
result.is_zero()
}
#[quickcheck]
fn prop_single_element_behavior(premium: u64, limit: u64, target: u64) -> bool {
let result =
weighted_quick_select(vec![TokenAmount::from_atto(premium)], vec![limit], target);
if limit > target {
result == TokenAmount::from_atto(premium)
} else {
result.is_zero()
}
}
#[quickcheck]
fn prop_target_beyond_total_weight_returns_zero(input: MatchedPremiumsAndLimits) -> bool {
if input.limits.is_empty() {
return true;
}
let premium_amounts: Vec<TokenAmount> = input
.premiums
.iter()
.map(|&p| TokenAmount::from_atto(p))
.collect();
let total_weight: u64 = input.limits.iter().sum();
let result = weighted_quick_select(premium_amounts, input.limits, total_weight);
result.is_zero()
}
#[quickcheck]
fn prop_deterministic_result(input: MatchedPremiumsAndLimits, target: u64) -> bool {
let premium_amounts: Vec<TokenAmount> = input
.premiums
.iter()
.map(|&p| TokenAmount::from_atto(p))
.collect();
let result1 =
weighted_quick_select(premium_amounts.clone(), input.limits.clone(), target);
let result2 = weighted_quick_select(premium_amounts, input.limits, target);
result1 == result2
}
#[derive(Clone, Debug)]
struct OrderedPremiumPair {
low_premium: u64,
high_premium: u64,
weight_low: u64,
weight_high: u64,
}
impl Arbitrary for OrderedPremiumPair {
fn arbitrary(g: &mut Gen) -> Self {
const MAX_PREMIUM: u64 = u64::MAX / 2; let low = u64::arbitrary(g) % MAX_PREMIUM;
let high = low + (u64::arbitrary(g) % 1000).max(1);
OrderedPremiumPair {
low_premium: low,
high_premium: high,
weight_low: (u64::arbitrary(g) % BLOCK_GAS_LIMIT).max(1),
weight_high: (u64::arbitrary(g) % BLOCK_GAS_LIMIT).max(1),
}
}
}
#[quickcheck]
fn prop_result_respects_weight_ordering(pair: OrderedPremiumPair) -> bool {
let target = pair.weight_high - 1;
let result = weighted_quick_select(
vec![
TokenAmount::from_atto(pair.low_premium),
TokenAmount::from_atto(pair.high_premium),
],
vec![pair.weight_low, pair.weight_high],
target,
);
result == TokenAmount::from_atto(pair.high_premium)
}
#[quickcheck]
fn prop_equal_premiums_handled_correctly(
premium: u64,
limits: RealisticGasLimits,
target: u64,
) -> bool {
if limits.0.is_empty() {
return true;
}
let premiums = vec![TokenAmount::from_atto(premium); limits.0.len()];
let total_weight: u64 = limits.0.iter().sum();
let result = weighted_quick_select(premiums, limits.0, target);
if target < total_weight {
result == TokenAmount::from_atto(premium)
} else {
result.is_zero()
}
}
}
}