Skip to main content

iota_types/
deny_rule_governance.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::BTreeSet;
5
6use iota_sdk_types::{Address, ObjectId};
7use serde::{Deserialize, Serialize};
8
9/// Read access to a set of transaction deny rules.
10///
11/// Implemented by both a validator's local `TransactionDenyConfig` and the
12/// consensus-governed [`DenyRuleSet`], so the deny checks can run against
13/// either source without knowing which one is in effect.
14pub trait DenyRuleConfig: Send + Sync {
15    /// Whether `address` is denied as a transaction sender or gas sponsor.
16    fn is_address_denied(&self, address: &Address) -> bool;
17    /// Whether the object `id` is denied as an input or receiving object.
18    fn is_object_denied(&self, id: &ObjectId) -> bool;
19    /// Whether the package `id` is denied as a (transitive) dependency.
20    fn is_package_denied(&self, id: &ObjectId) -> bool;
21    /// Whether any address is denied; lets checks skip scanning signers when
22    /// there are none.
23    fn has_denied_addresses(&self) -> bool;
24    /// Whether any object is denied; lets checks skip scanning input and
25    /// receiving objects when there are none.
26    fn has_denied_objects(&self) -> bool;
27    /// Whether any package is denied; lets checks skip resolving package
28    /// dependencies (which loads packages from the store) when there are none.
29    fn has_denied_packages(&self) -> bool;
30    fn package_publish_disabled(&self) -> bool;
31    fn package_upgrade_disabled(&self) -> bool;
32    fn shared_object_disabled(&self) -> bool;
33    fn user_transaction_disabled(&self) -> bool;
34    fn receiving_objects_disabled(&self) -> bool;
35    fn move_authenticator_disabled(&self) -> bool;
36}
37
38/// A complete set of deny rules.
39///
40/// Deny lists use `BTreeSet` so the BCS encoding is deterministic across
41/// validators (a requirement for the consensus messages that carry this type).
42#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
43pub struct DenyRuleSet {
44    /// Addresses denied as transaction sender or gas sponsor. A denied
45    /// address can still receive objects.
46    pub denied_addresses: BTreeSet<Address>,
47    /// Objects denied as transaction inputs or receiving objects.
48    pub denied_objects: BTreeSet<ObjectId>,
49    /// Packages denied as a (transitive) dependency of any command; upgrading
50    /// a denied package is denied too.
51    pub denied_packages: BTreeSet<ObjectId>,
52    /// Denies all package publishing.
53    pub package_publish_disabled: bool,
54    /// Denies all package upgrades.
55    pub package_upgrade_disabled: bool,
56    /// Denies transactions that use shared objects as inputs.
57    pub shared_object_disabled: bool,
58    /// Denies all user transactions (kill switch).
59    pub user_transaction_disabled: bool,
60    /// Denies transactions that contain receiving objects.
61    pub receiving_objects_disabled: bool,
62    /// Denies transactions signed with a Move authenticator.
63    pub move_authenticator_disabled: bool,
64}
65
66impl DenyRuleConfig for DenyRuleSet {
67    fn is_address_denied(&self, address: &Address) -> bool {
68        self.denied_addresses.contains(address)
69    }
70
71    fn is_object_denied(&self, id: &ObjectId) -> bool {
72        self.denied_objects.contains(id)
73    }
74
75    fn is_package_denied(&self, id: &ObjectId) -> bool {
76        self.denied_packages.contains(id)
77    }
78
79    fn has_denied_addresses(&self) -> bool {
80        !self.denied_addresses.is_empty()
81    }
82
83    fn has_denied_objects(&self) -> bool {
84        !self.denied_objects.is_empty()
85    }
86
87    fn has_denied_packages(&self) -> bool {
88        !self.denied_packages.is_empty()
89    }
90
91    fn package_publish_disabled(&self) -> bool {
92        self.package_publish_disabled
93    }
94
95    fn package_upgrade_disabled(&self) -> bool {
96        self.package_upgrade_disabled
97    }
98
99    fn shared_object_disabled(&self) -> bool {
100        self.shared_object_disabled
101    }
102
103    fn user_transaction_disabled(&self) -> bool {
104        self.user_transaction_disabled
105    }
106
107    fn receiving_objects_disabled(&self) -> bool {
108        self.receiving_objects_disabled
109    }
110
111    fn move_authenticator_disabled(&self) -> bool {
112        self.move_authenticator_disabled
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use iota_sdk_types::{Address, ObjectId};
119
120    use crate::deny_rule_governance::{DenyRuleConfig, DenyRuleSet};
121
122    fn sample_rule_set() -> DenyRuleSet {
123        DenyRuleSet {
124            denied_addresses: [Address::new([1u8; 32]), Address::new([2u8; 32])]
125                .into_iter()
126                .collect(),
127            denied_objects: [ObjectId::new([3u8; 32])].into_iter().collect(),
128            denied_packages: [ObjectId::new([4u8; 32])].into_iter().collect(),
129            package_publish_disabled: true,
130            package_upgrade_disabled: false,
131            shared_object_disabled: true,
132            user_transaction_disabled: false,
133            receiving_objects_disabled: true,
134            move_authenticator_disabled: false,
135        }
136    }
137
138    #[test]
139    fn deny_rule_set_bcs_round_trip() {
140        let rules = sample_rule_set();
141        let bytes = bcs::to_bytes(&rules).unwrap();
142        assert_eq!(rules, bcs::from_bytes(&bytes).unwrap());
143    }
144
145    #[test]
146    fn deny_rule_config_reflects_set_contents() {
147        let rules = sample_rule_set();
148
149        assert!(rules.is_address_denied(&Address::new([1u8; 32])));
150        assert!(!rules.is_address_denied(&Address::new([9u8; 32])));
151        assert!(rules.is_object_denied(&ObjectId::new([3u8; 32])));
152        assert!(!rules.is_object_denied(&ObjectId::new([9u8; 32])));
153        assert!(rules.is_package_denied(&ObjectId::new([4u8; 32])));
154        assert!(!rules.is_package_denied(&ObjectId::new([9u8; 32])));
155
156        assert!(rules.has_denied_addresses());
157        assert!(rules.has_denied_objects());
158        assert!(rules.has_denied_packages());
159
160        assert!(rules.package_publish_disabled());
161        assert!(!rules.package_upgrade_disabled());
162        assert!(rules.shared_object_disabled());
163        assert!(!rules.user_transaction_disabled());
164        assert!(rules.receiving_objects_disabled());
165        assert!(!rules.move_authenticator_disabled());
166    }
167
168    /// An empty set denies nothing.
169    #[test]
170    fn empty_deny_rule_set_denies_nothing() {
171        let rules = DenyRuleSet::default();
172        assert!(!rules.is_address_denied(&Address::new([1u8; 32])));
173        assert!(!rules.is_object_denied(&ObjectId::new([1u8; 32])));
174        assert!(!rules.has_denied_addresses());
175        assert!(!rules.has_denied_objects());
176        assert!(!rules.has_denied_packages());
177        assert!(!rules.user_transaction_disabled());
178    }
179}