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, TransactionEffectsV2, 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 unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
375 shared_object_refs,
376 BTreeSet::new(),
377 &object_changes,
378 );
379 let inner = self.into_inner();
380
381 let effects = TransactionEffects::new_from_execution_v2(
382 status,
383 epoch,
384 gas_cost_summary,
385 unchanged_consensus_objects,
386 *transaction_digest,
387 lamport_version,
388 object_changes,
389 gas_coin,
390 if inner.events.data.is_empty() {
391 None
392 } else {
393 Some(inner.events.digest())
394 },
395 transaction_dependencies.into_iter().collect(),
396 );
397
398 (inner, effects)
399 }
400
401 #[cfg(debug_assertions)]
403 fn check_invariants(&self) {
404 debug_assert!(
406 {
407 self.execution_results
408 .written_objects
409 .keys()
410 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
411 },
412 "Object both written and deleted."
413 );
414
415 debug_assert!(
417 {
418 self.mutable_input_refs
419 .keys()
420 .all(|id| self.execution_results.modified_objects.contains(id))
421 },
422 "Mutable input not modified."
423 );
424
425 debug_assert!(
426 {
427 self.execution_results
428 .written_objects
429 .values()
430 .all(|obj| obj.previous_transaction == self.tx_digest)
431 },
432 "Object previous transaction not properly set",
433 );
434 }
435
436 pub fn mutate_input_object(&mut self, object: Object) {
438 let id = object.id();
439 debug_assert!(self.input_objects.contains_key(&id));
440 debug_assert!(!object.is_immutable());
441 self.execution_results.modified_objects.insert(id);
442 self.execution_results.written_objects.insert(id, object);
443 }
444
445 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
449 let id = new_object.id();
450 let old_ref = old_object.compute_object_reference();
451 debug_assert_eq!(old_ref.0, id);
452 self.loaded_runtime_objects.insert(
453 id,
454 DynamicallyLoadedObjectMetadata {
455 version: old_ref.1,
456 digest: old_ref.2,
457 owner: old_object.owner.clone(),
458 storage_rebate: old_object.storage_rebate,
459 previous_transaction: old_object.previous_transaction,
460 },
461 );
462 self.execution_results.modified_objects.insert(id);
463 self.execution_results
464 .written_objects
465 .insert(id, new_object);
466 }
467
468 pub fn upgrade_system_package(&mut self, package: Object) {
472 let id = package.id();
473 assert!(package.is_package() && is_system_package(id));
474 self.execution_results.modified_objects.insert(id);
475 self.execution_results.written_objects.insert(id, package);
476 }
477
478 pub fn create_object(&mut self, object: Object) {
480 debug_assert!(
485 object.is_immutable() || object.version() == SequenceNumber::MIN,
486 "Created mutable objects should not have a version set",
487 );
488 let id = object.id();
489 self.execution_results.created_object_ids.insert(id);
490 self.execution_results.written_objects.insert(id, object);
491 }
492
493 pub fn delete_input_object(&mut self, id: &ObjectID) {
495 debug_assert!(!self.execution_results.written_objects.contains_key(id));
497 debug_assert!(self.input_objects.contains_key(id));
498 self.execution_results.modified_objects.insert(*id);
499 self.execution_results.deleted_object_ids.insert(*id);
500 }
501
502 pub fn drop_writes(&mut self) {
503 self.execution_results.drop_writes();
504 }
505
506 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
507 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
509 self.execution_results
510 .written_objects
511 .get(id)
512 .or_else(|| self.input_objects.get(id))
513 }
514
515 pub fn save_loaded_runtime_objects(
516 &mut self,
517 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
518 ) {
519 #[cfg(debug_assertions)]
520 {
521 for (id, v1) in &loaded_runtime_objects {
522 if let Some(v2) = self.loaded_runtime_objects.get(id) {
523 assert_eq!(v1, v2);
524 }
525 }
526 for (id, v1) in &self.loaded_runtime_objects {
527 if let Some(v2) = loaded_runtime_objects.get(id) {
528 assert_eq!(v1, v2);
529 }
530 }
531 }
532 self.loaded_runtime_objects.extend(loaded_runtime_objects);
535 }
536
537 pub fn save_wrapped_object_containers(
538 &mut self,
539 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
540 ) {
541 #[cfg(debug_assertions)]
542 {
543 for (id, container1) in &wrapped_object_containers {
544 if let Some(container2) = self.wrapped_object_containers.get(id) {
545 assert_eq!(container1, container2);
546 }
547 }
548 for (id, container1) in &self.wrapped_object_containers {
549 if let Some(container2) = wrapped_object_containers.get(id) {
550 assert_eq!(container1, container2);
551 }
552 }
553 }
554 self.wrapped_object_containers
557 .extend(wrapped_object_containers);
558 }
559
560 pub fn estimate_effects_size_upperbound(&self) -> usize {
561 if self.protocol_config.enable_effects_v2() {
562 TransactionEffects::estimate_effects_size_upperbound_v2(
563 self.execution_results.written_objects.len(),
564 self.execution_results.modified_objects.len(),
565 self.input_objects.len(),
566 )
567 } else {
568 let num_deletes = self.execution_results.deleted_object_ids.len()
569 + self
570 .execution_results
571 .modified_objects
572 .iter()
573 .filter(|id| {
574 !self.execution_results.written_objects.contains_key(id)
576 && !self.execution_results.deleted_object_ids.contains(id)
577 })
578 .count();
579 TransactionEffects::estimate_effects_size_upperbound_v1(
581 self.execution_results.written_objects.len(),
582 self.mutable_input_refs.len(),
583 num_deletes,
584 self.input_objects.len(),
585 )
586 }
587 }
588
589 pub fn written_objects_size(&self) -> usize {
590 self.execution_results
591 .written_objects
592 .values()
593 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
594 }
595
596 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
601 if unmetered_storage_rebate == 0 {
602 return;
606 }
607 tracing::debug!(
608 "Amount of unmetered storage rebate from system tx: {:?}",
609 unmetered_storage_rebate
610 );
611 let mut system_state_wrapper = self
612 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
613 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
614 .clone();
615 assert_eq!(system_state_wrapper.storage_rebate, 0);
618 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
619 self.mutate_input_object(system_state_wrapper);
620 }
621
622 fn get_object_modified_at(
628 &self,
629 object_id: &ObjectID,
630 ) -> Option<DynamicallyLoadedObjectMetadata> {
631 if self.execution_results.modified_objects.contains(object_id) {
632 Some(
633 self.mutable_input_refs
634 .get(object_id)
635 .map(
636 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
637 version: *version,
638 digest: *digest,
639 owner: owner.clone(),
640 storage_rebate: self.input_objects[object_id].storage_rebate,
642 previous_transaction: self.input_objects[object_id]
643 .previous_transaction,
644 },
645 )
646 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
647 .unwrap_or_else(|| {
648 debug_assert!(is_system_package(*object_id));
649 let package_obj =
650 self.store.get_package_object(object_id).unwrap().unwrap();
651 let obj = package_obj.object();
652 DynamicallyLoadedObjectMetadata {
653 version: obj.version(),
654 digest: obj.digest(),
655 owner: obj.owner.clone(),
656 storage_rebate: obj.storage_rebate,
657 previous_transaction: obj.previous_transaction,
658 }
659 }),
660 )
661 } else {
662 None
663 }
664 }
665}
666
667impl TemporaryStore<'_> {
668 pub fn check_ownership_invariants(
671 &self,
672 sender: &SuiAddress,
673 gas_charger: &mut GasCharger,
674 mutable_inputs: &HashSet<ObjectID>,
675 is_epoch_change: bool,
676 ) -> SuiResult<()> {
677 let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().iter().map(|g| &g.0).collect();
678 let mut authenticated_for_mutation: HashSet<_> = self
680 .input_objects
681 .iter()
682 .filter_map(|(id, obj)| {
683 if gas_objs.contains(id) {
684 return None;
689 }
690 match &obj.owner {
691 Owner::AddressOwner(a) => {
692 assert!(sender == a, "Input object not owned by sender");
693 Some(id)
694 }
695 Owner::Shared { .. } => Some(id),
696 Owner::Immutable => {
697 None
708 }
709 Owner::ObjectOwner(_parent) => {
710 unreachable!("Input objects must be address owned, shared, or immutable")
711 }
712 Owner::ConsensusAddressOwner { .. } => {
713 unimplemented!(
714 "ConsensusAddressOwner does not exist for this execution version"
715 )
716 }
717 Owner::Party { .. } => {
718 unimplemented!("Party does not exist for this execution version")
719 }
720 }
721 })
722 .filter(|id| {
723 mutable_inputs.contains(id)
726 })
727 .copied()
728 .collect();
729
730 let mut objects_to_authenticate = self
732 .execution_results
733 .modified_objects
734 .iter()
735 .filter(|id| !gas_objs.contains(id))
736 .copied()
737 .collect::<Vec<_>>();
738 while let Some(to_authenticate) = objects_to_authenticate.pop() {
740 if authenticated_for_mutation.contains(&to_authenticate) {
741 continue;
743 }
744 let wrapped_parent = self.wrapped_object_containers.get(&to_authenticate);
745 let parent = if let Some(container_id) = wrapped_parent {
746 *container_id
749 } else {
750 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
751 panic!(
752 "
753 Failed to load object {to_authenticate:?}. \n\
754 If it cannot be loaded, \
755 we would expect it to be in the wrapped object map: {:?}",
756 &self.wrapped_object_containers
757 )
758 };
759 match &old_obj.owner {
760 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
761 Owner::AddressOwner(parent) => {
762 ObjectID::from(*parent)
766 }
767 owner @ Owner::Shared { .. } => panic!(
768 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
769 Potentially covering objects in: {authenticated_for_mutation:#?}",
770 ),
771 Owner::Immutable => {
772 assert!(
773 is_epoch_change,
774 "Immutable objects cannot be written, except for \
775 Sui Framework/Move stdlib upgrades at epoch change boundaries"
776 );
777 assert!(
781 is_system_package(to_authenticate),
782 "Only system packages can be upgraded"
783 );
784 continue;
785 }
786 Owner::ConsensusAddressOwner { .. } => {
787 unimplemented!(
788 "ConsensusAddressOwner does not exist for this execution version"
789 )
790 }
791 Owner::Party { .. } => {
792 unimplemented!("Party does not exist for this execution version")
793 }
794 }
795 };
796 authenticated_for_mutation.insert(to_authenticate);
798 objects_to_authenticate.push(parent);
799 }
800 Ok(())
801 }
802}
803
804impl TemporaryStore<'_> {
805 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
812 let old_storage_rebates: Vec<_> = self
814 .execution_results
815 .written_objects
816 .keys()
817 .map(|object_id| {
818 self.get_object_modified_at(object_id)
819 .map(|metadata| metadata.storage_rebate)
820 .unwrap_or_default()
821 })
822 .collect();
823 for (object, old_storage_rebate) in self
824 .execution_results
825 .written_objects
826 .values_mut()
827 .zip(old_storage_rebates)
828 {
829 let new_object_size = object.object_size_for_gas_metering();
831 let new_storage_rebate = gas_charger.track_storage_mutation(
833 object.id(),
834 new_object_size,
835 old_storage_rebate,
836 );
837 object.storage_rebate = new_storage_rebate;
838 }
839
840 self.collect_rebate(gas_charger);
841 }
842
843 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
844 for object_id in &self.execution_results.modified_objects {
845 if self
846 .execution_results
847 .written_objects
848 .contains_key(object_id)
849 {
850 continue;
851 }
852 let storage_rebate = self
854 .get_object_modified_at(object_id)
855 .unwrap()
857 .storage_rebate;
858 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
859 }
860 }
861
862 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
863 assert_invariant!(
864 self.execution_results
865 .created_object_ids
866 .iter()
867 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
868 && !self.execution_results.modified_objects.contains(id)),
869 "Created object IDs cannot also be deleted or modified"
870 );
871 assert_invariant!(
872 self.execution_results.modified_objects.iter().all(|id| {
873 self.mutable_input_refs.contains_key(id)
874 || self.loaded_runtime_objects.contains_key(id)
875 || is_system_package(*id)
876 }),
877 "A modified object must be either a mutable input, a loaded child object, or a system package"
878 );
879 Ok(())
880 }
881}
882impl TemporaryStore<'_> {
887 pub fn advance_epoch_safe_mode(
888 &mut self,
889 params: &AdvanceEpochParams,
890 protocol_config: &ProtocolConfig,
891 ) {
892 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
893 .expect("System state wrapper object must exist");
894 let (old_object, new_object) =
895 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
896 self.mutate_child_object(old_object, new_object);
897 }
898}
899
900type ModifiedObjectInfo<'a> = (
901 ObjectID,
902 Option<DynamicallyLoadedObjectMetadata>,
904 Option<&'a Object>,
905);
906
907impl TemporaryStore<'_> {
908 fn get_input_sui(
909 &self,
910 id: &ObjectID,
911 expected_version: SequenceNumber,
912 layout_resolver: &mut impl LayoutResolver,
913 ) -> Result<u64, ExecutionError> {
914 if let Some(obj) = self.input_objects.get(id) {
915 if obj.version() != expected_version {
917 invariant_violation!(
918 "Version mismatching when resolving input object to check conservation--\
919 expected {}, got {}",
920 expected_version,
921 obj.version(),
922 );
923 }
924 obj.get_total_sui(layout_resolver).map_err(|e| {
925 make_invariant_violation!(
926 "Failed looking up input SUI in SUI conservation checking for input with \
927 type {:?}: {e:#?}",
928 obj.struct_tag(),
929 )
930 })
931 } else {
932 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
934 invariant_violation!(
935 "Failed looking up dynamic field {id} in SUI conservation checking"
936 );
937 };
938 obj.get_total_sui(layout_resolver).map_err(|e| {
939 make_invariant_violation!(
940 "Failed looking up input SUI in SUI conservation checking for type \
941 {:?}: {e:#?}",
942 obj.struct_tag(),
943 )
944 })
945 }
946 }
947
948 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
953 self.execution_results
954 .modified_objects
955 .iter()
956 .map(|id| {
957 let metadata = self.get_object_modified_at(id);
958 let output = self.execution_results.written_objects.get(id);
959 (*id, metadata, output)
960 })
961 .chain(
962 self.execution_results
963 .written_objects
964 .iter()
965 .filter_map(|(id, object)| {
966 if self.execution_results.modified_objects.contains(id) {
967 None
968 } else {
969 Some((*id, None, Some(object)))
970 }
971 }),
972 )
973 .collect()
974 }
975
976 pub fn check_sui_conserved(
990 &self,
991 simple_conservation_checks: bool,
992 gas_summary: &GasCostSummary,
993 ) -> Result<(), ExecutionError> {
994 if !simple_conservation_checks {
995 return Ok(());
996 }
997 let mut total_input_rebate = 0;
999 let mut total_output_rebate = 0;
1001 for (_, input, output) in self.get_modified_objects() {
1002 if let Some(input) = input {
1003 total_input_rebate += input.storage_rebate;
1004 }
1005 if let Some(object) = output {
1006 total_output_rebate += object.storage_rebate;
1007 }
1008 }
1009
1010 if gas_summary.storage_cost == 0 {
1011 if total_input_rebate
1023 != total_output_rebate
1024 + gas_summary.storage_rebate
1025 + gas_summary.non_refundable_storage_fee
1026 {
1027 return Err(ExecutionError::invariant_violation(format!(
1028 "SUI conservation failed -- no storage charges in gas summary \
1029 and total storage input rebate {} not equal \
1030 to total storage output rebate {}",
1031 total_input_rebate, total_output_rebate,
1032 )));
1033 }
1034 } else {
1035 if total_input_rebate
1038 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1039 {
1040 return Err(ExecutionError::invariant_violation(format!(
1041 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1042 {} SUI in tx storage rebate or tx non-refundable storage rebate",
1043 total_input_rebate, gas_summary.non_refundable_storage_fee,
1044 )));
1045 }
1046
1047 if gas_summary.storage_cost != total_output_rebate {
1050 return Err(ExecutionError::invariant_violation(format!(
1051 "SUI conservation failed -- {} SUI charged for storage, \
1052 {} SUI in storage rebate field of output objects",
1053 gas_summary.storage_cost, total_output_rebate
1054 )));
1055 }
1056 }
1057 Ok(())
1058 }
1059
1060 pub fn check_sui_conserved_expensive(
1073 &self,
1074 gas_summary: &GasCostSummary,
1075 advance_epoch_gas_summary: Option<(u64, u64)>,
1076 layout_resolver: &mut impl LayoutResolver,
1077 ) -> Result<(), ExecutionError> {
1078 let mut total_input_sui = 0;
1080 let mut total_output_sui = 0;
1082 for (id, input, output) in self.get_modified_objects() {
1083 if let Some(input) = input {
1084 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1085 }
1086 if let Some(object) = output {
1087 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1088 make_invariant_violation!(
1089 "Failed looking up output SUI in SUI conservation checking for \
1090 mutated type {:?}: {e:#?}",
1091 object.struct_tag(),
1092 )
1093 })?;
1094 }
1095 }
1096 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1101 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1102 total_input_sui += epoch_fees;
1103 total_output_sui += epoch_rebates;
1104 }
1105 if total_input_sui != total_output_sui {
1106 return Err(ExecutionError::invariant_violation(format!(
1107 "SUI conservation failed: input={}, output={}, \
1108 this transaction either mints or burns SUI",
1109 total_input_sui, total_output_sui,
1110 )));
1111 }
1112 Ok(())
1113 }
1114}
1115
1116impl ChildObjectResolver for TemporaryStore<'_> {
1117 fn read_child_object(
1118 &self,
1119 parent: &ObjectID,
1120 child: &ObjectID,
1121 child_version_upper_bound: SequenceNumber,
1122 ) -> SuiResult<Option<Object>> {
1123 let obj_opt = self.execution_results.written_objects.get(child);
1124 if obj_opt.is_some() {
1125 Ok(obj_opt.cloned())
1126 } else {
1127 self.store
1128 .read_child_object(parent, child, child_version_upper_bound)
1129 }
1130 }
1131
1132 fn get_object_received_at_version(
1133 &self,
1134 owner: &ObjectID,
1135 receiving_object_id: &ObjectID,
1136 receive_object_at_version: SequenceNumber,
1137 epoch_id: EpochId,
1138 ) -> SuiResult<Option<Object>> {
1139 debug_assert!(!self
1142 .execution_results
1143 .written_objects
1144 .contains_key(receiving_object_id));
1145 debug_assert!(!self
1146 .execution_results
1147 .deleted_object_ids
1148 .contains(receiving_object_id));
1149 self.store.get_object_received_at_version(
1150 owner,
1151 receiving_object_id,
1152 receive_object_at_version,
1153 epoch_id,
1154 )
1155 }
1156}
1157
1158impl Storage for TemporaryStore<'_> {
1159 fn reset(&mut self) {
1160 self.drop_writes();
1161 }
1162
1163 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1164 TemporaryStore::read_object(self, id)
1165 }
1166
1167 fn record_execution_results(
1169 &mut self,
1170 results: ExecutionResults,
1171 ) -> Result<(), ExecutionError> {
1172 let ExecutionResults::V2(results) = results else {
1173 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1174 };
1175 self.execution_results
1178 .merge_results(
1179 results, false, true,
1180 )
1181 .unwrap();
1182 Ok(())
1183 }
1184
1185 fn save_loaded_runtime_objects(
1186 &mut self,
1187 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1188 ) {
1189 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1190 }
1191
1192 fn save_wrapped_object_containers(
1193 &mut self,
1194 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1195 ) {
1196 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1197 }
1198
1199 fn check_coin_deny_list(
1200 &self,
1201 _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1202 ) -> DenyListResult {
1203 unreachable!("Coin denylist v2 is not supported in sui-execution v2");
1204 }
1205
1206 fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1207 unreachable!(
1208 "Generated object IDs are not recorded in ExecutionResults in sui-execution v2"
1209 );
1210 }
1211}
1212
1213impl BackingPackageStore for TemporaryStore<'_> {
1214 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1215 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1222 Ok(Some(PackageObject::new(obj.clone())))
1223 } else {
1224 self.store.get_package_object(package_id).inspect(|obj| {
1225 if let Some(v) = obj {
1227 if !self
1228 .runtime_packages_loaded_from_db
1229 .read()
1230 .contains_key(package_id)
1231 {
1232 self.runtime_packages_loaded_from_db
1237 .write()
1238 .insert(*package_id, v.clone());
1239 }
1240 }
1241 })
1242 }
1243 }
1244}
1245
1246impl ParentSync for TemporaryStore<'_> {
1247 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1248 unreachable!("Never called in newer protocol versions")
1249 }
1250}