templar_curator_primitives/policy/target_set/
mod.rs

1//! Shared helpers for target-set validation.
2
3use alloc::collections::BTreeSet;
4use alloc::vec::Vec;
5use templar_vault_kernel::TargetId;
6
7use super::{
8    market_lock::MarketLockSet,
9    refresh_plan::{RefreshPlan, RefreshPlanError},
10    withdraw_route::{build_withdraw_route, WithdrawRouteError},
11};
12
13/// Returns the first duplicate item found in insertion order.
14#[must_use]
15pub fn find_first_duplicate<T: Ord + Copy>(items: &[T]) -> Option<T> {
16    let mut seen = BTreeSet::new();
17    for item in items {
18        if !seen.insert(*item) {
19            return Some(*item);
20        }
21    }
22    None
23}
24
25/// Returns true when all items are unique.
26#[must_use]
27pub fn has_unique_items<T: Ord + Copy>(items: &[T]) -> bool {
28    find_first_duplicate(items).is_none()
29}
30
31/// Returns the first duplicate target ID in insertion order.
32#[must_use]
33pub fn find_duplicate_target_id(targets: &[TargetId]) -> Option<TargetId> {
34    find_first_duplicate(targets)
35}
36
37/// Returns true when all target IDs are unique.
38#[must_use]
39pub fn validate_no_duplicate_targets(targets: &[TargetId]) -> bool {
40    has_unique_items(targets)
41}
42
43/// Build a withdraw plan from target principals.
44pub fn build_withdraw_plan_from_target_principals(
45    principals: &[(TargetId, u128)],
46    target_amount: u128,
47) -> Result<Vec<(TargetId, u128)>, WithdrawRouteError> {
48    let route = build_withdraw_route(principals, target_amount)?;
49    Ok(route
50        .entries
51        .iter()
52        .map(|entry| (entry.target_id, entry.max_amount))
53        .collect())
54}
55
56/// Return locked target IDs from a candidate target list.
57#[must_use]
58pub fn find_locked_targets(
59    lock_set: &MarketLockSet,
60    targets: &[TargetId],
61    current_ns: u64,
62) -> Vec<TargetId> {
63    lock_set.find_locked_targets(targets, current_ns)
64}
65
66/// Check if a target is currently locked.
67#[must_use]
68pub fn is_target_locked(lock_set: &MarketLockSet, target: TargetId, current_ns: u64) -> bool {
69    lock_set.is_locked(target, current_ns)
70}
71
72/// Return all currently locked target IDs.
73#[must_use]
74pub fn get_locked_targets(lock_set: &MarketLockSet, current_ns: u64) -> Vec<TargetId> {
75    lock_set.locked_targets(current_ns)
76}
77
78/// Build and validate a refresh plan from target IDs.
79pub fn build_refresh_plan_from_targets(
80    targets: &[TargetId],
81    cooldown_ns: u64,
82    last_refresh_ns: u64,
83) -> Result<RefreshPlan, RefreshPlanError> {
84    let plan = RefreshPlan::new(targets.to_vec())
85        .with_cooldown(cooldown_ns)
86        .with_last_refresh(last_refresh_ns);
87    plan.validate()?;
88    Ok(plan)
89}