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_AUTHENTICATOR_STATE_OBJECT_ID, SUI_CLOCK_OBJECT_ID, SUI_CLOCK_OBJECT_SHARED_VERSION,
25 SUI_RANDOMNESS_STATE_OBJECT_ID,
26 };
27 use sui_types::{
28 base_types::{SequenceNumber, SuiAddress},
29 error::SuiError,
30 fp_bail, fp_ensure,
31 gas::SuiGasStatus,
32 object::{Object, Owner},
33 };
34 use tracing::error;
35 use tracing::instrument;
36
37 trait IntoChecked {
38 fn into_checked(self) -> CheckedInputObjects;
39 }
40
41 impl IntoChecked for InputObjects {
42 fn into_checked(self) -> CheckedInputObjects {
43 CheckedInputObjects::new_with_checked_transaction_inputs(self)
44 }
45 }
46
47 fn get_gas_status(
52 objects: &InputObjects,
53 gas: &[ObjectRef],
54 protocol_config: &ProtocolConfig,
55 reference_gas_price: u64,
56 transaction: &TransactionData,
57 gas_ownership_checks: bool,
58 ) -> SuiResult<SuiGasStatus> {
59 if transaction.kind().is_system_tx() {
60 Ok(SuiGasStatus::new_unmetered())
61 } else {
62 check_gas(
63 objects,
64 protocol_config,
65 reference_gas_price,
66 gas,
67 transaction,
68 gas_ownership_checks,
69 )
70 }
71 }
72
73 #[instrument(level = "trace", skip_all)]
74 pub fn check_transaction_input(
75 protocol_config: &ProtocolConfig,
76 reference_gas_price: u64,
77 transaction: &TransactionData,
78 input_objects: InputObjects,
79 receiving_objects: &ReceivingObjects,
80 metrics: &Arc<BytecodeVerifierMetrics>,
81 verifier_signing_config: &VerifierSigningConfig,
82 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
83 let gas_status = check_transaction_input_inner(
84 protocol_config,
85 reference_gas_price,
86 transaction,
87 &input_objects,
88 &[],
89 )?;
90 check_receiving_objects(&input_objects, receiving_objects)?;
91 check_non_system_packages_to_be_published(
93 transaction,
94 protocol_config,
95 metrics,
96 verifier_signing_config,
97 )?;
98
99 Ok((gas_status, input_objects.into_checked()))
100 }
101
102 pub fn check_transaction_input_with_given_gas(
103 protocol_config: &ProtocolConfig,
104 reference_gas_price: u64,
105 transaction: &TransactionData,
106 mut input_objects: InputObjects,
107 receiving_objects: ReceivingObjects,
108 gas_object: Object,
109 metrics: &Arc<BytecodeVerifierMetrics>,
110 verifier_signing_config: &VerifierSigningConfig,
111 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
112 let gas_object_ref = gas_object.compute_object_reference();
113 input_objects.push(ObjectReadResult::new_from_gas_object(&gas_object));
114
115 let gas_status = check_transaction_input_inner(
116 protocol_config,
117 reference_gas_price,
118 transaction,
119 &input_objects,
120 &[gas_object_ref],
121 )?;
122 check_receiving_objects(&input_objects, &receiving_objects)?;
123 check_non_system_packages_to_be_published(
125 transaction,
126 protocol_config,
127 metrics,
128 verifier_signing_config,
129 )?;
130
131 Ok((gas_status, input_objects.into_checked()))
132 }
133
134 #[instrument(level = "trace", skip_all)]
139 pub fn check_certificate_input(
140 cert: &VerifiedExecutableTransaction,
141 input_objects: InputObjects,
142 protocol_config: &ProtocolConfig,
143 reference_gas_price: u64,
144 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
145 let transaction = cert.data().transaction_data();
146 let gas_status = check_transaction_input_inner(
147 protocol_config,
148 reference_gas_price,
149 transaction,
150 &input_objects,
151 &[],
152 )?;
153 Ok((gas_status, input_objects.into_checked()))
157 }
158
159 pub fn check_dev_inspect_input(
162 config: &ProtocolConfig,
163 transaction: &TransactionData,
164 input_objects: InputObjects,
165 _receiving_objects: ReceivingObjects,
167 reference_gas_price: u64,
168 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
169 let kind = transaction.kind();
170 kind.validity_check(config)?;
171 if kind.is_system_tx() {
172 return Err(UserInputError::Unsupported(format!(
173 "Transaction kind {} is not supported in dev-inspect",
174 kind
175 ))
176 .into());
177 }
178 let mut used_objects: HashSet<SuiAddress> = HashSet::new();
179 for input_object in input_objects.iter() {
180 let Some(object) = input_object.as_object() else {
181 continue;
183 };
184
185 if !object.is_immutable() {
186 fp_ensure!(
187 used_objects.insert(object.id().into()),
188 UserInputError::MutableObjectUsedMoreThanOnce {
189 object_id: object.id()
190 }
191 .into()
192 );
193 }
194 }
195
196 let gas_status = get_gas_status(
197 &input_objects,
198 &transaction.gas_data().payment, config,
200 reference_gas_price,
201 transaction,
202 false, )?;
204
205 Ok((gas_status, input_objects.into_checked()))
206 }
207
208 fn check_transaction_input_inner(
210 protocol_config: &ProtocolConfig,
211 reference_gas_price: u64,
212 transaction: &TransactionData,
213 input_objects: &InputObjects,
214 gas_override: &[ObjectRef],
216 ) -> SuiResult<SuiGasStatus> {
217 let gas = if gas_override.is_empty() {
218 transaction.gas()
219 } else {
220 gas_override
221 };
222
223 let gas_status = get_gas_status(
224 input_objects,
225 gas,
226 protocol_config,
227 reference_gas_price,
228 transaction,
229 true, )?;
231 check_objects(transaction, input_objects)?;
232
233 Ok(gas_status)
234 }
235
236 fn check_receiving_objects(
237 input_objects: &InputObjects,
238 receiving_objects: &ReceivingObjects,
239 ) -> Result<(), SuiError> {
240 let mut objects_in_txn: HashSet<_> = input_objects
241 .object_kinds()
242 .map(|x| x.object_id())
243 .collect();
244
245 for ReceivingObjectReadResult {
253 object_ref: (object_id, version, object_digest),
254 object,
255 } in receiving_objects.iter()
256 {
257 fp_ensure!(
258 *version < SequenceNumber::MAX,
259 UserInputError::InvalidSequenceNumber.into()
260 );
261
262 let Some(object) = object.as_object() else {
263 continue;
265 };
266
267 if !(object.owner.is_address_owned()
268 && object.version() == *version
269 && object.digest() == *object_digest)
270 {
271 fp_ensure!(
273 object.version() == *version,
274 UserInputError::ObjectVersionUnavailableForConsumption {
275 provided_obj_ref: (*object_id, *version, *object_digest),
276 current_version: object.version(),
277 }
278 .into()
279 );
280
281 fp_ensure!(
283 !object.is_package(),
284 UserInputError::MovePackageAsObject {
285 object_id: *object_id
286 }
287 .into()
288 );
289
290 let expected_digest = object.digest();
292 fp_ensure!(
293 expected_digest == *object_digest,
294 UserInputError::InvalidObjectDigest {
295 object_id: *object_id,
296 expected_digest
297 }
298 .into()
299 );
300
301 match object.owner {
302 Owner::AddressOwner(_) => {
303 debug_assert!(
304 false,
305 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
306 (*object_id, *version, *object_id),
307 object
308 );
309 error!(
310 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
311 (*object_id, *version, *object_id),
312 object
313 );
314 fp_bail!(
317 UserInputError::ObjectNotFound {
318 object_id: *object_id,
319 version: Some(*version),
320 }
321 .into()
322 )
323 }
324 Owner::ObjectOwner(owner) => {
325 fp_bail!(
326 UserInputError::InvalidChildObjectArgument {
327 child_id: object.id(),
328 parent_id: owner.into(),
329 }
330 .into()
331 )
332 }
333 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => {
334 fp_bail!(UserInputError::NotSharedObjectError.into())
335 }
336 Owner::Immutable => fp_bail!(
337 UserInputError::MutableParameterExpected {
338 object_id: *object_id
339 }
340 .into()
341 ),
342 };
343 }
344
345 fp_ensure!(
346 !objects_in_txn.contains(object_id),
347 UserInputError::DuplicateObjectRefInput.into()
348 );
349
350 objects_in_txn.insert(*object_id);
351 }
352 Ok(())
353 }
354
355 #[instrument(level = "trace", skip_all)]
358 fn check_gas(
359 objects: &InputObjects,
360 protocol_config: &ProtocolConfig,
361 reference_gas_price: u64,
362 gas: &[ObjectRef],
363 transaction: &TransactionData,
364 gas_ownership_checks: bool,
365 ) -> SuiResult<SuiGasStatus> {
366 let gas_budget = transaction.gas_budget();
367 let gas_price = transaction.gas_price();
368 let gas_paid_from_address_balance = transaction.is_gas_paid_from_address_balance();
369
370 let gas_status =
371 SuiGasStatus::new(gas_budget, gas_price, reference_gas_price, protocol_config)?;
372
373 let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
376 let mut gas_objects = vec![];
377 for obj_ref in gas {
378 let obj = objects.get(&obj_ref.0);
379 let obj = *obj.ok_or(UserInputError::ObjectNotFound {
380 object_id: obj_ref.0,
381 version: Some(obj_ref.1),
382 })?;
383 gas_objects.push(obj);
384 }
385
386 if !gas_paid_from_address_balance {
389 if gas_ownership_checks {
390 gas_status.check_gas_objects(&gas_objects)?;
391 }
392 gas_status.check_gas_data(&gas_objects, gas_budget)?;
393 }
394 Ok(gas_status)
395 }
396
397 #[instrument(level = "trace", skip_all)]
400 fn check_objects(transaction: &TransactionData, objects: &InputObjects) -> 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 if !transaction.is_genesis_tx() && objects.is_empty() {
415 return Err(UserInputError::ObjectInputArityViolation);
416 }
417
418 let gas_coins: HashSet<ObjectID> =
419 HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
420 for object in objects.iter() {
421 let input_object_kind = object.input_object_kind;
422
423 match &object.object {
424 ObjectReadResultKind::Object(object) => {
425 let owner_address = if gas_coins.contains(&object.id()) {
427 transaction.gas_owner()
428 } else {
429 transaction.sender()
430 };
431 let system_transaction = transaction.is_system_tx();
434 check_one_object(
435 &owner_address,
436 input_object_kind,
437 object,
438 system_transaction,
439 )?;
440 }
441 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => (),
443 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
445 }
446 }
447
448 Ok(())
449 }
450
451 fn check_one_object(
453 owner: &SuiAddress,
454 object_kind: InputObjectKind,
455 object: &Object,
456 system_transaction: bool,
457 ) -> UserInputResult {
458 match object_kind {
459 InputObjectKind::MovePackage(package_id) => {
460 fp_ensure!(
461 object.data.try_as_package().is_some(),
462 UserInputError::MoveObjectAsPackage {
463 object_id: package_id
464 }
465 );
466 }
467 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
468 fp_ensure!(
469 !object.is_package(),
470 UserInputError::MovePackageAsObject { object_id }
471 );
472 fp_ensure!(
473 sequence_number < SequenceNumber::MAX,
474 UserInputError::InvalidSequenceNumber
475 );
476
477 assert_eq!(
479 object.version(),
480 sequence_number,
481 "The fetched object version {} does not match the requested version {}, object id: {}",
482 object.version(),
483 sequence_number,
484 object.id(),
485 );
486
487 let expected_digest = object.digest();
489 fp_ensure!(
490 expected_digest == object_digest,
491 UserInputError::InvalidObjectDigest {
492 object_id,
493 expected_digest
494 }
495 );
496
497 match object.owner {
498 Owner::Immutable => {
499 }
501 Owner::AddressOwner(actual_owner) => {
502 fp_ensure!(
504 owner == &actual_owner,
505 UserInputError::IncorrectUserSignature {
506 error: format!(
507 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
508 ),
509 }
510 );
511 }
512 Owner::ObjectOwner(owner) => {
513 return Err(UserInputError::InvalidChildObjectArgument {
514 child_id: object.id(),
515 parent_id: owner.into(),
516 });
517 }
518 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => {
519 return Err(UserInputError::NotOwnedObjectError);
522 }
523 };
524 }
525 InputObjectKind::SharedMoveObject {
526 id: SUI_CLOCK_OBJECT_ID,
527 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
528 mutability: SharedObjectMutability::Mutable,
529 } => {
530 if system_transaction {
533 return Ok(());
534 } else {
535 return Err(UserInputError::ImmutableParameterExpectedError {
536 object_id: SUI_CLOCK_OBJECT_ID,
537 });
538 }
539 }
540 InputObjectKind::SharedMoveObject {
541 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
542 ..
543 } => {
544 if system_transaction {
545 return Ok(());
546 } else {
547 return Err(UserInputError::InaccessibleSystemObject {
548 object_id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
549 });
550 }
551 }
552 InputObjectKind::SharedMoveObject {
553 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
554 mutability: SharedObjectMutability::Mutable,
555 ..
556 } => {
557 if system_transaction {
560 return Ok(());
561 } else {
562 return Err(UserInputError::ImmutableParameterExpectedError {
563 object_id: SUI_RANDOMNESS_STATE_OBJECT_ID,
564 });
565 }
566 }
567 InputObjectKind::SharedMoveObject {
568 id: object_id,
569 initial_shared_version: input_initial_shared_version,
570 ..
571 } => {
572 fp_ensure!(
573 object.version() < SequenceNumber::MAX,
574 UserInputError::InvalidSequenceNumber
575 );
576
577 match &object.owner {
578 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
579 return Err(UserInputError::NotSharedObjectError);
581 }
582 Owner::Shared {
583 initial_shared_version: actual_initial_shared_version,
584 } => {
585 fp_ensure!(
586 input_initial_shared_version == *actual_initial_shared_version,
587 UserInputError::SharedObjectStartingVersionMismatch
588 )
589 }
590 Owner::ConsensusAddressOwner {
591 start_version: actual_initial_shared_version,
592 owner: actual_owner,
593 } => {
594 fp_ensure!(
595 input_initial_shared_version == *actual_initial_shared_version,
596 UserInputError::SharedObjectStartingVersionMismatch
597 );
598 fp_ensure!(
600 owner == actual_owner,
601 UserInputError::IncorrectUserSignature {
602 error: format!(
603 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
604 ),
605 }
606 )
607 }
608 }
609 }
610 };
611 Ok(())
612 }
613
614 #[instrument(level = "trace", skip_all)]
616 pub fn check_non_system_packages_to_be_published(
617 transaction: &TransactionData,
618 protocol_config: &ProtocolConfig,
619 metrics: &Arc<BytecodeVerifierMetrics>,
620 verifier_signing_config: &VerifierSigningConfig,
621 ) -> UserInputResult<()> {
622 if transaction.is_system_tx() {
624 return Ok(());
625 }
626
627 let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
628 return Ok(());
629 };
630
631 let signing_limits = Some(verifier_signing_config.limits_for_signing());
633 let mut verifier = sui_execution::verifier(protocol_config, signing_limits, metrics);
634 let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
635
636 let shared_meter_verifier_timer = metrics
638 .verifier_runtime_per_ptb_success_latency
639 .start_timer();
640
641 let verifier_status = pt
642 .non_system_packages_to_be_published()
643 .try_for_each(|module_bytes| {
644 verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
645 })
646 .map_err(|e| UserInputError::PackageVerificationTimeout { err: e.to_string() });
647
648 match verifier_status {
649 Ok(_) => {
650 shared_meter_verifier_timer.stop_and_record();
652 }
653 Err(err) => {
654 metrics
657 .verifier_runtime_per_ptb_timeout_latency
658 .observe(shared_meter_verifier_timer.stop_and_discard());
659 return Err(err);
660 }
661 };
662
663 Ok(())
664 }
665}