1use crate::gas_charger::GasCharger;
5use parking_lot::RwLock;
6use std::collections::{BTreeMap, BTreeSet, HashSet};
7use sui_protocol_config::ProtocolConfig;
8use sui_types::base_types::VersionDigest;
9use sui_types::committee::EpochId;
10use sui_types::digests::ObjectDigest;
11use sui_types::effects::{TransactionEffects, TransactionEvents};
12use sui_types::execution::{
13 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
14};
15use sui_types::execution_status::ExecutionStatus;
16use sui_types::inner_temporary_store::InnerTemporaryStore;
17use sui_types::layout_resolver::LayoutResolver;
18use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
19use sui_types::sui_system_state::{get_sui_system_state_wrapper, AdvanceEpochParams};
20use sui_types::{
21 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
22 effects::EffectsObjectChange,
23 error::{ExecutionError, SuiResult},
24 gas::GasCostSummary,
25 object::Object,
26 object::Owner,
27 storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
28 transaction::InputObjects,
29 TypeTag,
30};
31use sui_types::{is_system_package, SUI_SYSTEM_STATE_OBJECT_ID};
32
33pub struct TemporaryStore<'backing> {
34 store: &'backing dyn BackingStore,
40 tx_digest: TransactionDigest,
41 input_objects: BTreeMap<ObjectID, Object>,
42 deleted_consensus_objects: BTreeMap<ObjectID, SequenceNumber>,
43 lamport_timestamp: SequenceNumber,
45 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>, execution_results: ExecutionResultsV2,
47 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
49 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
51 protocol_config: &'backing ProtocolConfig,
52
53 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
56
57 receiving_objects: Vec<ObjectRef>,
60}
61
62impl<'backing> TemporaryStore<'backing> {
63 pub fn new(
66 store: &'backing dyn BackingStore,
67 input_objects: InputObjects,
68 receiving_objects: Vec<ObjectRef>,
69 tx_digest: TransactionDigest,
70 protocol_config: &'backing ProtocolConfig,
71 ) -> Self {
72 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
73 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
74 let deleted_consensus_objects = input_objects.consensus_stream_ended_objects();
75 let objects = input_objects.into_object_map();
76 #[cfg(debug_assertions)]
77 {
78 assert!(objects
80 .keys()
81 .collect::<HashSet<_>>()
82 .intersection(
83 &receiving_objects
84 .iter()
85 .map(|oref| &oref.0)
86 .collect::<HashSet<_>>()
87 )
88 .next()
89 .is_none());
90 }
91 Self {
92 store,
93 tx_digest,
94 input_objects: objects,
95 deleted_consensus_objects,
96 lamport_timestamp,
97 mutable_input_refs,
98 execution_results: ExecutionResultsV2::default(),
99 protocol_config,
100 loaded_runtime_objects: BTreeMap::new(),
101 wrapped_object_containers: BTreeMap::new(),
102 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
103 receiving_objects,
104 }
105 }
106
107 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
109 &self.input_objects
110 }
111
112 pub fn update_object_version_and_prev_tx(&mut self) {
113 self.execution_results.update_version_and_previous_tx(
114 self.lamport_timestamp,
115 self.tx_digest,
116 &self.input_objects,
117 false,
118 );
119
120 #[cfg(debug_assertions)]
121 {
122 self.check_invariants();
123 }
124 }
125
126 pub fn into_inner(self) -> InnerTemporaryStore {
128 let results = self.execution_results;
129 InnerTemporaryStore {
130 input_objects: self.input_objects,
131 mutable_inputs: self.mutable_input_refs,
132 stream_ended_consensus_objects: self.deleted_consensus_objects,
133 written: results.written_objects,
134 events: TransactionEvents {
135 data: results.user_events,
136 },
137 accumulator_events: vec![],
139 loaded_runtime_objects: self.loaded_runtime_objects,
140 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
141 lamport_version: self.lamport_timestamp,
142 binary_config: self.protocol_config.binary_config(None),
143 accumulator_running_max_withdraws: BTreeMap::new(),
144 }
145 }
146
147 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
151 let mut to_be_updated = vec![];
152 for id in self.mutable_input_refs.keys() {
153 if !self.execution_results.modified_objects.contains(id) {
154 to_be_updated.push(self.input_objects[id].clone());
158 }
159 }
160 for object in to_be_updated {
161 self.mutate_input_object(object.clone());
163 }
164 }
165
166 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
167 let results = &self.execution_results;
168 let all_ids = results
169 .created_object_ids
170 .iter()
171 .chain(&results.deleted_object_ids)
172 .chain(&results.modified_objects)
173 .chain(results.written_objects.keys())
174 .collect::<BTreeSet<_>>();
175 all_ids
176 .into_iter()
177 .map(|id| {
178 (
179 *id,
180 EffectsObjectChange::new(
181 self.get_object_modified_at(id)
182 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
183 results.written_objects.get(id),
184 results.created_object_ids.contains(id),
185 results.deleted_object_ids.contains(id),
186 ),
187 )
188 })
189 .collect()
190 }
191
192 pub fn into_effects(
193 mut self,
194 shared_object_refs: Vec<SharedInput>,
195 transaction_digest: &TransactionDigest,
196 mut transaction_dependencies: BTreeSet<TransactionDigest>,
197 gas_cost_summary: GasCostSummary,
198 status: ExecutionStatus,
199 gas_charger: &mut GasCharger,
200 epoch: EpochId,
201 ) -> (InnerTemporaryStore, TransactionEffects) {
202 self.update_object_version_and_prev_tx();
203
204 for (id, expected_version, expected_digest) in &self.receiving_objects {
207 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
211 let loaded_via_receive = obj_meta.version == *expected_version
215 && obj_meta.digest == *expected_digest
216 && obj_meta.owner.is_address_owned();
217 if loaded_via_receive {
218 transaction_dependencies.insert(obj_meta.previous_transaction);
219 }
220 }
221 }
222
223 if self.protocol_config.enable_effects_v2() {
224 self.into_effects_v2(
225 shared_object_refs,
226 transaction_digest,
227 transaction_dependencies,
228 gas_cost_summary,
229 status,
230 gas_charger,
231 epoch,
232 )
233 } else {
234 let shared_object_refs = shared_object_refs
235 .into_iter()
236 .map(|shared_input| match shared_input {
237 SharedInput::Existing(oref) => oref,
238 SharedInput::ConsensusStreamEnded(_) => {
239 unreachable!("Shared object deletion not supported in effects v1")
240 }
241 SharedInput::Cancelled(_) => {
242 unreachable!("Per object congestion control not supported in effects v1.")
243 }
244 })
245 .collect();
246 self.into_effects_v1(
247 shared_object_refs,
248 transaction_digest,
249 transaction_dependencies,
250 gas_cost_summary,
251 status,
252 gas_charger,
253 epoch,
254 )
255 }
256 }
257
258 fn into_effects_v1(
259 self,
260 shared_object_refs: Vec<ObjectRef>,
261 transaction_digest: &TransactionDigest,
262 transaction_dependencies: BTreeSet<TransactionDigest>,
263 gas_cost_summary: GasCostSummary,
264 status: ExecutionStatus,
265 gas_charger: &mut GasCharger,
266 epoch: EpochId,
267 ) -> (InnerTemporaryStore, TransactionEffects) {
268 let updated_gas_object_info = if let Some(coin_id) = gas_charger.gas_coin() {
269 let object = &self.execution_results.written_objects[&coin_id];
270 (object.compute_object_reference(), object.owner.clone())
271 } else {
272 (
273 (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN),
274 Owner::AddressOwner(SuiAddress::default()),
275 )
276 };
277 let lampot_version = self.lamport_timestamp;
278
279 let mut created = vec![];
280 let mut mutated = vec![];
281 let mut unwrapped = vec![];
282 let mut deleted = vec![];
283 let mut unwrapped_then_deleted = vec![];
284 let mut wrapped = vec![];
285 let mut modified_at_versions = vec![];
288 let mut deleted_at_versions = vec![];
289 self.execution_results
290 .written_objects
291 .iter()
292 .for_each(|(id, object)| {
293 let object_ref = object.compute_object_reference();
294 let owner = object.owner.clone();
295 if let Some(old_object_meta) = self.get_object_modified_at(id) {
296 modified_at_versions.push((*id, old_object_meta.version));
297 mutated.push((object_ref, owner));
298 } else if self.execution_results.created_object_ids.contains(id) {
299 created.push((object_ref, owner));
300 } else {
301 unwrapped.push((object_ref, owner));
302 }
303 });
304 self.execution_results
305 .modified_objects
306 .iter()
307 .filter(|id| !self.execution_results.written_objects.contains_key(id))
308 .for_each(|id| {
309 let old_object_meta = self.get_object_modified_at(id).unwrap();
310 deleted_at_versions.push((*id, old_object_meta.version));
311 if self.execution_results.deleted_object_ids.contains(id) {
312 deleted.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_DELETED));
313 } else {
314 wrapped.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_WRAPPED));
315 }
316 });
317 self.execution_results
318 .deleted_object_ids
319 .iter()
320 .filter(|id| !self.execution_results.modified_objects.contains(id))
321 .for_each(|id| {
322 unwrapped_then_deleted.push((
323 *id,
324 lampot_version,
325 ObjectDigest::OBJECT_DIGEST_DELETED,
326 ));
327 });
328 modified_at_versions.extend(deleted_at_versions);
329
330 let inner = self.into_inner();
331 let effects = TransactionEffects::new_from_execution_v1(
332 status,
333 epoch,
334 gas_cost_summary,
335 modified_at_versions,
336 shared_object_refs,
337 *transaction_digest,
338 created,
339 mutated,
340 unwrapped,
341 deleted,
342 unwrapped_then_deleted,
343 wrapped,
344 updated_gas_object_info,
345 if inner.events.data.is_empty() {
346 None
347 } else {
348 Some(inner.events.digest())
349 },
350 transaction_dependencies.into_iter().collect(),
351 );
352 (inner, effects)
353 }
354
355 fn into_effects_v2(
356 self,
357 shared_object_refs: Vec<SharedInput>,
358 transaction_digest: &TransactionDigest,
359 transaction_dependencies: BTreeSet<TransactionDigest>,
360 gas_cost_summary: GasCostSummary,
361 status: ExecutionStatus,
362 gas_charger: &mut GasCharger,
363 epoch: EpochId,
364 ) -> (InnerTemporaryStore, TransactionEffects) {
365 let gas_coin = gas_charger.gas_coin();
370
371 let object_changes = self.get_object_changes();
372
373 let lamport_version = self.lamport_timestamp;
374 let inner = self.into_inner();
375
376 let effects = TransactionEffects::new_from_execution_v2(
377 status,
378 epoch,
379 gas_cost_summary,
380 shared_object_refs,
382 BTreeSet::new(),
383 *transaction_digest,
384 lamport_version,
385 object_changes,
386 gas_coin,
387 if inner.events.data.is_empty() {
388 None
389 } else {
390 Some(inner.events.digest())
391 },
392 transaction_dependencies.into_iter().collect(),
393 );
394
395 (inner, effects)
396 }
397
398 #[cfg(debug_assertions)]
400 fn check_invariants(&self) {
401 debug_assert!(
403 {
404 self.execution_results
405 .written_objects
406 .keys()
407 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
408 },
409 "Object both written and deleted."
410 );
411
412 debug_assert!(
414 {
415 self.mutable_input_refs
416 .keys()
417 .all(|id| self.execution_results.modified_objects.contains(id))
418 },
419 "Mutable input not modified."
420 );
421
422 debug_assert!(
423 {
424 self.execution_results
425 .written_objects
426 .values()
427 .all(|obj| obj.previous_transaction == self.tx_digest)
428 },
429 "Object previous transaction not properly set",
430 );
431 }
432
433 pub fn mutate_input_object(&mut self, object: Object) {
435 let id = object.id();
436 debug_assert!(self.input_objects.contains_key(&id));
437 debug_assert!(!object.is_immutable());
438 self.execution_results.modified_objects.insert(id);
439 self.execution_results.written_objects.insert(id, object);
440 }
441
442 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
446 let id = new_object.id();
447 let old_ref = old_object.compute_object_reference();
448 debug_assert_eq!(old_ref.0, id);
449 self.loaded_runtime_objects.insert(
450 id,
451 DynamicallyLoadedObjectMetadata {
452 version: old_ref.1,
453 digest: old_ref.2,
454 owner: old_object.owner.clone(),
455 storage_rebate: old_object.storage_rebate,
456 previous_transaction: old_object.previous_transaction,
457 },
458 );
459 self.execution_results.modified_objects.insert(id);
460 self.execution_results
461 .written_objects
462 .insert(id, new_object);
463 }
464
465 pub fn upgrade_system_package(&mut self, package: Object) {
469 let id = package.id();
470 assert!(package.is_package() && is_system_package(id));
471 self.execution_results.modified_objects.insert(id);
472 self.execution_results.written_objects.insert(id, package);
473 }
474
475 pub fn create_object(&mut self, object: Object) {
477 debug_assert!(
482 object.is_immutable() || object.version() == SequenceNumber::MIN,
483 "Created mutable objects should not have a version set",
484 );
485 let id = object.id();
486 self.execution_results.created_object_ids.insert(id);
487 self.execution_results.written_objects.insert(id, object);
488 }
489
490 pub fn delete_input_object(&mut self, id: &ObjectID) {
492 debug_assert!(!self.execution_results.written_objects.contains_key(id));
494 debug_assert!(self.input_objects.contains_key(id));
495 self.execution_results.modified_objects.insert(*id);
496 self.execution_results.deleted_object_ids.insert(*id);
497 }
498
499 pub fn drop_writes(&mut self) {
500 self.execution_results.drop_writes();
501 }
502
503 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
504 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
506 self.execution_results
507 .written_objects
508 .get(id)
509 .or_else(|| self.input_objects.get(id))
510 }
511
512 pub fn save_loaded_runtime_objects(
513 &mut self,
514 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
515 ) {
516 #[cfg(debug_assertions)]
517 {
518 for (id, v1) in &loaded_runtime_objects {
519 if let Some(v2) = self.loaded_runtime_objects.get(id) {
520 assert_eq!(v1, v2);
521 }
522 }
523 for (id, v1) in &self.loaded_runtime_objects {
524 if let Some(v2) = loaded_runtime_objects.get(id) {
525 assert_eq!(v1, v2);
526 }
527 }
528 }
529 self.loaded_runtime_objects.extend(loaded_runtime_objects);
532 }
533
534 pub fn save_wrapped_object_containers(
535 &mut self,
536 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
537 ) {
538 #[cfg(debug_assertions)]
539 {
540 for (id, container1) in &wrapped_object_containers {
541 if let Some(container2) = self.wrapped_object_containers.get(id) {
542 assert_eq!(container1, container2);
543 }
544 }
545 for (id, container1) in &self.wrapped_object_containers {
546 if let Some(container2) = wrapped_object_containers.get(id) {
547 assert_eq!(container1, container2);
548 }
549 }
550 }
551 self.wrapped_object_containers
554 .extend(wrapped_object_containers);
555 }
556
557 pub fn estimate_effects_size_upperbound(&self) -> usize {
558 if self.protocol_config.enable_effects_v2() {
559 TransactionEffects::estimate_effects_size_upperbound_v2(
560 self.execution_results.written_objects.len(),
561 self.execution_results.modified_objects.len(),
562 self.input_objects.len(),
563 )
564 } else {
565 let num_deletes = self.execution_results.deleted_object_ids.len()
566 + self
567 .execution_results
568 .modified_objects
569 .iter()
570 .filter(|id| {
571 !self.execution_results.written_objects.contains_key(id)
573 && !self.execution_results.deleted_object_ids.contains(id)
574 })
575 .count();
576 TransactionEffects::estimate_effects_size_upperbound_v1(
578 self.execution_results.written_objects.len(),
579 self.mutable_input_refs.len(),
580 num_deletes,
581 self.input_objects.len(),
582 )
583 }
584 }
585
586 pub fn written_objects_size(&self) -> usize {
587 self.execution_results
588 .written_objects
589 .values()
590 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
591 }
592
593 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
598 if unmetered_storage_rebate == 0 {
599 return;
603 }
604 tracing::debug!(
605 "Amount of unmetered storage rebate from system tx: {:?}",
606 unmetered_storage_rebate
607 );
608 let mut system_state_wrapper = self
609 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
610 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
611 .clone();
612 assert_eq!(system_state_wrapper.storage_rebate, 0);
615 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
616 self.mutate_input_object(system_state_wrapper);
617 }
618
619 fn get_object_modified_at(
625 &self,
626 object_id: &ObjectID,
627 ) -> Option<DynamicallyLoadedObjectMetadata> {
628 if self.execution_results.modified_objects.contains(object_id) {
629 Some(
630 self.mutable_input_refs
631 .get(object_id)
632 .map(
633 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
634 version: *version,
635 digest: *digest,
636 owner: owner.clone(),
637 storage_rebate: self.input_objects[object_id].storage_rebate,
639 previous_transaction: self.input_objects[object_id]
640 .previous_transaction,
641 },
642 )
643 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
644 .unwrap_or_else(|| {
645 debug_assert!(is_system_package(*object_id));
646 let package_obj =
647 self.store.get_package_object(object_id).unwrap().unwrap();
648 let obj = package_obj.object();
649 DynamicallyLoadedObjectMetadata {
650 version: obj.version(),
651 digest: obj.digest(),
652 owner: obj.owner.clone(),
653 storage_rebate: obj.storage_rebate,
654 previous_transaction: obj.previous_transaction,
655 }
656 }),
657 )
658 } else {
659 None
660 }
661 }
662}
663
664impl TemporaryStore<'_> {
665 pub fn check_ownership_invariants(
668 &self,
669 sender: &SuiAddress,
670 gas_charger: &mut GasCharger,
671 mutable_inputs: &HashSet<ObjectID>,
672 is_epoch_change: bool,
673 ) -> SuiResult<()> {
674 let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().iter().map(|g| &g.0).collect();
675 let mut authenticated_for_mutation: HashSet<_> = self
677 .input_objects
678 .iter()
679 .filter_map(|(id, obj)| {
680 if gas_objs.contains(id) {
681 return None;
686 }
687 match &obj.owner {
688 Owner::AddressOwner(a) => {
689 assert!(sender == a, "Input object not owned by sender");
690 Some(id)
691 }
692 Owner::Shared { .. } => Some(id),
693 Owner::Immutable => {
694 None
705 }
706 Owner::ObjectOwner(_parent) => {
707 unreachable!("Input objects must be address owned, shared, or immutable")
708 }
709 Owner::ConsensusAddressOwner { .. } => {
710 unimplemented!(
711 "ConsensusAddressOwner does not exist for this execution version"
712 )
713 }
714 }
715 })
716 .filter(|id| {
717 mutable_inputs.contains(id)
720 })
721 .copied()
722 .collect();
723
724 let mut objects_to_authenticate = self
726 .execution_results
727 .modified_objects
728 .iter()
729 .filter(|id| !gas_objs.contains(id))
730 .copied()
731 .collect::<Vec<_>>();
732 while let Some(to_authenticate) = objects_to_authenticate.pop() {
734 if authenticated_for_mutation.contains(&to_authenticate) {
735 continue;
737 }
738 let wrapped_parent = self.wrapped_object_containers.get(&to_authenticate);
739 let parent = if let Some(container_id) = wrapped_parent {
740 *container_id
743 } else {
744 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
745 panic!(
746 "
747 Failed to load object {to_authenticate:?}. \n\
748 If it cannot be loaded, \
749 we would expect it to be in the wrapped object map: {:?}",
750 &self.wrapped_object_containers
751 )
752 };
753 match &old_obj.owner {
754 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
755 Owner::AddressOwner(parent) => {
756 ObjectID::from(*parent)
760 }
761 owner @ Owner::Shared { .. } => panic!(
762 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
763 Potentially covering objects in: {authenticated_for_mutation:#?}",
764 ),
765 Owner::Immutable => {
766 assert!(
767 is_epoch_change,
768 "Immutable objects cannot be written, except for \
769 Sui Framework/Move stdlib upgrades at epoch change boundaries"
770 );
771 assert!(
775 is_system_package(to_authenticate),
776 "Only system packages can be upgraded"
777 );
778 continue;
779 }
780 Owner::ConsensusAddressOwner { .. } => {
781 unimplemented!(
782 "ConsensusAddressOwner does not exist for this execution version"
783 )
784 }
785 }
786 };
787 authenticated_for_mutation.insert(to_authenticate);
789 objects_to_authenticate.push(parent);
790 }
791 Ok(())
792 }
793}
794
795impl TemporaryStore<'_> {
796 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
803 let old_storage_rebates: Vec<_> = self
805 .execution_results
806 .written_objects
807 .keys()
808 .map(|object_id| {
809 self.get_object_modified_at(object_id)
810 .map(|metadata| metadata.storage_rebate)
811 .unwrap_or_default()
812 })
813 .collect();
814 for (object, old_storage_rebate) in self
815 .execution_results
816 .written_objects
817 .values_mut()
818 .zip(old_storage_rebates)
819 {
820 let new_object_size = object.object_size_for_gas_metering();
822 let new_storage_rebate = gas_charger.track_storage_mutation(
824 object.id(),
825 new_object_size,
826 old_storage_rebate,
827 );
828 object.storage_rebate = new_storage_rebate;
829 }
830
831 self.collect_rebate(gas_charger);
832 }
833
834 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
835 for object_id in &self.execution_results.modified_objects {
836 if self
837 .execution_results
838 .written_objects
839 .contains_key(object_id)
840 {
841 continue;
842 }
843 let storage_rebate = self
845 .get_object_modified_at(object_id)
846 .unwrap()
848 .storage_rebate;
849 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
850 }
851 }
852
853 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
854 assert_invariant!(
855 self.execution_results
856 .created_object_ids
857 .iter()
858 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
859 && !self.execution_results.modified_objects.contains(id)),
860 "Created object IDs cannot also be deleted or modified"
861 );
862 assert_invariant!(
863 self.execution_results.modified_objects.iter().all(|id| {
864 self.mutable_input_refs.contains_key(id)
865 || self.loaded_runtime_objects.contains_key(id)
866 || is_system_package(*id)
867 }),
868 "A modified object must be either a mutable input, a loaded child object, or a system package"
869 );
870 Ok(())
871 }
872}
873impl TemporaryStore<'_> {
878 pub fn advance_epoch_safe_mode(
879 &mut self,
880 params: &AdvanceEpochParams,
881 protocol_config: &ProtocolConfig,
882 ) {
883 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
884 .expect("System state wrapper object must exist");
885 let (old_object, new_object) =
886 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
887 self.mutate_child_object(old_object, new_object);
888 }
889}
890
891type ModifiedObjectInfo<'a> = (
892 ObjectID,
893 Option<DynamicallyLoadedObjectMetadata>,
895 Option<&'a Object>,
896);
897
898impl TemporaryStore<'_> {
899 fn get_input_sui(
900 &self,
901 id: &ObjectID,
902 expected_version: SequenceNumber,
903 layout_resolver: &mut impl LayoutResolver,
904 ) -> Result<u64, ExecutionError> {
905 if let Some(obj) = self.input_objects.get(id) {
906 if obj.version() != expected_version {
908 invariant_violation!(
909 "Version mismatching when resolving input object to check conservation--\
910 expected {}, got {}",
911 expected_version,
912 obj.version(),
913 );
914 }
915 obj.get_total_sui(layout_resolver).map_err(|e| {
916 make_invariant_violation!(
917 "Failed looking up input SUI in SUI conservation checking for input with \
918 type {:?}: {e:#?}",
919 obj.struct_tag(),
920 )
921 })
922 } else {
923 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
925 invariant_violation!(
926 "Failed looking up dynamic field {id} in SUI conservation checking"
927 );
928 };
929 obj.get_total_sui(layout_resolver).map_err(|e| {
930 make_invariant_violation!(
931 "Failed looking up input SUI in SUI conservation checking for type \
932 {:?}: {e:#?}",
933 obj.struct_tag(),
934 )
935 })
936 }
937 }
938
939 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
944 self.execution_results
945 .modified_objects
946 .iter()
947 .map(|id| {
948 let metadata = self.get_object_modified_at(id);
949 let output = self.execution_results.written_objects.get(id);
950 (*id, metadata, output)
951 })
952 .chain(
953 self.execution_results
954 .written_objects
955 .iter()
956 .filter_map(|(id, object)| {
957 if self.execution_results.modified_objects.contains(id) {
958 None
959 } else {
960 Some((*id, None, Some(object)))
961 }
962 }),
963 )
964 .collect()
965 }
966
967 pub fn check_sui_conserved(
981 &self,
982 simple_conservation_checks: bool,
983 gas_summary: &GasCostSummary,
984 ) -> Result<(), ExecutionError> {
985 if !simple_conservation_checks {
986 return Ok(());
987 }
988 let mut total_input_rebate = 0;
990 let mut total_output_rebate = 0;
992 for (_, input, output) in self.get_modified_objects() {
993 if let Some(input) = input {
994 total_input_rebate += input.storage_rebate;
995 }
996 if let Some(object) = output {
997 total_output_rebate += object.storage_rebate;
998 }
999 }
1000
1001 if gas_summary.storage_cost == 0 {
1002 if total_input_rebate
1014 != total_output_rebate
1015 + gas_summary.storage_rebate
1016 + gas_summary.non_refundable_storage_fee
1017 {
1018 return Err(ExecutionError::invariant_violation(format!(
1019 "SUI conservation failed -- no storage charges in gas summary \
1020 and total storage input rebate {} not equal \
1021 to total storage output rebate {}",
1022 total_input_rebate, total_output_rebate,
1023 )));
1024 }
1025 } else {
1026 if total_input_rebate
1029 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1030 {
1031 return Err(ExecutionError::invariant_violation(format!(
1032 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1033 {} SUI in tx storage rebate or tx non-refundable storage rebate",
1034 total_input_rebate, gas_summary.non_refundable_storage_fee,
1035 )));
1036 }
1037
1038 if gas_summary.storage_cost != total_output_rebate {
1041 return Err(ExecutionError::invariant_violation(format!(
1042 "SUI conservation failed -- {} SUI charged for storage, \
1043 {} SUI in storage rebate field of output objects",
1044 gas_summary.storage_cost, total_output_rebate
1045 )));
1046 }
1047 }
1048 Ok(())
1049 }
1050
1051 pub fn check_sui_conserved_expensive(
1064 &self,
1065 gas_summary: &GasCostSummary,
1066 advance_epoch_gas_summary: Option<(u64, u64)>,
1067 layout_resolver: &mut impl LayoutResolver,
1068 ) -> Result<(), ExecutionError> {
1069 let mut total_input_sui = 0;
1071 let mut total_output_sui = 0;
1073 for (id, input, output) in self.get_modified_objects() {
1074 if let Some(input) = input {
1075 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1076 }
1077 if let Some(object) = output {
1078 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1079 make_invariant_violation!(
1080 "Failed looking up output SUI in SUI conservation checking for \
1081 mutated type {:?}: {e:#?}",
1082 object.struct_tag(),
1083 )
1084 })?;
1085 }
1086 }
1087 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1092 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1093 total_input_sui += epoch_fees;
1094 total_output_sui += epoch_rebates;
1095 }
1096 if total_input_sui != total_output_sui {
1097 return Err(ExecutionError::invariant_violation(format!(
1098 "SUI conservation failed: input={}, output={}, \
1099 this transaction either mints or burns SUI",
1100 total_input_sui, total_output_sui,
1101 )));
1102 }
1103 Ok(())
1104 }
1105}
1106
1107impl ChildObjectResolver for TemporaryStore<'_> {
1108 fn read_child_object(
1109 &self,
1110 parent: &ObjectID,
1111 child: &ObjectID,
1112 child_version_upper_bound: SequenceNumber,
1113 ) -> SuiResult<Option<Object>> {
1114 let obj_opt = self.execution_results.written_objects.get(child);
1115 if obj_opt.is_some() {
1116 Ok(obj_opt.cloned())
1117 } else {
1118 self.store
1119 .read_child_object(parent, child, child_version_upper_bound)
1120 }
1121 }
1122
1123 fn get_object_received_at_version(
1124 &self,
1125 owner: &ObjectID,
1126 receiving_object_id: &ObjectID,
1127 receive_object_at_version: SequenceNumber,
1128 epoch_id: EpochId,
1129 ) -> SuiResult<Option<Object>> {
1130 debug_assert!(!self
1133 .execution_results
1134 .written_objects
1135 .contains_key(receiving_object_id));
1136 debug_assert!(!self
1137 .execution_results
1138 .deleted_object_ids
1139 .contains(receiving_object_id));
1140 self.store.get_object_received_at_version(
1141 owner,
1142 receiving_object_id,
1143 receive_object_at_version,
1144 epoch_id,
1145 )
1146 }
1147}
1148
1149impl Storage for TemporaryStore<'_> {
1150 fn reset(&mut self) {
1151 self.drop_writes();
1152 }
1153
1154 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1155 TemporaryStore::read_object(self, id)
1156 }
1157
1158 fn record_execution_results(
1160 &mut self,
1161 results: ExecutionResults,
1162 ) -> Result<(), ExecutionError> {
1163 let ExecutionResults::V2(results) = results else {
1164 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1165 };
1166 self.execution_results.merge_results(results);
1169 Ok(())
1170 }
1171
1172 fn save_loaded_runtime_objects(
1173 &mut self,
1174 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1175 ) {
1176 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1177 }
1178
1179 fn save_wrapped_object_containers(
1180 &mut self,
1181 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1182 ) {
1183 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1184 }
1185
1186 fn check_coin_deny_list(
1187 &self,
1188 _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1189 ) -> DenyListResult {
1190 unreachable!("Coin denylist v2 is not supported in sui-execution v2");
1191 }
1192
1193 fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1194 unreachable!(
1195 "Generated object IDs are not recorded in ExecutionResults in sui-execution v2"
1196 );
1197 }
1198}
1199
1200impl BackingPackageStore for TemporaryStore<'_> {
1201 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1202 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1209 Ok(Some(PackageObject::new(obj.clone())))
1210 } else {
1211 self.store.get_package_object(package_id).inspect(|obj| {
1212 if let Some(v) = obj {
1214 if !self
1215 .runtime_packages_loaded_from_db
1216 .read()
1217 .contains_key(package_id)
1218 {
1219 self.runtime_packages_loaded_from_db
1224 .write()
1225 .insert(*package_id, v.clone());
1226 }
1227 }
1228 })
1229 }
1230 }
1231}
1232
1233impl ParentSync for TemporaryStore<'_> {
1234 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1235 unreachable!("Never called in newer protocol versions")
1236 }
1237}