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::gas::SuiGasStatusAPI;
18 use sui_types::metrics::BytecodeVerifierMetrics;
19 use sui_types::object::ObjectPermission;
20 use sui_types::transaction::{
21 CheckedInputObjects, InputObjectKind, InputObjects, ObjectReadResult, ObjectReadResultKind,
22 ReceivingObjectReadResult, ReceivingObjects, SharedObjectMutability, TransactionData,
23 TransactionDataAPI, TransactionKind,
24 };
25 use sui_types::{
26 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_ADDRESS_ALIAS_STATE_OBJECT_ID, SUI_BRIDGE_OBJECT_ID,
27 SUI_CLOCK_OBJECT_ID, SUI_COIN_REGISTRY_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID,
28 SUI_DISPLAY_REGISTRY_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
29 };
30 use sui_types::{
31 base_types::{SequenceNumber, SuiAddress},
32 coin_reservation::ParsedDigest,
33 error::SuiError,
34 fp_bail, fp_ensure,
35 gas::SuiGasStatus,
36 object::{Object, Owner},
37 };
38 use tracing::error;
39 use tracing::instrument;
40
41 trait IntoChecked {
42 fn into_checked(self) -> CheckedInputObjects;
43 }
44
45 impl IntoChecked for InputObjects {
46 fn into_checked(self) -> CheckedInputObjects {
47 CheckedInputObjects::new_with_checked_transaction_inputs(self)
48 }
49 }
50
51 fn get_gas_status(
56 objects: &InputObjects,
57 gas: &[ObjectRef],
58 protocol_config: &ProtocolConfig,
59 reference_gas_price: u64,
60 transaction: &TransactionData,
61 gas_ownership_checks: bool,
62 ) -> SuiResult<SuiGasStatus> {
63 if transaction.kind().is_system_tx() {
64 Ok(SuiGasStatus::new_unmetered())
65 } else {
66 let is_gasless =
67 protocol_config.enable_gasless() && transaction.is_gasless_transaction();
68 check_gas(
69 objects,
70 protocol_config,
71 reference_gas_price,
72 gas,
73 transaction,
74 gas_ownership_checks,
75 is_gasless,
76 )
77 }
78 }
79
80 #[instrument(level = "trace", skip_all)]
81 pub fn check_transaction_input(
82 protocol_config: &ProtocolConfig,
83 reference_gas_price: u64,
84 transaction: &TransactionData,
85 input_objects: InputObjects,
86 receiving_objects: &ReceivingObjects,
87 metrics: &Arc<BytecodeVerifierMetrics>,
88 verifier_signing_config: &VerifierSigningConfig,
89 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
90 let gas_status = check_transaction_input_inner(
91 protocol_config,
92 reference_gas_price,
93 transaction,
94 &input_objects,
95 &[],
96 )?;
97 check_receiving_objects(&input_objects, receiving_objects)?;
98 check_non_system_packages_to_be_published(
100 transaction,
101 protocol_config,
102 metrics,
103 verifier_signing_config,
104 )?;
105
106 Ok((gas_status, input_objects.into_checked()))
107 }
108
109 pub fn check_transaction_input_with_given_gas(
110 protocol_config: &ProtocolConfig,
111 reference_gas_price: u64,
112 transaction: &TransactionData,
113 mut input_objects: InputObjects,
114 receiving_objects: ReceivingObjects,
115 gas_object: Object,
116 metrics: &Arc<BytecodeVerifierMetrics>,
117 verifier_signing_config: &VerifierSigningConfig,
118 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
119 let gas_object_ref = gas_object.compute_object_reference();
120 input_objects.push(ObjectReadResult::new_from_gas_object(&gas_object));
121
122 let gas_status = check_transaction_input_inner(
123 protocol_config,
124 reference_gas_price,
125 transaction,
126 &input_objects,
127 &[gas_object_ref],
128 )?;
129 check_receiving_objects(&input_objects, &receiving_objects)?;
130 check_non_system_packages_to_be_published(
132 transaction,
133 protocol_config,
134 metrics,
135 verifier_signing_config,
136 )?;
137
138 Ok((gas_status, input_objects.into_checked()))
139 }
140
141 #[instrument(level = "trace", skip_all)]
146 pub fn check_certificate_input(
147 cert: &VerifiedExecutableTransaction,
148 input_objects: InputObjects,
149 protocol_config: &ProtocolConfig,
150 reference_gas_price: u64,
151 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
152 let transaction = cert.data().transaction_data();
153 let gas_status = check_transaction_input_inner(
154 protocol_config,
155 reference_gas_price,
156 transaction,
157 &input_objects,
158 &[],
159 )?;
160 Ok((gas_status, input_objects.into_checked()))
164 }
165
166 pub fn check_dev_inspect_input(
169 config: &ProtocolConfig,
170 transaction: &TransactionData,
171 input_objects: InputObjects,
172 _receiving_objects: ReceivingObjects,
174 reference_gas_price: u64,
175 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
176 let kind = transaction.kind();
177 kind.validity_check(config)?;
178 if kind.is_system_tx() {
179 return Err(UserInputError::Unsupported(format!(
180 "Transaction kind {} is not supported in dev-inspect",
181 kind
182 ))
183 .into());
184 }
185 let mut used_objects: HashSet<SuiAddress> = HashSet::new();
186 for input_object in input_objects.iter() {
187 let Some(object) = input_object.as_object() else {
188 continue;
190 };
191
192 if !object.is_immutable() {
193 fp_ensure!(
194 used_objects.insert(object.id().into()),
195 UserInputError::MutableObjectUsedMoreThanOnce {
196 object_id: object.id()
197 }
198 .into()
199 );
200 }
201 }
202
203 let gas_status = get_gas_status(
204 &input_objects,
205 &transaction.gas_data().payment, config,
207 reference_gas_price,
208 transaction,
209 false, )?;
211
212 Ok((gas_status, input_objects.into_checked()))
213 }
214
215 fn check_transaction_input_inner(
217 protocol_config: &ProtocolConfig,
218 reference_gas_price: u64,
219 transaction: &TransactionData,
220 input_objects: &InputObjects,
221 gas_override: &[ObjectRef],
223 ) -> SuiResult<SuiGasStatus> {
224 let gas = if gas_override.is_empty() {
225 transaction.gas()
226 } else {
227 gas_override
228 };
229
230 let gas_status = get_gas_status(
231 input_objects,
232 gas,
233 protocol_config,
234 reference_gas_price,
235 transaction,
236 true, )?;
238 check_objects(transaction, input_objects, protocol_config)?;
239 check_replay_protection(transaction, input_objects)?;
240
241 if protocol_config.enable_gasless() && transaction.is_gasless_transaction() {
242 check_gasless_object_inputs(input_objects, protocol_config)?;
243 }
244
245 Ok(gas_status)
246 }
247
248 fn check_replay_protection(
256 transaction: &TransactionData,
257 input_objects: &InputObjects,
258 ) -> UserInputResult<()> {
259 let has_replay_protection = transaction.expiration().is_replay_protected()
260 || !transaction.gas_data().payment.is_empty()
261 || input_objects
262 .iter()
263 .any(|obj| obj.is_replay_protected_input());
264
265 if !has_replay_protection {
266 return Err(UserInputError::InvalidExpiration {
267 error: "Transactions must either have address-owned inputs, or a ValidDuring expiration with at most two epochs of validity"
268 .to_string(),
269 });
270 }
271
272 Ok(())
273 }
274
275 fn check_receiving_objects(
276 input_objects: &InputObjects,
277 receiving_objects: &ReceivingObjects,
278 ) -> Result<(), SuiError> {
279 let mut objects_in_txn: HashSet<_> = input_objects
280 .object_kinds()
281 .map(|x| x.object_id())
282 .collect();
283
284 for ReceivingObjectReadResult {
292 object_ref: (object_id, version, object_digest),
293 object,
294 } in receiving_objects.iter()
295 {
296 fp_ensure!(
297 *version < SequenceNumber::MAX,
298 UserInputError::InvalidSequenceNumber.into()
299 );
300
301 let Some(object) = object.as_object() else {
302 continue;
304 };
305
306 if !(object.owner.is_address_owned()
307 && object.version() == *version
308 && object.digest() == *object_digest)
309 {
310 fp_ensure!(
312 object.version() == *version,
313 UserInputError::ObjectVersionUnavailableForConsumption {
314 provided_obj_ref: (*object_id, *version, *object_digest),
315 current_version: object.version(),
316 }
317 .into()
318 );
319
320 fp_ensure!(
322 !object.is_package(),
323 UserInputError::MovePackageAsObject {
324 object_id: *object_id
325 }
326 .into()
327 );
328
329 let expected_digest = object.digest();
331 fp_ensure!(
332 expected_digest == *object_digest,
333 UserInputError::InvalidObjectDigest {
334 object_id: *object_id,
335 expected_digest
336 }
337 .into()
338 );
339
340 match object.owner {
341 Owner::AddressOwner(_) => {
342 debug_assert!(
343 false,
344 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
345 (*object_id, *version, *object_id),
346 object
347 );
348 error!(
349 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
350 (*object_id, *version, *object_id),
351 object
352 );
353 fp_bail!(
356 UserInputError::ObjectNotFound {
357 object_id: *object_id,
358 version: Some(*version),
359 }
360 .into()
361 )
362 }
363 Owner::ObjectOwner(owner) => {
364 fp_bail!(
365 UserInputError::InvalidChildObjectArgument {
366 child_id: object.id(),
367 parent_id: owner.into(),
368 }
369 .into()
370 )
371 }
372 Owner::Shared { .. }
373 | Owner::ConsensusAddressOwner { .. }
374 | Owner::Party { .. } => {
375 fp_bail!(UserInputError::NotSharedObjectError.into())
376 }
377 Owner::Immutable => fp_bail!(
378 UserInputError::MutableParameterExpected {
379 object_id: *object_id
380 }
381 .into()
382 ),
383 };
384 }
385
386 fp_ensure!(
387 !objects_in_txn.contains(object_id),
388 UserInputError::DuplicateObjectRefInput.into()
389 );
390
391 objects_in_txn.insert(*object_id);
392 }
393 Ok(())
394 }
395
396 #[instrument(level = "trace", skip_all)]
399 fn check_gas(
400 objects: &InputObjects,
401 protocol_config: &ProtocolConfig,
402 reference_gas_price: u64,
403 gas: &[ObjectRef],
404 transaction: &TransactionData,
405 gas_ownership_checks: bool,
406 is_gasless: bool,
407 ) -> SuiResult<SuiGasStatus> {
408 let gas_budget = transaction.gas_budget();
409 let gas_price = transaction.gas_price();
410 let gas_paid_from_address_balance = transaction.is_gas_paid_from_address_balance();
411
412 let gas_status = if is_gasless {
413 debug_assert_ne!(reference_gas_price, 0);
414 let rgp = reference_gas_price.max(1);
415 let compute_cap = protocol_config.gasless_max_computation_units() * rgp;
416 SuiGasStatus::new(compute_cap, rgp, reference_gas_price, protocol_config)?
417 } else {
418 SuiGasStatus::new(gas_budget, gas_price, reference_gas_price, protocol_config)?
419 };
420
421 let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
424
425 let (gas_objects, available_address_balance_gas) = if gas_paid_from_address_balance {
426 (vec![], gas_budget)
429 } else {
430 let mut available_address_balance_gas: u64 = 0;
433 let mut gas_objects = vec![];
434 for obj_ref in gas {
435 if let Ok(parsed) = ParsedDigest::try_from(obj_ref.2) {
436 available_address_balance_gas =
437 available_address_balance_gas.saturating_add(parsed.reservation_amount());
438 } else {
439 let obj = objects.get(&obj_ref.0);
440 let obj = *obj.ok_or(UserInputError::ObjectNotFound {
441 object_id: obj_ref.0,
442 version: Some(obj_ref.1),
443 })?;
444 gas_objects.push(obj);
445 }
446 }
447 (gas_objects, available_address_balance_gas)
448 };
449
450 if !is_gasless {
451 if gas_ownership_checks {
452 gas_status.check_gas_objects(&gas_objects)?;
453 }
454 gas_status.check_gas_balance(
455 &gas_objects,
456 gas_budget,
457 available_address_balance_gas,
458 )?;
459 }
460 Ok(gas_status)
461 }
462
463 #[instrument(level = "trace", skip_all)]
466 fn check_objects(
467 transaction: &TransactionData,
468 objects: &InputObjects,
469 protocol_config: &ProtocolConfig,
470 ) -> UserInputResult<()> {
471 let mut used_objects: HashSet<SuiAddress> = HashSet::new();
473 for object in objects.iter() {
474 if object.is_mutable() {
475 fp_ensure!(
476 used_objects.insert(object.id().into()),
477 UserInputError::MutableObjectUsedMoreThanOnce {
478 object_id: object.id()
479 }
480 );
481 }
482 }
483
484 let gas_only_contains_coin_reservations = !transaction.gas().is_empty()
488 && transaction
489 .gas()
490 .iter()
491 .all(|obj_ref| ParsedDigest::is_coin_reservation_digest(&obj_ref.2));
492
493 let allow_empty_objects = protocol_config.enable_coin_reservation_obj_refs()
494 && (transaction.is_gas_paid_from_address_balance()
495 || gas_only_contains_coin_reservations);
496 if !transaction.is_genesis_tx() && objects.is_empty() && !allow_empty_objects {
497 return Err(UserInputError::ObjectInputArityViolation);
498 }
499
500 let gas_coins: HashSet<ObjectID> =
501 HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
502 for object in objects.iter() {
503 let input_object_kind = object.input_object_kind;
504
505 match &object.object {
506 ObjectReadResultKind::Object(object) => {
507 let owner_address = if gas_coins.contains(&object.id()) {
509 transaction.gas_owner()
510 } else {
511 transaction.sender()
512 };
513 let system_transaction = transaction.is_system_tx();
516 check_one_object(
517 &owner_address,
518 input_object_kind,
519 object,
520 system_transaction,
521 )?;
522 }
523 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => (),
525 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
527 }
528 }
529
530 Ok(())
531 }
532
533 fn check_one_object(
535 owner: &SuiAddress,
536 object_kind: InputObjectKind,
537 object: &Object,
538 system_transaction: bool,
539 ) -> UserInputResult {
540 if matches!(object.owner, Owner::Party { .. }) {
542 return Err(UserInputError::Unsupported(
543 "Party-owned objects are not yet supported".to_string(),
544 ));
545 }
546
547 match object_kind {
548 InputObjectKind::MovePackage(package_id) => {
549 fp_ensure!(
550 object.data.try_as_package().is_some(),
551 UserInputError::MoveObjectAsPackage {
552 object_id: package_id
553 }
554 );
555 }
556 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
557 fp_ensure!(
558 !object.is_package(),
559 UserInputError::MovePackageAsObject { object_id }
560 );
561 fp_ensure!(
562 sequence_number < SequenceNumber::MAX,
563 UserInputError::InvalidSequenceNumber
564 );
565
566 assert_eq!(
568 object.version(),
569 sequence_number,
570 "The fetched object version {} does not match the requested version {}, object id: {}",
571 object.version(),
572 sequence_number,
573 object.id(),
574 );
575
576 let expected_digest = object.digest();
578 fp_ensure!(
579 expected_digest == object_digest,
580 UserInputError::InvalidObjectDigest {
581 object_id,
582 expected_digest
583 }
584 );
585
586 match object.owner {
587 Owner::Immutable => {
588 }
590 Owner::AddressOwner(actual_owner) => {
591 fp_ensure!(
593 owner == &actual_owner,
594 UserInputError::IncorrectUserSignature {
595 error: format!(
596 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
597 ),
598 }
599 );
600 }
601 Owner::ObjectOwner(owner) => {
602 return Err(UserInputError::InvalidChildObjectArgument {
603 child_id: object.id(),
604 parent_id: owner.into(),
605 });
606 }
607 Owner::Shared { .. }
608 | Owner::ConsensusAddressOwner { .. }
609 | Owner::Party { .. } => {
610 return Err(UserInputError::NotOwnedObjectError);
613 }
614 };
615 }
616 InputObjectKind::SharedMoveObject {
617 id: object_id,
618 initial_shared_version: input_initial_shared_version,
619 mutability,
620 } => {
621 fp_ensure!(
622 object.version() < SequenceNumber::MAX,
623 UserInputError::InvalidSequenceNumber
624 );
625
626 if object_id.is_system_object() {
627 if system_transaction {
630 return Ok(());
631 }
632
633 match (object_id, mutability) {
634 (SUI_SYSTEM_STATE_OBJECT_ID, _)
636 | (SUI_ADDRESS_ALIAS_STATE_OBJECT_ID, _)
637 | (SUI_COIN_REGISTRY_OBJECT_ID, _)
638 | (SUI_DISPLAY_REGISTRY_OBJECT_ID, _)
639 | (SUI_DENY_LIST_OBJECT_ID, _)
640 | (SUI_BRIDGE_OBJECT_ID, _)
641
642 | (SUI_CLOCK_OBJECT_ID, SharedObjectMutability::Immutable)
644 | (SUI_RANDOMNESS_STATE_OBJECT_ID, SharedObjectMutability::Immutable)
645 | (SUI_ACCUMULATOR_ROOT_OBJECT_ID, SharedObjectMutability::Immutable) => (),
646
647 _ => {
649 return Err(UserInputError::ImmutableParameterExpectedError {
650 object_id,
651 });
652 }
653 }
654 }
655
656 match &object.owner {
657 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
658 return Err(UserInputError::NotSharedObjectError);
660 }
661 Owner::Shared {
662 initial_shared_version: actual_initial_shared_version,
663 } => {
664 fp_ensure!(
665 input_initial_shared_version == *actual_initial_shared_version,
666 UserInputError::SharedObjectStartingVersionMismatch
667 )
668 }
669 Owner::ConsensusAddressOwner {
670 start_version: actual_initial_shared_version,
671 owner: actual_owner,
672 } => {
673 fp_ensure!(
674 input_initial_shared_version == *actual_initial_shared_version,
675 UserInputError::SharedObjectStartingVersionMismatch
676 );
677 fp_ensure!(
679 owner == actual_owner,
680 UserInputError::IncorrectUserSignature {
681 error: format!(
682 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
683 ),
684 }
685 )
686 }
687
688 Owner::Party {
689 start_version: actual_initial_shared_version,
690 permissions,
691 } => {
692 fp_ensure!(
693 input_initial_shared_version == *actual_initial_shared_version,
694 UserInputError::SharedObjectStartingVersionMismatch
695 );
696 let sender_permissions = permissions.permissions_for(owner);
698 match mutability {
699 SharedObjectMutability::Immutable => {
700 fp_ensure!(
702 sender_permissions.can_use_immutably(),
703 UserInputError::IncorrectUserSignature {
704 error: format!(
705 "Sender address {owner:?} does not have immutable access permissions for object {object_id:?} with party ownership. The required permission is {}, but the permissions for the sender for this object are {sender_permissions}",
706 ObjectPermission::ImmutableUsage,
707 ),
708 }
709 )
710 }
711 SharedObjectMutability::Mutable => {
712 fp_ensure!(
714 sender_permissions.can_use_mutably(),
715 UserInputError::IncorrectUserSignature {
716 error: format!(
717 "Sender address {owner:?} does not have mutable access permissions for object {object_id:?} with party ownership. The required permission is {}, but the permissions for the sender for this object are {sender_permissions}",
718 ObjectPermission::MutableUsage,
719 ),
720 }
721 )
722 }
723 SharedObjectMutability::NonExclusiveWrite => {
724 todo!("Party WIP")
726 }
727 }
728 }
729 }
730 }
731 };
732 Ok(())
733 }
734
735 pub fn check_gasless_object_inputs(
738 input_objects: &InputObjects,
739 protocol_config: &ProtocolConfig,
740 ) -> UserInputResult<()> {
741 let allowed_token_types =
742 sui_types::transaction::get_gasless_allowed_token_types(protocol_config);
743
744 for obj_read in input_objects.iter() {
745 let Some(object) = obj_read.as_object() else {
746 continue;
747 };
748 if object.is_package() {
749 continue;
750 }
751 match object.owner() {
752 Owner::AddressOwner(_) | Owner::ConsensusAddressOwner { .. } => (),
753 Owner::Immutable
754 | Owner::Shared { .. }
755 | Owner::ObjectOwner(_)
756 | Owner::Party { .. } => {
757 return Err(UserInputError::Unsupported(
758 "Gasless transactions only support owned object inputs".to_string(),
759 ));
760 }
761 }
762 let coin_type = object.coin_type_maybe().ok_or_else(|| {
764 UserInputError::Unsupported(
765 "Gasless transactions can only use Coin<T> object inputs, \
766 but found a non-Coin object"
767 .to_string(),
768 )
769 })?;
770 fp_ensure!(
771 allowed_token_types.contains_key(&coin_type),
772 UserInputError::Unsupported(
773 "Gasless transactions only support allowlisted types for Coin inputs"
774 .to_string()
775 )
776 );
777 }
778 Ok(())
779 }
780
781 #[instrument(level = "trace", skip_all)]
783 pub fn check_non_system_packages_to_be_published(
784 transaction: &TransactionData,
785 protocol_config: &ProtocolConfig,
786 metrics: &Arc<BytecodeVerifierMetrics>,
787 verifier_signing_config: &VerifierSigningConfig,
788 ) -> UserInputResult<()> {
789 if transaction.is_system_tx() {
791 return Ok(());
792 }
793
794 let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
795 return Ok(());
796 };
797
798 let signing_limits = Some(verifier_signing_config.limits_for_signing());
800 let mut verifier = sui_execution::verifier(protocol_config, signing_limits, metrics);
801 let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
802
803 let shared_meter_verifier_timer = metrics
805 .verifier_runtime_per_ptb_success_latency
806 .start_timer();
807
808 let verifier_status = pt
809 .non_system_packages_to_be_published()
810 .try_for_each(|module_bytes| {
811 verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
812 })
813 .map_err(|e| UserInputError::PackageVerificationTimeout { err: e.to_string() });
814
815 match verifier_status {
816 Ok(_) => {
817 shared_meter_verifier_timer.stop_and_record();
819 }
820 Err(err) => {
821 metrics
824 .verifier_runtime_per_ptb_timeout_latency
825 .observe(shared_meter_verifier_timer.stop_and_discard());
826 return Err(err);
827 }
828 };
829
830 Ok(())
831 }
832}