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