1use crate::gas_charger::GasCharger;
5use mysten_metrics::monitored_scope;
6use parking_lot::RwLock;
7use std::collections::{BTreeMap, BTreeSet, HashSet};
8use sui_protocol_config::ProtocolConfig;
9use sui_types::accumulator_event::AccumulatorEvent;
10use sui_types::base_types::VersionDigest;
11use sui_types::committee::EpochId;
12use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
13use sui_types::effects::{AccumulatorWriteV1, TransactionEffects, TransactionEvents};
14use sui_types::error::ExecutionErrorKind;
15use sui_types::execution::{
16 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
17};
18use sui_types::execution_status::ExecutionStatus;
19use sui_types::inner_temporary_store::InnerTemporaryStore;
20use sui_types::layout_resolver::LayoutResolver;
21use sui_types::object::Data;
22use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
23use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
24use sui_types::{
25 SUI_DENY_LIST_OBJECT_ID,
26 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
27 effects::EffectsObjectChange,
28 error::{ExecutionError, SuiResult},
29 gas::GasCostSummary,
30 object::Object,
31 object::Owner,
32 storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
33 transaction::InputObjects,
34};
35use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
36
37pub struct TemporaryStore<'backing> {
38 store: &'backing dyn BackingStore,
44 tx_digest: TransactionDigest,
45 input_objects: BTreeMap<ObjectID, Object>,
46
47 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
50
51 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
52 lamport_timestamp: SequenceNumber,
54 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
57 execution_results: ExecutionResultsV2,
58 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
60 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
62 protocol_config: &'backing ProtocolConfig,
63
64 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
67
68 receiving_objects: Vec<ObjectRef>,
71
72 generated_runtime_ids: BTreeSet<ObjectID>,
76
77 cur_epoch: EpochId,
80
81 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
84}
85
86impl<'backing> TemporaryStore<'backing> {
87 pub fn new(
90 store: &'backing dyn BackingStore,
91 input_objects: InputObjects,
92 receiving_objects: Vec<ObjectRef>,
93 tx_digest: TransactionDigest,
94 protocol_config: &'backing ProtocolConfig,
95 cur_epoch: EpochId,
96 ) -> Self {
97 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
98 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
99
100 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
101 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
102 let objects = input_objects.into_object_map();
103 #[cfg(debug_assertions)]
104 {
105 assert!(
107 objects
108 .keys()
109 .collect::<HashSet<_>>()
110 .intersection(
111 &receiving_objects
112 .iter()
113 .map(|oref| &oref.0)
114 .collect::<HashSet<_>>()
115 )
116 .next()
117 .is_none()
118 );
119 }
120 Self {
121 store,
122 tx_digest,
123 input_objects: objects,
124 non_exclusive_input_original_versions,
125 stream_ended_consensus_objects,
126 lamport_timestamp,
127 mutable_input_refs,
128 execution_results: ExecutionResultsV2::default(),
129 protocol_config,
130 loaded_runtime_objects: BTreeMap::new(),
131 wrapped_object_containers: BTreeMap::new(),
132 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
133 receiving_objects,
134 generated_runtime_ids: BTreeSet::new(),
135 cur_epoch,
136 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
137 }
138 }
139
140 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
142 &self.input_objects
143 }
144
145 pub fn update_object_version_and_prev_tx(&mut self) {
146 self.execution_results.update_version_and_previous_tx(
147 self.lamport_timestamp,
148 self.tx_digest,
149 &self.input_objects,
150 self.protocol_config.reshare_at_same_initial_version(),
151 );
152
153 #[cfg(debug_assertions)]
154 {
155 self.check_invariants();
156 }
157 }
158
159 pub fn into_inner(self) -> InnerTemporaryStore {
161 let results = self.execution_results;
162 InnerTemporaryStore {
163 input_objects: self.input_objects,
164 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
165 mutable_inputs: self.mutable_input_refs,
166 written: results.written_objects,
167 events: TransactionEvents {
168 data: results.user_events,
169 },
170 accumulator_events: results.accumulator_events,
171 loaded_runtime_objects: self.loaded_runtime_objects,
172 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
173 lamport_version: self.lamport_timestamp,
174 binary_config: self.protocol_config.binary_config(None),
175 }
176 }
177
178 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
182 let mut to_be_updated = vec![];
183 for id in self.mutable_input_refs.keys() {
185 if !self.execution_results.modified_objects.contains(id) {
186 to_be_updated.push(self.input_objects[id].clone());
190 }
191 }
192 for object in to_be_updated {
193 self.mutate_input_object(object.clone());
195 }
196 }
197
198 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
199 let results = &self.execution_results;
200 let all_ids = results
201 .created_object_ids
202 .iter()
203 .chain(&results.deleted_object_ids)
204 .chain(&results.modified_objects)
205 .chain(results.written_objects.keys())
206 .collect::<BTreeSet<_>>();
207 all_ids
208 .into_iter()
209 .map(|id| {
210 (
211 *id,
212 EffectsObjectChange::new(
213 self.get_object_modified_at(id)
214 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
215 results.written_objects.get(id),
216 results.created_object_ids.contains(id),
217 results.deleted_object_ids.contains(id),
218 ),
219 )
220 })
221 .chain(results.accumulator_events.iter().cloned().map(
222 |AccumulatorEvent {
223 accumulator_obj,
224 write,
225 }| {
226 (
227 *accumulator_obj.inner(),
228 EffectsObjectChange::new_from_accumulator_write(write),
229 )
230 },
231 ))
232 .collect()
233 }
234
235 pub fn into_effects(
236 mut self,
237 shared_object_refs: Vec<SharedInput>,
238 transaction_digest: &TransactionDigest,
239 mut transaction_dependencies: BTreeSet<TransactionDigest>,
240 gas_cost_summary: GasCostSummary,
241 status: ExecutionStatus,
242 gas_charger: &mut GasCharger,
243 epoch: EpochId,
244 ) -> (InnerTemporaryStore, TransactionEffects) {
245 self.update_object_version_and_prev_tx();
246
247 for (id, expected_version, expected_digest) in &self.receiving_objects {
250 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
254 let loaded_via_receive = obj_meta.version == *expected_version
258 && obj_meta.digest == *expected_digest
259 && obj_meta.owner.is_address_owned();
260 if loaded_via_receive {
261 transaction_dependencies.insert(obj_meta.previous_transaction);
262 }
263 }
264 }
265
266 assert!(self.protocol_config.enable_effects_v2());
267
268 let gas_coin = gas_charger.gas_coin();
273
274 let object_changes = self.get_object_changes();
275
276 let lamport_version = self.lamport_timestamp;
277 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
279 let inner = self.into_inner();
280
281 let effects = TransactionEffects::new_from_execution_v2(
282 status,
283 epoch,
284 gas_cost_summary,
285 shared_object_refs,
287 loaded_per_epoch_config_objects,
288 *transaction_digest,
289 lamport_version,
290 object_changes,
291 gas_coin,
292 if inner.events.data.is_empty() {
293 None
294 } else {
295 Some(inner.events.digest())
296 },
297 transaction_dependencies.into_iter().collect(),
298 );
299
300 (inner, effects)
301 }
302
303 #[cfg(debug_assertions)]
305 fn check_invariants(&self) {
306 debug_assert!(
308 {
309 self.execution_results
310 .written_objects
311 .keys()
312 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
313 },
314 "Object both written and deleted."
315 );
316
317 debug_assert!(
319 {
320 self.mutable_input_refs
321 .keys()
322 .all(|id| self.execution_results.modified_objects.contains(id))
323 },
324 "Mutable input not modified."
325 );
326
327 debug_assert!(
328 {
329 self.execution_results
330 .written_objects
331 .values()
332 .all(|obj| obj.previous_transaction == self.tx_digest)
333 },
334 "Object previous transaction not properly set",
335 );
336 }
337
338 pub fn mutate_input_object(&mut self, object: Object) {
340 let id = object.id();
341 debug_assert!(self.input_objects.contains_key(&id));
342 debug_assert!(!object.is_immutable());
343 self.execution_results.modified_objects.insert(id);
344 self.execution_results.written_objects.insert(id, object);
345 }
346
347 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
351 let id = new_object.id();
352 let old_ref = old_object.compute_object_reference();
353 debug_assert_eq!(old_ref.0, id);
354 self.loaded_runtime_objects.insert(
355 id,
356 DynamicallyLoadedObjectMetadata {
357 version: old_ref.1,
358 digest: old_ref.2,
359 owner: old_object.owner.clone(),
360 storage_rebate: old_object.storage_rebate,
361 previous_transaction: old_object.previous_transaction,
362 },
363 );
364 self.execution_results.modified_objects.insert(id);
365 self.execution_results
366 .written_objects
367 .insert(id, new_object);
368 }
369
370 pub fn upgrade_system_package(&mut self, package: Object) {
374 let id = package.id();
375 assert!(package.is_package() && is_system_package(id));
376 self.execution_results.modified_objects.insert(id);
377 self.execution_results.written_objects.insert(id, package);
378 }
379
380 pub fn create_object(&mut self, object: Object) {
382 debug_assert!(
387 object.is_immutable() || object.version() == SequenceNumber::MIN,
388 "Created mutable objects should not have a version set",
389 );
390 let id = object.id();
391 self.execution_results.created_object_ids.insert(id);
392 self.execution_results.written_objects.insert(id, object);
393 }
394
395 pub fn delete_input_object(&mut self, id: &ObjectID) {
397 debug_assert!(!self.execution_results.written_objects.contains_key(id));
399 debug_assert!(self.input_objects.contains_key(id));
400 self.execution_results.modified_objects.insert(*id);
401 self.execution_results.deleted_object_ids.insert(*id);
402 }
403
404 pub fn drop_writes(&mut self) {
405 self.execution_results.drop_writes();
406 }
407
408 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
409 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
411 self.execution_results
412 .written_objects
413 .get(id)
414 .or_else(|| self.input_objects.get(id))
415 }
416
417 pub fn save_loaded_runtime_objects(
418 &mut self,
419 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
420 ) {
421 #[cfg(debug_assertions)]
422 {
423 for (id, v1) in &loaded_runtime_objects {
424 if let Some(v2) = self.loaded_runtime_objects.get(id) {
425 assert_eq!(v1, v2);
426 }
427 }
428 for (id, v1) in &self.loaded_runtime_objects {
429 if let Some(v2) = loaded_runtime_objects.get(id) {
430 assert_eq!(v1, v2);
431 }
432 }
433 }
434 self.loaded_runtime_objects.extend(loaded_runtime_objects);
437 }
438
439 pub fn save_wrapped_object_containers(
440 &mut self,
441 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
442 ) {
443 #[cfg(debug_assertions)]
444 {
445 for (id, container1) in &wrapped_object_containers {
446 if let Some(container2) = self.wrapped_object_containers.get(id) {
447 assert_eq!(container1, container2);
448 }
449 }
450 for (id, container1) in &self.wrapped_object_containers {
451 if let Some(container2) = wrapped_object_containers.get(id) {
452 assert_eq!(container1, container2);
453 }
454 }
455 }
456 self.wrapped_object_containers
459 .extend(wrapped_object_containers);
460 }
461
462 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
463 #[cfg(debug_assertions)]
464 {
465 for id in &self.generated_runtime_ids {
466 assert!(!generated_ids.contains(id))
467 }
468 for id in &generated_ids {
469 assert!(!self.generated_runtime_ids.contains(id));
470 }
471 }
472 self.generated_runtime_ids.extend(generated_ids);
473 }
474
475 pub fn estimate_effects_size_upperbound(&self) -> usize {
476 TransactionEffects::estimate_effects_size_upperbound_v2(
477 self.execution_results.written_objects.len(),
478 self.execution_results.modified_objects.len(),
479 self.input_objects.len(),
480 )
481 }
482
483 pub fn written_objects_size(&self) -> usize {
484 self.execution_results
485 .written_objects
486 .values()
487 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
488 }
489
490 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
495 if unmetered_storage_rebate == 0 {
496 return;
500 }
501 tracing::debug!(
502 "Amount of unmetered storage rebate from system tx: {:?}",
503 unmetered_storage_rebate
504 );
505 let mut system_state_wrapper = self
506 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
507 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
508 .clone();
509 assert_eq!(system_state_wrapper.storage_rebate, 0);
512 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
513 self.mutate_input_object(system_state_wrapper);
514 }
515
516 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
519 let obj_id = *event.accumulator_obj.inner();
520 for existing in self.execution_results.accumulator_events.iter_mut() {
521 if *existing.accumulator_obj.inner() == obj_id {
522 existing.write =
523 AccumulatorWriteV1::merge(vec![existing.write.clone(), event.write]);
524 return;
525 }
526 }
527 self.execution_results.accumulator_events.push(event);
528 }
529
530 fn get_object_modified_at(
536 &self,
537 object_id: &ObjectID,
538 ) -> Option<DynamicallyLoadedObjectMetadata> {
539 if self.execution_results.modified_objects.contains(object_id) {
540 Some(
541 self.mutable_input_refs
542 .get(object_id)
543 .map(
544 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
545 version: *version,
546 digest: *digest,
547 owner: owner.clone(),
548 storage_rebate: self.input_objects[object_id].storage_rebate,
550 previous_transaction: self.input_objects[object_id]
551 .previous_transaction,
552 },
553 )
554 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
555 .unwrap_or_else(|| {
556 debug_assert!(is_system_package(*object_id));
557 let package_obj =
558 self.store.get_package_object(object_id).unwrap().unwrap();
559 let obj = package_obj.object();
560 DynamicallyLoadedObjectMetadata {
561 version: obj.version(),
562 digest: obj.digest(),
563 owner: obj.owner.clone(),
564 storage_rebate: obj.storage_rebate,
565 previous_transaction: obj.previous_transaction,
566 }
567 }),
568 )
569 } else {
570 None
571 }
572 }
573}
574
575impl TemporaryStore<'_> {
576 pub fn check_ownership_invariants(
579 &self,
580 sender: &SuiAddress,
581 sponsor: &Option<SuiAddress>,
582 gas_charger: &mut GasCharger,
583 mutable_inputs: &HashSet<ObjectID>,
584 is_epoch_change: bool,
585 ) -> SuiResult<()> {
586 let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().map(|g| &g.0).collect();
587 let gas_owner = sponsor.as_ref().unwrap_or(sender);
588
589 let mut authenticated_for_mutation: HashSet<_> = self
591 .input_objects
592 .iter()
593 .filter_map(|(id, obj)| {
594 match &obj.owner {
595 Owner::AddressOwner(a) => {
596 if gas_objs.contains(id) {
597 assert!(
599 a == gas_owner,
600 "Gas object must be owned by sender or sponsor"
601 );
602 } else {
603 assert!(sender == a, "Input object must be owned by sender");
604 }
605 Some(id)
606 }
607 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => Some(id),
608 Owner::Immutable => {
609 None
620 }
621 Owner::ObjectOwner(_parent) => {
622 unreachable!(
623 "Input objects must be address owned, shared, consensus, or immutable"
624 )
625 }
626 }
627 })
628 .filter(|id| {
629 mutable_inputs.contains(id)
632 })
633 .copied()
634 .chain(self.generated_runtime_ids.iter().copied())
637 .collect();
638
639 authenticated_for_mutation.insert((*sender).into());
641 if let Some(sponsor) = sponsor {
642 authenticated_for_mutation.insert((*sponsor).into());
643 }
644
645 let mut objects_to_authenticate = self
647 .execution_results
648 .modified_objects
649 .iter()
650 .copied()
651 .collect::<Vec<_>>();
652
653 while let Some(to_authenticate) = objects_to_authenticate.pop() {
654 if authenticated_for_mutation.contains(&to_authenticate) {
655 continue;
657 }
658
659 let parent = if let Some(container_id) =
660 self.wrapped_object_containers.get(&to_authenticate)
661 {
662 *container_id
664 } else {
665 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
668 panic!(
669 "Failed to load object {to_authenticate:?}.\n \
670 If it cannot be loaded, we would expect it to be in the wrapped object map: {:#?}",
671 &self.wrapped_object_containers
672 )
673 };
674
675 match &old_obj.owner {
676 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
679 Owner::AddressOwner(parent)
684 | Owner::ConsensusAddressOwner { owner: parent, .. } => {
685 ObjectID::from(*parent)
689 }
690 owner @ Owner::Shared { .. } => {
693 panic!(
694 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
695 Potentially covering objects in: {authenticated_for_mutation:#?}"
696 );
697 }
698 Owner::Immutable => {
699 assert!(
700 is_epoch_change,
701 "Immutable objects cannot be written, except for \
702 Sui Framework/Move stdlib upgrades at epoch change boundaries"
703 );
704 assert!(
708 is_system_package(to_authenticate),
709 "Only system packages can be upgraded"
710 );
711 continue;
712 }
713 }
714 };
715
716 authenticated_for_mutation.insert(to_authenticate);
718 objects_to_authenticate.push(parent);
719 }
720 Ok(())
721 }
722}
723
724impl TemporaryStore<'_> {
725 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
732 let old_storage_rebates: Vec<_> = self
734 .execution_results
735 .written_objects
736 .keys()
737 .map(|object_id| {
738 self.get_object_modified_at(object_id)
739 .map(|metadata| metadata.storage_rebate)
740 .unwrap_or_default()
741 })
742 .collect();
743 for (object, old_storage_rebate) in self
744 .execution_results
745 .written_objects
746 .values_mut()
747 .zip(old_storage_rebates)
748 {
749 let new_object_size = object.object_size_for_gas_metering();
751 let new_storage_rebate = gas_charger.track_storage_mutation(
753 object.id(),
754 new_object_size,
755 old_storage_rebate,
756 );
757 object.storage_rebate = new_storage_rebate;
758 }
759
760 self.collect_rebate(gas_charger);
761 }
762
763 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
764 for object_id in &self.execution_results.modified_objects {
765 if self
766 .execution_results
767 .written_objects
768 .contains_key(object_id)
769 {
770 continue;
771 }
772 let storage_rebate = self
774 .get_object_modified_at(object_id)
775 .unwrap()
777 .storage_rebate;
778 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
779 }
780 }
781
782 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
783 assert_invariant!(
784 self.execution_results
785 .created_object_ids
786 .iter()
787 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
788 && !self.execution_results.modified_objects.contains(id)),
789 "Created object IDs cannot also be deleted or modified"
790 );
791 assert_invariant!(
792 self.execution_results.modified_objects.iter().all(|id| {
793 self.mutable_input_refs.contains_key(id)
794 || self.loaded_runtime_objects.contains_key(id)
795 || is_system_package(*id)
796 }),
797 "A modified object must be either a mutable input, a loaded child object, or a system package"
798 );
799 Ok(())
800 }
801}
802impl TemporaryStore<'_> {
807 pub fn advance_epoch_safe_mode(
808 &mut self,
809 params: &AdvanceEpochParams,
810 protocol_config: &ProtocolConfig,
811 ) {
812 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
813 .expect("System state wrapper object must exist");
814 let (old_object, new_object) =
815 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
816 self.mutate_child_object(old_object, new_object);
817 }
818}
819
820type ModifiedObjectInfo<'a> = (
821 ObjectID,
822 Option<DynamicallyLoadedObjectMetadata>,
824 Option<&'a Object>,
825);
826
827impl TemporaryStore<'_> {
828 fn get_input_sui(
829 &self,
830 id: &ObjectID,
831 expected_version: SequenceNumber,
832 layout_resolver: &mut impl LayoutResolver,
833 ) -> Result<u64, ExecutionError> {
834 if let Some(obj) = self.input_objects.get(id) {
835 if obj.version() != expected_version {
837 invariant_violation!(
838 "Version mismatching when resolving input object to check conservation--\
839 expected {}, got {}",
840 expected_version,
841 obj.version(),
842 );
843 }
844 obj.get_total_sui(layout_resolver).map_err(|e| {
845 make_invariant_violation!(
846 "Failed looking up input SUI in SUI conservation checking for input with \
847 type {:?}: {e:#?}",
848 obj.struct_tag(),
849 )
850 })
851 } else {
852 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
854 invariant_violation!(
855 "Failed looking up dynamic field {id} in SUI conservation checking"
856 );
857 };
858 obj.get_total_sui(layout_resolver).map_err(|e| {
859 make_invariant_violation!(
860 "Failed looking up input SUI in SUI conservation checking for type \
861 {:?}: {e:#?}",
862 obj.struct_tag(),
863 )
864 })
865 }
866 }
867
868 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
873 self.execution_results
874 .modified_objects
875 .iter()
876 .map(|id| {
877 let metadata = self.get_object_modified_at(id);
878 let output = self.execution_results.written_objects.get(id);
879 (*id, metadata, output)
880 })
881 .chain(
882 self.execution_results
883 .written_objects
884 .iter()
885 .filter_map(|(id, object)| {
886 if self.execution_results.modified_objects.contains(id) {
887 None
888 } else {
889 Some((*id, None, Some(object)))
890 }
891 }),
892 )
893 .collect()
894 }
895
896 pub fn check_sui_conserved(
910 &self,
911 simple_conservation_checks: bool,
912 gas_summary: &GasCostSummary,
913 ) -> Result<(), ExecutionError> {
914 if !simple_conservation_checks {
915 return Ok(());
916 }
917 let mut total_input_rebate = 0;
919 let mut total_output_rebate = 0;
921 for (_, input, output) in self.get_modified_objects() {
922 if let Some(input) = input {
923 total_input_rebate += input.storage_rebate;
924 }
925 if let Some(object) = output {
926 total_output_rebate += object.storage_rebate;
927 }
928 }
929
930 if gas_summary.storage_cost == 0 {
931 if total_input_rebate
943 != total_output_rebate
944 + gas_summary.storage_rebate
945 + gas_summary.non_refundable_storage_fee
946 {
947 return Err(ExecutionError::invariant_violation(format!(
948 "SUI conservation failed -- no storage charges in gas summary \
949 and total storage input rebate {} not equal \
950 to total storage output rebate {}",
951 total_input_rebate, total_output_rebate,
952 )));
953 }
954 } else {
955 if total_input_rebate
958 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
959 {
960 return Err(ExecutionError::invariant_violation(format!(
961 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
962 {} SUI in tx storage rebate or tx non-refundable storage rebate",
963 total_input_rebate, gas_summary.non_refundable_storage_fee,
964 )));
965 }
966
967 if gas_summary.storage_cost != total_output_rebate {
970 return Err(ExecutionError::invariant_violation(format!(
971 "SUI conservation failed -- {} SUI charged for storage, \
972 {} SUI in storage rebate field of output objects",
973 gas_summary.storage_cost, total_output_rebate
974 )));
975 }
976 }
977 Ok(())
978 }
979
980 pub fn check_sui_conserved_expensive(
993 &self,
994 gas_summary: &GasCostSummary,
995 advance_epoch_gas_summary: Option<(u64, u64)>,
996 layout_resolver: &mut impl LayoutResolver,
997 ) -> Result<(), ExecutionError> {
998 let mut total_input_sui = 0;
1000 let mut total_output_sui = 0;
1002
1003 total_input_sui += self.execution_results.settlement_input_sui;
1007 total_output_sui += self.execution_results.settlement_output_sui;
1008
1009 for (id, input, output) in self.get_modified_objects() {
1010 if let Some(input) = input {
1011 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1012 }
1013 if let Some(object) = output {
1014 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1015 make_invariant_violation!(
1016 "Failed looking up output SUI in SUI conservation checking for \
1017 mutated type {:?}: {e:#?}",
1018 object.struct_tag(),
1019 )
1020 })?;
1021 }
1022 }
1023
1024 for event in &self.execution_results.accumulator_events {
1025 let (input, output) = event.total_sui_in_event();
1026 total_input_sui += input;
1027 total_output_sui += output;
1028 }
1029
1030 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1035 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1036 total_input_sui += epoch_fees;
1037 total_output_sui += epoch_rebates;
1038 }
1039 if total_input_sui != total_output_sui {
1040 return Err(ExecutionError::invariant_violation(format!(
1041 "SUI conservation failed: input={}, output={}, \
1042 this transaction either mints or burns SUI",
1043 total_input_sui, total_output_sui,
1044 )));
1045 }
1046 Ok(())
1047 }
1048}
1049
1050impl ChildObjectResolver for TemporaryStore<'_> {
1051 fn read_child_object(
1052 &self,
1053 parent: &ObjectID,
1054 child: &ObjectID,
1055 child_version_upper_bound: SequenceNumber,
1056 ) -> SuiResult<Option<Object>> {
1057 let obj_opt = self.execution_results.written_objects.get(child);
1058 if obj_opt.is_some() {
1059 Ok(obj_opt.cloned())
1060 } else {
1061 let _scope = monitored_scope("Execution::read_child_object");
1062 self.store
1063 .read_child_object(parent, child, child_version_upper_bound)
1064 }
1065 }
1066
1067 fn get_object_received_at_version(
1068 &self,
1069 owner: &ObjectID,
1070 receiving_object_id: &ObjectID,
1071 receive_object_at_version: SequenceNumber,
1072 epoch_id: EpochId,
1073 ) -> SuiResult<Option<Object>> {
1074 debug_assert!(
1077 !self
1078 .execution_results
1079 .written_objects
1080 .contains_key(receiving_object_id)
1081 );
1082 debug_assert!(
1083 !self
1084 .execution_results
1085 .deleted_object_ids
1086 .contains(receiving_object_id)
1087 );
1088 self.store.get_object_received_at_version(
1089 owner,
1090 receiving_object_id,
1091 receive_object_at_version,
1092 epoch_id,
1093 )
1094 }
1095}
1096
1097fn was_object_mutated(object: &Object, original: &Object) -> bool {
1100 let data_equal = match (&object.data, &original.data) {
1101 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1102 (Data::Package(a), Data::Package(b)) => a == b,
1105 _ => false,
1106 };
1107
1108 let owner_equal = match (&object.owner, &original.owner) {
1109 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1113 (
1114 Owner::ConsensusAddressOwner { owner: a, .. },
1115 Owner::ConsensusAddressOwner { owner: b, .. },
1116 ) => a == b,
1117 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1118 (Owner::Immutable, Owner::Immutable) => true,
1119 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1120
1121 (Owner::AddressOwner(_), _)
1124 | (Owner::Immutable, _)
1125 | (Owner::ObjectOwner(_), _)
1126 | (Owner::Shared { .. }, _)
1127 | (Owner::ConsensusAddressOwner { .. }, _) => false,
1128 };
1129
1130 !data_equal || !owner_equal
1131}
1132
1133impl Storage for TemporaryStore<'_> {
1134 fn reset(&mut self) {
1135 self.drop_writes();
1136 }
1137
1138 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1139 TemporaryStore::read_object(self, id)
1140 }
1141
1142 fn record_execution_results(
1144 &mut self,
1145 results: ExecutionResults,
1146 ) -> Result<(), ExecutionError> {
1147 let ExecutionResults::V2(mut results) = results else {
1148 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1149 };
1150
1151 let mut to_remove = Vec::new();
1153 for (id, original) in &self.non_exclusive_input_original_versions {
1154 if results
1156 .written_objects
1157 .get(id)
1158 .map(|obj| was_object_mutated(obj, original))
1159 .unwrap_or(true)
1160 {
1161 return Err(ExecutionError::new_with_source(
1162 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1163 "Non-exclusive write input object has been modified or deleted",
1164 ));
1165 }
1166 to_remove.push(*id);
1167 }
1168
1169 for id in to_remove {
1170 results.written_objects.remove(&id);
1171 results.modified_objects.remove(&id);
1172 }
1173
1174 self.execution_results.merge_results(results);
1177
1178 Ok(())
1179 }
1180
1181 fn save_loaded_runtime_objects(
1182 &mut self,
1183 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1184 ) {
1185 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1186 }
1187
1188 fn save_wrapped_object_containers(
1189 &mut self,
1190 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1191 ) {
1192 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1193 }
1194
1195 fn check_coin_deny_list(
1196 &self,
1197 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1198 ) -> DenyListResult {
1199 let result = check_coin_deny_list_v2_during_execution(
1200 receiving_funds_type_and_owners,
1201 self.cur_epoch,
1202 self.store.as_object_store(),
1203 );
1204 if result.num_non_gas_coin_owners > 0
1207 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1208 {
1209 self.loaded_per_epoch_config_objects
1210 .write()
1211 .insert(SUI_DENY_LIST_OBJECT_ID);
1212 }
1213 result
1214 }
1215
1216 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1217 TemporaryStore::save_generated_object_ids(self, generated_ids)
1218 }
1219}
1220
1221impl BackingPackageStore for TemporaryStore<'_> {
1222 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1223 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1230 Ok(Some(PackageObject::new(obj.clone())))
1231 } else {
1232 self.store.get_package_object(package_id).inspect(|obj| {
1233 if let Some(v) = obj
1235 && !self
1236 .runtime_packages_loaded_from_db
1237 .read()
1238 .contains_key(package_id)
1239 {
1240 self.runtime_packages_loaded_from_db
1245 .write()
1246 .insert(*package_id, v.clone());
1247 }
1248 })
1249 }
1250 }
1251}
1252
1253impl ParentSync for TemporaryStore<'_> {
1254 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1255 unreachable!("Never called in newer protocol versions")
1256 }
1257}