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(any(feature = "borsh-schema", feature = "schemars"))]
6use alloc::string::ToString;
7use alloc::vec::Vec;
8#[cfg(feature = "schemars")]
9use alloc::{borrow::ToOwned, boxed::Box, vec};
10
11use crate::types::Address;
12use derive_more::IsVariant;
13
14fn normalize_addresses(mut addresses: Vec<Address>) -> Vec<Address> {
15    let mut deduped = Vec::with_capacity(addresses.len());
16    for address in addresses.drain(..) {
17        if !deduped.contains(&address) {
18            deduped.push(address);
19        }
20    }
21    deduped
22}
23
24/// Lightweight tag indicating why an actor was restricted.
25///
26#[templar_vault_macros::vault_derive(borsh_schema, schemars)]
27#[derive(Clone, Copy, PartialEq, Eq)]
28pub enum RestrictionKind {
29    /// Vault is paused.
30    Paused,
31    /// Actor appears on the blacklist.
32    Blacklisted,
33    /// Actor is not on the whitelist.
34    NotWhitelisted,
35}
36
37#[templar_vault_macros::vault_derive(borsh, borsh_schema, schemars, serde)]
38#[derive(Clone, PartialEq, Eq, IsVariant)]
39pub enum RestrictionMode {
40    /// Blacklist - specified actors are blocked.
41    #[cfg_attr(feature = "serde", serde(rename = "BlackList"))]
42    Blacklist(Vec<Address>),
43    /// Whitelist - only specified actors are allowed.
44    #[cfg_attr(feature = "serde", serde(rename = "WhiteList"))]
45    Whitelist(Vec<Address>),
46}
47
48pub type Restrictions = RestrictionMode;
49
50impl RestrictionMode {
51    #[must_use]
52    pub fn blacklist(addresses: Vec<Address>) -> Self {
53        Self::Blacklist(normalize_addresses(addresses))
54    }
55
56    #[must_use]
57    pub fn whitelist(addresses: Vec<Address>) -> Self {
58        Self::Whitelist(normalize_addresses(addresses))
59    }
60
61    #[must_use]
62    pub fn normalized(self) -> Self {
63        match self {
64            Self::Blacklist(addresses) => Self::Blacklist(normalize_addresses(addresses)),
65            Self::Whitelist(addresses) => Self::Whitelist(normalize_addresses(addresses)),
66        }
67    }
68
69    /// Check if the given actor is restricted.
70    ///
71    /// Returns `Some(kind)` if blocked, `None` if allowed.
72    /// The returned [`RestrictionKind`] is a lightweight tag — no allocations.
73    ///
74    /// # Arguments
75    /// * `actor_id` - The actor to check.
76    pub fn is_restricted(&self, actor_id: &Address) -> Option<RestrictionKind> {
77        match self {
78            Self::Blacklist(blacklist) => {
79                if blacklist.contains(actor_id) {
80                    Some(RestrictionKind::Blacklisted)
81                } else {
82                    None
83                }
84            }
85            Self::Whitelist(whitelist) => {
86                if whitelist.contains(actor_id) {
87                    None
88                } else {
89                    Some(RestrictionKind::NotWhitelisted)
90                }
91            }
92        }
93    }
94
95    #[must_use]
96    pub fn is_restricted_allowing_self(
97        &self,
98        actor_id: &Address,
99        self_id: &Address,
100    ) -> Option<RestrictionKind> {
101        match self {
102            Self::Whitelist(_) if actor_id == self_id => None,
103            _ => self.is_restricted(actor_id),
104        }
105    }
106
107    // Note: is_paused(), is_blacklist(), is_whitelist() are auto-generated by derive_more::IsVariant
108}
109
110#[cfg(test)]
111mod tests;