1use crate::execution_mode::ExecutionMode;
5use crate::gas_charger::GasCharger;
6use move_vm_runtime::runtime::MoveRuntime;
7use mysten_common::ZipDebugEqIteratorExt;
8use mysten_metrics::monitored_scope;
9use parking_lot::RwLock;
10use std::collections::{BTreeMap, BTreeSet, HashSet};
11use std::sync::Arc;
12use sui_protocol_config::ProtocolConfig;
13use sui_types::accumulator_event::AccumulatorEvent;
14use sui_types::accumulator_root::AccumulatorObjId;
15use sui_types::base_types::VersionDigest;
16use sui_types::committee::EpochId;
17use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
18use sui_types::effects::{
19 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
20 TransactionEffectsV2, TransactionEvents,
21};
22use sui_types::execution::{
23 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
24};
25use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
26use sui_types::inner_temporary_store::InnerTemporaryStore;
27use sui_types::object::Data;
28use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
29use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
30use sui_types::transaction::{GasData, TransactionKind};
31use sui_types::{
32 SUI_DENY_LIST_OBJECT_ID,
33 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
34 effects::EffectsObjectChange,
35 error::{ExecutionError, SuiResult},
36 gas::GasCostSummary,
37 object::Object,
38 object::Owner,
39 storage::{BackingPackageStore, RuntimeObjectResolver, Storage},
40 transaction::InputObjects,
41};
42use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
43
44pub(crate) mod invariants;
45use invariants::InvariantChecker;
46
47pub struct TemporaryStore<'backing> {
48 store: &'backing dyn BackingStore,
54 tx_digest: TransactionDigest,
55 input_objects: BTreeMap<ObjectID, Object>,
56
57 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
60
61 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
62 lamport_timestamp: SequenceNumber,
64 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
67 execution_results: ExecutionResultsV2,
68 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
70 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
72 protocol_config: &'backing ProtocolConfig,
73
74 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
77
78 receiving_objects: Vec<ObjectRef>,
81
82 generated_runtime_ids: BTreeSet<ObjectID>,
86
87 cur_epoch: EpochId,
90
91 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
94
95 invariants: InvariantChecker,
99}
100
101impl<'backing> TemporaryStore<'backing> {
102 pub fn new(
105 store: &'backing dyn BackingStore,
106 input_objects: InputObjects,
107 receiving_objects: Vec<ObjectRef>,
108 tx_digest: TransactionDigest,
109 protocol_config: &'backing ProtocolConfig,
110 cur_epoch: EpochId,
111 _system_object_versions: BTreeMap<ObjectID, SequenceNumber>,
112 ) -> Self {
113 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
114 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
115
116 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
117 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
118 let objects = input_objects.into_object_map();
119 #[cfg(debug_assertions)]
120 {
121 assert!(
123 objects
124 .keys()
125 .collect::<HashSet<_>>()
126 .intersection(
127 &receiving_objects
128 .iter()
129 .map(|oref| &oref.0)
130 .collect::<HashSet<_>>()
131 )
132 .next()
133 .is_none()
134 );
135 }
136 Self {
137 store,
138 tx_digest,
139 input_objects: objects,
140 non_exclusive_input_original_versions,
141 stream_ended_consensus_objects,
142 lamport_timestamp,
143 mutable_input_refs,
144 execution_results: ExecutionResultsV2::default(),
145 protocol_config,
146 loaded_runtime_objects: BTreeMap::new(),
147 wrapped_object_containers: BTreeMap::new(),
148 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
149 receiving_objects,
150 generated_runtime_ids: BTreeSet::new(),
151 cur_epoch,
152 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
153 invariants: InvariantChecker::new(),
154 }
155 }
156
157 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
159 &self.input_objects
160 }
161
162 pub fn update_object_version_and_prev_tx(&mut self) {
163 self.execution_results.update_version_and_previous_tx(
164 self.lamport_timestamp,
165 self.tx_digest,
166 &self.input_objects,
167 self.protocol_config.reshare_at_same_initial_version(),
168 );
169
170 #[cfg(debug_assertions)]
171 {
172 self.check_invariants();
173 }
174 }
175
176 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
177 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
178 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
179 for event in &self.execution_results.accumulator_events {
180 match &event.write.value {
181 AccumulatorValue::Integer(amount) => match event.write.operation {
182 AccumulatorOperation::Split => {
183 let entry = running_net_withdraws
184 .entry(event.accumulator_obj)
185 .or_default();
186 *entry += *amount as i128;
187 if *entry > 0 {
188 let max_entry = running_max_withdraws
189 .entry(event.accumulator_obj)
190 .or_default();
191 *max_entry = (*max_entry).max(*entry as u128);
192 }
193 }
194 AccumulatorOperation::Merge => {
195 let entry = running_net_withdraws
196 .entry(event.accumulator_obj)
197 .or_default();
198 *entry -= *amount as i128;
199 }
200 },
201 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
202 }
203 }
204 running_max_withdraws
205 }
206
207 pub(crate) fn check_accumulator_amounts_representable(&self) -> Result<(), ExecutionError> {
235 let supply = sui_types::gas_coin::TOTAL_SUPPLY_MIST as u128;
236 let mut merge_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
237 let mut split_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
238 let mut total_sui_split: u128 = 0;
240 for event in &self.execution_results.accumulator_events {
241 let AccumulatorValue::Integer(amount) = event.write.value else {
242 continue;
243 };
244 let amount = amount as u128;
245 let is_sui = sui_types::gas_coin::GasCoin::is_gas_balance_type(&event.write.address.ty);
248 let limit = if is_sui { supply } else { u64::MAX as u128 };
249 let total = match event.write.operation {
250 AccumulatorOperation::Merge => {
251 merge_totals.entry(event.accumulator_obj).or_default()
252 }
253 AccumulatorOperation::Split => {
254 split_totals.entry(event.accumulator_obj).or_default()
255 }
256 };
257 *total += amount;
258 if *total > limit {
259 return Err(ExecutionError::new_with_source(
260 ExecutionErrorKind::CoinBalanceOverflow,
261 format!(
262 "accumulator balance change for {:?} exceeds the representable limit \
263 (gross total {}, limit {})",
264 event.accumulator_obj, *total, limit
265 ),
266 ));
267 }
268 if is_sui && matches!(event.write.operation, AccumulatorOperation::Split) {
269 total_sui_split += amount;
270 if total_sui_split > supply {
271 return Err(ExecutionError::new_with_source(
272 ExecutionErrorKind::CoinBalanceOverflow,
273 format!(
274 "total SUI withdrawn across all accumulators ({total_sui_split}) \
275 exceeds the total supply ({supply})"
276 ),
277 ));
278 }
279 }
280 }
281 Ok(())
282 }
283
284 fn merge_accumulator_events(&mut self) {
286 self.execution_results.accumulator_events = self
287 .execution_results
288 .accumulator_events
289 .iter()
290 .fold(
291 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
292 |mut map, event| {
293 map.entry(event.accumulator_obj)
294 .or_default()
295 .push(event.write.clone());
296 map
297 },
298 )
299 .into_iter()
300 .map(|(obj_id, writes)| {
301 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
302 })
303 .collect();
304 }
305
306 pub fn into_inner(
308 self,
309 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
310 ) -> InnerTemporaryStore {
311 let results = self.execution_results;
312 InnerTemporaryStore {
313 input_objects: self.input_objects,
314 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
315 mutable_inputs: self.mutable_input_refs,
316 written: results.written_objects,
317 events: TransactionEvents {
318 data: results.user_events,
319 },
320 accumulator_events: results.accumulator_events,
321 loaded_runtime_objects: self.loaded_runtime_objects,
322 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
323 lamport_version: self.lamport_timestamp,
324 binary_config: self.protocol_config.binary_config(None),
325 accumulator_running_max_withdraws,
326 }
327 }
328
329 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
333 let mut to_be_updated = vec![];
334 for id in self.mutable_input_refs.keys() {
336 if !self.execution_results.modified_objects.contains(id) {
337 to_be_updated.push(self.input_objects[id].clone());
341 }
342 }
343 for object in to_be_updated {
344 self.mutate_input_object(object.clone());
346 }
347 }
348
349 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
350 let results = &self.execution_results;
351 let all_ids = results
352 .created_object_ids
353 .iter()
354 .chain(&results.deleted_object_ids)
355 .chain(&results.modified_objects)
356 .chain(results.written_objects.keys())
357 .collect::<BTreeSet<_>>();
358 all_ids
359 .into_iter()
360 .map(|id| {
361 (
362 *id,
363 EffectsObjectChange::new(
364 self.get_object_modified_at(id)
365 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
366 results.written_objects.get(id),
367 results.created_object_ids.contains(id),
368 results.deleted_object_ids.contains(id),
369 ),
370 )
371 })
372 .chain(results.accumulator_events.iter().cloned().map(
373 |AccumulatorEvent {
374 accumulator_obj,
375 write,
376 }| {
377 (
378 *accumulator_obj.inner(),
379 EffectsObjectChange::new_from_accumulator_write(write),
380 )
381 },
382 ))
383 .collect()
384 }
385
386 pub fn into_effects(
387 mut self,
388 shared_object_refs: Vec<SharedInput>,
389 transaction_digest: &TransactionDigest,
390 mut transaction_dependencies: BTreeSet<TransactionDigest>,
391 gas_cost_summary: GasCostSummary,
392 status: ExecutionStatus,
393 gas_coin: Option<ObjectID>,
394 epoch: EpochId,
395 ) -> (InnerTemporaryStore, TransactionEffects) {
396 for (id, obj) in &self.execution_results.written_objects {
399 assert!(
400 !matches!(obj.owner, Owner::Party { .. }),
401 "Party-owned objects are not yet supported (object {id})"
402 );
403 }
404
405 self.update_object_version_and_prev_tx();
406 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
408 self.merge_accumulator_events();
409
410 for (id, expected_version, expected_digest) in &self.receiving_objects {
413 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
417 let loaded_via_receive = obj_meta.version == *expected_version
421 && obj_meta.digest == *expected_digest
422 && obj_meta.owner.is_address_owned();
423 if loaded_via_receive {
424 transaction_dependencies.insert(obj_meta.previous_transaction);
425 }
426 }
427 }
428
429 assert!(self.protocol_config.enable_effects_v2());
430
431 let object_changes = self.get_object_changes();
432
433 let lamport_version = self.lamport_timestamp;
434 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
436 let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
437 shared_object_refs,
438 loaded_per_epoch_config_objects,
439 &object_changes,
440 );
441 let inner = self.into_inner(accumulator_running_max_withdraws);
442
443 let effects = TransactionEffects::new_from_execution_v2(
444 status,
445 epoch,
446 gas_cost_summary,
447 unchanged_consensus_objects,
448 *transaction_digest,
449 lamport_version,
450 object_changes,
451 gas_coin,
452 if inner.events.data.is_empty() {
453 None
454 } else {
455 Some(inner.events.digest())
456 },
457 transaction_dependencies.into_iter().collect(),
458 );
459
460 (inner, effects)
461 }
462
463 #[cfg(debug_assertions)]
465 fn check_invariants(&self) {
466 debug_assert!(
468 {
469 self.execution_results
470 .written_objects
471 .keys()
472 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
473 },
474 "Object both written and deleted."
475 );
476
477 debug_assert!(
479 {
480 self.mutable_input_refs
481 .keys()
482 .all(|id| self.execution_results.modified_objects.contains(id))
483 },
484 "Mutable input not modified."
485 );
486
487 debug_assert!(
488 {
489 self.execution_results
490 .written_objects
491 .values()
492 .all(|obj| obj.previous_transaction == self.tx_digest)
493 },
494 "Object previous transaction not properly set",
495 );
496 }
497
498 pub fn mutate_input_object(&mut self, object: Object) {
500 let id = object.id();
501 debug_assert!(self.input_objects.contains_key(&id));
502 debug_assert!(!object.is_immutable());
503 self.execution_results.modified_objects.insert(id);
504 self.execution_results.written_objects.insert(id, object);
505 }
506
507 pub fn mutate_new_or_input_object(&mut self, object: Object) {
508 let id = object.id();
509 debug_assert!(!object.is_immutable());
510 if self.input_objects.contains_key(&id) {
511 self.execution_results.modified_objects.insert(id);
512 }
513 self.execution_results.written_objects.insert(id, object);
514 }
515
516 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
520 let id = new_object.id();
521 let old_ref = old_object.compute_object_reference();
522 debug_assert_eq!(old_ref.0, id);
523 self.loaded_runtime_objects.insert(
524 id,
525 DynamicallyLoadedObjectMetadata {
526 version: old_ref.1,
527 digest: old_ref.2,
528 owner: old_object.owner.clone(),
529 storage_rebate: old_object.storage_rebate,
530 previous_transaction: old_object.previous_transaction,
531 },
532 );
533 self.execution_results.modified_objects.insert(id);
534 self.execution_results
535 .written_objects
536 .insert(id, new_object);
537 }
538
539 pub fn upgrade_system_package(&mut self, package: Object) {
543 let id = package.id();
544 assert!(package.is_package() && is_system_package(id));
545 self.execution_results.modified_objects.insert(id);
546 self.execution_results.written_objects.insert(id, package);
547 }
548
549 pub fn create_object(&mut self, object: Object) {
551 debug_assert!(
556 object.is_immutable() || object.version() == SequenceNumber::MIN,
557 "Created mutable objects should not have a version set",
558 );
559 let id = object.id();
560 self.execution_results.created_object_ids.insert(id);
561 self.execution_results.written_objects.insert(id, object);
562 }
563
564 pub fn delete_input_object(&mut self, id: &ObjectID) {
566 debug_assert!(!self.execution_results.written_objects.contains_key(id));
568 debug_assert!(self.input_objects.contains_key(id));
569 self.execution_results.modified_objects.insert(*id);
570 self.execution_results.deleted_object_ids.insert(*id);
571 }
572
573 pub fn drop_writes(&mut self) {
574 self.execution_results.drop_writes();
575 self.invariants.clear();
577 }
578
579 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
580 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
582 self.execution_results
583 .written_objects
584 .get(id)
585 .or_else(|| self.input_objects.get(id))
586 }
587
588 pub fn save_loaded_runtime_objects(
589 &mut self,
590 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
591 ) {
592 #[cfg(debug_assertions)]
593 {
594 for (id, v1) in &loaded_runtime_objects {
595 if let Some(v2) = self.loaded_runtime_objects.get(id) {
596 assert_eq!(v1, v2);
597 }
598 }
599 for (id, v1) in &self.loaded_runtime_objects {
600 if let Some(v2) = loaded_runtime_objects.get(id) {
601 assert_eq!(v1, v2);
602 }
603 }
604 }
605 self.loaded_runtime_objects.extend(loaded_runtime_objects);
608 }
609
610 pub fn save_wrapped_object_containers(
611 &mut self,
612 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
613 ) {
614 #[cfg(debug_assertions)]
615 {
616 for (id, container1) in &wrapped_object_containers {
617 if let Some(container2) = self.wrapped_object_containers.get(id) {
618 assert_eq!(container1, container2);
619 }
620 }
621 for (id, container1) in &self.wrapped_object_containers {
622 if let Some(container2) = wrapped_object_containers.get(id) {
623 assert_eq!(container1, container2);
624 }
625 }
626 }
627 self.wrapped_object_containers
630 .extend(wrapped_object_containers);
631 }
632
633 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
634 #[cfg(debug_assertions)]
635 {
636 for id in &self.generated_runtime_ids {
637 assert!(!generated_ids.contains(id))
638 }
639 for id in &generated_ids {
640 assert!(!self.generated_runtime_ids.contains(id));
641 }
642 }
643 self.generated_runtime_ids.extend(generated_ids);
644 }
645
646 pub fn estimate_effects_size_upperbound(&self) -> usize {
647 TransactionEffects::estimate_effects_size_upperbound_v2(
648 self.execution_results.written_objects.len(),
649 self.execution_results.modified_objects.len(),
650 self.input_objects.len(),
651 )
652 }
653
654 pub fn written_objects_size(&self) -> usize {
655 self.execution_results
656 .written_objects
657 .values()
658 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
659 }
660
661 pub fn check_gasless_execution_requirements(
667 &self,
668 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
669 ) -> Result<(), String> {
670 if !self.execution_results.written_objects.is_empty() {
671 return Err("Gasless transactions cannot create or mutate objects".to_string());
672 }
673
674 let input_coin_ids: BTreeSet<ObjectID> = self
675 .input_objects
676 .iter()
677 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
678 .map(|(id, _)| *id)
679 .collect();
680 if self.execution_results.deleted_object_ids != input_coin_ids {
681 return Err(format!(
682 "Gasless transaction must destroy exactly its input Coins. \
683 Expected: {input_coin_ids:?}, deleted: {:?}",
684 self.execution_results.deleted_object_ids
685 ));
686 }
687
688 let allowed_types =
689 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
690
691 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
694 &self.execution_results.accumulator_events,
695 )
696 .fold(
697 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
698 |mut totals, (address, token_type, signed_amount)| {
699 *totals.entry((address, token_type)).or_default() += signed_amount;
700 totals
701 },
702 );
703
704 for ((recipient, token_type), net_amount) in &net_totals {
705 if *net_amount <= 0 {
706 continue;
707 }
708 if let Some(&min_amount) = allowed_types.get(token_type)
709 && *net_amount < i128::from(min_amount)
710 {
711 return Err(format!(
712 "Gasless transfer of {net_amount} to {recipient} is below \
713 minimum {min_amount} for token type {token_type}"
714 ));
715 }
716 }
717
718 if let Some(reservations) = withdrawal_reservations {
719 for ((owner, token_type), &reserved) in reservations {
720 let net = net_totals
721 .get(&(*owner, token_type.clone()))
722 .copied()
723 .unwrap_or(0);
724 let remaining = (reserved as i128).saturating_add(net);
725 if remaining > 0
726 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
727 && min_balance_remaining > 0
728 && remaining < min_balance_remaining as i128
729 {
730 return Err(format!(
731 "Gasless withdrawal leaves {remaining} unused for {owner}, \
732 below minimum {min_balance_remaining} for token type {token_type}"
733 ));
734 }
735 }
736 }
737
738 Ok(())
739 }
740
741 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
746 if unmetered_storage_rebate == 0 {
747 return;
751 }
752 tracing::debug!(
753 "Amount of unmetered storage rebate from system tx: {:?}",
754 unmetered_storage_rebate
755 );
756 let mut system_state_wrapper = self
757 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
758 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
759 .clone();
760 assert_eq!(system_state_wrapper.storage_rebate, 0);
763 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
764 self.mutate_input_object(system_state_wrapper);
765 }
766
767 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
769 self.execution_results.accumulator_events.push(event);
770 }
771
772 fn get_object_modified_at(
778 &self,
779 object_id: &ObjectID,
780 ) -> Option<DynamicallyLoadedObjectMetadata> {
781 if self.execution_results.modified_objects.contains(object_id) {
782 Some(
783 self.mutable_input_refs
784 .get(object_id)
785 .map(
786 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
787 version: *version,
788 digest: *digest,
789 owner: owner.clone(),
790 storage_rebate: self.input_objects[object_id].storage_rebate,
792 previous_transaction: self.input_objects[object_id]
793 .previous_transaction,
794 },
795 )
796 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
797 .unwrap_or_else(|| {
798 debug_assert!(is_system_package(*object_id));
799 let package_obj =
800 self.store.get_package_object(object_id).unwrap().unwrap();
801 let obj = package_obj.object();
802 DynamicallyLoadedObjectMetadata {
803 version: obj.version(),
804 digest: obj.digest(),
805 owner: obj.owner.clone(),
806 storage_rebate: obj.storage_rebate,
807 previous_transaction: obj.previous_transaction,
808 }
809 }),
810 )
811 } else {
812 None
813 }
814 }
815
816 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
817 self.protocol_config
818 }
819
820 pub(crate) fn set_invariant_inputs(
825 &mut self,
826 transaction_kind: &TransactionKind,
827 gas_data: &GasData,
828 transaction_signer: SuiAddress,
829 ) {
830 self.invariants
831 .set_transaction_inputs(transaction_kind, gas_data, transaction_signer);
832 }
833
834 pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
837 &self,
838 move_vm: &Arc<MoveRuntime>,
839 enable_expensive_checks: bool,
840 cost_summary: &GasCostSummary,
841 ) -> Result<(), ExecutionError> {
842 self.invariants.check_conservation_invariants::<Mode>(
843 self,
844 move_vm,
845 enable_expensive_checks,
846 cost_summary,
847 )
848 }
849
850 pub(crate) fn check_ownership_invariants(
853 &self,
854 sender: &SuiAddress,
855 sponsor: &Option<SuiAddress>,
856 gas_charger: &GasCharger,
857 mutable_inputs: &HashSet<ObjectID>,
858 is_epoch_change: bool,
859 ) -> SuiResult<()> {
860 self.invariants.check_ownership_invariants(
861 self,
862 sender,
863 sponsor,
864 gas_charger,
865 mutable_inputs,
866 is_epoch_change,
867 )
868 }
869}
870
871impl TemporaryStore<'_> {
872 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
879 let old_storage_rebates: Vec<_> = self
881 .execution_results
882 .written_objects
883 .keys()
884 .map(|object_id| {
885 self.get_object_modified_at(object_id)
886 .map(|metadata| metadata.storage_rebate)
887 .unwrap_or_default()
888 })
889 .collect();
890 for (object, old_storage_rebate) in self
891 .execution_results
892 .written_objects
893 .values_mut()
894 .zip_debug_eq(old_storage_rebates)
895 {
896 let new_object_size = object.object_size_for_gas_metering();
898 let new_storage_rebate = gas_charger.track_storage_mutation(
900 object.id(),
901 new_object_size,
902 old_storage_rebate,
903 );
904 object.storage_rebate = new_storage_rebate;
905 }
906
907 self.collect_rebate(gas_charger);
908 }
909
910 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
911 for object_id in &self.execution_results.modified_objects {
912 if self
913 .execution_results
914 .written_objects
915 .contains_key(object_id)
916 {
917 continue;
918 }
919 let storage_rebate = self
921 .get_object_modified_at(object_id)
922 .unwrap()
924 .storage_rebate;
925 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
926 }
927 }
928
929 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
930 &self,
931 ) -> Result<(), Mode::Error> {
932 assert_invariant!(
933 self.execution_results
934 .created_object_ids
935 .iter()
936 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
937 && !self.execution_results.modified_objects.contains(id)),
938 "Created object IDs cannot also be deleted or modified"
939 );
940 assert_invariant!(
941 self.execution_results.modified_objects.iter().all(|id| {
942 self.mutable_input_refs.contains_key(id)
943 || self.loaded_runtime_objects.contains_key(id)
944 || is_system_package(*id)
945 }),
946 "A modified object must be either a mutable input, a loaded child object, or a system package"
947 );
948 Ok(())
949 }
950}
951impl TemporaryStore<'_> {
956 pub fn advance_epoch_safe_mode(
957 &mut self,
958 params: &AdvanceEpochParams,
959 protocol_config: &ProtocolConfig,
960 ) {
961 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
962 .expect("System state wrapper object must exist");
963 let (old_object, new_object) =
964 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
965 self.mutate_child_object(old_object, new_object);
966 }
967}
968
969impl RuntimeObjectResolver for TemporaryStore<'_> {
970 fn read_child_object(
971 &self,
972 parent: &ObjectID,
973 child: &ObjectID,
974 child_version_upper_bound: SequenceNumber,
975 ) -> SuiResult<Option<Object>> {
976 let obj_opt = self.execution_results.written_objects.get(child);
977 if obj_opt.is_some() {
978 Ok(obj_opt.cloned())
979 } else {
980 let _scope = monitored_scope("Execution::read_child_object");
981 self.store
982 .read_child_object(parent, child, child_version_upper_bound)
983 }
984 }
985
986 fn get_object_received_at_version(
987 &self,
988 owner: &ObjectID,
989 receiving_object_id: &ObjectID,
990 receive_object_at_version: SequenceNumber,
991 epoch_id: EpochId,
992 ) -> SuiResult<Option<Object>> {
993 debug_assert!(
996 !self
997 .execution_results
998 .written_objects
999 .contains_key(receiving_object_id)
1000 );
1001 debug_assert!(
1002 !self
1003 .execution_results
1004 .deleted_object_ids
1005 .contains(receiving_object_id)
1006 );
1007 self.store.get_object_received_at_version(
1008 owner,
1009 receiving_object_id,
1010 receive_object_at_version,
1011 epoch_id,
1012 )
1013 }
1014}
1015
1016fn was_object_mutated(object: &Object, original: &Object) -> bool {
1019 let data_equal = match (&object.data, &original.data) {
1020 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1021 (Data::Package(a), Data::Package(b)) => a == b,
1024 _ => false,
1025 };
1026
1027 let owner_equal = match (&object.owner, &original.owner) {
1028 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1032 (
1033 Owner::ConsensusAddressOwner { owner: a, .. },
1034 Owner::ConsensusAddressOwner { owner: b, .. },
1035 ) => a == b,
1036 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1037 (Owner::Immutable, Owner::Immutable) => true,
1038 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1039 (
1040 Owner::Party {
1041 permissions: a,
1042 start_version: _,
1043 },
1044 Owner::Party {
1045 permissions: b,
1046 start_version: _,
1047 },
1048 ) => a == b,
1049
1050 (Owner::AddressOwner(_), _)
1053 | (Owner::Immutable, _)
1054 | (Owner::ObjectOwner(_), _)
1055 | (Owner::Shared { .. }, _)
1056 | (Owner::ConsensusAddressOwner { .. }, _)
1057 | (Owner::Party { .. }, _) => false,
1058 };
1059
1060 !data_equal || !owner_equal
1061}
1062
1063impl Storage for TemporaryStore<'_> {
1064 fn reset(&mut self) {
1065 self.drop_writes();
1066 }
1067
1068 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1069 TemporaryStore::read_object(self, id)
1070 }
1071
1072 fn record_execution_results(
1074 &mut self,
1075 results: ExecutionResults,
1076 ) -> Result<(), ExecutionError> {
1077 let ExecutionResults::V2(mut results) = results else {
1078 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1079 };
1080
1081 let mut to_remove = Vec::new();
1083 for (id, original) in &self.non_exclusive_input_original_versions {
1084 if results
1086 .written_objects
1087 .get(id)
1088 .map(|obj| was_object_mutated(obj, original))
1089 .unwrap_or(true)
1090 {
1091 return Err(ExecutionError::new_with_source(
1092 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1093 "Non-exclusive write input object has been modified or deleted",
1094 ));
1095 }
1096 to_remove.push(*id);
1097 }
1098
1099 for id in to_remove {
1100 results.written_objects.remove(&id);
1101 results.modified_objects.remove(&id);
1102 }
1103
1104 let event_start = self.execution_results.accumulator_events.len();
1110 self.execution_results.merge_results(
1111 results, true, true,
1112 )?;
1113 let event_end = self.execution_results.accumulator_events.len();
1114 self.invariants
1115 .record_ptb_event_range(event_start, event_end);
1116
1117 Ok(())
1118 }
1119
1120 fn save_loaded_runtime_objects(
1121 &mut self,
1122 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1123 ) {
1124 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1125 }
1126
1127 fn save_wrapped_object_containers(
1128 &mut self,
1129 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1130 ) {
1131 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1132 }
1133
1134 fn check_coin_deny_list(
1135 &self,
1136 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1137 ) -> DenyListResult {
1138 let result = check_coin_deny_list_v2_during_execution(
1139 receiving_funds_type_and_owners,
1140 self.cur_epoch,
1141 self.store.as_object_store(),
1142 );
1143 if result.num_non_gas_coin_owners > 0
1146 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1147 {
1148 self.loaded_per_epoch_config_objects
1149 .write()
1150 .insert(SUI_DENY_LIST_OBJECT_ID);
1151 }
1152 result
1153 }
1154
1155 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1156 TemporaryStore::save_generated_object_ids(self, generated_ids)
1157 }
1158}
1159
1160impl BackingPackageStore for TemporaryStore<'_> {
1161 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1162 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1169 Ok(Some(PackageObject::new(obj.clone())))
1170 } else {
1171 self.store.get_package_object(package_id).inspect(|obj| {
1172 if let Some(v) = obj
1174 && !self
1175 .runtime_packages_loaded_from_db
1176 .read()
1177 .contains_key(package_id)
1178 {
1179 self.runtime_packages_loaded_from_db
1184 .write()
1185 .insert(*package_id, v.clone());
1186 }
1187 })
1188 }
1189 }
1190}