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 pub fn get_gas_status(
52 objects: &InputObjects,
53 gas: &[ObjectRef],
54 protocol_config: &ProtocolConfig,
55 reference_gas_price: u64,
56 transaction: &TransactionData,
57 ) -> SuiResult<SuiGasStatus> {
58 check_gas(
59 objects,
60 protocol_config,
61 reference_gas_price,
62 gas,
63 transaction.gas_budget(),
64 transaction.gas_price(),
65 transaction.kind(),
66 )
67 }
68
69 #[instrument(level = "trace", skip_all)]
70 pub fn check_transaction_input(
71 protocol_config: &ProtocolConfig,
72 reference_gas_price: u64,
73 transaction: &TransactionData,
74 input_objects: InputObjects,
75 receiving_objects: &ReceivingObjects,
76 metrics: &Arc<BytecodeVerifierMetrics>,
77 verifier_signing_config: &VerifierSigningConfig,
78 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
79 let gas_status = check_transaction_input_inner(
80 protocol_config,
81 reference_gas_price,
82 transaction,
83 &input_objects,
84 &[],
85 )?;
86 check_receiving_objects(&input_objects, receiving_objects)?;
87 check_non_system_packages_to_be_published(
89 transaction,
90 protocol_config,
91 metrics,
92 verifier_signing_config,
93 )?;
94
95 Ok((gas_status, input_objects.into_checked()))
96 }
97
98 pub fn check_transaction_input_with_given_gas(
99 protocol_config: &ProtocolConfig,
100 reference_gas_price: u64,
101 transaction: &TransactionData,
102 mut input_objects: InputObjects,
103 receiving_objects: ReceivingObjects,
104 gas_object: Object,
105 metrics: &Arc<BytecodeVerifierMetrics>,
106 verifier_signing_config: &VerifierSigningConfig,
107 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
108 let gas_object_ref = gas_object.compute_object_reference();
109 input_objects.push(ObjectReadResult::new_from_gas_object(&gas_object));
110
111 let gas_status = check_transaction_input_inner(
112 protocol_config,
113 reference_gas_price,
114 transaction,
115 &input_objects,
116 &[gas_object_ref],
117 )?;
118 check_receiving_objects(&input_objects, &receiving_objects)?;
119 check_non_system_packages_to_be_published(
121 transaction,
122 protocol_config,
123 metrics,
124 verifier_signing_config,
125 )?;
126
127 Ok((gas_status, input_objects.into_checked()))
128 }
129
130 #[instrument(level = "trace", skip_all)]
135 pub fn check_certificate_input(
136 cert: &VerifiedExecutableTransaction,
137 input_objects: InputObjects,
138 protocol_config: &ProtocolConfig,
139 reference_gas_price: u64,
140 ) -> SuiResult<(SuiGasStatus, CheckedInputObjects)> {
141 let transaction = cert.data().transaction_data();
142 let gas_status = check_transaction_input_inner(
143 protocol_config,
144 reference_gas_price,
145 transaction,
146 &input_objects,
147 &[],
148 )?;
149 Ok((gas_status, input_objects.into_checked()))
153 }
154
155 pub fn check_dev_inspect_input(
158 config: &ProtocolConfig,
159 kind: &TransactionKind,
160 input_objects: InputObjects,
161 _receiving_objects: ReceivingObjects,
163 ) -> SuiResult<CheckedInputObjects> {
164 kind.validity_check(config)?;
165 if kind.is_system_tx() {
166 return Err(UserInputError::Unsupported(format!(
167 "Transaction kind {} is not supported in dev-inspect",
168 kind
169 ))
170 .into());
171 }
172 let mut used_objects: HashSet<SuiAddress> = HashSet::new();
173 for input_object in input_objects.iter() {
174 let Some(object) = input_object.as_object() else {
175 continue;
177 };
178
179 if !object.is_immutable() {
180 fp_ensure!(
181 used_objects.insert(object.id().into()),
182 UserInputError::MutableObjectUsedMoreThanOnce {
183 object_id: object.id()
184 }
185 .into()
186 );
187 }
188 }
189
190 Ok(input_objects.into_checked())
191 }
192
193 fn check_transaction_input_inner(
195 protocol_config: &ProtocolConfig,
196 reference_gas_price: u64,
197 transaction: &TransactionData,
198 input_objects: &InputObjects,
199 gas_override: &[ObjectRef],
201 ) -> SuiResult<SuiGasStatus> {
202 let gas = if gas_override.is_empty() {
203 transaction.gas()
204 } else {
205 gas_override
206 };
207
208 let gas_status = get_gas_status(
209 input_objects,
210 gas,
211 protocol_config,
212 reference_gas_price,
213 transaction,
214 )?;
215 check_objects(transaction, input_objects)?;
216
217 Ok(gas_status)
218 }
219
220 fn check_receiving_objects(
221 input_objects: &InputObjects,
222 receiving_objects: &ReceivingObjects,
223 ) -> Result<(), SuiError> {
224 let mut objects_in_txn: HashSet<_> = input_objects
225 .object_kinds()
226 .map(|x| x.object_id())
227 .collect();
228
229 for ReceivingObjectReadResult {
237 object_ref: (object_id, version, object_digest),
238 object,
239 } in receiving_objects.iter()
240 {
241 fp_ensure!(
242 *version < SequenceNumber::MAX,
243 UserInputError::InvalidSequenceNumber.into()
244 );
245
246 let Some(object) = object.as_object() else {
247 continue;
249 };
250
251 if !(object.owner.is_address_owned()
252 && object.version() == *version
253 && object.digest() == *object_digest)
254 {
255 fp_ensure!(
257 object.version() == *version,
258 UserInputError::ObjectVersionUnavailableForConsumption {
259 provided_obj_ref: (*object_id, *version, *object_digest),
260 current_version: object.version(),
261 }
262 .into()
263 );
264
265 fp_ensure!(
267 !object.is_package(),
268 UserInputError::MovePackageAsObject {
269 object_id: *object_id
270 }
271 .into()
272 );
273
274 let expected_digest = object.digest();
276 fp_ensure!(
277 expected_digest == *object_digest,
278 UserInputError::InvalidObjectDigest {
279 object_id: *object_id,
280 expected_digest
281 }
282 .into()
283 );
284
285 match object.owner {
286 Owner::AddressOwner(_) => {
287 debug_assert!(
288 false,
289 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
290 (*object_id, *version, *object_id),
291 object
292 );
293 error!(
294 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
295 (*object_id, *version, *object_id),
296 object
297 );
298 fp_bail!(
301 UserInputError::ObjectNotFound {
302 object_id: *object_id,
303 version: Some(*version),
304 }
305 .into()
306 )
307 }
308 Owner::ObjectOwner(owner) => {
309 fp_bail!(
310 UserInputError::InvalidChildObjectArgument {
311 child_id: object.id(),
312 parent_id: owner.into(),
313 }
314 .into()
315 )
316 }
317 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => {
318 fp_bail!(UserInputError::NotSharedObjectError.into())
319 }
320 Owner::Immutable => fp_bail!(
321 UserInputError::MutableParameterExpected {
322 object_id: *object_id
323 }
324 .into()
325 ),
326 };
327 }
328
329 fp_ensure!(
330 !objects_in_txn.contains(object_id),
331 UserInputError::DuplicateObjectRefInput.into()
332 );
333
334 objects_in_txn.insert(*object_id);
335 }
336 Ok(())
337 }
338
339 #[instrument(level = "trace", skip_all)]
342 fn check_gas(
343 objects: &InputObjects,
344 protocol_config: &ProtocolConfig,
345 reference_gas_price: u64,
346 gas: &[ObjectRef],
347 gas_budget: u64,
348 gas_price: u64,
349 tx_kind: &TransactionKind,
350 ) -> SuiResult<SuiGasStatus> {
351 if tx_kind.is_system_tx() {
352 Ok(SuiGasStatus::new_unmetered())
353 } else {
354 let gas_status =
355 SuiGasStatus::new(gas_budget, gas_price, reference_gas_price, protocol_config)?;
356
357 let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
360 let mut gas_objects = vec![];
361 for obj_ref in gas {
362 let obj = objects.get(&obj_ref.0);
363 let obj = *obj.ok_or(UserInputError::ObjectNotFound {
364 object_id: obj_ref.0,
365 version: Some(obj_ref.1),
366 })?;
367 gas_objects.push(obj);
368 }
369 gas_status.check_gas_balance(&gas_objects, gas_budget)?;
370 Ok(gas_status)
371 }
372 }
373
374 #[instrument(level = "trace", skip_all)]
377 fn check_objects(transaction: &TransactionData, objects: &InputObjects) -> UserInputResult<()> {
378 let mut used_objects: HashSet<SuiAddress> = HashSet::new();
380 for object in objects.iter() {
381 if object.is_mutable() {
382 fp_ensure!(
383 used_objects.insert(object.id().into()),
384 UserInputError::MutableObjectUsedMoreThanOnce {
385 object_id: object.id()
386 }
387 );
388 }
389 }
390
391 if !transaction.is_genesis_tx() && objects.is_empty() {
392 return Err(UserInputError::ObjectInputArityViolation);
393 }
394
395 let gas_coins: HashSet<ObjectID> =
396 HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
397 for object in objects.iter() {
398 let input_object_kind = object.input_object_kind;
399
400 match &object.object {
401 ObjectReadResultKind::Object(object) => {
402 let owner_address = if gas_coins.contains(&object.id()) {
404 transaction.gas_owner()
405 } else {
406 transaction.sender()
407 };
408 let system_transaction = transaction.is_system_tx();
411 check_one_object(
412 &owner_address,
413 input_object_kind,
414 object,
415 system_transaction,
416 )?;
417 }
418 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => (),
420 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
422 }
423 }
424
425 Ok(())
426 }
427
428 fn check_one_object(
430 owner: &SuiAddress,
431 object_kind: InputObjectKind,
432 object: &Object,
433 system_transaction: bool,
434 ) -> UserInputResult {
435 match object_kind {
436 InputObjectKind::MovePackage(package_id) => {
437 fp_ensure!(
438 object.data.try_as_package().is_some(),
439 UserInputError::MoveObjectAsPackage {
440 object_id: package_id
441 }
442 );
443 }
444 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
445 fp_ensure!(
446 !object.is_package(),
447 UserInputError::MovePackageAsObject { object_id }
448 );
449 fp_ensure!(
450 sequence_number < SequenceNumber::MAX,
451 UserInputError::InvalidSequenceNumber
452 );
453
454 assert_eq!(
456 object.version(),
457 sequence_number,
458 "The fetched object version {} does not match the requested version {}, object id: {}",
459 object.version(),
460 sequence_number,
461 object.id(),
462 );
463
464 let expected_digest = object.digest();
466 fp_ensure!(
467 expected_digest == object_digest,
468 UserInputError::InvalidObjectDigest {
469 object_id,
470 expected_digest
471 }
472 );
473
474 match object.owner {
475 Owner::Immutable => {
476 }
478 Owner::AddressOwner(actual_owner) => {
479 fp_ensure!(
481 owner == &actual_owner,
482 UserInputError::IncorrectUserSignature {
483 error: format!(
484 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
485 ),
486 }
487 );
488 }
489 Owner::ObjectOwner(owner) => {
490 return Err(UserInputError::InvalidChildObjectArgument {
491 child_id: object.id(),
492 parent_id: owner.into(),
493 });
494 }
495 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => {
496 return Err(UserInputError::NotOwnedObjectError);
499 }
500 };
501 }
502 InputObjectKind::SharedMoveObject {
503 id: SUI_CLOCK_OBJECT_ID,
504 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
505 mutability: SharedObjectMutability::Mutable,
506 } => {
507 if system_transaction {
510 return Ok(());
511 } else {
512 return Err(UserInputError::ImmutableParameterExpectedError {
513 object_id: SUI_CLOCK_OBJECT_ID,
514 });
515 }
516 }
517 InputObjectKind::SharedMoveObject {
518 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
519 ..
520 } => {
521 if system_transaction {
522 return Ok(());
523 } else {
524 return Err(UserInputError::InaccessibleSystemObject {
525 object_id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
526 });
527 }
528 }
529 InputObjectKind::SharedMoveObject {
530 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
531 mutability: SharedObjectMutability::Mutable,
532 ..
533 } => {
534 if system_transaction {
537 return Ok(());
538 } else {
539 return Err(UserInputError::ImmutableParameterExpectedError {
540 object_id: SUI_RANDOMNESS_STATE_OBJECT_ID,
541 });
542 }
543 }
544 InputObjectKind::SharedMoveObject {
545 id: object_id,
546 initial_shared_version: input_initial_shared_version,
547 ..
548 } => {
549 fp_ensure!(
550 object.version() < SequenceNumber::MAX,
551 UserInputError::InvalidSequenceNumber
552 );
553
554 match &object.owner {
555 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
556 return Err(UserInputError::NotSharedObjectError);
558 }
559 Owner::Shared {
560 initial_shared_version: actual_initial_shared_version,
561 } => {
562 fp_ensure!(
563 input_initial_shared_version == *actual_initial_shared_version,
564 UserInputError::SharedObjectStartingVersionMismatch
565 )
566 }
567 Owner::ConsensusAddressOwner {
568 start_version: actual_initial_shared_version,
569 owner: actual_owner,
570 } => {
571 fp_ensure!(
572 input_initial_shared_version == *actual_initial_shared_version,
573 UserInputError::SharedObjectStartingVersionMismatch
574 );
575 fp_ensure!(
577 owner == actual_owner,
578 UserInputError::IncorrectUserSignature {
579 error: format!(
580 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
581 ),
582 }
583 )
584 }
585 }
586 }
587 };
588 Ok(())
589 }
590
591 #[instrument(level = "trace", skip_all)]
593 pub fn check_non_system_packages_to_be_published(
594 transaction: &TransactionData,
595 protocol_config: &ProtocolConfig,
596 metrics: &Arc<BytecodeVerifierMetrics>,
597 verifier_signing_config: &VerifierSigningConfig,
598 ) -> UserInputResult<()> {
599 if transaction.is_system_tx() {
601 return Ok(());
602 }
603
604 let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
605 return Ok(());
606 };
607
608 let signing_limits = Some(verifier_signing_config.limits_for_signing());
610 let mut verifier = sui_execution::verifier(protocol_config, signing_limits, metrics);
611 let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
612
613 let shared_meter_verifier_timer = metrics
615 .verifier_runtime_per_ptb_success_latency
616 .start_timer();
617
618 let verifier_status = pt
619 .non_system_packages_to_be_published()
620 .try_for_each(|module_bytes| {
621 verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
622 })
623 .map_err(|e| UserInputError::PackageVerificationTimeout { err: e.to_string() });
624
625 match verifier_status {
626 Ok(_) => {
627 shared_meter_verifier_timer.stop_and_record();
629 }
630 Err(err) => {
631 metrics
634 .verifier_runtime_per_ptb_timeout_latency
635 .observe(shared_meter_verifier_timer.stop_and_discard());
636 return Err(err);
637 }
638 };
639
640 Ok(())
641 }
642}