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 protocol_config: ProtocolConfig,
50
51 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
54
55 receiving_objects: Vec<ObjectRef>,
58}
59
60impl<'backing> TemporaryStore<'backing> {
61 pub fn new(
64 store: &'backing dyn BackingStore,
65 input_objects: InputObjects,
66 receiving_objects: Vec<ObjectRef>,
67 tx_digest: TransactionDigest,
68 protocol_config: &ProtocolConfig,
69 ) -> Self {
70 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
71 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
72 let deleted_consensus_objects = input_objects.consensus_stream_ended_objects();
73 let objects = input_objects.into_object_map();
74 Self {
75 store,
76 tx_digest,
77 input_objects: objects,
78 deleted_consensus_objects,
79 lamport_timestamp,
80 mutable_input_refs,
81 execution_results: ExecutionResultsV2::default(),
82 protocol_config: protocol_config.clone(),
83 loaded_runtime_objects: BTreeMap::new(),
84 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
85 receiving_objects,
86 }
87 }
88
89 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
91 &self.input_objects
92 }
93
94 pub fn update_object_version_and_prev_tx(&mut self) {
95 self.execution_results.update_version_and_previous_tx(
96 self.lamport_timestamp,
97 self.tx_digest,
98 &self.input_objects,
99 false,
100 );
101
102 #[cfg(debug_assertions)]
103 {
104 self.check_invariants();
105 }
106 }
107
108 pub fn into_inner(self) -> InnerTemporaryStore {
110 let results = self.execution_results;
111 InnerTemporaryStore {
112 input_objects: self.input_objects,
113 stream_ended_consensus_objects: self.deleted_consensus_objects,
114 mutable_inputs: self.mutable_input_refs,
115 written: results.written_objects,
116 events: TransactionEvents {
117 data: results.user_events,
118 },
119 accumulator_events: vec![],
121 loaded_runtime_objects: self.loaded_runtime_objects,
122 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
123 lamport_version: self.lamport_timestamp,
124 binary_config: self.protocol_config.binary_config(None),
125 accumulator_running_max_withdraws: BTreeMap::new(),
126 }
127 }
128
129 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
133 let mut to_be_updated = vec![];
134 for id in self.mutable_input_refs.keys() {
135 if !self.execution_results.modified_objects.contains(id) {
136 to_be_updated.push(self.input_objects[id].clone());
140 }
141 }
142 for object in to_be_updated {
143 self.mutate_input_object(object.clone());
145 }
146 }
147
148 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
149 let results = &self.execution_results;
150 let all_ids = results
151 .created_object_ids
152 .iter()
153 .chain(&results.deleted_object_ids)
154 .chain(&results.modified_objects)
155 .chain(results.written_objects.keys())
156 .collect::<BTreeSet<_>>();
157 all_ids
158 .into_iter()
159 .map(|id| {
160 (
161 *id,
162 EffectsObjectChange::new(
163 self.get_object_modified_at(id)
164 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
165 results.written_objects.get(id),
166 results.created_object_ids.contains(id),
167 results.deleted_object_ids.contains(id),
168 ),
169 )
170 })
171 .collect()
172 }
173
174 pub fn into_effects(
175 mut self,
176 shared_object_refs: Vec<SharedInput>,
177 transaction_digest: &TransactionDigest,
178 mut transaction_dependencies: BTreeSet<TransactionDigest>,
179 gas_cost_summary: GasCostSummary,
180 status: ExecutionStatus,
181 gas_charger: &mut GasCharger,
182 epoch: EpochId,
183 ) -> (InnerTemporaryStore, TransactionEffects) {
184 self.update_object_version_and_prev_tx();
185
186 for (id, expected_version, expected_digest) in &self.receiving_objects {
189 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
193 let loaded_via_receive = obj_meta.version == *expected_version
197 && obj_meta.digest == *expected_digest
198 && obj_meta.owner.is_address_owned();
199 if loaded_via_receive {
200 transaction_dependencies.insert(obj_meta.previous_transaction);
201 }
202 }
203 }
204
205 if self.protocol_config.enable_effects_v2() {
206 self.into_effects_v2(
207 shared_object_refs,
208 transaction_digest,
209 transaction_dependencies,
210 gas_cost_summary,
211 status,
212 gas_charger,
213 epoch,
214 )
215 } else {
216 let shared_object_refs = shared_object_refs
217 .into_iter()
218 .map(|shared_input| match shared_input {
219 SharedInput::Existing(oref) => oref,
220 SharedInput::ConsensusStreamEnded(_) => {
221 unreachable!("Shared object deletion not supported in effects v1")
222 }
223 SharedInput::Cancelled(_) => {
224 unreachable!("Per object congestion control not supported in effects v1.")
225 }
226 })
227 .collect();
228 self.into_effects_v1(
229 shared_object_refs,
230 transaction_digest,
231 transaction_dependencies,
232 gas_cost_summary,
233 status,
234 gas_charger,
235 epoch,
236 )
237 }
238 }
239
240 fn into_effects_v1(
241 self,
242 shared_object_refs: Vec<ObjectRef>,
243 transaction_digest: &TransactionDigest,
244 transaction_dependencies: BTreeSet<TransactionDigest>,
245 gas_cost_summary: GasCostSummary,
246 status: ExecutionStatus,
247 gas_charger: &mut GasCharger,
248 epoch: EpochId,
249 ) -> (InnerTemporaryStore, TransactionEffects) {
250 let updated_gas_object_info = if let Some(coin_id) = gas_charger.gas_coin() {
251 let object = &self.execution_results.written_objects[&coin_id];
252 (object.compute_object_reference(), object.owner.clone())
253 } else {
254 (
255 (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN),
256 Owner::AddressOwner(SuiAddress::default()),
257 )
258 };
259 let lampot_version = self.lamport_timestamp;
260
261 let mut created = vec![];
262 let mut mutated = vec![];
263 let mut unwrapped = vec![];
264 let mut deleted = vec![];
265 let mut unwrapped_then_deleted = vec![];
266 let mut wrapped = vec![];
267 let mut modified_at_versions = vec![];
270 let mut deleted_at_versions = vec![];
271 self.execution_results
272 .written_objects
273 .iter()
274 .for_each(|(id, object)| {
275 let object_ref = object.compute_object_reference();
276 let owner = object.owner.clone();
277 if let Some(old_object_meta) = self.get_object_modified_at(id) {
278 modified_at_versions.push((*id, old_object_meta.version));
279 mutated.push((object_ref, owner));
280 } else if self.execution_results.created_object_ids.contains(id) {
281 created.push((object_ref, owner));
282 } else {
283 unwrapped.push((object_ref, owner));
284 }
285 });
286 self.execution_results
287 .modified_objects
288 .iter()
289 .filter(|id| !self.execution_results.written_objects.contains_key(id))
290 .for_each(|id| {
291 let old_object_meta = self.get_object_modified_at(id).unwrap();
292 deleted_at_versions.push((*id, old_object_meta.version));
293 if self.execution_results.deleted_object_ids.contains(id) {
294 deleted.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_DELETED));
295 } else {
296 wrapped.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_WRAPPED));
297 }
298 });
299 self.execution_results
300 .deleted_object_ids
301 .iter()
302 .filter(|id| !self.execution_results.modified_objects.contains(id))
303 .for_each(|id| {
304 unwrapped_then_deleted.push((
305 *id,
306 lampot_version,
307 ObjectDigest::OBJECT_DIGEST_DELETED,
308 ));
309 });
310 modified_at_versions.extend(deleted_at_versions);
311
312 let inner = self.into_inner();
313 let effects = TransactionEffects::new_from_execution_v1(
314 status,
315 epoch,
316 gas_cost_summary,
317 modified_at_versions,
318 shared_object_refs,
319 *transaction_digest,
320 created,
321 mutated,
322 unwrapped,
323 deleted,
324 unwrapped_then_deleted,
325 wrapped,
326 updated_gas_object_info,
327 if inner.events.data.is_empty() {
328 None
329 } else {
330 Some(inner.events.digest())
331 },
332 transaction_dependencies.into_iter().collect(),
333 );
334 (inner, effects)
335 }
336
337 fn into_effects_v2(
338 self,
339 shared_object_refs: Vec<SharedInput>,
340 transaction_digest: &TransactionDigest,
341 transaction_dependencies: BTreeSet<TransactionDigest>,
342 gas_cost_summary: GasCostSummary,
343 status: ExecutionStatus,
344 gas_charger: &mut GasCharger,
345 epoch: EpochId,
346 ) -> (InnerTemporaryStore, TransactionEffects) {
347 let gas_coin = gas_charger.gas_coin();
352
353 let object_changes = self.get_object_changes();
354
355 let lamport_version = self.lamport_timestamp;
356 let inner = self.into_inner();
357
358 let effects = TransactionEffects::new_from_execution_v2(
359 status,
360 epoch,
361 gas_cost_summary,
362 shared_object_refs,
364 BTreeSet::new(),
365 *transaction_digest,
366 lamport_version,
367 object_changes,
368 gas_coin,
369 if inner.events.data.is_empty() {
370 None
371 } else {
372 Some(inner.events.digest())
373 },
374 transaction_dependencies.into_iter().collect(),
375 );
376
377 (inner, effects)
378 }
379
380 #[cfg(debug_assertions)]
382 fn check_invariants(&self) {
383 debug_assert!(
385 {
386 self.execution_results
387 .written_objects
388 .keys()
389 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
390 },
391 "Object both written and deleted."
392 );
393
394 debug_assert!(
396 {
397 self.mutable_input_refs
398 .keys()
399 .all(|id| self.execution_results.modified_objects.contains(id))
400 },
401 "Mutable input not modified."
402 );
403
404 debug_assert!(
405 {
406 self.execution_results
407 .written_objects
408 .values()
409 .all(|obj| obj.previous_transaction == self.tx_digest)
410 },
411 "Object previous transaction not properly set",
412 );
413 }
414
415 pub fn mutate_input_object(&mut self, object: Object) {
417 let id = object.id();
418 self.execution_results.modified_objects.insert(id);
419 self.execution_results.written_objects.insert(id, object);
420 }
421
422 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
426 let id = new_object.id();
427 let old_ref = old_object.compute_object_reference();
428 debug_assert_eq!(old_ref.0, id);
429 self.loaded_runtime_objects.insert(
430 id,
431 DynamicallyLoadedObjectMetadata {
432 version: old_ref.1,
433 digest: old_ref.2,
434 owner: old_object.owner.clone(),
435 storage_rebate: old_object.storage_rebate,
436 previous_transaction: old_object.previous_transaction,
437 },
438 );
439 self.execution_results.modified_objects.insert(id);
440 self.execution_results
441 .written_objects
442 .insert(id, new_object);
443 }
444
445 pub fn upgrade_system_package(&mut self, package: Object) {
451 let id = package.id();
452 assert!(package.is_package() && is_system_package(id));
453 self.execution_results.modified_objects.insert(id);
454 self.execution_results.written_objects.insert(id, package);
455 }
456
457 pub fn create_object(&mut self, object: Object) {
459 debug_assert!(
464 object.is_immutable() || object.version() == SequenceNumber::MIN,
465 "Created mutable objects should not have a version set",
466 );
467 let id = object.id();
468 self.execution_results.created_object_ids.insert(id);
469 self.execution_results.written_objects.insert(id, object);
470 }
471
472 pub fn delete_input_object(&mut self, id: &ObjectID) {
474 debug_assert!(!self.execution_results.written_objects.contains_key(id));
476 self.execution_results.modified_objects.insert(*id);
477 self.execution_results.deleted_object_ids.insert(*id);
478 }
479
480 pub fn drop_writes(&mut self) {
481 self.execution_results.drop_writes();
482 }
483
484 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
485 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
487 self.execution_results
488 .written_objects
489 .get(id)
490 .or_else(|| self.input_objects.get(id))
491 }
492
493 pub fn save_loaded_runtime_objects(
494 &mut self,
495 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
496 ) {
497 #[cfg(debug_assertions)]
498 {
499 for (id, v1) in &loaded_runtime_objects {
500 if let Some(v2) = self.loaded_runtime_objects.get(id) {
501 assert_eq!(v1, v2);
502 }
503 }
504 for (id, v1) in &self.loaded_runtime_objects {
505 if let Some(v2) = loaded_runtime_objects.get(id) {
506 assert_eq!(v1, v2);
507 }
508 }
509 }
510 self.loaded_runtime_objects.extend(loaded_runtime_objects);
513 }
514
515 pub fn estimate_effects_size_upperbound(&self) -> usize {
516 if self.protocol_config.enable_effects_v2() {
517 TransactionEffects::estimate_effects_size_upperbound_v2(
518 self.execution_results.written_objects.len(),
519 self.execution_results.modified_objects.len(),
520 self.input_objects.len(),
521 )
522 } else {
523 let num_deletes = self.execution_results.deleted_object_ids.len()
524 + self
525 .execution_results
526 .modified_objects
527 .iter()
528 .filter(|id| {
529 !self.execution_results.written_objects.contains_key(id)
531 && !self.execution_results.deleted_object_ids.contains(id)
532 })
533 .count();
534 TransactionEffects::estimate_effects_size_upperbound_v1(
536 self.execution_results.written_objects.len(),
537 self.mutable_input_refs.len(),
538 num_deletes,
539 self.input_objects.len(),
540 )
541 }
542 }
543
544 pub fn written_objects_size(&self) -> usize {
545 self.execution_results
546 .written_objects
547 .values()
548 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
549 }
550
551 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
556 if unmetered_storage_rebate == 0 {
557 return;
561 }
562 tracing::debug!(
563 "Amount of unmetered storage rebate from system tx: {:?}",
564 unmetered_storage_rebate
565 );
566 let mut system_state_wrapper = self
567 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
568 .expect("0x5 object must be muated in system tx with unmetered storage rebate")
569 .clone();
570 assert_eq!(system_state_wrapper.storage_rebate, 0);
573 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
574 self.mutate_input_object(system_state_wrapper);
575 }
576
577 fn get_object_modified_at(
583 &self,
584 object_id: &ObjectID,
585 ) -> Option<DynamicallyLoadedObjectMetadata> {
586 if self.execution_results.modified_objects.contains(object_id) {
587 Some(
588 self.mutable_input_refs
589 .get(object_id)
590 .map(
591 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
592 version: *version,
593 digest: *digest,
594 owner: owner.clone(),
595 storage_rebate: self.input_objects[object_id].storage_rebate,
597 previous_transaction: self.input_objects[object_id]
598 .previous_transaction,
599 },
600 )
601 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
602 .unwrap_or_else(|| {
603 debug_assert!(is_system_package(*object_id));
604 let obj = self.store.get_object(object_id).unwrap();
605 DynamicallyLoadedObjectMetadata {
606 version: obj.version(),
607 digest: obj.digest(),
608 owner: obj.owner.clone(),
609 storage_rebate: obj.storage_rebate,
610 previous_transaction: obj.previous_transaction,
611 }
612 }),
613 )
614 } else {
615 None
616 }
617 }
618}
619
620impl TemporaryStore<'_> {
621 fn get_objects_to_authenticate(
623 &self,
624 sender: &SuiAddress,
625 gas_charger: &mut GasCharger,
626 is_epoch_change: bool,
627 ) -> SuiResult<(Vec<ObjectID>, HashSet<ObjectID>)> {
628 let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().iter().map(|g| &g.0).collect();
629 let mut objs_to_authenticate = Vec::new();
630 let mut authenticated_objs = HashSet::new();
631 for (id, obj) in &self.input_objects {
632 if gas_objs.contains(id) {
633 continue;
638 }
639 match &obj.owner {
640 Owner::AddressOwner(a) => {
641 assert!(sender == a, "Input object not owned by sender");
642 authenticated_objs.insert(*id);
643 }
644 Owner::Shared { .. } => {
645 authenticated_objs.insert(*id);
646 }
647 Owner::Immutable => {
648 }
659 Owner::ObjectOwner(_parent) => {
660 unreachable!("Input objects must be address owned, shared, or immutable")
661 }
662 Owner::ConsensusAddressOwner { .. } => {
663 unimplemented!(
664 "ConsensusAddressOwner does not exist for this execution version"
665 )
666 }
667 Owner::Party { .. } => {
668 unimplemented!("Party does not exist for this execution version")
669 }
670 }
671 }
672
673 for id in &self.execution_results.modified_objects {
674 if authenticated_objs.contains(id) || gas_objs.contains(id) {
675 continue;
676 }
677 let old_obj = self.store.get_object(id).unwrap_or_else(|| {
678 panic!("Modified object must exist in the store: ID = {:?}", id)
679 });
680 match &old_obj.owner {
681 Owner::ObjectOwner(_) | Owner::AddressOwner(_) => {
684 objs_to_authenticate.push(*id);
685 }
686 Owner::Shared { .. } => {
687 unreachable!("Should already be in authenticated_objs")
688 }
689 Owner::Immutable => {
690 assert!(is_epoch_change, "Immutable objects cannot be written, except for Sui Framework/Move stdlib upgrades at epoch change boundaries");
691 assert!(
694 is_system_package(*id),
695 "Only system packages can be upgraded"
696 );
697 }
698 Owner::ConsensusAddressOwner { .. } => {
699 unimplemented!(
700 "ConsensusAddressOwner does not exist for this execution version"
701 )
702 }
703 Owner::Party { .. } => {
704 unimplemented!("Party does not exist for this execution version")
705 }
706 }
707 }
708 Ok((objs_to_authenticate, authenticated_objs))
709 }
710
711 pub fn check_ownership_invariants(
713 &self,
714 sender: &SuiAddress,
715 gas_charger: &mut GasCharger,
716 is_epoch_change: bool,
717 ) -> SuiResult<()> {
718 let (mut objects_to_authenticate, mut authenticated_objects) =
719 self.get_objects_to_authenticate(sender, gas_charger, is_epoch_change)?;
720
721 let mut covered = BTreeMap::new();
723 while let Some(to_authenticate) = objects_to_authenticate.pop() {
724 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
725 continue;
728 };
729 let parent = match &old_obj.owner {
730 Owner::ObjectOwner(parent) | Owner::AddressOwner(parent) => ObjectID::from(*parent),
731 owner => panic!(
732 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
733 Potentially covering objects in: {covered:#?}",
734 ),
735 };
736
737 if authenticated_objects.contains(&parent) {
738 authenticated_objects.insert(to_authenticate);
739 } else if !covered.contains_key(&parent) {
740 objects_to_authenticate.push(parent);
741 }
742
743 covered.insert(to_authenticate, parent);
744 }
745 Ok(())
746 }
747}
748
749impl TemporaryStore<'_> {
750 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
757 let old_storage_rebates: Vec<_> = self
759 .execution_results
760 .written_objects
761 .keys()
762 .map(|object_id| {
763 self.get_object_modified_at(object_id)
764 .map(|metadata| metadata.storage_rebate)
765 .unwrap_or_default()
766 })
767 .collect();
768 for (object, old_storage_rebate) in self
769 .execution_results
770 .written_objects
771 .values_mut()
772 .zip(old_storage_rebates)
773 {
774 let new_object_size = object.object_size_for_gas_metering();
776 let new_storage_rebate = gas_charger.track_storage_mutation(
778 object.id(),
779 new_object_size,
780 old_storage_rebate,
781 );
782 object.storage_rebate = new_storage_rebate;
783 }
784
785 self.collect_rebate(gas_charger);
786 }
787
788 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
789 for object_id in &self.execution_results.modified_objects {
790 if self
791 .execution_results
792 .written_objects
793 .contains_key(object_id)
794 {
795 continue;
796 }
797 let storage_rebate = self
799 .get_object_modified_at(object_id)
800 .unwrap()
802 .storage_rebate;
803 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
804 }
805 }
806
807 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
808 assert_invariant!(
809 self.execution_results
810 .created_object_ids
811 .iter()
812 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
813 && !self.execution_results.modified_objects.contains(id)),
814 "Created object IDs cannot also be deleted or modified"
815 );
816 assert_invariant!(
817 self.execution_results.modified_objects.iter().all(|id| {
818 self.mutable_input_refs.contains_key(id)
819 || self.loaded_runtime_objects.contains_key(id)
820 || is_system_package(*id)
821 }),
822 "A modified object must be either a mutable input, a loaded child object, or a system package"
823 );
824 Ok(())
825 }
826}
827impl TemporaryStore<'_> {
832 pub fn advance_epoch_safe_mode(
833 &mut self,
834 params: &AdvanceEpochParams,
835 protocol_config: &ProtocolConfig,
836 ) {
837 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
838 .expect("System state wrapper object must exist");
839 let (old_object, new_object) =
840 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
841 self.mutate_child_object(old_object, new_object);
842 }
843}
844
845type ModifiedObjectInfo<'a> = (
846 ObjectID,
847 Option<DynamicallyLoadedObjectMetadata>,
849 Option<&'a Object>,
850);
851
852impl TemporaryStore<'_> {
853 fn get_input_sui(
854 &self,
855 id: &ObjectID,
856 expected_version: SequenceNumber,
857 layout_resolver: &mut impl LayoutResolver,
858 ) -> Result<u64, ExecutionError> {
859 if let Some(obj) = self.input_objects.get(id) {
860 if obj.version() != expected_version {
862 invariant_violation!(
863 "Version mismatching when resolving input object to check conservation--\
864 expected {}, got {}",
865 expected_version,
866 obj.version(),
867 );
868 }
869 obj.get_total_sui(layout_resolver).map_err(|e| {
870 make_invariant_violation!(
871 "Failed looking up input SUI in SUI conservation checking for input with \
872 type {:?}: {e:#?}",
873 obj.struct_tag(),
874 )
875 })
876 } else {
877 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
879 invariant_violation!(
880 "Failed looking up dynamic field {id} in SUI conservation checking"
881 );
882 };
883 obj.get_total_sui(layout_resolver).map_err(|e| {
884 make_invariant_violation!(
885 "Failed looking up input SUI in SUI conservation checking for type \
886 {:?}: {e:#?}",
887 obj.struct_tag(),
888 )
889 })
890 }
891 }
892
893 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
898 self.execution_results
899 .modified_objects
900 .iter()
901 .map(|id| {
902 let metadata = self.get_object_modified_at(id);
903 let output = self.execution_results.written_objects.get(id);
904 (*id, metadata, output)
905 })
906 .chain(
907 self.execution_results
908 .written_objects
909 .iter()
910 .filter_map(|(id, object)| {
911 if self.execution_results.modified_objects.contains(id) {
912 None
913 } else {
914 Some((*id, None, Some(object)))
915 }
916 }),
917 )
918 .collect()
919 }
920
921 pub fn check_sui_conserved(
935 &self,
936 simple_conservation_checks: bool,
937 gas_summary: &GasCostSummary,
938 ) -> Result<(), ExecutionError> {
939 if !simple_conservation_checks {
940 return Ok(());
941 }
942 let mut total_input_rebate = 0;
944 let mut total_output_rebate = 0;
946 for (_, input, output) in self.get_modified_objects() {
947 if let Some(input) = input {
948 total_input_rebate += input.storage_rebate;
949 }
950 if let Some(object) = output {
951 total_output_rebate += object.storage_rebate;
952 }
953 }
954
955 if gas_summary.storage_cost == 0 {
956 if total_input_rebate
968 != total_output_rebate
969 + gas_summary.storage_rebate
970 + gas_summary.non_refundable_storage_fee
971 {
972 return Err(ExecutionError::invariant_violation(format!(
973 "SUI conservation failed -- no storage charges in gas summary \
974 and total storage input rebate {} not equal \
975 to total storage output rebate {}",
976 total_input_rebate, total_output_rebate,
977 )));
978 }
979 } else {
980 if total_input_rebate
983 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
984 {
985 return Err(ExecutionError::invariant_violation(format!(
986 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
987 {} SUI in tx storage rebate or tx non-refundable storage rebate",
988 total_input_rebate, gas_summary.non_refundable_storage_fee,
989 )));
990 }
991
992 if gas_summary.storage_cost != total_output_rebate {
995 return Err(ExecutionError::invariant_violation(format!(
996 "SUI conservation failed -- {} SUI charged for storage, \
997 {} SUI in storage rebate field of output objects",
998 gas_summary.storage_cost, total_output_rebate
999 )));
1000 }
1001 }
1002 Ok(())
1003 }
1004
1005 pub fn check_sui_conserved_expensive(
1018 &self,
1019 gas_summary: &GasCostSummary,
1020 advance_epoch_gas_summary: Option<(u64, u64)>,
1021 layout_resolver: &mut impl LayoutResolver,
1022 ) -> Result<(), ExecutionError> {
1023 let mut total_input_sui = 0;
1025 let mut total_output_sui = 0;
1027 for (id, input, output) in self.get_modified_objects() {
1028 if let Some(input) = input {
1029 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1030 }
1031 if let Some(object) = output {
1032 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1033 make_invariant_violation!(
1034 "Failed looking up output SUI in SUI conservation checking for \
1035 mutated type {:?}: {e:#?}",
1036 object.struct_tag(),
1037 )
1038 })?;
1039 }
1040 }
1041 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1046 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1047 total_input_sui += epoch_fees;
1048 total_output_sui += epoch_rebates;
1049 }
1050 if total_input_sui != total_output_sui {
1051 return Err(ExecutionError::invariant_violation(format!(
1052 "SUI conservation failed: input={}, output={}, \
1053 this transaction either mints or burns SUI",
1054 total_input_sui, total_output_sui,
1055 )));
1056 }
1057 Ok(())
1058 }
1059}
1060
1061impl ChildObjectResolver for TemporaryStore<'_> {
1062 fn read_child_object(
1063 &self,
1064 parent: &ObjectID,
1065 child: &ObjectID,
1066 child_version_upper_bound: SequenceNumber,
1067 ) -> SuiResult<Option<Object>> {
1068 let obj_opt = self.execution_results.written_objects.get(child);
1069 if obj_opt.is_some() {
1070 Ok(obj_opt.cloned())
1071 } else {
1072 self.store
1073 .read_child_object(parent, child, child_version_upper_bound)
1074 }
1075 }
1076
1077 fn get_object_received_at_version(
1078 &self,
1079 owner: &ObjectID,
1080 receiving_object_id: &ObjectID,
1081 receive_object_at_version: SequenceNumber,
1082 epoch_id: EpochId,
1083 ) -> SuiResult<Option<Object>> {
1084 debug_assert!(!self
1087 .execution_results
1088 .written_objects
1089 .contains_key(receiving_object_id));
1090 debug_assert!(!self
1091 .execution_results
1092 .deleted_object_ids
1093 .contains(receiving_object_id));
1094 self.store.get_object_received_at_version(
1095 owner,
1096 receiving_object_id,
1097 receive_object_at_version,
1098 epoch_id,
1099 )
1100 }
1101}
1102
1103impl Storage for TemporaryStore<'_> {
1104 fn reset(&mut self) {
1105 self.drop_writes();
1106 }
1107
1108 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1109 TemporaryStore::read_object(self, id)
1110 }
1111
1112 fn record_execution_results(
1114 &mut self,
1115 results: ExecutionResults,
1116 ) -> Result<(), ExecutionError> {
1117 let ExecutionResults::V2(results) = results else {
1118 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1119 };
1120 self.execution_results
1123 .merge_results(
1124 results, false, true,
1125 )
1126 .unwrap();
1127 Ok(())
1128 }
1129
1130 fn save_loaded_runtime_objects(
1131 &mut self,
1132 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1133 ) {
1134 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1135 }
1136
1137 fn save_wrapped_object_containers(
1138 &mut self,
1139 _wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1140 ) {
1141 unreachable!("Unused in v1")
1142 }
1143
1144 fn check_coin_deny_list(
1145 &self,
1146 _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1147 ) -> DenyListResult {
1148 unreachable!("Coin denylist v2 is not supported in sui-execution v1");
1149 }
1150
1151 fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1152 unreachable!(
1153 "Generated object IDs are not recorded in ExecutionResults in sui-execution v1"
1154 );
1155 }
1156}
1157
1158impl BackingPackageStore for TemporaryStore<'_> {
1159 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1160 if let Some(obj) = self.read_object(package_id) {
1167 Ok(Some(PackageObject::new(obj.clone())))
1168 } else {
1169 self.store.get_package_object(package_id).inspect(|obj| {
1170 if let Some(v) = obj {
1172 if !self
1173 .runtime_packages_loaded_from_db
1174 .read()
1175 .contains_key(package_id)
1176 {
1177 self.runtime_packages_loaded_from_db
1180 .write()
1181 .insert(*package_id, v.clone());
1182 }
1183 }
1184 })
1185 }
1186 }
1187}
1188
1189impl ParentSync for TemporaryStore<'_> {
1190 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1191 unreachable!("Never called in newer protocol versions")
1192 }
1193}