iota_transaction_checks/
deny.rs1use iota_sdk_types::{Command, ObjectReference, Transaction, UserSignature};
6use iota_types::{
7 deny_rule_governance::DenyRuleConfig,
8 error::{IotaError, IotaResult, UserInputError},
9 storage::BackingPackageStore,
10 transaction::{InputObjectKind, TransactionAPI, TransactionKindExt},
11};
12use tracing::instrument;
13macro_rules! deny_if_true {
14 ($cond:expr, $msg:expr) => {
15 if ($cond) {
16 return Err(IotaError::UserInput {
17 error: UserInputError::TransactionDenied {
18 error: $msg.to_string(),
19 },
20 });
21 }
22 };
23}
24
25#[instrument(level = "trace", skip_all, fields(tx_digest = ?tx.digest()))]
28pub fn check_transaction_for_validation(
29 tx: &Transaction,
30 tx_signatures: &[UserSignature],
31 input_object_kinds: &[InputObjectKind],
32 receiving_objects: &[ObjectReference],
33 filter_config: &dyn DenyRuleConfig,
34 package_store: &dyn BackingPackageStore,
35) -> IotaResult {
36 check_disabled_features(filter_config, tx, tx_signatures)?;
37
38 check_signers(filter_config, tx)?;
39
40 check_input_objects(filter_config, input_object_kinds)?;
41
42 check_package_dependencies(filter_config, tx, package_store)?;
43
44 check_receiving_objects(filter_config, receiving_objects)?;
45
46 Ok(())
47}
48
49#[instrument(level = "trace", skip_all)]
50fn check_receiving_objects(
51 filter_config: &dyn DenyRuleConfig,
52 receiving_objects: &[ObjectReference],
53) -> IotaResult {
54 deny_if_true!(
55 filter_config.receiving_objects_disabled() && !receiving_objects.is_empty(),
56 "Receiving objects is temporarily disabled".to_string()
57 );
58 if !filter_config.has_denied_objects() {
59 return Ok(());
60 }
61 for receiving_object in receiving_objects {
62 deny_if_true!(
63 filter_config.is_object_denied(&receiving_object.object_id),
64 format!(
65 "Access to object {:?} is temporarily disabled",
66 receiving_object.object_id
67 )
68 );
69 }
70 Ok(())
71}
72
73#[instrument(level = "trace", skip_all)]
74fn check_disabled_features(
75 filter_config: &dyn DenyRuleConfig,
76 tx: &Transaction,
77 tx_signatures: &[UserSignature],
78) -> IotaResult {
79 deny_if_true!(
80 filter_config.user_transaction_disabled(),
81 "Transaction signing is temporarily disabled"
82 );
83
84 tx_signatures.iter().try_for_each(|s| {
85 if let UserSignature::MoveAuthenticator(_) = s {
86 deny_if_true!(
87 filter_config.move_authenticator_disabled(),
88 "MoveAuthenticator is temporarily disabled"
89 );
90 }
91 Ok(())
92 })?;
93
94 if !filter_config.package_publish_disabled() && !filter_config.package_upgrade_disabled() {
95 return Ok(());
96 }
97
98 for command in tx.kind().iter_commands() {
99 deny_if_true!(
100 filter_config.package_publish_disabled() && matches!(command, Command::Publish(..)),
101 "Package publish is temporarily disabled"
102 );
103 deny_if_true!(
104 filter_config.package_upgrade_disabled() && matches!(command, Command::Upgrade(..)),
105 "Package upgrade is temporarily disabled"
106 );
107 }
108 Ok(())
109}
110
111#[instrument(level = "trace", skip_all)]
112fn check_signers(filter_config: &dyn DenyRuleConfig, tx: &Transaction) -> IotaResult {
113 if !filter_config.has_denied_addresses() {
114 return Ok(());
115 }
116 for signer in tx.signers() {
117 deny_if_true!(
118 filter_config.is_address_denied(&signer),
119 format!(
120 "Access to account address {:?} is temporarily disabled",
121 signer
122 )
123 );
124 }
125 Ok(())
126}
127
128#[instrument(level = "trace", skip_all)]
129fn check_input_objects(
130 filter_config: &dyn DenyRuleConfig,
131 input_object_kinds: &[InputObjectKind],
132) -> IotaResult {
133 let shared_object_disabled = filter_config.shared_object_disabled();
134 if !filter_config.has_denied_objects() && !shared_object_disabled {
135 return Ok(());
137 }
138 for input_object_kind in input_object_kinds {
139 let id = input_object_kind.object_id();
140 deny_if_true!(
141 filter_config.is_object_denied(&id),
142 format!("Access to input object {id} is temporarily disabled")
143 );
144 deny_if_true!(
145 shared_object_disabled && input_object_kind.is_shared_object(),
146 "Usage of shared object in transactions is temporarily disabled"
147 );
148 }
149 Ok(())
150}
151
152#[instrument(level = "trace", skip_all)]
153fn check_package_dependencies(
154 filter_config: &dyn DenyRuleConfig,
155 tx: &Transaction,
156 package_store: &dyn BackingPackageStore,
157) -> IotaResult {
158 if !filter_config.has_denied_packages() {
159 return Ok(());
160 }
161 let mut dependencies = vec![];
162 for command in tx.kind().iter_commands() {
163 match command {
164 Command::Publish(cmd) => {
165 dependencies.extend(cmd.dependencies.iter().copied());
169 }
170 Command::Upgrade(cmd) => {
171 dependencies.extend(cmd.dependencies.iter().copied());
172 dependencies.push(cmd.package);
175 }
176 Command::MoveCall(cmd) => {
177 let package = package_store.get_package_object(&cmd.package)?.ok_or(
178 IotaError::UserInput {
179 error: UserInputError::ObjectNotFound {
180 object_id: cmd.package,
181 version: None,
182 },
183 },
184 )?;
185 dependencies.extend(
191 package
192 .move_package()
193 .linkage_table()
194 .values()
195 .map(|upgrade_info| upgrade_info.upgraded_id),
196 );
197 dependencies.push(package.move_package().id());
198 }
199 Command::TransferObjects(..)
200 | Command::SplitCoins(..)
201 | Command::MergeCoins(..)
202 | Command::MakeMoveVector(..) => {}
203 _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
204 }
205 }
206 for dep in dependencies {
207 deny_if_true!(
208 filter_config.is_package_denied(&dep),
209 format!("Access to package {dep} is temporarily disabled")
210 );
211 }
212 Ok(())
213}