1use crate::execution_mode::ExecutionMode;
5use crate::gas_charger::GasCharger;
6use move_vm_runtime::runtime::MoveRuntime;
7use mysten_common::{ZipDebugEqIteratorExt, debug_fatal};
8use mysten_metrics::monitored_scope;
9use parking_lot::RwLock;
10use std::cell::RefCell;
11use std::collections::{BTreeMap, BTreeSet, HashSet};
12use std::sync::Arc;
13use sui_protocol_config::ProtocolConfig;
14use sui_types::accumulator_event::AccumulatorEvent;
15use sui_types::accumulator_root::{
16 AccumulatorObjId, AccumulatorValue as AccumulatorRootValue, EmptyUnsettledObjectFunds,
17 UnsettledObjectFundsRead,
18};
19use sui_types::base_types::{SystemObjectVersions, VersionDigest};
20use sui_types::coin_reservation::ParsedDigest;
21use sui_types::committee::EpochId;
22use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
23use sui_types::effects::{
24 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
25 TransactionEffectsV2, TransactionEvents,
26};
27use sui_types::error::SuiErrorKind;
28use sui_types::execution::{
29 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
30};
31use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
32use sui_types::inner_temporary_store::InnerTemporaryStore;
33use sui_types::object::Data;
34use sui_types::storage::{BackingStore, DenyListResult, ObjectFundsResolver, PackageObject};
35use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
36use sui_types::transaction::{Command, GasData, TransactionKind, is_gasless_transaction};
37use sui_types::{
38 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID,
39 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
40 digests::ObjectDigest,
41 effects::EffectsObjectChange,
42 error::{ExecutionError, SuiResult},
43 gas::GasCostSummary,
44 object::Object,
45 object::Owner,
46 storage::{BackingPackageStore, RuntimeObjectResolver, Storage},
47 transaction::InputObjects,
48};
49use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
50
51pub(crate) mod invariants;
52use invariants::InvariantChecker;
53
54type AllowanceIds = BTreeMap<(SuiAddress, TypeTag), Vec<ObjectID>>;
56
57#[derive(Default)]
58struct PostExecutionCheckInputs {
59 input_reservations: BTreeMap<(SuiAddress, TypeTag), u64>,
62 allowance_ids: AllowanceIds,
65 advance_epoch_gas_summary: Option<(u64, u64)>,
68 is_genesis: bool,
70 declared_packages: Option<Vec<(usize, BTreeSet<ObjectID>)>>,
73}
74
75impl PostExecutionCheckInputs {
76 fn new(transaction: (&TransactionKind, &GasData, SuiAddress), enable_gasless: bool) -> Self {
77 let (transaction_kind, gas_data, transaction_signer) = transaction;
78 let (input_reservations, allowance_ids) = compute_input_reservations(
79 transaction_kind,
80 gas_data,
81 transaction_signer,
82 enable_gasless,
83 );
84 Self {
85 input_reservations,
86 allowance_ids,
87 advance_epoch_gas_summary: transaction_kind.get_advance_epoch_tx_gas_summary(),
88 is_genesis: matches!(transaction_kind, TransactionKind::Genesis(_)),
89 declared_packages: declared_packages(transaction_kind),
90 }
91 }
92}
93
94pub struct TemporaryStore<'backing> {
95 store: &'backing dyn BackingStore,
101 tx_digest: TransactionDigest,
102 input_objects: BTreeMap<ObjectID, Object>,
103 post_execution_check_inputs: PostExecutionCheckInputs,
106
107 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
110
111 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
112 lamport_timestamp: SequenceNumber,
114 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
117 execution_results: ExecutionResultsV2,
118 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
120 protocol_config: &'backing ProtocolConfig,
121
122 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
125
126 receiving_objects: Vec<ObjectRef>,
129
130 cur_epoch: EpochId,
133
134 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
137
138 invariants: InvariantChecker,
140
141 system_object_versions: SystemObjectVersions,
143
144 loaded_system_objects: RefCell<BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>>,
148
149 unsettled_object_funds: &'backing dyn UnsettledObjectFundsRead,
150}
151
152impl<'backing> TemporaryStore<'backing> {
153 #[allow(clippy::too_many_arguments)]
156 pub(crate) fn new(
157 store: &'backing dyn BackingStore,
158 input_objects: InputObjects,
159 receiving_objects: Vec<ObjectRef>,
160 tx_digest: TransactionDigest,
161 protocol_config: &'backing ProtocolConfig,
162 cur_epoch: EpochId,
163 system_object_versions: SystemObjectVersions,
164 transaction: (&TransactionKind, &GasData, SuiAddress),
165 unsettled_object_funds: &'backing dyn UnsettledObjectFundsRead,
166 ) -> Self {
167 let post_execution_check_inputs =
168 PostExecutionCheckInputs::new(transaction, protocol_config.enable_gasless());
169 Self::new_with_input_objects(
170 store,
171 input_objects,
172 receiving_objects,
173 tx_digest,
174 protocol_config,
175 cur_epoch,
176 system_object_versions,
177 post_execution_check_inputs,
178 unsettled_object_funds,
179 )
180 }
181
182 pub(crate) fn new_for_genesis_state_update(
183 store: &'backing dyn BackingStore,
184 tx_digest: TransactionDigest,
185 protocol_config: &'backing ProtocolConfig,
186 ) -> Self {
187 Self::new_with_input_objects(
188 store,
189 InputObjects::new(vec![]),
190 vec![],
191 tx_digest,
192 protocol_config,
193 0,
194 SystemObjectVersions::empty(),
195 PostExecutionCheckInputs {
196 is_genesis: true,
197 ..Default::default()
198 },
199 &EmptyUnsettledObjectFunds,
202 )
203 }
204
205 fn new_with_input_objects(
206 store: &'backing dyn BackingStore,
207 input_objects: InputObjects,
208 receiving_objects: Vec<ObjectRef>,
209 tx_digest: TransactionDigest,
210 protocol_config: &'backing ProtocolConfig,
211 cur_epoch: EpochId,
212 system_object_versions: SystemObjectVersions,
213 post_execution_check_inputs: PostExecutionCheckInputs,
214 unsettled_object_funds: &'backing dyn UnsettledObjectFundsRead,
215 ) -> Self {
216 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
217 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
218
219 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
220 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
221 let objects = input_objects.into_object_map();
222 #[cfg(debug_assertions)]
223 {
224 assert!(
226 objects
227 .keys()
228 .collect::<HashSet<_>>()
229 .intersection(
230 &receiving_objects
231 .iter()
232 .map(|oref| &oref.0)
233 .collect::<HashSet<_>>()
234 )
235 .next()
236 .is_none()
237 );
238 }
239 Self {
240 store,
241 tx_digest,
242 input_objects: objects,
243 non_exclusive_input_original_versions,
244 stream_ended_consensus_objects,
245 lamport_timestamp,
246 mutable_input_refs,
247 execution_results: ExecutionResultsV2::default(),
248 protocol_config,
249 loaded_runtime_objects: BTreeMap::new(),
250 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
251 receiving_objects,
252 cur_epoch,
253 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
254 post_execution_check_inputs,
255 invariants: InvariantChecker::default(),
256 system_object_versions,
257 loaded_system_objects: RefCell::new(BTreeMap::new()),
258 unsettled_object_funds,
259 }
260 }
261
262 pub fn load_implicitly_read_system_object(&self, object_id: &ObjectID) -> Option<Object> {
268 let version = match self.system_object_versions.get(object_id) {
269 Some(version) => version,
270 None => {
271 debug_fatal!(
272 "system_object_versions must contain entry for object_id: {:?}",
273 object_id
274 );
275 return None;
276 }
277 };
278 let object = self
279 .store
280 .load_implicitly_read_system_object(object_id, version)?;
283 self.loaded_system_objects
286 .borrow_mut()
287 .insert(*object_id, (object.version(), object.digest()));
288 Some(object)
289 }
290
291 pub fn unsettled_object_funds(&self) -> &dyn UnsettledObjectFundsRead {
292 self.unsettled_object_funds
293 }
294
295 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
297 &self.input_objects
298 }
299
300 pub fn update_object_version_and_prev_tx(&mut self) {
301 self.execution_results.update_version_and_previous_tx(
302 self.lamport_timestamp,
303 self.tx_digest,
304 &self.input_objects,
305 self.protocol_config.reshare_at_same_initial_version(),
306 );
307
308 #[cfg(debug_assertions)]
309 {
310 self.check_invariants();
311 }
312 }
313
314 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
315 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
316 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
317 for event in &self.execution_results.accumulator_events {
318 match &event.write.value {
319 AccumulatorValue::Integer(amount) => match event.write.operation {
320 AccumulatorOperation::Split => {
321 let entry = running_net_withdraws
322 .entry(event.accumulator_obj)
323 .or_default();
324 *entry += *amount as i128;
325 if *entry > 0 {
326 let max_entry = running_max_withdraws
327 .entry(event.accumulator_obj)
328 .or_default();
329 *max_entry = (*max_entry).max(*entry as u128);
330 }
331 }
332 AccumulatorOperation::Merge => {
333 let entry = running_net_withdraws
334 .entry(event.accumulator_obj)
335 .or_default();
336 *entry -= *amount as i128;
337 }
338 },
339 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
340 }
341 }
342 running_max_withdraws
343 }
344
345 pub(crate) fn check_accumulator_amounts_representable(&self) -> Result<(), ExecutionError> {
373 let supply = sui_types::gas_coin::TOTAL_SUPPLY_MIST as u128;
374 let mut merge_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
375 let mut split_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
376 let mut total_sui_split: u128 = 0;
378 for event in &self.execution_results.accumulator_events {
379 let AccumulatorValue::Integer(amount) = event.write.value else {
380 continue;
381 };
382 let amount = amount as u128;
383 let is_sui = sui_types::gas_coin::GasCoin::is_gas_balance_type(&event.write.address.ty);
386 let limit = if is_sui { supply } else { u64::MAX as u128 };
387 let total = match event.write.operation {
388 AccumulatorOperation::Merge => {
389 merge_totals.entry(event.accumulator_obj).or_default()
390 }
391 AccumulatorOperation::Split => {
392 split_totals.entry(event.accumulator_obj).or_default()
393 }
394 };
395 *total += amount;
396 if *total > limit {
397 return Err(ExecutionError::new_with_source(
398 ExecutionErrorKind::CoinBalanceOverflow,
399 format!(
400 "accumulator balance change for {:?} exceeds the representable limit \
401 (gross total {}, limit {})",
402 event.accumulator_obj, *total, limit
403 ),
404 ));
405 }
406 if is_sui && matches!(event.write.operation, AccumulatorOperation::Split) {
407 total_sui_split += amount;
408 if total_sui_split > supply {
409 return Err(ExecutionError::new_with_source(
410 ExecutionErrorKind::CoinBalanceOverflow,
411 format!(
412 "total SUI withdrawn across all accumulators ({total_sui_split}) \
413 exceeds the total supply ({supply})"
414 ),
415 ));
416 }
417 }
418 }
419 Ok(())
420 }
421
422 fn merge_accumulator_events(&mut self) {
424 self.execution_results.accumulator_events = self
425 .execution_results
426 .accumulator_events
427 .iter()
428 .fold(
429 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
430 |mut map, event| {
431 map.entry(event.accumulator_obj)
432 .or_default()
433 .push(event.write.clone());
434 map
435 },
436 )
437 .into_iter()
438 .map(|(obj_id, writes)| {
439 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
440 })
441 .collect();
442 }
443
444 pub fn into_inner(
446 self,
447 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
448 ) -> InnerTemporaryStore {
449 let results = self.execution_results;
450 InnerTemporaryStore {
451 input_objects: self.input_objects,
452 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
453 mutable_inputs: self.mutable_input_refs,
454 written: results.written_objects,
455 events: TransactionEvents {
456 data: results.user_events,
457 },
458 accumulator_events: results.accumulator_events,
459 loaded_runtime_objects: self.loaded_runtime_objects,
460 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
461 lamport_version: self.lamport_timestamp,
462 binary_config: self.protocol_config.binary_config(None),
463 accumulator_running_max_withdraws,
464 }
465 }
466
467 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
471 let mut to_be_updated = vec![];
472 for id in self.mutable_input_refs.keys() {
474 if !self.execution_results.modified_objects.contains(id) {
475 to_be_updated.push(self.input_objects[id].clone());
479 }
480 }
481 for object in to_be_updated {
482 self.mutate_input_object(object.clone());
484 }
485 }
486
487 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
488 let results = &self.execution_results;
489 let all_ids = results
490 .created_object_ids
491 .iter()
492 .chain(&results.deleted_object_ids)
493 .chain(&results.modified_objects)
494 .chain(results.written_objects.keys())
495 .collect::<BTreeSet<_>>();
496 all_ids
497 .into_iter()
498 .map(|id| {
499 (
500 *id,
501 EffectsObjectChange::new(
502 self.get_object_modified_at(id)
503 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
504 results.written_objects.get(id),
505 results.created_object_ids.contains(id),
506 results.deleted_object_ids.contains(id),
507 ),
508 )
509 })
510 .chain(results.accumulator_events.iter().cloned().map(
511 |AccumulatorEvent {
512 accumulator_obj,
513 write,
514 }| {
515 (
516 *accumulator_obj.inner(),
517 EffectsObjectChange::new_from_accumulator_write(write),
518 )
519 },
520 ))
521 .collect()
522 }
523
524 pub fn into_effects(
525 mut self,
526 shared_object_refs: Vec<SharedInput>,
527 transaction_digest: &TransactionDigest,
528 mut transaction_dependencies: BTreeSet<TransactionDigest>,
529 gas_cost_summary: GasCostSummary,
530 status: ExecutionStatus,
531 gas_coin: Option<ObjectID>,
532 epoch: EpochId,
533 ) -> (InnerTemporaryStore, TransactionEffects) {
534 for (id, obj) in &self.execution_results.written_objects {
537 assert!(
538 !matches!(obj.owner, Owner::Party { .. }),
539 "Party-owned objects are not yet supported (object {id})"
540 );
541 }
542
543 self.update_object_version_and_prev_tx();
544 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
546 self.merge_accumulator_events();
547
548 for (id, expected_version, expected_digest) in &self.receiving_objects {
551 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
555 let loaded_via_receive = obj_meta.version == *expected_version
559 && obj_meta.digest == *expected_digest
560 && obj_meta.owner.is_address_owned();
561 if loaded_via_receive {
562 transaction_dependencies.insert(obj_meta.previous_transaction);
563 }
564 }
565 }
566
567 assert!(self.protocol_config.enable_effects_v2());
568
569 let object_changes = self.get_object_changes();
570
571 let lamport_version = self.lamport_timestamp;
572 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
574 let loaded_system_objects = self.loaded_system_objects.borrow().clone();
575 let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
576 shared_object_refs,
577 loaded_per_epoch_config_objects,
578 &object_changes,
579 loaded_system_objects,
580 );
581 let inner = self.into_inner(accumulator_running_max_withdraws);
582
583 let effects = TransactionEffects::new_from_execution_v2(
584 status,
585 epoch,
586 gas_cost_summary,
587 unchanged_consensus_objects,
588 *transaction_digest,
589 lamport_version,
590 object_changes,
591 gas_coin,
592 if inner.events.data.is_empty() {
593 None
594 } else {
595 Some(inner.events.digest())
596 },
597 transaction_dependencies.into_iter().collect(),
598 );
599
600 (inner, effects)
601 }
602
603 #[cfg(debug_assertions)]
605 fn check_invariants(&self) {
606 debug_assert!(
608 {
609 self.execution_results
610 .written_objects
611 .keys()
612 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
613 },
614 "Object both written and deleted."
615 );
616
617 debug_assert!(
619 {
620 self.mutable_input_refs
621 .keys()
622 .all(|id| self.execution_results.modified_objects.contains(id))
623 },
624 "Mutable input not modified."
625 );
626
627 debug_assert!(
628 {
629 self.execution_results
630 .written_objects
631 .values()
632 .all(|obj| obj.previous_transaction == self.tx_digest)
633 },
634 "Object previous transaction not properly set",
635 );
636 }
637
638 pub fn mutate_input_object(&mut self, object: Object) {
640 let id = object.id();
641 debug_assert!(self.input_objects.contains_key(&id));
642 debug_assert!(!object.is_immutable());
643 self.execution_results.modified_objects.insert(id);
644 self.execution_results.written_objects.insert(id, object);
645 }
646
647 pub fn mutate_new_or_input_object(&mut self, object: Object) {
648 let id = object.id();
649 debug_assert!(!object.is_immutable());
650 if self.input_objects.contains_key(&id) {
651 self.execution_results.modified_objects.insert(id);
652 }
653 self.execution_results.written_objects.insert(id, object);
654 }
655
656 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
660 let id = new_object.id();
661 let old_ref = old_object.compute_object_reference();
662 debug_assert_eq!(old_ref.0, id);
663 self.loaded_runtime_objects.insert(
664 id,
665 DynamicallyLoadedObjectMetadata {
666 version: old_ref.1,
667 digest: old_ref.2,
668 owner: old_object.owner.clone(),
669 storage_rebate: old_object.storage_rebate,
670 previous_transaction: old_object.previous_transaction,
671 },
672 );
673 self.execution_results.modified_objects.insert(id);
674 self.execution_results
675 .written_objects
676 .insert(id, new_object);
677 }
678
679 pub fn upgrade_system_package(&mut self, package: Object) {
683 let id = package.id();
684 assert!(package.is_package() && is_system_package(id));
685 self.execution_results.modified_objects.insert(id);
686 self.execution_results.written_objects.insert(id, package);
687 }
688
689 pub fn create_object(&mut self, object: Object) {
691 debug_assert!(
696 object.is_immutable() || object.version() == SequenceNumber::MIN,
697 "Created mutable objects should not have a version set",
698 );
699 let id = object.id();
700 self.execution_results.created_object_ids.insert(id);
701 self.execution_results.written_objects.insert(id, object);
702 }
703
704 pub fn delete_input_object(&mut self, id: &ObjectID) {
706 debug_assert!(!self.execution_results.written_objects.contains_key(id));
708 debug_assert!(self.input_objects.contains_key(id));
709 self.execution_results.modified_objects.insert(*id);
710 self.execution_results.deleted_object_ids.insert(*id);
711 }
712
713 pub fn drop_writes(&mut self) {
714 self.execution_results.drop_writes();
715 self.invariants = InvariantChecker::default();
716 }
717
718 pub(crate) fn into_bump_only(self) -> Self {
723 let Self {
724 store,
726 tx_digest,
727 input_objects,
728 non_exclusive_input_original_versions,
729 stream_ended_consensus_objects,
730 lamport_timestamp,
731 mutable_input_refs,
732 receiving_objects,
733 cur_epoch,
734 protocol_config,
735 post_execution_check_inputs,
736 system_object_versions,
737 loaded_runtime_objects,
739 runtime_packages_loaded_from_db,
740 loaded_per_epoch_config_objects,
741 loaded_system_objects,
742 unsettled_object_funds,
743 execution_results: _,
745 invariants: _,
746 } = self;
747 let mut bump_only = Self {
748 store,
749 tx_digest,
750 input_objects,
751 non_exclusive_input_original_versions,
752 stream_ended_consensus_objects,
753 lamport_timestamp,
754 mutable_input_refs,
755 receiving_objects,
756 cur_epoch,
757 protocol_config,
758 loaded_runtime_objects,
759 runtime_packages_loaded_from_db,
760 loaded_per_epoch_config_objects,
761 post_execution_check_inputs,
762 system_object_versions,
763 loaded_system_objects,
764 unsettled_object_funds,
765 execution_results: ExecutionResultsV2::default(),
766 invariants: InvariantChecker::default(),
767 };
768 bump_only.ensure_active_inputs_mutated();
770 bump_only
771 }
772
773 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
774 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
776 self.execution_results
777 .written_objects
778 .get(id)
779 .or_else(|| self.input_objects.get(id))
780 }
781
782 pub fn save_loaded_runtime_objects(
783 &mut self,
784 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
785 ) {
786 #[cfg(debug_assertions)]
787 {
788 for (id, v1) in &loaded_runtime_objects {
789 if let Some(v2) = self.loaded_runtime_objects.get(id) {
790 assert_eq!(v1, v2);
791 }
792 }
793 for (id, v1) in &self.loaded_runtime_objects {
794 if let Some(v2) = loaded_runtime_objects.get(id) {
795 assert_eq!(v1, v2);
796 }
797 }
798 }
799 self.loaded_runtime_objects.extend(loaded_runtime_objects);
802 }
803
804 pub fn save_wrapped_object_containers(
805 &mut self,
806 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
807 ) {
808 self.invariants
809 .save_wrapped_object_containers(wrapped_object_containers);
810 }
811
812 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
813 self.invariants.save_generated_object_ids(generated_ids);
814 }
815
816 pub fn estimate_effects_size_upperbound(&self) -> usize {
817 TransactionEffects::estimate_effects_size_upperbound_v2(
818 self.execution_results.written_objects.len(),
819 self.execution_results.modified_objects.len(),
820 self.input_objects.len(),
821 )
822 }
823
824 pub fn written_objects_size(&self) -> usize {
825 self.execution_results
826 .written_objects
827 .values()
828 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
829 }
830
831 pub(crate) fn check_gasless_execution_requirements(&self) -> Result<(), String> {
834 use sui_types::balance::Balance;
835
836 let withdrawal_reservations = self
839 .post_execution_check_inputs
840 .input_reservations
841 .iter()
842 .filter_map(|((owner, ty), amount)| {
843 Balance::maybe_get_balance_type_param(ty)
844 .map(|coin_type| ((*owner, coin_type), *amount))
845 })
846 .collect();
847 self.check_gasless_execution_requirements_with_reservations(Some(&withdrawal_reservations))
848 }
849
850 pub(crate) fn check_gasless_execution_requirements_with_reservations(
861 &self,
862 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
863 ) -> Result<(), String> {
864 if !self.execution_results.written_objects.is_empty() {
865 return Err("Gasless transactions cannot create or mutate objects".to_string());
866 }
867
868 let input_coin_ids: BTreeSet<ObjectID> = self
869 .input_objects
870 .iter()
871 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
872 .map(|(id, _)| *id)
873 .collect();
874 if self.execution_results.deleted_object_ids != input_coin_ids {
875 return Err(format!(
876 "Gasless transaction must destroy exactly its input Coins. \
877 Expected: {input_coin_ids:?}, deleted: {:?}",
878 self.execution_results.deleted_object_ids
879 ));
880 }
881
882 let allowed_types =
883 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
884
885 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
888 &self.execution_results.accumulator_events,
889 )
890 .fold(
891 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
892 |mut totals, (address, token_type, signed_amount)| {
893 *totals.entry((address, token_type)).or_default() += signed_amount;
894 totals
895 },
896 );
897
898 for ((recipient, token_type), net_amount) in &net_totals {
899 if *net_amount <= 0 {
900 continue;
901 }
902 if let Some(&min_amount) = allowed_types.get(token_type)
903 && *net_amount < i128::from(min_amount)
904 {
905 return Err(format!(
906 "Gasless transfer of {net_amount} to {recipient} is below \
907 minimum {min_amount} for token type {token_type}"
908 ));
909 }
910 }
911
912 if let Some(reservations) = withdrawal_reservations {
913 for ((owner, token_type), &reserved) in reservations {
914 let net = net_totals
915 .get(&(*owner, token_type.clone()))
916 .copied()
917 .unwrap_or(0);
918 let remaining = (reserved as i128).saturating_add(net);
919 if remaining > 0
920 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
921 && min_balance_remaining > 0
922 && remaining < min_balance_remaining as i128
923 {
924 return Err(format!(
925 "Gasless withdrawal leaves {remaining} unused for {owner}, \
926 below minimum {min_balance_remaining} for token type {token_type}"
927 ));
928 }
929 }
930 }
931
932 Ok(())
933 }
934
935 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
940 if unmetered_storage_rebate == 0 {
941 return;
945 }
946 tracing::debug!(
947 "Amount of unmetered storage rebate from system tx: {:?}",
948 unmetered_storage_rebate
949 );
950 let mut system_state_wrapper = self
951 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
952 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
953 .clone();
954 assert_eq!(system_state_wrapper.storage_rebate, 0);
957 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
958 self.mutate_input_object(system_state_wrapper);
959 }
960
961 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
963 self.execution_results.accumulator_events.push(event);
964 }
965
966 fn get_object_modified_at(
972 &self,
973 object_id: &ObjectID,
974 ) -> Option<DynamicallyLoadedObjectMetadata> {
975 if self.execution_results.modified_objects.contains(object_id) {
976 Some(
977 self.mutable_input_refs
978 .get(object_id)
979 .map(
980 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
981 version: *version,
982 digest: *digest,
983 owner: owner.clone(),
984 storage_rebate: self.input_objects[object_id].storage_rebate,
986 previous_transaction: self.input_objects[object_id]
987 .previous_transaction,
988 },
989 )
990 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
991 .unwrap_or_else(|| {
992 debug_assert!(is_system_package(*object_id));
993 let package_obj =
994 self.store.get_package_object(object_id).unwrap().unwrap();
995 let obj = package_obj.object();
996 DynamicallyLoadedObjectMetadata {
997 version: obj.version(),
998 digest: obj.digest(),
999 owner: obj.owner.clone(),
1000 storage_rebate: obj.storage_rebate,
1001 previous_transaction: obj.previous_transaction,
1002 }
1003 }),
1004 )
1005 } else {
1006 None
1007 }
1008 }
1009
1010 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
1011 self.protocol_config
1012 }
1013
1014 pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
1017 &self,
1018 move_vm: &Arc<MoveRuntime>,
1019 enable_expensive_checks: bool,
1020 cost_summary: &GasCostSummary,
1021 ) -> Result<(), ExecutionError> {
1022 self.invariants.check_conservation_invariants::<Mode>(
1023 self,
1024 move_vm,
1025 enable_expensive_checks,
1026 cost_summary,
1027 )
1028 }
1029
1030 pub(crate) fn check_published_packages(&self) -> Result<(), ExecutionError> {
1034 self.invariants.check_published_packages(self)
1035 }
1036
1037 pub(crate) fn check_ownership_invariants(
1038 &self,
1039 sender: &SuiAddress,
1040 sponsor: &Option<SuiAddress>,
1041 gas_charger: &GasCharger,
1042 is_epoch_change: bool,
1043 ) -> SuiResult<()> {
1044 self.invariants.check_ownership_invariants(
1045 self,
1046 sender,
1047 sponsor,
1048 gas_charger,
1049 is_epoch_change,
1050 )
1051 }
1052}
1053
1054impl TemporaryStore<'_> {
1055 pub(crate) fn collect_storage_and_rebate(
1062 &mut self,
1063 gas_charger: &mut GasCharger,
1064 ) -> Result<(), ExecutionError> {
1065 let old_storage_rebates: Vec<_> = self
1067 .execution_results
1068 .written_objects
1069 .keys()
1070 .map(|object_id| {
1071 self.get_object_modified_at(object_id)
1072 .map(|metadata| metadata.storage_rebate)
1073 .unwrap_or_default()
1074 })
1075 .collect();
1076 for (object, old_storage_rebate) in self
1077 .execution_results
1078 .written_objects
1079 .values_mut()
1080 .zip_debug_eq(old_storage_rebates)
1081 {
1082 let new_object_size = object.object_size_for_gas_metering();
1084 let new_storage_rebate = gas_charger
1086 .track_storage_mutation(object.id(), new_object_size, old_storage_rebate)
1087 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1088 object.storage_rebate = new_storage_rebate;
1089 }
1090
1091 self.collect_rebate(gas_charger)
1092 }
1093
1094 pub(crate) fn collect_rebate(
1095 &self,
1096 gas_charger: &mut GasCharger,
1097 ) -> Result<(), ExecutionError> {
1098 for object_id in &self.execution_results.modified_objects {
1099 if self
1100 .execution_results
1101 .written_objects
1102 .contains_key(object_id)
1103 {
1104 continue;
1105 }
1106 let storage_rebate = self
1108 .get_object_modified_at(object_id)
1109 .unwrap()
1111 .storage_rebate;
1112 gas_charger
1113 .track_storage_mutation(*object_id, 0, storage_rebate)
1114 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1115 }
1116 Ok(())
1117 }
1118
1119 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1120 &self,
1121 ) -> Result<(), Mode::Error> {
1122 assert_invariant!(
1123 self.execution_results
1124 .created_object_ids
1125 .iter()
1126 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1127 && !self.execution_results.modified_objects.contains(id)),
1128 "Created object IDs cannot also be deleted or modified"
1129 );
1130 assert_invariant!(
1131 self.execution_results.modified_objects.iter().all(|id| {
1132 self.mutable_input_refs.contains_key(id)
1133 || self.loaded_runtime_objects.contains_key(id)
1134 || is_system_package(*id)
1135 }),
1136 "A modified object must be either a mutable input, a loaded child object, or a system package"
1137 );
1138 Ok(())
1139 }
1140}
1141impl TemporaryStore<'_> {
1146 pub fn advance_epoch_safe_mode(
1147 &mut self,
1148 params: &AdvanceEpochParams,
1149 protocol_config: &ProtocolConfig,
1150 ) {
1151 let wrapper = get_sui_system_state_wrapper(self.store)
1152 .expect("System state wrapper object must exist");
1153 let (old_object, new_object) =
1154 wrapper.advance_epoch_safe_mode(params, self.store, protocol_config);
1155 self.mutate_child_object(old_object, new_object);
1156 }
1157}
1158
1159impl RuntimeObjectResolver for TemporaryStore<'_> {
1160 fn read_child_object(
1161 &self,
1162 parent: &ObjectID,
1163 child: &ObjectID,
1164 child_version_upper_bound: SequenceNumber,
1165 ) -> SuiResult<Option<Object>> {
1166 let obj_opt = self.execution_results.written_objects.get(child);
1167 if obj_opt.is_some() {
1168 Ok(obj_opt.cloned())
1169 } else {
1170 let _scope = monitored_scope("Execution::read_child_object");
1171 self.store
1172 .read_child_object(parent, child, child_version_upper_bound)
1173 }
1174 }
1175
1176 fn get_object_received_at_version(
1177 &self,
1178 owner: &ObjectID,
1179 receiving_object_id: &ObjectID,
1180 receive_object_at_version: SequenceNumber,
1181 epoch_id: EpochId,
1182 ) -> SuiResult<Option<Object>> {
1183 debug_assert!(
1186 !self
1187 .execution_results
1188 .written_objects
1189 .contains_key(receiving_object_id)
1190 );
1191 debug_assert!(
1192 !self
1193 .execution_results
1194 .deleted_object_ids
1195 .contains(receiving_object_id)
1196 );
1197 self.store.get_object_received_at_version(
1198 owner,
1199 receiving_object_id,
1200 receive_object_at_version,
1201 epoch_id,
1202 )
1203 }
1204}
1205
1206impl ObjectFundsResolver for TemporaryStore<'_> {
1207 fn object_available_balance(&self, owner: SuiAddress, type_: &TypeTag) -> SuiResult<u128> {
1211 let required_version = self
1212 .load_implicitly_read_system_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1213 .ok_or(SuiErrorKind::ExecutionInvariantViolation)?
1214 .version();
1215
1216 let settled = AccumulatorRootValue::load(self, Some(required_version), owner, type_)?
1217 .and_then(|value| value.as_u128())
1218 .unwrap_or(0);
1219
1220 let unsettled = self.unsettled_object_funds.get_unsettled_object_withdraw(
1221 &AccumulatorRootValue::get_field_id(owner, type_)?,
1222 required_version,
1223 );
1224 settled
1225 .checked_sub(unsettled)
1226 .ok_or_else(|| SuiErrorKind::ExecutionInvariantViolation.into())
1227 }
1228}
1229
1230fn compute_input_reservations(
1239 transaction_kind: &TransactionKind,
1240 gas_data: &GasData,
1241 transaction_signer: SuiAddress,
1242 enable_gasless: bool,
1243) -> (BTreeMap<(SuiAddress, TypeTag), u64>, AllowanceIds) {
1244 use sui_types::balance::Balance;
1245 use sui_types::gas_coin::GAS;
1246 use sui_types::transaction::{Reservation, WithdrawFrom, is_gas_paid_from_address_balance};
1247
1248 let is_gasless = enable_gasless && is_gasless_transaction(gas_data, transaction_kind);
1249 let mut reservations: BTreeMap<(SuiAddress, TypeTag), u64> = BTreeMap::new();
1250 let mut allowance_ids = AllowanceIds::new();
1251 let sui_balance_type = Balance::type_tag(GAS::type_tag());
1252
1253 for arg in transaction_kind.get_funds_withdrawals() {
1254 let ty = arg.type_arg.to_type_tag();
1255 let owner = match arg.withdraw_from {
1256 WithdrawFrom::Sender => transaction_signer,
1257 WithdrawFrom::Sponsor => gas_data.owner,
1258 WithdrawFrom::SenderAllowance { funder, allowance } => {
1261 allowance_ids
1262 .entry((funder, ty.clone()))
1263 .or_default()
1264 .push(allowance);
1265 funder
1266 }
1267 };
1268 let Reservation::MaxAmountU64(reservation) = arg.reservation;
1269 let entry = reservations.entry((owner, ty)).or_insert(0);
1270 *entry = entry.saturating_add(reservation);
1271 }
1272
1273 if !is_gasless && is_gas_paid_from_address_balance(gas_data, transaction_kind) {
1276 let entry = reservations
1277 .entry((gas_data.owner, sui_balance_type.clone()))
1278 .or_insert(0);
1279 *entry = entry.saturating_add(gas_data.budget);
1280 }
1281
1282 for entry in &gas_data.payment {
1283 if let Ok(parsed) = ParsedDigest::try_from(entry.2) {
1284 let entry = reservations
1285 .entry((gas_data.owner, sui_balance_type.clone()))
1286 .or_insert(0);
1287 *entry = entry.saturating_add(parsed.reservation_amount());
1288 }
1289 }
1290
1291 (reservations, allowance_ids)
1292}
1293
1294fn declared_packages(
1297 transaction_kind: &TransactionKind,
1298) -> Option<Vec<(usize, BTreeSet<ObjectID>)>> {
1299 let TransactionKind::ProgrammableTransaction(pt) = transaction_kind else {
1300 return None;
1301 };
1302 Some(
1303 pt.commands
1304 .iter()
1305 .filter_map(|command| match command {
1306 Command::Publish(modules, dep_ids) | Command::Upgrade(modules, dep_ids, _, _) => {
1307 Some((modules.len(), dep_ids.iter().copied().collect()))
1308 }
1309 _ => None,
1310 })
1311 .collect(),
1312 )
1313}
1314
1315fn was_object_mutated(object: &Object, original: &Object) -> bool {
1318 let data_equal = match (&object.data, &original.data) {
1319 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1320 (Data::Package(a), Data::Package(b)) => a == b,
1323 _ => false,
1324 };
1325
1326 let owner_equal = match (&object.owner, &original.owner) {
1327 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1331 (
1332 Owner::ConsensusAddressOwner { owner: a, .. },
1333 Owner::ConsensusAddressOwner { owner: b, .. },
1334 ) => a == b,
1335 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1336 (Owner::Immutable, Owner::Immutable) => true,
1337 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1338 (
1339 Owner::Party {
1340 permissions: a,
1341 start_version: _,
1342 },
1343 Owner::Party {
1344 permissions: b,
1345 start_version: _,
1346 },
1347 ) => a == b,
1348
1349 (Owner::AddressOwner(_), _)
1352 | (Owner::Immutable, _)
1353 | (Owner::ObjectOwner(_), _)
1354 | (Owner::Shared { .. }, _)
1355 | (Owner::ConsensusAddressOwner { .. }, _)
1356 | (Owner::Party { .. }, _) => false,
1357 };
1358
1359 !data_equal || !owner_equal
1360}
1361
1362impl Storage for TemporaryStore<'_> {
1363 fn reset(&mut self) {
1364 self.drop_writes();
1365 }
1366
1367 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1368 TemporaryStore::read_object(self, id)
1369 }
1370
1371 fn record_execution_results(
1373 &mut self,
1374 results: ExecutionResults,
1375 ) -> Result<(), ExecutionError> {
1376 let ExecutionResults::V2(mut results) = results else {
1377 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1378 };
1379
1380 let mut to_remove = Vec::new();
1382 for (id, original) in &self.non_exclusive_input_original_versions {
1383 if results
1385 .written_objects
1386 .get(id)
1387 .map(|obj| was_object_mutated(obj, original))
1388 .unwrap_or(true)
1389 {
1390 return Err(ExecutionError::new_with_source(
1391 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1392 "Non-exclusive write input object has been modified or deleted",
1393 ));
1394 }
1395 to_remove.push(*id);
1396 }
1397
1398 for id in to_remove {
1399 results.written_objects.remove(&id);
1400 results.modified_objects.remove(&id);
1401 }
1402
1403 let event_start = self.execution_results.accumulator_events.len();
1409 self.execution_results.merge_results(
1410 results, true, true,
1411 )?;
1412 let event_end = self.execution_results.accumulator_events.len();
1413 self.invariants
1414 .record_ptb_event_range(event_start, event_end);
1415
1416 Ok(())
1417 }
1418
1419 fn save_loaded_runtime_objects(
1420 &mut self,
1421 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1422 ) {
1423 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1424 }
1425
1426 fn save_wrapped_object_containers(
1427 &mut self,
1428 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1429 ) {
1430 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1431 }
1432
1433 fn check_coin_deny_list(
1434 &self,
1435 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1436 ) -> DenyListResult {
1437 let result = check_coin_deny_list_v2_during_execution(
1438 receiving_funds_type_and_owners,
1439 self.cur_epoch,
1440 self.store,
1441 );
1442 if result.num_non_gas_coin_owners > 0
1445 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1446 {
1447 self.loaded_per_epoch_config_objects
1448 .write()
1449 .insert(SUI_DENY_LIST_OBJECT_ID);
1450 }
1451 result
1452 }
1453
1454 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1455 TemporaryStore::save_generated_object_ids(self, generated_ids)
1456 }
1457}
1458
1459impl BackingPackageStore for TemporaryStore<'_> {
1460 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1461 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1468 Ok(Some(PackageObject::new(obj.clone())))
1469 } else {
1470 self.store.get_package_object(package_id).inspect(|obj| {
1471 if let Some(v) = obj
1473 && !self
1474 .runtime_packages_loaded_from_db
1475 .read()
1476 .contains_key(package_id)
1477 {
1478 self.runtime_packages_loaded_from_db
1483 .write()
1484 .insert(*package_id, v.clone());
1485 }
1486 })
1487 }
1488 }
1489}