templar_vault_kernel/restrictions/
mod.rs

1//! Chain-agnostic restrictions (gates) for vault access control.
2//!
3//! Portable across NEAR and Soroban.
4
5#[cfg(feature = "borsh-schema")]
6use alloc::string::ToString;
7use alloc::vec::Vec;
8#[cfg(feature = "schemars")]
9use alloc::{borrow::ToOwned, boxed::Box, vec};
10
11use derive_more::IsVariant;
12
13use crate::types::Address;
14
15/// Lightweight tag indicating why an actor was restricted.
16///
17#[templar_vault_macros::vault_derive(borsh_schema, schemars)]
18#[derive(Clone, Copy, PartialEq, Eq)]
19pub enum RestrictionKind {
20    /// Vault is paused.
21    Paused,
22    /// Actor appears on the blacklist.
23    Blacklisted,
24    /// Actor is not on the whitelist.
25    NotWhitelisted,
26}
27
28/// Restrictions that can be applied to the vault.
29///
30/// Supports Pausing, Whitelist, and Blacklist functionality.
31#[templar_vault_macros::vault_derive(borsh, borsh_schema, postcard, schemars, serde)]
32#[derive(Clone, PartialEq, Eq, IsVariant)]
33pub enum Restrictions {
34    /// Vault is paused - all operations blocked.
35    Paused,
36    /// Blacklist - specified actors are blocked.
37    #[cfg_attr(feature = "serde", serde(rename = "BlackList"))]
38    Blacklist(Vec<Address>),
39    /// Whitelist - only specified actors are allowed.
40    #[cfg_attr(feature = "serde", serde(rename = "WhiteList"))]
41    Whitelist(Vec<Address>),
42}
43
44impl Restrictions {
45    #[inline]
46    fn normalize_addresses(addresses: &mut Vec<Address>) {
47        addresses.sort_unstable();
48        addresses.dedup();
49    }
50
51    #[must_use]
52    pub fn normalized(mut self) -> Self {
53        match &mut self {
54            Restrictions::Blacklist(addresses) | Restrictions::Whitelist(addresses) => {
55                Self::normalize_addresses(addresses);
56            }
57            Restrictions::Paused => {}
58        }
59        self
60    }
61
62    #[inline]
63    fn contains_address(addresses: &[Address], actor_id: &Address) -> bool {
64        if addresses.is_sorted() {
65            addresses.binary_search(actor_id).is_ok()
66        } else {
67            addresses.iter().any(|addr| addr == actor_id)
68        }
69    }
70
71    /// Check if the given actor is restricted.
72    ///
73    /// Returns `Some(kind)` if blocked, `None` if allowed.
74    /// The returned [`RestrictionKind`] is a lightweight tag — no allocations.
75    ///
76    /// # Arguments
77    /// * `actor_id` - The actor to check.
78    /// * `self_id` - The vault's own identity (whitelist allows self by default).
79    pub fn is_restricted(&self, actor_id: &Address, self_id: &Address) -> Option<RestrictionKind> {
80        match self {
81            Restrictions::Paused => Some(RestrictionKind::Paused),
82            Restrictions::Blacklist(blacklist) => {
83                if Self::contains_address(blacklist, actor_id) {
84                    Some(RestrictionKind::Blacklisted)
85                } else {
86                    None
87                }
88            }
89            Restrictions::Whitelist(whitelist) => {
90                if Self::contains_address(whitelist, actor_id) || actor_id == self_id {
91                    None
92                } else {
93                    Some(RestrictionKind::NotWhitelisted)
94                }
95            }
96        }
97    }
98
99    // Note: is_paused(), is_blacklist(), is_whitelist() are auto-generated by derive_more::IsVariant
100}
101
102#[cfg(test)]
103mod tests;