1use crate::execution_mode::ExecutionMode;
5use crate::gas_charger::GasCharger;
6use move_binary_format::errors::{PartialVMError, PartialVMResult};
7use move_core_types::vm_status::StatusCode;
8use move_vm_runtime::runtime::MoveRuntime;
9use mysten_common::{ZipDebugEqIteratorExt, debug_fatal};
10use mysten_metrics::monitored_scope;
11use parking_lot::RwLock;
12use std::cell::{OnceCell, RefCell};
13use std::collections::{BTreeMap, BTreeSet, HashSet};
14use std::sync::Arc;
15use sui_protocol_config::ProtocolConfig;
16use sui_types::accumulator_event::AccumulatorEvent;
17use sui_types::accumulator_root::AccumulatorObjId;
18use sui_types::base_types::VersionDigest;
19use sui_types::committee::EpochId;
20use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
21use sui_types::effects::{
22 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
23 TransactionEffectsV2, TransactionEvents,
24};
25use sui_types::execution::{
26 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, ExecutionRetryError,
27 SharedInput,
28};
29use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
30use sui_types::inner_temporary_store::InnerTemporaryStore;
31use sui_types::object::Data;
32use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
33use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
34use sui_types::transaction::{GasData, TransactionKind};
35use sui_types::{
36 SUI_DENY_LIST_OBJECT_ID,
37 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
38 digests::ObjectDigest,
39 effects::EffectsObjectChange,
40 error::{ExecutionError, SuiResult},
41 gas::GasCostSummary,
42 object::Object,
43 object::Owner,
44 storage::{BackingPackageStore, RuntimeObjectResolver, Storage},
45 transaction::InputObjects,
46};
47use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
48
49pub(crate) mod invariants;
50use invariants::InvariantChecker;
51
52pub struct TemporaryStore<'backing> {
53 store: &'backing dyn BackingStore,
59 tx_digest: TransactionDigest,
60 input_objects: BTreeMap<ObjectID, Object>,
61
62 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
65
66 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
67 lamport_timestamp: SequenceNumber,
69 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
72 execution_results: ExecutionResultsV2,
73 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
75 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
77 protocol_config: &'backing ProtocolConfig,
78
79 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
82
83 receiving_objects: Vec<ObjectRef>,
86
87 generated_runtime_ids: BTreeSet<ObjectID>,
91
92 cur_epoch: EpochId,
95
96 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
99
100 invariants: InvariantChecker,
104
105 system_object_versions: BTreeMap<ObjectID, SequenceNumber>,
111
112 loaded_system_objects: RefCell<BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>>,
118
119 retry_request: OnceCell<ExecutionRetryError>,
129}
130
131impl<'backing> TemporaryStore<'backing> {
132 pub fn new(
135 store: &'backing dyn BackingStore,
136 input_objects: InputObjects,
137 receiving_objects: Vec<ObjectRef>,
138 tx_digest: TransactionDigest,
139 protocol_config: &'backing ProtocolConfig,
140 cur_epoch: EpochId,
141 system_object_versions: BTreeMap<ObjectID, SequenceNumber>,
142 ) -> Self {
143 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
144 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
145
146 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
147 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
148 let objects = input_objects.into_object_map();
149 #[cfg(debug_assertions)]
150 {
151 assert!(
153 objects
154 .keys()
155 .collect::<HashSet<_>>()
156 .intersection(
157 &receiving_objects
158 .iter()
159 .map(|oref| &oref.0)
160 .collect::<HashSet<_>>()
161 )
162 .next()
163 .is_none()
164 );
165 }
166 Self {
167 store,
168 tx_digest,
169 input_objects: objects,
170 non_exclusive_input_original_versions,
171 stream_ended_consensus_objects,
172 lamport_timestamp,
173 mutable_input_refs,
174 execution_results: ExecutionResultsV2::default(),
175 protocol_config,
176 loaded_runtime_objects: BTreeMap::new(),
177 wrapped_object_containers: BTreeMap::new(),
178 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
179 receiving_objects,
180 generated_runtime_ids: BTreeSet::new(),
181 cur_epoch,
182 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
183 invariants: InvariantChecker::new(),
184 system_object_versions,
185 loaded_system_objects: RefCell::new(BTreeMap::new()),
186 retry_request: OnceCell::new(),
187 }
188 }
189
190 pub fn check_system_object_available(&self, object_id: &ObjectID) -> PartialVMResult<()> {
199 let Some(required_version) = self.system_object_versions.get(object_id).copied() else {
203 debug_fatal!("system object {object_id} read without an assigned version");
204 return Err(
205 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
206 format!("system object {object_id} read without an assigned version"),
207 ),
208 );
209 };
210 let Some(object_at_required) = self.store.get_object_by_key(object_id, required_version)
214 else {
215 let retry = ExecutionRetryError::SystemObjectUnavailable {
225 object_id: *object_id,
226 version: required_version,
227 };
228 let message = retry.to_string();
229 let _ = self.retry_request.set(retry);
230 return Err(
232 PartialVMError::new(StatusCode::SYSTEM_OBJECT_NOT_AVAILABLE_LOCALLY)
233 .with_message(message),
234 );
235 };
236
237 self.loaded_system_objects
243 .borrow_mut()
244 .insert(*object_id, (required_version, object_at_required.digest()));
245 Ok(())
246 }
247
248 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
250 &self.input_objects
251 }
252
253 pub fn update_object_version_and_prev_tx(&mut self) {
254 self.execution_results.update_version_and_previous_tx(
255 self.lamport_timestamp,
256 self.tx_digest,
257 &self.input_objects,
258 self.protocol_config.reshare_at_same_initial_version(),
259 );
260
261 #[cfg(debug_assertions)]
262 {
263 self.check_invariants();
264 }
265 }
266
267 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
268 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
269 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
270 for event in &self.execution_results.accumulator_events {
271 match &event.write.value {
272 AccumulatorValue::Integer(amount) => match event.write.operation {
273 AccumulatorOperation::Split => {
274 let entry = running_net_withdraws
275 .entry(event.accumulator_obj)
276 .or_default();
277 *entry += *amount as i128;
278 if *entry > 0 {
279 let max_entry = running_max_withdraws
280 .entry(event.accumulator_obj)
281 .or_default();
282 *max_entry = (*max_entry).max(*entry as u128);
283 }
284 }
285 AccumulatorOperation::Merge => {
286 let entry = running_net_withdraws
287 .entry(event.accumulator_obj)
288 .or_default();
289 *entry -= *amount as i128;
290 }
291 },
292 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
293 }
294 }
295 running_max_withdraws
296 }
297
298 pub(crate) fn check_accumulator_amounts_representable(&self) -> Result<(), ExecutionError> {
326 let supply = sui_types::gas_coin::TOTAL_SUPPLY_MIST as u128;
327 let mut merge_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
328 let mut split_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
329 let mut total_sui_split: u128 = 0;
331 for event in &self.execution_results.accumulator_events {
332 let AccumulatorValue::Integer(amount) = event.write.value else {
333 continue;
334 };
335 let amount = amount as u128;
336 let is_sui = sui_types::gas_coin::GasCoin::is_gas_balance_type(&event.write.address.ty);
339 let limit = if is_sui { supply } else { u64::MAX as u128 };
340 let total = match event.write.operation {
341 AccumulatorOperation::Merge => {
342 merge_totals.entry(event.accumulator_obj).or_default()
343 }
344 AccumulatorOperation::Split => {
345 split_totals.entry(event.accumulator_obj).or_default()
346 }
347 };
348 *total += amount;
349 if *total > limit {
350 return Err(ExecutionError::new_with_source(
351 ExecutionErrorKind::CoinBalanceOverflow,
352 format!(
353 "accumulator balance change for {:?} exceeds the representable limit \
354 (gross total {}, limit {})",
355 event.accumulator_obj, *total, limit
356 ),
357 ));
358 }
359 if is_sui && matches!(event.write.operation, AccumulatorOperation::Split) {
360 total_sui_split += amount;
361 if total_sui_split > supply {
362 return Err(ExecutionError::new_with_source(
363 ExecutionErrorKind::CoinBalanceOverflow,
364 format!(
365 "total SUI withdrawn across all accumulators ({total_sui_split}) \
366 exceeds the total supply ({supply})"
367 ),
368 ));
369 }
370 }
371 }
372 Ok(())
373 }
374
375 fn merge_accumulator_events(&mut self) {
377 self.execution_results.accumulator_events = self
378 .execution_results
379 .accumulator_events
380 .iter()
381 .fold(
382 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
383 |mut map, event| {
384 map.entry(event.accumulator_obj)
385 .or_default()
386 .push(event.write.clone());
387 map
388 },
389 )
390 .into_iter()
391 .map(|(obj_id, writes)| {
392 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
393 })
394 .collect();
395 }
396
397 pub fn into_inner(
399 self,
400 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
401 ) -> InnerTemporaryStore {
402 let results = self.execution_results;
403 InnerTemporaryStore {
404 input_objects: self.input_objects,
405 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
406 mutable_inputs: self.mutable_input_refs,
407 written: results.written_objects,
408 events: TransactionEvents {
409 data: results.user_events,
410 },
411 accumulator_events: results.accumulator_events,
412 loaded_runtime_objects: self.loaded_runtime_objects,
413 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
414 lamport_version: self.lamport_timestamp,
415 binary_config: self.protocol_config.binary_config(None),
416 accumulator_running_max_withdraws,
417 retry_request: self.retry_request.into_inner(),
418 }
419 }
420
421 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
425 let mut to_be_updated = vec![];
426 for id in self.mutable_input_refs.keys() {
428 if !self.execution_results.modified_objects.contains(id) {
429 to_be_updated.push(self.input_objects[id].clone());
433 }
434 }
435 for object in to_be_updated {
436 self.mutate_input_object(object.clone());
438 }
439 }
440
441 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
442 let results = &self.execution_results;
443 let all_ids = results
444 .created_object_ids
445 .iter()
446 .chain(&results.deleted_object_ids)
447 .chain(&results.modified_objects)
448 .chain(results.written_objects.keys())
449 .collect::<BTreeSet<_>>();
450 all_ids
451 .into_iter()
452 .map(|id| {
453 (
454 *id,
455 EffectsObjectChange::new(
456 self.get_object_modified_at(id)
457 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
458 results.written_objects.get(id),
459 results.created_object_ids.contains(id),
460 results.deleted_object_ids.contains(id),
461 ),
462 )
463 })
464 .chain(results.accumulator_events.iter().cloned().map(
465 |AccumulatorEvent {
466 accumulator_obj,
467 write,
468 }| {
469 (
470 *accumulator_obj.inner(),
471 EffectsObjectChange::new_from_accumulator_write(write),
472 )
473 },
474 ))
475 .collect()
476 }
477
478 pub fn into_effects(
479 mut self,
480 shared_object_refs: Vec<SharedInput>,
481 transaction_digest: &TransactionDigest,
482 mut transaction_dependencies: BTreeSet<TransactionDigest>,
483 gas_cost_summary: GasCostSummary,
484 status: ExecutionStatus,
485 gas_coin: Option<ObjectID>,
486 epoch: EpochId,
487 ) -> (InnerTemporaryStore, TransactionEffects) {
488 for (id, obj) in &self.execution_results.written_objects {
491 assert!(
492 !matches!(obj.owner, Owner::Party { .. }),
493 "Party-owned objects are not yet supported (object {id})"
494 );
495 }
496
497 self.update_object_version_and_prev_tx();
498 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
500 self.merge_accumulator_events();
501
502 for (id, expected_version, expected_digest) in &self.receiving_objects {
505 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
509 let loaded_via_receive = obj_meta.version == *expected_version
513 && obj_meta.digest == *expected_digest
514 && obj_meta.owner.is_address_owned();
515 if loaded_via_receive {
516 transaction_dependencies.insert(obj_meta.previous_transaction);
517 }
518 }
519 }
520
521 assert!(self.protocol_config.enable_effects_v2());
522
523 let object_changes = self.get_object_changes();
524
525 let lamport_version = self.lamport_timestamp;
526 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
528 let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
529 shared_object_refs,
530 loaded_per_epoch_config_objects,
531 &object_changes,
532 );
533 let inner = self.into_inner(accumulator_running_max_withdraws);
534
535 let effects = TransactionEffects::new_from_execution_v2(
536 status,
537 epoch,
538 gas_cost_summary,
539 unchanged_consensus_objects,
540 *transaction_digest,
541 lamport_version,
542 object_changes,
543 gas_coin,
544 if inner.events.data.is_empty() {
545 None
546 } else {
547 Some(inner.events.digest())
548 },
549 transaction_dependencies.into_iter().collect(),
550 );
551
552 (inner, effects)
553 }
554
555 #[cfg(debug_assertions)]
557 fn check_invariants(&self) {
558 debug_assert!(
560 {
561 self.execution_results
562 .written_objects
563 .keys()
564 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
565 },
566 "Object both written and deleted."
567 );
568
569 debug_assert!(
571 {
572 self.mutable_input_refs
573 .keys()
574 .all(|id| self.execution_results.modified_objects.contains(id))
575 },
576 "Mutable input not modified."
577 );
578
579 debug_assert!(
580 {
581 self.execution_results
582 .written_objects
583 .values()
584 .all(|obj| obj.previous_transaction == self.tx_digest)
585 },
586 "Object previous transaction not properly set",
587 );
588 }
589
590 pub fn mutate_input_object(&mut self, object: Object) {
592 let id = object.id();
593 debug_assert!(self.input_objects.contains_key(&id));
594 debug_assert!(!object.is_immutable());
595 self.execution_results.modified_objects.insert(id);
596 self.execution_results.written_objects.insert(id, object);
597 }
598
599 pub fn mutate_new_or_input_object(&mut self, object: Object) {
600 let id = object.id();
601 debug_assert!(!object.is_immutable());
602 if self.input_objects.contains_key(&id) {
603 self.execution_results.modified_objects.insert(id);
604 }
605 self.execution_results.written_objects.insert(id, object);
606 }
607
608 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
612 let id = new_object.id();
613 let old_ref = old_object.compute_object_reference();
614 debug_assert_eq!(old_ref.0, id);
615 self.loaded_runtime_objects.insert(
616 id,
617 DynamicallyLoadedObjectMetadata {
618 version: old_ref.1,
619 digest: old_ref.2,
620 owner: old_object.owner.clone(),
621 storage_rebate: old_object.storage_rebate,
622 previous_transaction: old_object.previous_transaction,
623 },
624 );
625 self.execution_results.modified_objects.insert(id);
626 self.execution_results
627 .written_objects
628 .insert(id, new_object);
629 }
630
631 pub fn upgrade_system_package(&mut self, package: Object) {
635 let id = package.id();
636 assert!(package.is_package() && is_system_package(id));
637 self.execution_results.modified_objects.insert(id);
638 self.execution_results.written_objects.insert(id, package);
639 }
640
641 pub fn create_object(&mut self, object: Object) {
643 debug_assert!(
648 object.is_immutable() || object.version() == SequenceNumber::MIN,
649 "Created mutable objects should not have a version set",
650 );
651 let id = object.id();
652 self.execution_results.created_object_ids.insert(id);
653 self.execution_results.written_objects.insert(id, object);
654 }
655
656 pub fn delete_input_object(&mut self, id: &ObjectID) {
658 debug_assert!(!self.execution_results.written_objects.contains_key(id));
660 debug_assert!(self.input_objects.contains_key(id));
661 self.execution_results.modified_objects.insert(*id);
662 self.execution_results.deleted_object_ids.insert(*id);
663 }
664
665 pub fn drop_writes(&mut self) {
666 self.execution_results.drop_writes();
667 self.invariants.clear();
669 }
670
671 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
672 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
674 self.execution_results
675 .written_objects
676 .get(id)
677 .or_else(|| self.input_objects.get(id))
678 }
679
680 pub fn save_loaded_runtime_objects(
681 &mut self,
682 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
683 ) {
684 #[cfg(debug_assertions)]
685 {
686 for (id, v1) in &loaded_runtime_objects {
687 if let Some(v2) = self.loaded_runtime_objects.get(id) {
688 assert_eq!(v1, v2);
689 }
690 }
691 for (id, v1) in &self.loaded_runtime_objects {
692 if let Some(v2) = loaded_runtime_objects.get(id) {
693 assert_eq!(v1, v2);
694 }
695 }
696 }
697 self.loaded_runtime_objects.extend(loaded_runtime_objects);
700 }
701
702 pub fn save_wrapped_object_containers(
703 &mut self,
704 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
705 ) {
706 #[cfg(debug_assertions)]
707 {
708 for (id, container1) in &wrapped_object_containers {
709 if let Some(container2) = self.wrapped_object_containers.get(id) {
710 assert_eq!(container1, container2);
711 }
712 }
713 for (id, container1) in &self.wrapped_object_containers {
714 if let Some(container2) = wrapped_object_containers.get(id) {
715 assert_eq!(container1, container2);
716 }
717 }
718 }
719 self.wrapped_object_containers
722 .extend(wrapped_object_containers);
723 }
724
725 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
726 #[cfg(debug_assertions)]
727 {
728 for id in &self.generated_runtime_ids {
729 assert!(!generated_ids.contains(id))
730 }
731 for id in &generated_ids {
732 assert!(!self.generated_runtime_ids.contains(id));
733 }
734 }
735 self.generated_runtime_ids.extend(generated_ids);
736 }
737
738 pub fn estimate_effects_size_upperbound(&self) -> usize {
739 TransactionEffects::estimate_effects_size_upperbound_v2(
740 self.execution_results.written_objects.len(),
741 self.execution_results.modified_objects.len(),
742 self.input_objects.len(),
743 )
744 }
745
746 pub fn written_objects_size(&self) -> usize {
747 self.execution_results
748 .written_objects
749 .values()
750 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
751 }
752
753 pub fn check_gasless_execution_requirements(
759 &self,
760 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
761 ) -> Result<(), String> {
762 if !self.execution_results.written_objects.is_empty() {
763 return Err("Gasless transactions cannot create or mutate objects".to_string());
764 }
765
766 let input_coin_ids: BTreeSet<ObjectID> = self
767 .input_objects
768 .iter()
769 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
770 .map(|(id, _)| *id)
771 .collect();
772 if self.execution_results.deleted_object_ids != input_coin_ids {
773 return Err(format!(
774 "Gasless transaction must destroy exactly its input Coins. \
775 Expected: {input_coin_ids:?}, deleted: {:?}",
776 self.execution_results.deleted_object_ids
777 ));
778 }
779
780 let allowed_types =
781 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
782
783 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
786 &self.execution_results.accumulator_events,
787 )
788 .fold(
789 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
790 |mut totals, (address, token_type, signed_amount)| {
791 *totals.entry((address, token_type)).or_default() += signed_amount;
792 totals
793 },
794 );
795
796 for ((recipient, token_type), net_amount) in &net_totals {
797 if *net_amount <= 0 {
798 continue;
799 }
800 if let Some(&min_amount) = allowed_types.get(token_type)
801 && *net_amount < i128::from(min_amount)
802 {
803 return Err(format!(
804 "Gasless transfer of {net_amount} to {recipient} is below \
805 minimum {min_amount} for token type {token_type}"
806 ));
807 }
808 }
809
810 if let Some(reservations) = withdrawal_reservations {
811 for ((owner, token_type), &reserved) in reservations {
812 let net = net_totals
813 .get(&(*owner, token_type.clone()))
814 .copied()
815 .unwrap_or(0);
816 let remaining = (reserved as i128).saturating_add(net);
817 if remaining > 0
818 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
819 && min_balance_remaining > 0
820 && remaining < min_balance_remaining as i128
821 {
822 return Err(format!(
823 "Gasless withdrawal leaves {remaining} unused for {owner}, \
824 below minimum {min_balance_remaining} for token type {token_type}"
825 ));
826 }
827 }
828 }
829
830 Ok(())
831 }
832
833 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
838 if unmetered_storage_rebate == 0 {
839 return;
843 }
844 tracing::debug!(
845 "Amount of unmetered storage rebate from system tx: {:?}",
846 unmetered_storage_rebate
847 );
848 let mut system_state_wrapper = self
849 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
850 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
851 .clone();
852 assert_eq!(system_state_wrapper.storage_rebate, 0);
855 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
856 self.mutate_input_object(system_state_wrapper);
857 }
858
859 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
861 self.execution_results.accumulator_events.push(event);
862 }
863
864 fn get_object_modified_at(
870 &self,
871 object_id: &ObjectID,
872 ) -> Option<DynamicallyLoadedObjectMetadata> {
873 if self.execution_results.modified_objects.contains(object_id) {
874 Some(
875 self.mutable_input_refs
876 .get(object_id)
877 .map(
878 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
879 version: *version,
880 digest: *digest,
881 owner: owner.clone(),
882 storage_rebate: self.input_objects[object_id].storage_rebate,
884 previous_transaction: self.input_objects[object_id]
885 .previous_transaction,
886 },
887 )
888 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
889 .unwrap_or_else(|| {
890 debug_assert!(is_system_package(*object_id));
891 let package_obj =
892 self.store.get_package_object(object_id).unwrap().unwrap();
893 let obj = package_obj.object();
894 DynamicallyLoadedObjectMetadata {
895 version: obj.version(),
896 digest: obj.digest(),
897 owner: obj.owner.clone(),
898 storage_rebate: obj.storage_rebate,
899 previous_transaction: obj.previous_transaction,
900 }
901 }),
902 )
903 } else {
904 None
905 }
906 }
907
908 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
909 self.protocol_config
910 }
911
912 pub(crate) fn set_invariant_inputs(
917 &mut self,
918 transaction_kind: &TransactionKind,
919 gas_data: &GasData,
920 transaction_signer: SuiAddress,
921 ) {
922 self.invariants
923 .set_transaction_inputs(transaction_kind, gas_data, transaction_signer);
924 }
925
926 pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
929 &self,
930 move_vm: &Arc<MoveRuntime>,
931 enable_expensive_checks: bool,
932 cost_summary: &GasCostSummary,
933 ) -> Result<(), ExecutionError> {
934 self.invariants.check_conservation_invariants::<Mode>(
935 self,
936 move_vm,
937 enable_expensive_checks,
938 cost_summary,
939 )
940 }
941
942 pub(crate) fn check_ownership_invariants(
945 &self,
946 sender: &SuiAddress,
947 sponsor: &Option<SuiAddress>,
948 gas_charger: &GasCharger,
949 mutable_inputs: &HashSet<ObjectID>,
950 is_epoch_change: bool,
951 ) -> SuiResult<()> {
952 self.invariants.check_ownership_invariants(
953 self,
954 sender,
955 sponsor,
956 gas_charger,
957 mutable_inputs,
958 is_epoch_change,
959 )
960 }
961}
962
963impl TemporaryStore<'_> {
964 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
971 let old_storage_rebates: Vec<_> = self
973 .execution_results
974 .written_objects
975 .keys()
976 .map(|object_id| {
977 self.get_object_modified_at(object_id)
978 .map(|metadata| metadata.storage_rebate)
979 .unwrap_or_default()
980 })
981 .collect();
982 for (object, old_storage_rebate) in self
983 .execution_results
984 .written_objects
985 .values_mut()
986 .zip_debug_eq(old_storage_rebates)
987 {
988 let new_object_size = object.object_size_for_gas_metering();
990 let new_storage_rebate = gas_charger.track_storage_mutation(
992 object.id(),
993 new_object_size,
994 old_storage_rebate,
995 );
996 object.storage_rebate = new_storage_rebate;
997 }
998
999 self.collect_rebate(gas_charger);
1000 }
1001
1002 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
1003 for object_id in &self.execution_results.modified_objects {
1004 if self
1005 .execution_results
1006 .written_objects
1007 .contains_key(object_id)
1008 {
1009 continue;
1010 }
1011 let storage_rebate = self
1013 .get_object_modified_at(object_id)
1014 .unwrap()
1016 .storage_rebate;
1017 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
1018 }
1019 }
1020
1021 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1022 &self,
1023 ) -> Result<(), Mode::Error> {
1024 assert_invariant!(
1025 self.execution_results
1026 .created_object_ids
1027 .iter()
1028 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1029 && !self.execution_results.modified_objects.contains(id)),
1030 "Created object IDs cannot also be deleted or modified"
1031 );
1032 assert_invariant!(
1033 self.execution_results.modified_objects.iter().all(|id| {
1034 self.mutable_input_refs.contains_key(id)
1035 || self.loaded_runtime_objects.contains_key(id)
1036 || is_system_package(*id)
1037 }),
1038 "A modified object must be either a mutable input, a loaded child object, or a system package"
1039 );
1040 Ok(())
1041 }
1042}
1043impl TemporaryStore<'_> {
1048 pub fn advance_epoch_safe_mode(
1049 &mut self,
1050 params: &AdvanceEpochParams,
1051 protocol_config: &ProtocolConfig,
1052 ) {
1053 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
1054 .expect("System state wrapper object must exist");
1055 let (old_object, new_object) =
1056 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
1057 self.mutate_child_object(old_object, new_object);
1058 }
1059}
1060
1061impl RuntimeObjectResolver for TemporaryStore<'_> {
1062 fn read_child_object(
1063 &self,
1064 parent: &ObjectID,
1065 child: &ObjectID,
1066 child_version_upper_bound: SequenceNumber,
1067 ) -> SuiResult<Option<Object>> {
1068 let obj_opt = self.execution_results.written_objects.get(child);
1069 if obj_opt.is_some() {
1070 Ok(obj_opt.cloned())
1071 } else {
1072 let _scope = monitored_scope("Execution::read_child_object");
1073 self.store
1074 .read_child_object(parent, child, child_version_upper_bound)
1075 }
1076 }
1077
1078 fn get_object_received_at_version(
1079 &self,
1080 owner: &ObjectID,
1081 receiving_object_id: &ObjectID,
1082 receive_object_at_version: SequenceNumber,
1083 epoch_id: EpochId,
1084 ) -> SuiResult<Option<Object>> {
1085 debug_assert!(
1088 !self
1089 .execution_results
1090 .written_objects
1091 .contains_key(receiving_object_id)
1092 );
1093 debug_assert!(
1094 !self
1095 .execution_results
1096 .deleted_object_ids
1097 .contains(receiving_object_id)
1098 );
1099 self.store.get_object_received_at_version(
1100 owner,
1101 receiving_object_id,
1102 receive_object_at_version,
1103 epoch_id,
1104 )
1105 }
1106}
1107
1108fn was_object_mutated(object: &Object, original: &Object) -> bool {
1111 let data_equal = match (&object.data, &original.data) {
1112 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1113 (Data::Package(a), Data::Package(b)) => a == b,
1116 _ => false,
1117 };
1118
1119 let owner_equal = match (&object.owner, &original.owner) {
1120 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1124 (
1125 Owner::ConsensusAddressOwner { owner: a, .. },
1126 Owner::ConsensusAddressOwner { owner: b, .. },
1127 ) => a == b,
1128 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1129 (Owner::Immutable, Owner::Immutable) => true,
1130 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1131 (
1132 Owner::Party {
1133 permissions: a,
1134 start_version: _,
1135 },
1136 Owner::Party {
1137 permissions: b,
1138 start_version: _,
1139 },
1140 ) => a == b,
1141
1142 (Owner::AddressOwner(_), _)
1145 | (Owner::Immutable, _)
1146 | (Owner::ObjectOwner(_), _)
1147 | (Owner::Shared { .. }, _)
1148 | (Owner::ConsensusAddressOwner { .. }, _)
1149 | (Owner::Party { .. }, _) => false,
1150 };
1151
1152 !data_equal || !owner_equal
1153}
1154
1155impl Storage for TemporaryStore<'_> {
1156 fn reset(&mut self) {
1157 self.drop_writes();
1158 }
1159
1160 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1161 TemporaryStore::read_object(self, id)
1162 }
1163
1164 fn record_execution_results(
1166 &mut self,
1167 results: ExecutionResults,
1168 ) -> Result<(), ExecutionError> {
1169 let ExecutionResults::V2(mut results) = results else {
1170 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1171 };
1172
1173 let mut to_remove = Vec::new();
1175 for (id, original) in &self.non_exclusive_input_original_versions {
1176 if results
1178 .written_objects
1179 .get(id)
1180 .map(|obj| was_object_mutated(obj, original))
1181 .unwrap_or(true)
1182 {
1183 return Err(ExecutionError::new_with_source(
1184 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1185 "Non-exclusive write input object has been modified or deleted",
1186 ));
1187 }
1188 to_remove.push(*id);
1189 }
1190
1191 for id in to_remove {
1192 results.written_objects.remove(&id);
1193 results.modified_objects.remove(&id);
1194 }
1195
1196 let event_start = self.execution_results.accumulator_events.len();
1202 self.execution_results.merge_results(
1203 results, true, true,
1204 )?;
1205 let event_end = self.execution_results.accumulator_events.len();
1206 self.invariants
1207 .record_ptb_event_range(event_start, event_end);
1208
1209 Ok(())
1210 }
1211
1212 fn save_loaded_runtime_objects(
1213 &mut self,
1214 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1215 ) {
1216 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1217 }
1218
1219 fn save_wrapped_object_containers(
1220 &mut self,
1221 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1222 ) {
1223 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1224 }
1225
1226 fn check_coin_deny_list(
1227 &self,
1228 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1229 ) -> DenyListResult {
1230 let result = check_coin_deny_list_v2_during_execution(
1231 receiving_funds_type_and_owners,
1232 self.cur_epoch,
1233 self.store.as_object_store(),
1234 );
1235 if result.num_non_gas_coin_owners > 0
1238 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1239 {
1240 self.loaded_per_epoch_config_objects
1241 .write()
1242 .insert(SUI_DENY_LIST_OBJECT_ID);
1243 }
1244 result
1245 }
1246
1247 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1248 TemporaryStore::save_generated_object_ids(self, generated_ids)
1249 }
1250}
1251
1252impl BackingPackageStore for TemporaryStore<'_> {
1253 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1254 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1261 Ok(Some(PackageObject::new(obj.clone())))
1262 } else {
1263 self.store.get_package_object(package_id).inspect(|obj| {
1264 if let Some(v) = obj
1266 && !self
1267 .runtime_packages_loaded_from_db
1268 .read()
1269 .contains_key(package_id)
1270 {
1271 self.runtime_packages_loaded_from_db
1276 .write()
1277 .insert(*package_id, v.clone());
1278 }
1279 })
1280 }
1281 }
1282}