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