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 }
668 }
669
670 for id in &self.execution_results.modified_objects {
671 if authenticated_objs.contains(id) || gas_objs.contains(id) {
672 continue;
673 }
674 let old_obj = self.store.get_object(id).unwrap_or_else(|| {
675 panic!("Modified object must exist in the store: ID = {:?}", id)
676 });
677 match &old_obj.owner {
678 Owner::ObjectOwner(_) | Owner::AddressOwner(_) => {
681 objs_to_authenticate.push(*id);
682 }
683 Owner::Shared { .. } => {
684 unreachable!("Should already be in authenticated_objs")
685 }
686 Owner::Immutable => {
687 assert!(is_epoch_change, "Immutable objects cannot be written, except for Sui Framework/Move stdlib upgrades at epoch change boundaries");
688 assert!(
691 is_system_package(*id),
692 "Only system packages can be upgraded"
693 );
694 }
695 Owner::ConsensusAddressOwner { .. } => {
696 unimplemented!(
697 "ConsensusAddressOwner does not exist for this execution version"
698 )
699 }
700 }
701 }
702 Ok((objs_to_authenticate, authenticated_objs))
703 }
704
705 pub fn check_ownership_invariants(
707 &self,
708 sender: &SuiAddress,
709 gas_charger: &mut GasCharger,
710 is_epoch_change: bool,
711 ) -> SuiResult<()> {
712 let (mut objects_to_authenticate, mut authenticated_objects) =
713 self.get_objects_to_authenticate(sender, gas_charger, is_epoch_change)?;
714
715 let mut covered = BTreeMap::new();
717 while let Some(to_authenticate) = objects_to_authenticate.pop() {
718 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
719 continue;
722 };
723 let parent = match &old_obj.owner {
724 Owner::ObjectOwner(parent) | Owner::AddressOwner(parent) => ObjectID::from(*parent),
725 owner => panic!(
726 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
727 Potentially covering objects in: {covered:#?}",
728 ),
729 };
730
731 if authenticated_objects.contains(&parent) {
732 authenticated_objects.insert(to_authenticate);
733 } else if !covered.contains_key(&parent) {
734 objects_to_authenticate.push(parent);
735 }
736
737 covered.insert(to_authenticate, parent);
738 }
739 Ok(())
740 }
741}
742
743impl TemporaryStore<'_> {
744 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
751 let old_storage_rebates: Vec<_> = self
753 .execution_results
754 .written_objects
755 .keys()
756 .map(|object_id| {
757 self.get_object_modified_at(object_id)
758 .map(|metadata| metadata.storage_rebate)
759 .unwrap_or_default()
760 })
761 .collect();
762 for (object, old_storage_rebate) in self
763 .execution_results
764 .written_objects
765 .values_mut()
766 .zip(old_storage_rebates)
767 {
768 let new_object_size = object.object_size_for_gas_metering();
770 let new_storage_rebate = gas_charger.track_storage_mutation(
772 object.id(),
773 new_object_size,
774 old_storage_rebate,
775 );
776 object.storage_rebate = new_storage_rebate;
777 }
778
779 self.collect_rebate(gas_charger);
780 }
781
782 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
783 for object_id in &self.execution_results.modified_objects {
784 if self
785 .execution_results
786 .written_objects
787 .contains_key(object_id)
788 {
789 continue;
790 }
791 let storage_rebate = self
793 .get_object_modified_at(object_id)
794 .unwrap()
796 .storage_rebate;
797 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
798 }
799 }
800
801 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
802 assert_invariant!(
803 self.execution_results
804 .created_object_ids
805 .iter()
806 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
807 && !self.execution_results.modified_objects.contains(id)),
808 "Created object IDs cannot also be deleted or modified"
809 );
810 assert_invariant!(
811 self.execution_results.modified_objects.iter().all(|id| {
812 self.mutable_input_refs.contains_key(id)
813 || self.loaded_runtime_objects.contains_key(id)
814 || is_system_package(*id)
815 }),
816 "A modified object must be either a mutable input, a loaded child object, or a system package"
817 );
818 Ok(())
819 }
820}
821impl TemporaryStore<'_> {
826 pub fn advance_epoch_safe_mode(
827 &mut self,
828 params: &AdvanceEpochParams,
829 protocol_config: &ProtocolConfig,
830 ) {
831 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
832 .expect("System state wrapper object must exist");
833 let (old_object, new_object) =
834 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
835 self.mutate_child_object(old_object, new_object);
836 }
837}
838
839type ModifiedObjectInfo<'a> = (
840 ObjectID,
841 Option<DynamicallyLoadedObjectMetadata>,
843 Option<&'a Object>,
844);
845
846impl TemporaryStore<'_> {
847 fn get_input_sui(
848 &self,
849 id: &ObjectID,
850 expected_version: SequenceNumber,
851 layout_resolver: &mut impl LayoutResolver,
852 ) -> Result<u64, ExecutionError> {
853 if let Some(obj) = self.input_objects.get(id) {
854 if obj.version() != expected_version {
856 invariant_violation!(
857 "Version mismatching when resolving input object to check conservation--\
858 expected {}, got {}",
859 expected_version,
860 obj.version(),
861 );
862 }
863 obj.get_total_sui(layout_resolver).map_err(|e| {
864 make_invariant_violation!(
865 "Failed looking up input SUI in SUI conservation checking for input with \
866 type {:?}: {e:#?}",
867 obj.struct_tag(),
868 )
869 })
870 } else {
871 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
873 invariant_violation!(
874 "Failed looking up dynamic field {id} in SUI conservation checking"
875 );
876 };
877 obj.get_total_sui(layout_resolver).map_err(|e| {
878 make_invariant_violation!(
879 "Failed looking up input SUI in SUI conservation checking for type \
880 {:?}: {e:#?}",
881 obj.struct_tag(),
882 )
883 })
884 }
885 }
886
887 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
892 self.execution_results
893 .modified_objects
894 .iter()
895 .map(|id| {
896 let metadata = self.get_object_modified_at(id);
897 let output = self.execution_results.written_objects.get(id);
898 (*id, metadata, output)
899 })
900 .chain(
901 self.execution_results
902 .written_objects
903 .iter()
904 .filter_map(|(id, object)| {
905 if self.execution_results.modified_objects.contains(id) {
906 None
907 } else {
908 Some((*id, None, Some(object)))
909 }
910 }),
911 )
912 .collect()
913 }
914
915 pub fn check_sui_conserved(
929 &self,
930 simple_conservation_checks: bool,
931 gas_summary: &GasCostSummary,
932 ) -> Result<(), ExecutionError> {
933 if !simple_conservation_checks {
934 return Ok(());
935 }
936 let mut total_input_rebate = 0;
938 let mut total_output_rebate = 0;
940 for (_, input, output) in self.get_modified_objects() {
941 if let Some(input) = input {
942 total_input_rebate += input.storage_rebate;
943 }
944 if let Some(object) = output {
945 total_output_rebate += object.storage_rebate;
946 }
947 }
948
949 if gas_summary.storage_cost == 0 {
950 if total_input_rebate
962 != total_output_rebate
963 + gas_summary.storage_rebate
964 + gas_summary.non_refundable_storage_fee
965 {
966 return Err(ExecutionError::invariant_violation(format!(
967 "SUI conservation failed -- no storage charges in gas summary \
968 and total storage input rebate {} not equal \
969 to total storage output rebate {}",
970 total_input_rebate, total_output_rebate,
971 )));
972 }
973 } else {
974 if total_input_rebate
977 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
978 {
979 return Err(ExecutionError::invariant_violation(format!(
980 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
981 {} SUI in tx storage rebate or tx non-refundable storage rebate",
982 total_input_rebate, gas_summary.non_refundable_storage_fee,
983 )));
984 }
985
986 if gas_summary.storage_cost != total_output_rebate {
989 return Err(ExecutionError::invariant_violation(format!(
990 "SUI conservation failed -- {} SUI charged for storage, \
991 {} SUI in storage rebate field of output objects",
992 gas_summary.storage_cost, total_output_rebate
993 )));
994 }
995 }
996 Ok(())
997 }
998
999 pub fn check_sui_conserved_expensive(
1012 &self,
1013 gas_summary: &GasCostSummary,
1014 advance_epoch_gas_summary: Option<(u64, u64)>,
1015 layout_resolver: &mut impl LayoutResolver,
1016 ) -> Result<(), ExecutionError> {
1017 let mut total_input_sui = 0;
1019 let mut total_output_sui = 0;
1021 for (id, input, output) in self.get_modified_objects() {
1022 if let Some(input) = input {
1023 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1024 }
1025 if let Some(object) = output {
1026 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1027 make_invariant_violation!(
1028 "Failed looking up output SUI in SUI conservation checking for \
1029 mutated type {:?}: {e:#?}",
1030 object.struct_tag(),
1031 )
1032 })?;
1033 }
1034 }
1035 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1040 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1041 total_input_sui += epoch_fees;
1042 total_output_sui += epoch_rebates;
1043 }
1044 if total_input_sui != total_output_sui {
1045 return Err(ExecutionError::invariant_violation(format!(
1046 "SUI conservation failed: input={}, output={}, \
1047 this transaction either mints or burns SUI",
1048 total_input_sui, total_output_sui,
1049 )));
1050 }
1051 Ok(())
1052 }
1053}
1054
1055impl ChildObjectResolver for TemporaryStore<'_> {
1056 fn read_child_object(
1057 &self,
1058 parent: &ObjectID,
1059 child: &ObjectID,
1060 child_version_upper_bound: SequenceNumber,
1061 ) -> SuiResult<Option<Object>> {
1062 let obj_opt = self.execution_results.written_objects.get(child);
1063 if obj_opt.is_some() {
1064 Ok(obj_opt.cloned())
1065 } else {
1066 self.store
1067 .read_child_object(parent, child, child_version_upper_bound)
1068 }
1069 }
1070
1071 fn get_object_received_at_version(
1072 &self,
1073 owner: &ObjectID,
1074 receiving_object_id: &ObjectID,
1075 receive_object_at_version: SequenceNumber,
1076 epoch_id: EpochId,
1077 ) -> SuiResult<Option<Object>> {
1078 debug_assert!(!self
1081 .execution_results
1082 .written_objects
1083 .contains_key(receiving_object_id));
1084 debug_assert!(!self
1085 .execution_results
1086 .deleted_object_ids
1087 .contains(receiving_object_id));
1088 self.store.get_object_received_at_version(
1089 owner,
1090 receiving_object_id,
1091 receive_object_at_version,
1092 epoch_id,
1093 )
1094 }
1095}
1096
1097impl Storage for TemporaryStore<'_> {
1098 fn reset(&mut self) {
1099 self.drop_writes();
1100 }
1101
1102 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1103 TemporaryStore::read_object(self, id)
1104 }
1105
1106 fn record_execution_results(
1108 &mut self,
1109 results: ExecutionResults,
1110 ) -> Result<(), ExecutionError> {
1111 let ExecutionResults::V2(results) = results else {
1112 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1113 };
1114 self.execution_results.merge_results(results);
1117 Ok(())
1118 }
1119
1120 fn save_loaded_runtime_objects(
1121 &mut self,
1122 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1123 ) {
1124 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1125 }
1126
1127 fn save_wrapped_object_containers(
1128 &mut self,
1129 _wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1130 ) {
1131 unreachable!("Unused in v1")
1132 }
1133
1134 fn check_coin_deny_list(
1135 &self,
1136 _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1137 ) -> DenyListResult {
1138 unreachable!("Coin denylist v2 is not supported in sui-execution v1");
1139 }
1140
1141 fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1142 unreachable!(
1143 "Generated object IDs are not recorded in ExecutionResults in sui-execution v1"
1144 );
1145 }
1146}
1147
1148impl BackingPackageStore for TemporaryStore<'_> {
1149 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1150 if let Some(obj) = self.read_object(package_id) {
1157 Ok(Some(PackageObject::new(obj.clone())))
1158 } else {
1159 self.store.get_package_object(package_id).inspect(|obj| {
1160 if let Some(v) = obj {
1162 if !self
1163 .runtime_packages_loaded_from_db
1164 .read()
1165 .contains_key(package_id)
1166 {
1167 self.runtime_packages_loaded_from_db
1170 .write()
1171 .insert(*package_id, v.clone());
1172 }
1173 }
1174 })
1175 }
1176 }
1177}
1178
1179impl ParentSync for TemporaryStore<'_> {
1180 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1181 unreachable!("Never called in newer protocol versions")
1182 }
1183}