1pub mod deny;
5
6pub use checked::*;
7
8#[sui_macros::with_checked_arithmetic]
9mod checked {
10 use std::collections::{BTreeMap, HashSet};
11 use std::sync::Arc;
12 use sui_config::verifier_signing_config::VerifierSigningConfig;
13 use sui_protocol_config::ProtocolConfig;
14 use sui_types::base_types::{ObjectID, ObjectRef};
15 use sui_types::error::{SuiResult, UserInputError, UserInputResult};
16 use sui_types::executable_transaction::VerifiedExecutableTransaction;
17 use sui_types::metrics::BytecodeVerifierMetrics;
18 use sui_types::transaction::{
19 CheckedInputObjects, InputObjectKind, InputObjects, ObjectReadResult, ObjectReadResultKind,
20 ReceivingObjectReadResult, ReceivingObjects, SharedObjectMutability, TransactionData,
21 TransactionDataAPI, TransactionKind,
22 };
23 use sui_types::{
24 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_ADDRESS_ALIAS_STATE_OBJECT_ID, SUI_BRIDGE_OBJECT_ID,
25 SUI_CLOCK_OBJECT_ID, SUI_COIN_REGISTRY_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID,
26 SUI_DISPLAY_REGISTRY_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
27 };
28 use sui_types::{
29 base_types::{SequenceNumber, SuiAddress},
30 error::SuiError,
31 fp_bail, fp_ensure,
32 gas::SuiGasStatus,
33 object::{Object, Owner},
34 };
35 use tracing::error;
36 use tracing::instrument;
37
38 trait IntoChecked {
39 fn into_checked(self) -> CheckedInputObjects;
40 }
41
42 impl IntoChecked for InputObjects {
43 fn into_checked(self) -> CheckedInputObjects {
44 CheckedInputObjects::new_with_checked_transaction_inputs(self)
45 }
46 }
47
48 fn get_gas_status(
53 objects: &InputObjects,
54 gas: &[ObjectRef],
55 protocol_config: &ProtocolConfig,
56 reference_gas_price: u64,
57 transaction: &TransactionData,
58 gas_ownership_checks: bool,
59 ) -> SuiResult<SuiGasStatus> {
60 if transaction.kind().is_system_tx() {
61 Ok(SuiGasStatus::new_unmetered())
62 } else {
63 check_gas(
64 objects,
65 protocol_config,
66 reference_gas_price,
67 gas,
68 transaction,
69 gas_ownership_checks,
70 )
71 }
72 }
73
74 #[instrument(level = "trace", skip_all)]
75 pub fn check_transaction_input(
76 protocol_config: &ProtocolConfig,
77 reference_gas_price: u64,
78 transaction: &TransactionData,
79 input_objects: InputObjects,
80 receiving_objects: &ReceivingObjects,
81 metrics: &Arc<BytecodeVerifierMetrics>,
82 verifier_signing_config: &VerifierSigningConfig,
83 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
84 let gas_status = check_transaction_input_inner(
85 protocol_config,
86 reference_gas_price,
87 transaction,
88 &input_objects,
89 &[],
90 )?;
91 check_receiving_objects(&input_objects, receiving_objects)?;
92 check_non_system_packages_to_be_published(
94 transaction,
95 protocol_config,
96 metrics,
97 verifier_signing_config,
98 )?;
99
100 Ok((gas_status, input_objects.into_checked()))
101 }
102
103 pub fn check_transaction_input_with_given_gas(
104 protocol_config: &ProtocolConfig,
105 reference_gas_price: u64,
106 transaction: &TransactionData,
107 mut input_objects: InputObjects,
108 receiving_objects: ReceivingObjects,
109 gas_object: Object,
110 metrics: &Arc<BytecodeVerifierMetrics>,
111 verifier_signing_config: &VerifierSigningConfig,
112 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
113 let gas_object_ref = gas_object.compute_object_reference();
114 input_objects.push(ObjectReadResult::new_from_gas_object(&gas_object));
115
116 let gas_status = check_transaction_input_inner(
117 protocol_config,
118 reference_gas_price,
119 transaction,
120 &input_objects,
121 &[gas_object_ref],
122 )?;
123 check_receiving_objects(&input_objects, &receiving_objects)?;
124 check_non_system_packages_to_be_published(
126 transaction,
127 protocol_config,
128 metrics,
129 verifier_signing_config,
130 )?;
131
132 Ok((gas_status, input_objects.into_checked()))
133 }
134
135 #[instrument(level = "trace", skip_all)]
140 pub fn check_certificate_input(
141 cert: &VerifiedExecutableTransaction,
142 input_objects: InputObjects,
143 protocol_config: &ProtocolConfig,
144 reference_gas_price: u64,
145 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
146 let transaction = cert.data().transaction_data();
147 let gas_status = check_transaction_input_inner(
148 protocol_config,
149 reference_gas_price,
150 transaction,
151 &input_objects,
152 &[],
153 )?;
154 Ok((gas_status, input_objects.into_checked()))
158 }
159
160 pub fn check_dev_inspect_input(
163 config: &ProtocolConfig,
164 transaction: &TransactionData,
165 input_objects: InputObjects,
166 _receiving_objects: ReceivingObjects,
168 reference_gas_price: u64,
169 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
170 let kind = transaction.kind();
171 kind.validity_check(config)?;
172 if kind.is_system_tx() {
173 return Err(UserInputError::Unsupported(format!(
174 "Transaction kind {} is not supported in dev-inspect",
175 kind
176 ))
177 .into());
178 }
179 let mut used_objects: HashSet<SuiAddress> = HashSet::new();
180 for input_object in input_objects.iter() {
181 let Some(object) = input_object.as_object() else {
182 continue;
184 };
185
186 if !object.is_immutable() {
187 fp_ensure!(
188 used_objects.insert(object.id().into()),
189 UserInputError::MutableObjectUsedMoreThanOnce {
190 object_id: object.id()
191 }
192 .into()
193 );
194 }
195 }
196
197 let gas_status = get_gas_status(
198 &input_objects,
199 &transaction.gas_data().payment, config,
201 reference_gas_price,
202 transaction,
203 false, )?;
205
206 Ok((gas_status, input_objects.into_checked()))
207 }
208
209 fn check_transaction_input_inner(
211 protocol_config: &ProtocolConfig,
212 reference_gas_price: u64,
213 transaction: &TransactionData,
214 input_objects: &InputObjects,
215 gas_override: &[ObjectRef],
217 ) -> SuiResult<SuiGasStatus> {
218 let gas = if gas_override.is_empty() {
219 transaction.gas()
220 } else {
221 gas_override
222 };
223
224 let gas_status = get_gas_status(
225 input_objects,
226 gas,
227 protocol_config,
228 reference_gas_price,
229 transaction,
230 true, )?;
232 check_objects(transaction, input_objects)?;
233 check_replay_protection(transaction, input_objects)?;
234
235 Ok(gas_status)
236 }
237
238 fn check_replay_protection(
246 transaction: &TransactionData,
247 input_objects: &InputObjects,
248 ) -> UserInputResult<()> {
249 let has_replay_protection = transaction.expiration().is_replay_protected()
250 || !transaction.gas_data().payment.is_empty()
251 || input_objects
252 .iter()
253 .any(|obj| obj.is_replay_protected_input());
254
255 if !has_replay_protection {
256 return Err(UserInputError::InvalidExpiration {
257 error: "Transactions must either have address-owned inputs, or a ValidDuring expiration with at most two epochs of validity"
258 .to_string(),
259 });
260 }
261
262 Ok(())
263 }
264
265 fn check_receiving_objects(
266 input_objects: &InputObjects,
267 receiving_objects: &ReceivingObjects,
268 ) -> Result<(), SuiError> {
269 let mut objects_in_txn: HashSet<_> = input_objects
270 .object_kinds()
271 .map(|x| x.object_id())
272 .collect();
273
274 for ReceivingObjectReadResult {
282 object_ref: (object_id, version, object_digest),
283 object,
284 } in receiving_objects.iter()
285 {
286 fp_ensure!(
287 *version < SequenceNumber::MAX,
288 UserInputError::InvalidSequenceNumber.into()
289 );
290
291 let Some(object) = object.as_object() else {
292 continue;
294 };
295
296 if !(object.owner.is_address_owned()
297 && object.version() == *version
298 && object.digest() == *object_digest)
299 {
300 fp_ensure!(
302 object.version() == *version,
303 UserInputError::ObjectVersionUnavailableForConsumption {
304 provided_obj_ref: (*object_id, *version, *object_digest),
305 current_version: object.version(),
306 }
307 .into()
308 );
309
310 fp_ensure!(
312 !object.is_package(),
313 UserInputError::MovePackageAsObject {
314 object_id: *object_id
315 }
316 .into()
317 );
318
319 let expected_digest = object.digest();
321 fp_ensure!(
322 expected_digest == *object_digest,
323 UserInputError::InvalidObjectDigest {
324 object_id: *object_id,
325 expected_digest
326 }
327 .into()
328 );
329
330 match object.owner {
331 Owner::AddressOwner(_) => {
332 debug_assert!(
333 false,
334 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
335 (*object_id, *version, *object_id),
336 object
337 );
338 error!(
339 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
340 (*object_id, *version, *object_id),
341 object
342 );
343 fp_bail!(
346 UserInputError::ObjectNotFound {
347 object_id: *object_id,
348 version: Some(*version),
349 }
350 .into()
351 )
352 }
353 Owner::ObjectOwner(owner) => {
354 fp_bail!(
355 UserInputError::InvalidChildObjectArgument {
356 child_id: object.id(),
357 parent_id: owner.into(),
358 }
359 .into()
360 )
361 }
362 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => {
363 fp_bail!(UserInputError::NotSharedObjectError.into())
364 }
365 Owner::Immutable => fp_bail!(
366 UserInputError::MutableParameterExpected {
367 object_id: *object_id
368 }
369 .into()
370 ),
371 };
372 }
373
374 fp_ensure!(
375 !objects_in_txn.contains(object_id),
376 UserInputError::DuplicateObjectRefInput.into()
377 );
378
379 objects_in_txn.insert(*object_id);
380 }
381 Ok(())
382 }
383
384 #[instrument(level = "trace", skip_all)]
387 fn check_gas(
388 objects: &InputObjects,
389 protocol_config: &ProtocolConfig,
390 reference_gas_price: u64,
391 gas: &[ObjectRef],
392 transaction: &TransactionData,
393 gas_ownership_checks: bool,
394 ) -> SuiResult<SuiGasStatus> {
395 let gas_budget = transaction.gas_budget();
396 let gas_price = transaction.gas_price();
397 let gas_paid_from_address_balance = transaction.is_gas_paid_from_address_balance();
398
399 let gas_status =
400 SuiGasStatus::new(gas_budget, gas_price, reference_gas_price, protocol_config)?;
401
402 let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
405 let mut gas_objects = vec![];
406 for obj_ref in gas {
407 let obj = objects.get(&obj_ref.0);
408 let obj = *obj.ok_or(UserInputError::ObjectNotFound {
409 object_id: obj_ref.0,
410 version: Some(obj_ref.1),
411 })?;
412 gas_objects.push(obj);
413 }
414
415 if !gas_paid_from_address_balance {
418 if gas_ownership_checks {
419 gas_status.check_gas_objects(&gas_objects)?;
420 }
421 gas_status.check_gas_data(&gas_objects, gas_budget)?;
422 }
423 Ok(gas_status)
424 }
425
426 #[instrument(level = "trace", skip_all)]
429 fn check_objects(transaction: &TransactionData, objects: &InputObjects) -> UserInputResult<()> {
430 let mut used_objects: HashSet<SuiAddress> = HashSet::new();
432 for object in objects.iter() {
433 if object.is_mutable() {
434 fp_ensure!(
435 used_objects.insert(object.id().into()),
436 UserInputError::MutableObjectUsedMoreThanOnce {
437 object_id: object.id()
438 }
439 );
440 }
441 }
442
443 if !transaction.is_genesis_tx() && objects.is_empty() {
444 return Err(UserInputError::ObjectInputArityViolation);
445 }
446
447 let gas_coins: HashSet<ObjectID> =
448 HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
449 for object in objects.iter() {
450 let input_object_kind = object.input_object_kind;
451
452 match &object.object {
453 ObjectReadResultKind::Object(object) => {
454 let owner_address = if gas_coins.contains(&object.id()) {
456 transaction.gas_owner()
457 } else {
458 transaction.sender()
459 };
460 let system_transaction = transaction.is_system_tx();
463 check_one_object(
464 &owner_address,
465 input_object_kind,
466 object,
467 system_transaction,
468 )?;
469 }
470 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => (),
472 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
474 }
475 }
476
477 Ok(())
478 }
479
480 fn check_one_object(
482 owner: &SuiAddress,
483 object_kind: InputObjectKind,
484 object: &Object,
485 system_transaction: bool,
486 ) -> UserInputResult {
487 match object_kind {
488 InputObjectKind::MovePackage(package_id) => {
489 fp_ensure!(
490 object.data.try_as_package().is_some(),
491 UserInputError::MoveObjectAsPackage {
492 object_id: package_id
493 }
494 );
495 }
496 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
497 fp_ensure!(
498 !object.is_package(),
499 UserInputError::MovePackageAsObject { object_id }
500 );
501 fp_ensure!(
502 sequence_number < SequenceNumber::MAX,
503 UserInputError::InvalidSequenceNumber
504 );
505
506 assert_eq!(
508 object.version(),
509 sequence_number,
510 "The fetched object version {} does not match the requested version {}, object id: {}",
511 object.version(),
512 sequence_number,
513 object.id(),
514 );
515
516 let expected_digest = object.digest();
518 fp_ensure!(
519 expected_digest == object_digest,
520 UserInputError::InvalidObjectDigest {
521 object_id,
522 expected_digest
523 }
524 );
525
526 match object.owner {
527 Owner::Immutable => {
528 }
530 Owner::AddressOwner(actual_owner) => {
531 fp_ensure!(
533 owner == &actual_owner,
534 UserInputError::IncorrectUserSignature {
535 error: format!(
536 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
537 ),
538 }
539 );
540 }
541 Owner::ObjectOwner(owner) => {
542 return Err(UserInputError::InvalidChildObjectArgument {
543 child_id: object.id(),
544 parent_id: owner.into(),
545 });
546 }
547 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => {
548 return Err(UserInputError::NotOwnedObjectError);
551 }
552 };
553 }
554 InputObjectKind::SharedMoveObject {
555 id: object_id,
556 initial_shared_version: input_initial_shared_version,
557 mutability,
558 } => {
559 fp_ensure!(
560 object.version() < SequenceNumber::MAX,
561 UserInputError::InvalidSequenceNumber
562 );
563
564 if object_id.is_system_object() {
565 if system_transaction {
568 return Ok(());
569 }
570
571 match (object_id, mutability) {
572 (SUI_SYSTEM_STATE_OBJECT_ID, _)
574 | (SUI_ADDRESS_ALIAS_STATE_OBJECT_ID, _)
575 | (SUI_COIN_REGISTRY_OBJECT_ID, _)
576 | (SUI_DISPLAY_REGISTRY_OBJECT_ID, _)
577 | (SUI_DENY_LIST_OBJECT_ID, _)
578 | (SUI_BRIDGE_OBJECT_ID, _)
579
580 | (SUI_CLOCK_OBJECT_ID, SharedObjectMutability::Immutable)
582 | (SUI_RANDOMNESS_STATE_OBJECT_ID, SharedObjectMutability::Immutable)
583 | (SUI_ACCUMULATOR_ROOT_OBJECT_ID, SharedObjectMutability::Immutable) => (),
584
585 _ => {
587 return Err(UserInputError::ImmutableParameterExpectedError {
588 object_id,
589 });
590 }
591 }
592 }
593
594 match &object.owner {
595 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
596 return Err(UserInputError::NotSharedObjectError);
598 }
599 Owner::Shared {
600 initial_shared_version: actual_initial_shared_version,
601 } => {
602 fp_ensure!(
603 input_initial_shared_version == *actual_initial_shared_version,
604 UserInputError::SharedObjectStartingVersionMismatch
605 )
606 }
607 Owner::ConsensusAddressOwner {
608 start_version: actual_initial_shared_version,
609 owner: actual_owner,
610 } => {
611 fp_ensure!(
612 input_initial_shared_version == *actual_initial_shared_version,
613 UserInputError::SharedObjectStartingVersionMismatch
614 );
615 fp_ensure!(
617 owner == actual_owner,
618 UserInputError::IncorrectUserSignature {
619 error: format!(
620 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
621 ),
622 }
623 )
624 }
625 }
626 }
627 };
628 Ok(())
629 }
630
631 #[instrument(level = "trace", skip_all)]
633 pub fn check_non_system_packages_to_be_published(
634 transaction: &TransactionData,
635 protocol_config: &ProtocolConfig,
636 metrics: &Arc<BytecodeVerifierMetrics>,
637 verifier_signing_config: &VerifierSigningConfig,
638 ) -> UserInputResult<()> {
639 if transaction.is_system_tx() {
641 return Ok(());
642 }
643
644 let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
645 return Ok(());
646 };
647
648 let signing_limits = Some(verifier_signing_config.limits_for_signing());
650 let mut verifier = sui_execution::verifier(protocol_config, signing_limits, metrics);
651 let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
652
653 let shared_meter_verifier_timer = metrics
655 .verifier_runtime_per_ptb_success_latency
656 .start_timer();
657
658 let verifier_status = pt
659 .non_system_packages_to_be_published()
660 .try_for_each(|module_bytes| {
661 verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
662 })
663 .map_err(|e| UserInputError::PackageVerificationTimeout { err: e.to_string() });
664
665 match verifier_status {
666 Ok(_) => {
667 shared_meter_verifier_timer.stop_and_record();
669 }
670 Err(err) => {
671 metrics
674 .verifier_runtime_per_ptb_timeout_latency
675 .observe(shared_meter_verifier_timer.stop_and_discard());
676 return Err(err);
677 }
678 };
679
680 Ok(())
681 }
682}