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 Owner::Party { .. } => {
715 unimplemented!("Party does not exist for this execution version")
716 }
717 }
718 })
719 .filter(|id| {
720 mutable_inputs.contains(id)
723 })
724 .copied()
725 .collect();
726
727 let mut objects_to_authenticate = self
729 .execution_results
730 .modified_objects
731 .iter()
732 .filter(|id| !gas_objs.contains(id))
733 .copied()
734 .collect::<Vec<_>>();
735 while let Some(to_authenticate) = objects_to_authenticate.pop() {
737 if authenticated_for_mutation.contains(&to_authenticate) {
738 continue;
740 }
741 let wrapped_parent = self.wrapped_object_containers.get(&to_authenticate);
742 let parent = if let Some(container_id) = wrapped_parent {
743 *container_id
746 } else {
747 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
748 panic!(
749 "
750 Failed to load object {to_authenticate:?}. \n\
751 If it cannot be loaded, \
752 we would expect it to be in the wrapped object map: {:?}",
753 &self.wrapped_object_containers
754 )
755 };
756 match &old_obj.owner {
757 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
758 Owner::AddressOwner(parent) => {
759 ObjectID::from(*parent)
763 }
764 owner @ Owner::Shared { .. } => panic!(
765 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
766 Potentially covering objects in: {authenticated_for_mutation:#?}",
767 ),
768 Owner::Immutable => {
769 assert!(
770 is_epoch_change,
771 "Immutable objects cannot be written, except for \
772 Sui Framework/Move stdlib upgrades at epoch change boundaries"
773 );
774 assert!(
778 is_system_package(to_authenticate),
779 "Only system packages can be upgraded"
780 );
781 continue;
782 }
783 Owner::ConsensusAddressOwner { .. } => {
784 unimplemented!(
785 "ConsensusAddressOwner does not exist for this execution version"
786 )
787 }
788 Owner::Party { .. } => {
789 unimplemented!("Party does not exist for this execution version")
790 }
791 }
792 };
793 authenticated_for_mutation.insert(to_authenticate);
795 objects_to_authenticate.push(parent);
796 }
797 Ok(())
798 }
799}
800
801impl TemporaryStore<'_> {
802 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
809 let old_storage_rebates: Vec<_> = self
811 .execution_results
812 .written_objects
813 .keys()
814 .map(|object_id| {
815 self.get_object_modified_at(object_id)
816 .map(|metadata| metadata.storage_rebate)
817 .unwrap_or_default()
818 })
819 .collect();
820 for (object, old_storage_rebate) in self
821 .execution_results
822 .written_objects
823 .values_mut()
824 .zip(old_storage_rebates)
825 {
826 let new_object_size = object.object_size_for_gas_metering();
828 let new_storage_rebate = gas_charger.track_storage_mutation(
830 object.id(),
831 new_object_size,
832 old_storage_rebate,
833 );
834 object.storage_rebate = new_storage_rebate;
835 }
836
837 self.collect_rebate(gas_charger);
838 }
839
840 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
841 for object_id in &self.execution_results.modified_objects {
842 if self
843 .execution_results
844 .written_objects
845 .contains_key(object_id)
846 {
847 continue;
848 }
849 let storage_rebate = self
851 .get_object_modified_at(object_id)
852 .unwrap()
854 .storage_rebate;
855 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
856 }
857 }
858
859 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
860 assert_invariant!(
861 self.execution_results
862 .created_object_ids
863 .iter()
864 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
865 && !self.execution_results.modified_objects.contains(id)),
866 "Created object IDs cannot also be deleted or modified"
867 );
868 assert_invariant!(
869 self.execution_results.modified_objects.iter().all(|id| {
870 self.mutable_input_refs.contains_key(id)
871 || self.loaded_runtime_objects.contains_key(id)
872 || is_system_package(*id)
873 }),
874 "A modified object must be either a mutable input, a loaded child object, or a system package"
875 );
876 Ok(())
877 }
878}
879impl TemporaryStore<'_> {
884 pub fn advance_epoch_safe_mode(
885 &mut self,
886 params: &AdvanceEpochParams,
887 protocol_config: &ProtocolConfig,
888 ) {
889 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
890 .expect("System state wrapper object must exist");
891 let (old_object, new_object) =
892 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
893 self.mutate_child_object(old_object, new_object);
894 }
895}
896
897type ModifiedObjectInfo<'a> = (
898 ObjectID,
899 Option<DynamicallyLoadedObjectMetadata>,
901 Option<&'a Object>,
902);
903
904impl TemporaryStore<'_> {
905 fn get_input_sui(
906 &self,
907 id: &ObjectID,
908 expected_version: SequenceNumber,
909 layout_resolver: &mut impl LayoutResolver,
910 ) -> Result<u64, ExecutionError> {
911 if let Some(obj) = self.input_objects.get(id) {
912 if obj.version() != expected_version {
914 invariant_violation!(
915 "Version mismatching when resolving input object to check conservation--\
916 expected {}, got {}",
917 expected_version,
918 obj.version(),
919 );
920 }
921 obj.get_total_sui(layout_resolver).map_err(|e| {
922 make_invariant_violation!(
923 "Failed looking up input SUI in SUI conservation checking for input with \
924 type {:?}: {e:#?}",
925 obj.struct_tag(),
926 )
927 })
928 } else {
929 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
931 invariant_violation!(
932 "Failed looking up dynamic field {id} in SUI conservation checking"
933 );
934 };
935 obj.get_total_sui(layout_resolver).map_err(|e| {
936 make_invariant_violation!(
937 "Failed looking up input SUI in SUI conservation checking for type \
938 {:?}: {e:#?}",
939 obj.struct_tag(),
940 )
941 })
942 }
943 }
944
945 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
950 self.execution_results
951 .modified_objects
952 .iter()
953 .map(|id| {
954 let metadata = self.get_object_modified_at(id);
955 let output = self.execution_results.written_objects.get(id);
956 (*id, metadata, output)
957 })
958 .chain(
959 self.execution_results
960 .written_objects
961 .iter()
962 .filter_map(|(id, object)| {
963 if self.execution_results.modified_objects.contains(id) {
964 None
965 } else {
966 Some((*id, None, Some(object)))
967 }
968 }),
969 )
970 .collect()
971 }
972
973 pub fn check_sui_conserved(
987 &self,
988 simple_conservation_checks: bool,
989 gas_summary: &GasCostSummary,
990 ) -> Result<(), ExecutionError> {
991 if !simple_conservation_checks {
992 return Ok(());
993 }
994 let mut total_input_rebate = 0;
996 let mut total_output_rebate = 0;
998 for (_, input, output) in self.get_modified_objects() {
999 if let Some(input) = input {
1000 total_input_rebate += input.storage_rebate;
1001 }
1002 if let Some(object) = output {
1003 total_output_rebate += object.storage_rebate;
1004 }
1005 }
1006
1007 if gas_summary.storage_cost == 0 {
1008 if total_input_rebate
1020 != total_output_rebate
1021 + gas_summary.storage_rebate
1022 + gas_summary.non_refundable_storage_fee
1023 {
1024 return Err(ExecutionError::invariant_violation(format!(
1025 "SUI conservation failed -- no storage charges in gas summary \
1026 and total storage input rebate {} not equal \
1027 to total storage output rebate {}",
1028 total_input_rebate, total_output_rebate,
1029 )));
1030 }
1031 } else {
1032 if total_input_rebate
1035 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1036 {
1037 return Err(ExecutionError::invariant_violation(format!(
1038 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1039 {} SUI in tx storage rebate or tx non-refundable storage rebate",
1040 total_input_rebate, gas_summary.non_refundable_storage_fee,
1041 )));
1042 }
1043
1044 if gas_summary.storage_cost != total_output_rebate {
1047 return Err(ExecutionError::invariant_violation(format!(
1048 "SUI conservation failed -- {} SUI charged for storage, \
1049 {} SUI in storage rebate field of output objects",
1050 gas_summary.storage_cost, total_output_rebate
1051 )));
1052 }
1053 }
1054 Ok(())
1055 }
1056
1057 pub fn check_sui_conserved_expensive(
1070 &self,
1071 gas_summary: &GasCostSummary,
1072 advance_epoch_gas_summary: Option<(u64, u64)>,
1073 layout_resolver: &mut impl LayoutResolver,
1074 ) -> Result<(), ExecutionError> {
1075 let mut total_input_sui = 0;
1077 let mut total_output_sui = 0;
1079 for (id, input, output) in self.get_modified_objects() {
1080 if let Some(input) = input {
1081 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1082 }
1083 if let Some(object) = output {
1084 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1085 make_invariant_violation!(
1086 "Failed looking up output SUI in SUI conservation checking for \
1087 mutated type {:?}: {e:#?}",
1088 object.struct_tag(),
1089 )
1090 })?;
1091 }
1092 }
1093 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1098 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1099 total_input_sui += epoch_fees;
1100 total_output_sui += epoch_rebates;
1101 }
1102 if total_input_sui != total_output_sui {
1103 return Err(ExecutionError::invariant_violation(format!(
1104 "SUI conservation failed: input={}, output={}, \
1105 this transaction either mints or burns SUI",
1106 total_input_sui, total_output_sui,
1107 )));
1108 }
1109 Ok(())
1110 }
1111}
1112
1113impl ChildObjectResolver for TemporaryStore<'_> {
1114 fn read_child_object(
1115 &self,
1116 parent: &ObjectID,
1117 child: &ObjectID,
1118 child_version_upper_bound: SequenceNumber,
1119 ) -> SuiResult<Option<Object>> {
1120 let obj_opt = self.execution_results.written_objects.get(child);
1121 if obj_opt.is_some() {
1122 Ok(obj_opt.cloned())
1123 } else {
1124 self.store
1125 .read_child_object(parent, child, child_version_upper_bound)
1126 }
1127 }
1128
1129 fn get_object_received_at_version(
1130 &self,
1131 owner: &ObjectID,
1132 receiving_object_id: &ObjectID,
1133 receive_object_at_version: SequenceNumber,
1134 epoch_id: EpochId,
1135 ) -> SuiResult<Option<Object>> {
1136 debug_assert!(!self
1139 .execution_results
1140 .written_objects
1141 .contains_key(receiving_object_id));
1142 debug_assert!(!self
1143 .execution_results
1144 .deleted_object_ids
1145 .contains(receiving_object_id));
1146 self.store.get_object_received_at_version(
1147 owner,
1148 receiving_object_id,
1149 receive_object_at_version,
1150 epoch_id,
1151 )
1152 }
1153}
1154
1155impl Storage for TemporaryStore<'_> {
1156 fn reset(&mut self) {
1157 self.drop_writes();
1158 }
1159
1160 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1161 TemporaryStore::read_object(self, id)
1162 }
1163
1164 fn record_execution_results(
1166 &mut self,
1167 results: ExecutionResults,
1168 ) -> Result<(), ExecutionError> {
1169 let ExecutionResults::V2(results) = results else {
1170 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1171 };
1172 self.execution_results
1175 .merge_results(
1176 results, false, true,
1177 )
1178 .unwrap();
1179 Ok(())
1180 }
1181
1182 fn save_loaded_runtime_objects(
1183 &mut self,
1184 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1185 ) {
1186 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1187 }
1188
1189 fn save_wrapped_object_containers(
1190 &mut self,
1191 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1192 ) {
1193 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1194 }
1195
1196 fn check_coin_deny_list(
1197 &self,
1198 _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1199 ) -> DenyListResult {
1200 unreachable!("Coin denylist v2 is not supported in sui-execution v2");
1201 }
1202
1203 fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1204 unreachable!(
1205 "Generated object IDs are not recorded in ExecutionResults in sui-execution v2"
1206 );
1207 }
1208}
1209
1210impl BackingPackageStore for TemporaryStore<'_> {
1211 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1212 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1219 Ok(Some(PackageObject::new(obj.clone())))
1220 } else {
1221 self.store.get_package_object(package_id).inspect(|obj| {
1222 if let Some(v) = obj {
1224 if !self
1225 .runtime_packages_loaded_from_db
1226 .read()
1227 .contains_key(package_id)
1228 {
1229 self.runtime_packages_loaded_from_db
1234 .write()
1235 .insert(*package_id, v.clone());
1236 }
1237 }
1238 })
1239 }
1240 }
1241}
1242
1243impl ParentSync for TemporaryStore<'_> {
1244 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1245 unreachable!("Never called in newer protocol versions")
1246 }
1247}