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::{check_gas_budget_and_balance, check_gas_price};
18 use sui_types::metrics::BytecodeVerifierMetrics;
19 use sui_types::object::ObjectPermission;
20 use sui_types::transaction::{
21 CheckedInputObjects, InputObjectKind, InputObjects, 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_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
29 SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
30 };
31 use sui_types::{
32 base_types::{SequenceNumber, SuiAddress},
33 coin_reservation::ParsedDigest,
34 error::SuiError,
35 fp_bail, fp_ensure,
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 #[instrument(level = "trace", skip_all)]
52 pub fn check_transaction_input(
53 protocol_config: &ProtocolConfig,
54 reference_gas_price: u64,
55 transaction: &TransactionData,
56 input_objects: InputObjects,
57 receiving_objects: &ReceivingObjects,
58 metrics: &Arc<BytecodeVerifierMetrics>,
59 verifier_signing_config: &VerifierSigningConfig,
60 ) -> SuiResult<CheckedInputObjects> {
61 check_transaction_input_inner(
62 protocol_config,
63 reference_gas_price,
64 transaction,
65 &input_objects,
66 &[],
67 )?;
68 transaction.check_allowance_inputs(&input_objects)?;
69 check_receiving_objects(&input_objects, receiving_objects)?;
70 check_non_system_packages_to_be_published(
72 transaction,
73 protocol_config,
74 metrics,
75 verifier_signing_config,
76 )?;
77
78 Ok(input_objects.into_checked())
79 }
80
81 #[instrument(level = "trace", skip_all)]
86 pub fn check_certificate_input(
87 cert: &VerifiedExecutableTransaction,
88 input_objects: InputObjects,
89 protocol_config: &ProtocolConfig,
90 reference_gas_price: u64,
91 ) -> SuiResult<CheckedInputObjects> {
92 let transaction = cert.data().transaction_data();
93 check_transaction_input_inner(
94 protocol_config,
95 reference_gas_price,
96 transaction,
97 &input_objects,
98 &[],
99 )?;
100 Ok(input_objects.into_checked())
106 }
107
108 pub fn check_dev_inspect_input(
111 config: &ProtocolConfig,
112 transaction: &TransactionData,
113 input_objects: InputObjects,
114 _receiving_objects: ReceivingObjects,
116 reference_gas_price: u64,
117 ) -> SuiResult<CheckedInputObjects> {
118 let kind = transaction.kind();
119 kind.validity_check(config)?;
120 if kind.is_system_tx() {
121 return Err(UserInputError::Unsupported(format!(
122 "Transaction kind {} is not supported in dev-inspect",
123 kind
124 ))
125 .into());
126 }
127 let mut used_objects: HashSet<SuiAddress> = HashSet::new();
128 for input_object in input_objects.iter() {
129 let Some(object) = input_object.as_object() else {
130 continue;
132 };
133
134 if !object.is_immutable() {
135 fp_ensure!(
136 used_objects.insert(object.id().into()),
137 UserInputError::MutableObjectUsedMoreThanOnce {
138 object_id: object.id()
139 }
140 .into()
141 );
142 }
143 }
144
145 check_gas(
146 &input_objects,
147 config,
148 reference_gas_price,
149 &transaction.gas_data().payment,
150 transaction,
151 )?;
152
153 Ok(input_objects.into_checked())
154 }
155
156 fn check_transaction_input_inner(
158 protocol_config: &ProtocolConfig,
159 reference_gas_price: u64,
160 transaction: &TransactionData,
161 input_objects: &InputObjects,
162 gas_override: &[ObjectRef],
164 ) -> SuiResult {
165 let gas = if gas_override.is_empty() {
166 transaction.gas()
167 } else {
168 gas_override
169 };
170
171 check_gas(
172 input_objects,
173 protocol_config,
174 reference_gas_price,
175 gas,
176 transaction,
177 )?;
178 check_objects(transaction, input_objects, protocol_config)?;
179 check_replay_protection(transaction, input_objects)?;
180
181 if protocol_config.enable_gasless() && transaction.is_gasless_transaction() {
182 check_gasless_object_inputs(input_objects, protocol_config)?;
183 }
184
185 Ok(())
186 }
187
188 fn check_replay_protection(
196 transaction: &TransactionData,
197 input_objects: &InputObjects,
198 ) -> UserInputResult<()> {
199 if !transaction.has_replay_protection(input_objects.iter()) {
200 return Err(UserInputError::InvalidExpiration {
201 error: "Transactions must either have address-owned inputs, or a ValidDuring expiration with at most two epochs of validity"
202 .to_string(),
203 });
204 }
205
206 Ok(())
207 }
208
209 fn check_receiving_objects(
210 input_objects: &InputObjects,
211 receiving_objects: &ReceivingObjects,
212 ) -> Result<(), SuiError> {
213 let mut objects_in_txn: HashSet<_> = input_objects
214 .object_kinds()
215 .map(|x| x.object_id())
216 .collect();
217
218 for ReceivingObjectReadResult {
226 object_ref: (object_id, version, object_digest),
227 object,
228 } in receiving_objects.iter()
229 {
230 fp_ensure!(
231 *version < SequenceNumber::MAX,
232 UserInputError::InvalidSequenceNumber.into()
233 );
234
235 let Some(object) = object.as_object() else {
236 continue;
238 };
239
240 if !(object.owner.is_address_owned()
241 && object.version() == *version
242 && object.digest() == *object_digest)
243 {
244 fp_ensure!(
246 object.version() == *version,
247 UserInputError::ObjectVersionUnavailableForConsumption {
248 provided_obj_ref: (*object_id, *version, *object_digest),
249 current_version: object.version(),
250 }
251 .into()
252 );
253
254 fp_ensure!(
256 !object.is_package(),
257 UserInputError::MovePackageAsObject {
258 object_id: *object_id
259 }
260 .into()
261 );
262
263 let expected_digest = object.digest();
265 fp_ensure!(
266 expected_digest == *object_digest,
267 UserInputError::InvalidObjectDigest {
268 object_id: *object_id,
269 expected_digest
270 }
271 .into()
272 );
273
274 match object.owner {
275 Owner::AddressOwner(_) => {
276 debug_assert!(
277 false,
278 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
279 (*object_id, *version, *object_id),
280 object
281 );
282 error!(
283 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
284 (*object_id, *version, *object_id),
285 object
286 );
287 fp_bail!(
290 UserInputError::ObjectNotFound {
291 object_id: *object_id,
292 version: Some(*version),
293 }
294 .into()
295 )
296 }
297 Owner::ObjectOwner(owner) => {
298 fp_bail!(
299 UserInputError::InvalidChildObjectArgument {
300 child_id: object.id(),
301 parent_id: owner.into(),
302 }
303 .into()
304 )
305 }
306 Owner::Shared { .. }
307 | Owner::ConsensusAddressOwner { .. }
308 | Owner::Party { .. } => {
309 fp_bail!(UserInputError::NotSharedObjectError.into())
310 }
311 Owner::Immutable => fp_bail!(
312 UserInputError::MutableParameterExpected {
313 object_id: *object_id
314 }
315 .into()
316 ),
317 };
318 }
319
320 fp_ensure!(
321 !objects_in_txn.contains(object_id),
322 UserInputError::DuplicateObjectRefInput.into()
323 );
324
325 objects_in_txn.insert(*object_id);
326 }
327 Ok(())
328 }
329
330 #[instrument(level = "trace", skip_all)]
335 fn check_gas(
336 objects: &InputObjects,
337 protocol_config: &ProtocolConfig,
338 reference_gas_price: u64,
339 gas: &[ObjectRef],
340 transaction: &TransactionData,
341 ) -> SuiResult {
342 if transaction.kind().is_system_tx() {
343 return Ok(());
344 }
345 if protocol_config.enable_gasless() && transaction.is_gasless_transaction() {
346 debug_assert_ne!(reference_gas_price, 0);
347 return Ok(());
348 }
349 let gas_budget = transaction.gas_budget();
350 let gas_price = transaction.gas_price();
351 let gas_paid_from_address_balance = transaction.is_gas_paid_from_address_balance();
352 check_gas_price(gas_price, reference_gas_price, protocol_config)?;
353
354 let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
357
358 let (gas_objects, available_address_balance_gas) = if gas_paid_from_address_balance {
359 (vec![], gas_budget)
362 } else {
363 let mut available_address_balance_gas: u64 = 0;
366 let mut gas_objects = vec![];
367 for obj_ref in gas {
368 if let Ok(parsed) = ParsedDigest::try_from(obj_ref.2) {
369 available_address_balance_gas =
370 available_address_balance_gas.saturating_add(parsed.reservation_amount());
371 } else {
372 let obj = objects.get(&obj_ref.0);
373 let obj = *obj.ok_or(UserInputError::ObjectNotFound {
374 object_id: obj_ref.0,
375 version: Some(obj_ref.1),
376 })?;
377 gas_objects.push(obj);
378 }
379 }
380 (gas_objects, available_address_balance_gas)
381 };
382
383 check_gas_budget_and_balance(
384 &gas_objects,
385 gas_budget,
386 gas_price,
387 available_address_balance_gas,
388 protocol_config,
389 )?;
390 Ok(())
391 }
392
393 #[instrument(level = "trace", skip_all)]
396 fn check_objects(
397 transaction: &TransactionData,
398 objects: &InputObjects,
399 protocol_config: &ProtocolConfig,
400 ) -> UserInputResult<()> {
401 let mut used_objects: HashSet<SuiAddress> = HashSet::new();
403 for object in objects.iter() {
404 if object.is_mutable() {
405 fp_ensure!(
406 used_objects.insert(object.id().into()),
407 UserInputError::MutableObjectUsedMoreThanOnce {
408 object_id: object.id()
409 }
410 );
411 }
412 }
413
414 let gas_only_contains_coin_reservations = !transaction.gas().is_empty()
418 && transaction
419 .gas()
420 .iter()
421 .all(|obj_ref| ParsedDigest::is_coin_reservation_digest(&obj_ref.2));
422
423 let allow_empty_objects = protocol_config.enable_coin_reservation_obj_refs()
424 && (transaction.is_gas_paid_from_address_balance()
425 || gas_only_contains_coin_reservations);
426 if !transaction.is_genesis_tx() && objects.is_empty() && !allow_empty_objects {
427 return Err(UserInputError::ObjectInputArityViolation);
428 }
429
430 let gas_coins: HashSet<ObjectID> =
431 HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
432 for object in objects.iter() {
433 let input_object_kind = object.input_object_kind;
434
435 match &object.object {
436 ObjectReadResultKind::Object(object) => {
437 let owner_address = if gas_coins.contains(&object.id()) {
439 transaction.gas_owner()
440 } else {
441 transaction.sender()
442 };
443 let system_transaction = transaction.is_system_tx();
446 check_one_object(
447 &owner_address,
448 input_object_kind,
449 object,
450 system_transaction,
451 protocol_config,
452 )?;
453 }
454 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => (),
456 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
458 }
459 }
460
461 Ok(())
462 }
463
464 fn check_one_object(
466 owner: &SuiAddress,
467 object_kind: InputObjectKind,
468 object: &Object,
469 system_transaction: bool,
470 protocol_config: &ProtocolConfig,
471 ) -> UserInputResult {
472 if matches!(object.owner, Owner::Party { .. }) {
474 return Err(UserInputError::Unsupported(
475 "Party-owned objects are not yet supported".to_string(),
476 ));
477 }
478
479 match object_kind {
480 InputObjectKind::MovePackage(package_id) => {
481 fp_ensure!(
482 object.data.try_as_package().is_some(),
483 UserInputError::MoveObjectAsPackage {
484 object_id: package_id
485 }
486 );
487 }
488 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
489 fp_ensure!(
490 !object.is_package(),
491 UserInputError::MovePackageAsObject { object_id }
492 );
493 fp_ensure!(
494 sequence_number < SequenceNumber::MAX,
495 UserInputError::InvalidSequenceNumber
496 );
497
498 assert_eq!(
500 object.version(),
501 sequence_number,
502 "The fetched object version {} does not match the requested version {}, object id: {}",
503 object.version(),
504 sequence_number,
505 object.id(),
506 );
507
508 let expected_digest = object.digest();
510 fp_ensure!(
511 expected_digest == object_digest,
512 UserInputError::InvalidObjectDigest {
513 object_id,
514 expected_digest
515 }
516 );
517
518 match object.owner {
519 Owner::Immutable => {
520 }
522 Owner::AddressOwner(actual_owner) => {
523 fp_ensure!(
525 owner == &actual_owner,
526 UserInputError::IncorrectUserSignature {
527 error: format!(
528 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
529 ),
530 }
531 );
532 }
533 Owner::ObjectOwner(owner) => {
534 return Err(UserInputError::InvalidChildObjectArgument {
535 child_id: object.id(),
536 parent_id: owner.into(),
537 });
538 }
539 Owner::Shared { .. }
540 | Owner::ConsensusAddressOwner { .. }
541 | Owner::Party { .. } => {
542 return Err(UserInputError::NotOwnedObjectError);
545 }
546 };
547 }
548 InputObjectKind::SharedMoveObject {
549 id: object_id,
550 initial_shared_version: input_initial_shared_version,
551 mutability,
552 } => {
553 fp_ensure!(
554 object.version() < SequenceNumber::MAX,
555 UserInputError::InvalidSequenceNumber
556 );
557
558 if object_id.is_system_object() {
559 if system_transaction {
562 return Ok(());
563 }
564
565 match (object_id, mutability) {
566 (SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID, _)
568 if protocol_config.enable_forwarding_addresses() => (),
569 (SUI_SYSTEM_STATE_OBJECT_ID, _)
570 | (SUI_ADDRESS_ALIAS_STATE_OBJECT_ID, _)
571 | (SUI_COIN_REGISTRY_OBJECT_ID, _)
572 | (SUI_DISPLAY_REGISTRY_OBJECT_ID, _)
573 | (SUI_DENY_LIST_OBJECT_ID, _)
574 | (SUI_BRIDGE_OBJECT_ID, _)
575
576 | (SUI_CLOCK_OBJECT_ID, SharedObjectMutability::Immutable)
578 | (SUI_RANDOMNESS_STATE_OBJECT_ID, SharedObjectMutability::Immutable)
579 | (SUI_ACCUMULATOR_ROOT_OBJECT_ID, SharedObjectMutability::Immutable) => (),
580
581 _ => {
583 return Err(UserInputError::ImmutableParameterExpectedError {
584 object_id,
585 });
586 }
587 }
588 }
589
590 match &object.owner {
591 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
592 return Err(UserInputError::NotSharedObjectError);
594 }
595 Owner::Shared {
596 initial_shared_version: actual_initial_shared_version,
597 } => {
598 fp_ensure!(
599 input_initial_shared_version == *actual_initial_shared_version,
600 UserInputError::SharedObjectStartingVersionMismatch
601 )
602 }
603 Owner::ConsensusAddressOwner {
604 start_version: actual_initial_shared_version,
605 owner: actual_owner,
606 } => {
607 fp_ensure!(
608 input_initial_shared_version == *actual_initial_shared_version,
609 UserInputError::SharedObjectStartingVersionMismatch
610 );
611 fp_ensure!(
613 owner == actual_owner,
614 UserInputError::IncorrectUserSignature {
615 error: format!(
616 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
617 ),
618 }
619 )
620 }
621
622 Owner::Party {
623 start_version: actual_initial_shared_version,
624 permissions,
625 } => {
626 fp_ensure!(
627 input_initial_shared_version == *actual_initial_shared_version,
628 UserInputError::SharedObjectStartingVersionMismatch
629 );
630 let sender_permissions = permissions.permissions_for(owner);
632 match mutability {
633 SharedObjectMutability::Immutable => {
634 fp_ensure!(
636 sender_permissions.can_use_immutably(),
637 UserInputError::IncorrectUserSignature {
638 error: format!(
639 "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}",
640 ObjectPermission::ImmutableUsage,
641 ),
642 }
643 )
644 }
645 SharedObjectMutability::Mutable => {
646 fp_ensure!(
648 sender_permissions.can_use_mutably(),
649 UserInputError::IncorrectUserSignature {
650 error: format!(
651 "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}",
652 ObjectPermission::MutableUsage,
653 ),
654 }
655 )
656 }
657 SharedObjectMutability::NonExclusiveWrite => {
658 todo!("Party WIP")
660 }
661 }
662 }
663 }
664 }
665 };
666 Ok(())
667 }
668
669 pub fn check_gasless_object_inputs(
672 input_objects: &InputObjects,
673 protocol_config: &ProtocolConfig,
674 ) -> UserInputResult<()> {
675 let allowed_token_types =
676 sui_types::transaction::get_gasless_allowed_token_types(protocol_config);
677
678 for obj_read in input_objects.iter() {
679 let Some(object) = obj_read.as_object() else {
680 continue;
681 };
682 if object.is_package() {
683 continue;
684 }
685 match object.owner() {
686 Owner::AddressOwner(_) | Owner::ConsensusAddressOwner { .. } => (),
687 Owner::Immutable
688 | Owner::Shared { .. }
689 | Owner::ObjectOwner(_)
690 | Owner::Party { .. } => {
691 return Err(UserInputError::Unsupported(
692 "Gasless transactions only support owned object inputs".to_string(),
693 ));
694 }
695 }
696 let coin_type = object.coin_type_maybe().ok_or_else(|| {
698 UserInputError::Unsupported(
699 "Gasless transactions can only use Coin<T> object inputs, \
700 but found a non-Coin object"
701 .to_string(),
702 )
703 })?;
704 fp_ensure!(
705 allowed_token_types.contains_key(&coin_type),
706 UserInputError::Unsupported(
707 "Gasless transactions only support allowlisted types for Coin inputs"
708 .to_string()
709 )
710 );
711 }
712 Ok(())
713 }
714
715 #[instrument(level = "trace", skip_all)]
717 pub fn check_non_system_packages_to_be_published(
718 transaction: &TransactionData,
719 protocol_config: &ProtocolConfig,
720 metrics: &Arc<BytecodeVerifierMetrics>,
721 verifier_signing_config: &VerifierSigningConfig,
722 ) -> UserInputResult<()> {
723 if transaction.is_system_tx() {
725 return Ok(());
726 }
727
728 let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
729 return Ok(());
730 };
731
732 let signing_limits = Some(verifier_signing_config.limits_for_signing());
734 let mut verifier = sui_execution::verifier(protocol_config, signing_limits, metrics);
735 let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
736
737 let shared_meter_verifier_timer = metrics
739 .verifier_runtime_per_ptb_success_latency
740 .start_timer();
741
742 let verifier_status = pt
743 .non_system_packages_to_be_published()
744 .try_for_each(|module_bytes| {
745 verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
746 })
747 .map_err(|e| UserInputError::PackageVerificationTimeout { err: e.to_string() });
748
749 match verifier_status {
750 Ok(_) => {
751 shared_meter_verifier_timer.stop_and_record();
753 }
754 Err(err) => {
755 metrics
758 .verifier_runtime_per_ptb_timeout_latency
759 .observe(shared_meter_verifier_timer.stop_and_discard());
760 return Err(err);
761 }
762 };
763
764 Ok(())
765 }
766}