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 }
126 }
127
128 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
132 let mut to_be_updated = vec![];
133 for id in self.mutable_input_refs.keys() {
134 if !self.execution_results.modified_objects.contains(id) {
135 to_be_updated.push(self.input_objects[id].clone());
139 }
140 }
141 for object in to_be_updated {
142 self.mutate_input_object(object.clone());
144 }
145 }
146
147 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
148 let results = &self.execution_results;
149 let all_ids = results
150 .created_object_ids
151 .iter()
152 .chain(&results.deleted_object_ids)
153 .chain(&results.modified_objects)
154 .chain(results.written_objects.keys())
155 .collect::<BTreeSet<_>>();
156 all_ids
157 .into_iter()
158 .map(|id| {
159 (
160 *id,
161 EffectsObjectChange::new(
162 self.get_object_modified_at(id)
163 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
164 results.written_objects.get(id),
165 results.created_object_ids.contains(id),
166 results.deleted_object_ids.contains(id),
167 ),
168 )
169 })
170 .collect()
171 }
172
173 pub fn into_effects(
174 mut self,
175 shared_object_refs: Vec<SharedInput>,
176 transaction_digest: &TransactionDigest,
177 mut transaction_dependencies: BTreeSet<TransactionDigest>,
178 gas_cost_summary: GasCostSummary,
179 status: ExecutionStatus,
180 gas_charger: &mut GasCharger,
181 epoch: EpochId,
182 ) -> (InnerTemporaryStore, TransactionEffects) {
183 self.update_object_version_and_prev_tx();
184
185 for (id, expected_version, expected_digest) in &self.receiving_objects {
188 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
192 let loaded_via_receive = obj_meta.version == *expected_version
196 && obj_meta.digest == *expected_digest
197 && obj_meta.owner.is_address_owned();
198 if loaded_via_receive {
199 transaction_dependencies.insert(obj_meta.previous_transaction);
200 }
201 }
202 }
203
204 if self.protocol_config.enable_effects_v2() {
205 self.into_effects_v2(
206 shared_object_refs,
207 transaction_digest,
208 transaction_dependencies,
209 gas_cost_summary,
210 status,
211 gas_charger,
212 epoch,
213 )
214 } else {
215 let shared_object_refs = shared_object_refs
216 .into_iter()
217 .map(|shared_input| match shared_input {
218 SharedInput::Existing(oref) => oref,
219 SharedInput::ConsensusStreamEnded(_) => {
220 unreachable!("Shared object deletion not supported in effects v1")
221 }
222 SharedInput::Cancelled(_) => {
223 unreachable!("Per object congestion control not supported in effects v1.")
224 }
225 })
226 .collect();
227 self.into_effects_v1(
228 shared_object_refs,
229 transaction_digest,
230 transaction_dependencies,
231 gas_cost_summary,
232 status,
233 gas_charger,
234 epoch,
235 )
236 }
237 }
238
239 fn into_effects_v1(
240 self,
241 shared_object_refs: Vec<ObjectRef>,
242 transaction_digest: &TransactionDigest,
243 transaction_dependencies: BTreeSet<TransactionDigest>,
244 gas_cost_summary: GasCostSummary,
245 status: ExecutionStatus,
246 gas_charger: &mut GasCharger,
247 epoch: EpochId,
248 ) -> (InnerTemporaryStore, TransactionEffects) {
249 let updated_gas_object_info = if let Some(coin_id) = gas_charger.gas_coin() {
250 let object = &self.execution_results.written_objects[&coin_id];
251 (object.compute_object_reference(), object.owner.clone())
252 } else {
253 (
254 (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN),
255 Owner::AddressOwner(SuiAddress::default()),
256 )
257 };
258 let lampot_version = self.lamport_timestamp;
259
260 let mut created = vec![];
261 let mut mutated = vec![];
262 let mut unwrapped = vec![];
263 let mut deleted = vec![];
264 let mut unwrapped_then_deleted = vec![];
265 let mut wrapped = vec![];
266 let mut modified_at_versions = vec![];
269 let mut deleted_at_versions = vec![];
270 self.execution_results
271 .written_objects
272 .iter()
273 .for_each(|(id, object)| {
274 let object_ref = object.compute_object_reference();
275 let owner = object.owner.clone();
276 if let Some(old_object_meta) = self.get_object_modified_at(id) {
277 modified_at_versions.push((*id, old_object_meta.version));
278 mutated.push((object_ref, owner));
279 } else if self.execution_results.created_object_ids.contains(id) {
280 created.push((object_ref, owner));
281 } else {
282 unwrapped.push((object_ref, owner));
283 }
284 });
285 self.execution_results
286 .modified_objects
287 .iter()
288 .filter(|id| !self.execution_results.written_objects.contains_key(id))
289 .for_each(|id| {
290 let old_object_meta = self.get_object_modified_at(id).unwrap();
291 deleted_at_versions.push((*id, old_object_meta.version));
292 if self.execution_results.deleted_object_ids.contains(id) {
293 deleted.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_DELETED));
294 } else {
295 wrapped.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_WRAPPED));
296 }
297 });
298 self.execution_results
299 .deleted_object_ids
300 .iter()
301 .filter(|id| !self.execution_results.modified_objects.contains(id))
302 .for_each(|id| {
303 unwrapped_then_deleted.push((
304 *id,
305 lampot_version,
306 ObjectDigest::OBJECT_DIGEST_DELETED,
307 ));
308 });
309 modified_at_versions.extend(deleted_at_versions);
310
311 let inner = self.into_inner();
312 let effects = TransactionEffects::new_from_execution_v1(
313 status,
314 epoch,
315 gas_cost_summary,
316 modified_at_versions,
317 shared_object_refs,
318 *transaction_digest,
319 created,
320 mutated,
321 unwrapped,
322 deleted,
323 unwrapped_then_deleted,
324 wrapped,
325 updated_gas_object_info,
326 if inner.events.data.is_empty() {
327 None
328 } else {
329 Some(inner.events.digest())
330 },
331 transaction_dependencies.into_iter().collect(),
332 );
333 (inner, effects)
334 }
335
336 fn into_effects_v2(
337 self,
338 shared_object_refs: Vec<SharedInput>,
339 transaction_digest: &TransactionDigest,
340 transaction_dependencies: BTreeSet<TransactionDigest>,
341 gas_cost_summary: GasCostSummary,
342 status: ExecutionStatus,
343 gas_charger: &mut GasCharger,
344 epoch: EpochId,
345 ) -> (InnerTemporaryStore, TransactionEffects) {
346 let gas_coin = gas_charger.gas_coin();
351
352 let object_changes = self.get_object_changes();
353
354 let lamport_version = self.lamport_timestamp;
355 let inner = self.into_inner();
356
357 let effects = TransactionEffects::new_from_execution_v2(
358 status,
359 epoch,
360 gas_cost_summary,
361 shared_object_refs,
363 BTreeSet::new(),
364 *transaction_digest,
365 lamport_version,
366 object_changes,
367 gas_coin,
368 if inner.events.data.is_empty() {
369 None
370 } else {
371 Some(inner.events.digest())
372 },
373 transaction_dependencies.into_iter().collect(),
374 );
375
376 (inner, effects)
377 }
378
379 #[cfg(debug_assertions)]
381 fn check_invariants(&self) {
382 debug_assert!(
384 {
385 self.execution_results
386 .written_objects
387 .keys()
388 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
389 },
390 "Object both written and deleted."
391 );
392
393 debug_assert!(
395 {
396 self.mutable_input_refs
397 .keys()
398 .all(|id| self.execution_results.modified_objects.contains(id))
399 },
400 "Mutable input not modified."
401 );
402
403 debug_assert!(
404 {
405 self.execution_results
406 .written_objects
407 .values()
408 .all(|obj| obj.previous_transaction == self.tx_digest)
409 },
410 "Object previous transaction not properly set",
411 );
412 }
413
414 pub fn mutate_input_object(&mut self, object: Object) {
416 let id = object.id();
417 self.execution_results.modified_objects.insert(id);
418 self.execution_results.written_objects.insert(id, object);
419 }
420
421 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
425 let id = new_object.id();
426 let old_ref = old_object.compute_object_reference();
427 debug_assert_eq!(old_ref.0, id);
428 self.loaded_runtime_objects.insert(
429 id,
430 DynamicallyLoadedObjectMetadata {
431 version: old_ref.1,
432 digest: old_ref.2,
433 owner: old_object.owner.clone(),
434 storage_rebate: old_object.storage_rebate,
435 previous_transaction: old_object.previous_transaction,
436 },
437 );
438 self.execution_results.modified_objects.insert(id);
439 self.execution_results
440 .written_objects
441 .insert(id, new_object);
442 }
443
444 pub fn upgrade_system_package(&mut self, package: Object) {
450 let id = package.id();
451 assert!(package.is_package() && is_system_package(id));
452 self.execution_results.modified_objects.insert(id);
453 self.execution_results.written_objects.insert(id, package);
454 }
455
456 pub fn create_object(&mut self, object: Object) {
458 debug_assert!(
463 object.is_immutable() || object.version() == SequenceNumber::MIN,
464 "Created mutable objects should not have a version set",
465 );
466 let id = object.id();
467 self.execution_results.created_object_ids.insert(id);
468 self.execution_results.written_objects.insert(id, object);
469 }
470
471 pub fn delete_input_object(&mut self, id: &ObjectID) {
473 debug_assert!(!self.execution_results.written_objects.contains_key(id));
475 self.execution_results.modified_objects.insert(*id);
476 self.execution_results.deleted_object_ids.insert(*id);
477 }
478
479 pub fn drop_writes(&mut self) {
480 self.execution_results.drop_writes();
481 }
482
483 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
484 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
486 self.execution_results
487 .written_objects
488 .get(id)
489 .or_else(|| self.input_objects.get(id))
490 }
491
492 pub fn save_loaded_runtime_objects(
493 &mut self,
494 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
495 ) {
496 #[cfg(debug_assertions)]
497 {
498 for (id, v1) in &loaded_runtime_objects {
499 if let Some(v2) = self.loaded_runtime_objects.get(id) {
500 assert_eq!(v1, v2);
501 }
502 }
503 for (id, v1) in &self.loaded_runtime_objects {
504 if let Some(v2) = loaded_runtime_objects.get(id) {
505 assert_eq!(v1, v2);
506 }
507 }
508 }
509 self.loaded_runtime_objects.extend(loaded_runtime_objects);
512 }
513
514 pub fn estimate_effects_size_upperbound(&self) -> usize {
515 if self.protocol_config.enable_effects_v2() {
516 TransactionEffects::estimate_effects_size_upperbound_v2(
517 self.execution_results.written_objects.len(),
518 self.execution_results.modified_objects.len(),
519 self.input_objects.len(),
520 )
521 } else {
522 let num_deletes = self.execution_results.deleted_object_ids.len()
523 + self
524 .execution_results
525 .modified_objects
526 .iter()
527 .filter(|id| {
528 !self.execution_results.written_objects.contains_key(id)
530 && !self.execution_results.deleted_object_ids.contains(id)
531 })
532 .count();
533 TransactionEffects::estimate_effects_size_upperbound_v1(
535 self.execution_results.written_objects.len(),
536 self.mutable_input_refs.len(),
537 num_deletes,
538 self.input_objects.len(),
539 )
540 }
541 }
542
543 pub fn written_objects_size(&self) -> usize {
544 self.execution_results
545 .written_objects
546 .values()
547 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
548 }
549
550 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
555 if unmetered_storage_rebate == 0 {
556 return;
560 }
561 tracing::debug!(
562 "Amount of unmetered storage rebate from system tx: {:?}",
563 unmetered_storage_rebate
564 );
565 let mut system_state_wrapper = self
566 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
567 .expect("0x5 object must be muated in system tx with unmetered storage rebate")
568 .clone();
569 assert_eq!(system_state_wrapper.storage_rebate, 0);
572 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
573 self.mutate_input_object(system_state_wrapper);
574 }
575
576 fn get_object_modified_at(
582 &self,
583 object_id: &ObjectID,
584 ) -> Option<DynamicallyLoadedObjectMetadata> {
585 if self.execution_results.modified_objects.contains(object_id) {
586 Some(
587 self.mutable_input_refs
588 .get(object_id)
589 .map(
590 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
591 version: *version,
592 digest: *digest,
593 owner: owner.clone(),
594 storage_rebate: self.input_objects[object_id].storage_rebate,
596 previous_transaction: self.input_objects[object_id]
597 .previous_transaction,
598 },
599 )
600 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
601 .unwrap_or_else(|| {
602 debug_assert!(is_system_package(*object_id));
603 let obj = self.store.get_object(object_id).unwrap();
604 DynamicallyLoadedObjectMetadata {
605 version: obj.version(),
606 digest: obj.digest(),
607 owner: obj.owner.clone(),
608 storage_rebate: obj.storage_rebate,
609 previous_transaction: obj.previous_transaction,
610 }
611 }),
612 )
613 } else {
614 None
615 }
616 }
617}
618
619impl TemporaryStore<'_> {
620 fn get_objects_to_authenticate(
622 &self,
623 sender: &SuiAddress,
624 gas_charger: &mut GasCharger,
625 is_epoch_change: bool,
626 ) -> SuiResult<(Vec<ObjectID>, HashSet<ObjectID>)> {
627 let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().iter().map(|g| &g.0).collect();
628 let mut objs_to_authenticate = Vec::new();
629 let mut authenticated_objs = HashSet::new();
630 for (id, obj) in &self.input_objects {
631 if gas_objs.contains(id) {
632 continue;
637 }
638 match &obj.owner {
639 Owner::AddressOwner(a) => {
640 assert!(sender == a, "Input object not owned by sender");
641 authenticated_objs.insert(*id);
642 }
643 Owner::Shared { .. } => {
644 authenticated_objs.insert(*id);
645 }
646 Owner::Immutable => {
647 }
658 Owner::ObjectOwner(_parent) => {
659 unreachable!("Input objects must be address owned, shared, or immutable")
660 }
661 Owner::ConsensusAddressOwner { .. } => {
662 unimplemented!(
663 "ConsensusAddressOwner does not exist for this execution version"
664 )
665 }
666 }
667 }
668
669 for id in &self.execution_results.modified_objects {
670 if authenticated_objs.contains(id) || gas_objs.contains(id) {
671 continue;
672 }
673 let old_obj = self.store.get_object(id).unwrap_or_else(|| {
674 panic!("Modified object must exist in the store: ID = {:?}", id)
675 });
676 match &old_obj.owner {
677 Owner::ObjectOwner(_) | Owner::AddressOwner(_) => {
680 objs_to_authenticate.push(*id);
681 }
682 Owner::Shared { .. } => {
683 unreachable!("Should already be in authenticated_objs")
684 }
685 Owner::Immutable => {
686 assert!(is_epoch_change, "Immutable objects cannot be written, except for Sui Framework/Move stdlib upgrades at epoch change boundaries");
687 assert!(
690 is_system_package(*id),
691 "Only system packages can be upgraded"
692 );
693 }
694 Owner::ConsensusAddressOwner { .. } => {
695 unimplemented!(
696 "ConsensusAddressOwner does not exist for this execution version"
697 )
698 }
699 }
700 }
701 Ok((objs_to_authenticate, authenticated_objs))
702 }
703
704 pub fn check_ownership_invariants(
706 &self,
707 sender: &SuiAddress,
708 gas_charger: &mut GasCharger,
709 is_epoch_change: bool,
710 ) -> SuiResult<()> {
711 let (mut objects_to_authenticate, mut authenticated_objects) =
712 self.get_objects_to_authenticate(sender, gas_charger, is_epoch_change)?;
713
714 let mut covered = BTreeMap::new();
716 while let Some(to_authenticate) = objects_to_authenticate.pop() {
717 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
718 continue;
721 };
722 let parent = match &old_obj.owner {
723 Owner::ObjectOwner(parent) | Owner::AddressOwner(parent) => ObjectID::from(*parent),
724 owner => panic!(
725 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
726 Potentially covering objects in: {covered:#?}",
727 ),
728 };
729
730 if authenticated_objects.contains(&parent) {
731 authenticated_objects.insert(to_authenticate);
732 } else if !covered.contains_key(&parent) {
733 objects_to_authenticate.push(parent);
734 }
735
736 covered.insert(to_authenticate, parent);
737 }
738 Ok(())
739 }
740}
741
742impl TemporaryStore<'_> {
743 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
750 let old_storage_rebates: Vec<_> = self
752 .execution_results
753 .written_objects
754 .keys()
755 .map(|object_id| {
756 self.get_object_modified_at(object_id)
757 .map(|metadata| metadata.storage_rebate)
758 .unwrap_or_default()
759 })
760 .collect();
761 for (object, old_storage_rebate) in self
762 .execution_results
763 .written_objects
764 .values_mut()
765 .zip(old_storage_rebates)
766 {
767 let new_object_size = object.object_size_for_gas_metering();
769 let new_storage_rebate = gas_charger.track_storage_mutation(
771 object.id(),
772 new_object_size,
773 old_storage_rebate,
774 );
775 object.storage_rebate = new_storage_rebate;
776 }
777
778 self.collect_rebate(gas_charger);
779 }
780
781 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
782 for object_id in &self.execution_results.modified_objects {
783 if self
784 .execution_results
785 .written_objects
786 .contains_key(object_id)
787 {
788 continue;
789 }
790 let storage_rebate = self
792 .get_object_modified_at(object_id)
793 .unwrap()
795 .storage_rebate;
796 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
797 }
798 }
799
800 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
801 assert_invariant!(
802 self.execution_results
803 .created_object_ids
804 .iter()
805 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
806 && !self.execution_results.modified_objects.contains(id)),
807 "Created object IDs cannot also be deleted or modified"
808 );
809 assert_invariant!(
810 self.execution_results.modified_objects.iter().all(|id| {
811 self.mutable_input_refs.contains_key(id)
812 || self.loaded_runtime_objects.contains_key(id)
813 || is_system_package(*id)
814 }),
815 "A modified object must be either a mutable input, a loaded child object, or a system package"
816 );
817 Ok(())
818 }
819}
820impl TemporaryStore<'_> {
825 pub fn advance_epoch_safe_mode(
826 &mut self,
827 params: &AdvanceEpochParams,
828 protocol_config: &ProtocolConfig,
829 ) {
830 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
831 .expect("System state wrapper object must exist");
832 let (old_object, new_object) =
833 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
834 self.mutate_child_object(old_object, new_object);
835 }
836}
837
838type ModifiedObjectInfo<'a> = (
839 ObjectID,
840 Option<DynamicallyLoadedObjectMetadata>,
842 Option<&'a Object>,
843);
844
845impl TemporaryStore<'_> {
846 fn get_input_sui(
847 &self,
848 id: &ObjectID,
849 expected_version: SequenceNumber,
850 layout_resolver: &mut impl LayoutResolver,
851 ) -> Result<u64, ExecutionError> {
852 if let Some(obj) = self.input_objects.get(id) {
853 if obj.version() != expected_version {
855 invariant_violation!(
856 "Version mismatching when resolving input object to check conservation--\
857 expected {}, got {}",
858 expected_version,
859 obj.version(),
860 );
861 }
862 obj.get_total_sui(layout_resolver).map_err(|e| {
863 make_invariant_violation!(
864 "Failed looking up input SUI in SUI conservation checking for input with \
865 type {:?}: {e:#?}",
866 obj.struct_tag(),
867 )
868 })
869 } else {
870 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
872 invariant_violation!(
873 "Failed looking up dynamic field {id} in SUI conservation checking"
874 );
875 };
876 obj.get_total_sui(layout_resolver).map_err(|e| {
877 make_invariant_violation!(
878 "Failed looking up input SUI in SUI conservation checking for type \
879 {:?}: {e:#?}",
880 obj.struct_tag(),
881 )
882 })
883 }
884 }
885
886 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
891 self.execution_results
892 .modified_objects
893 .iter()
894 .map(|id| {
895 let metadata = self.get_object_modified_at(id);
896 let output = self.execution_results.written_objects.get(id);
897 (*id, metadata, output)
898 })
899 .chain(
900 self.execution_results
901 .written_objects
902 .iter()
903 .filter_map(|(id, object)| {
904 if self.execution_results.modified_objects.contains(id) {
905 None
906 } else {
907 Some((*id, None, Some(object)))
908 }
909 }),
910 )
911 .collect()
912 }
913
914 pub fn check_sui_conserved(
928 &self,
929 simple_conservation_checks: bool,
930 gas_summary: &GasCostSummary,
931 ) -> Result<(), ExecutionError> {
932 if !simple_conservation_checks {
933 return Ok(());
934 }
935 let mut total_input_rebate = 0;
937 let mut total_output_rebate = 0;
939 for (_, input, output) in self.get_modified_objects() {
940 if let Some(input) = input {
941 total_input_rebate += input.storage_rebate;
942 }
943 if let Some(object) = output {
944 total_output_rebate += object.storage_rebate;
945 }
946 }
947
948 if gas_summary.storage_cost == 0 {
949 if total_input_rebate
961 != total_output_rebate
962 + gas_summary.storage_rebate
963 + gas_summary.non_refundable_storage_fee
964 {
965 return Err(ExecutionError::invariant_violation(format!(
966 "SUI conservation failed -- no storage charges in gas summary \
967 and total storage input rebate {} not equal \
968 to total storage output rebate {}",
969 total_input_rebate, total_output_rebate,
970 )));
971 }
972 } else {
973 if total_input_rebate
976 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
977 {
978 return Err(ExecutionError::invariant_violation(format!(
979 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
980 {} SUI in tx storage rebate or tx non-refundable storage rebate",
981 total_input_rebate, gas_summary.non_refundable_storage_fee,
982 )));
983 }
984
985 if gas_summary.storage_cost != total_output_rebate {
988 return Err(ExecutionError::invariant_violation(format!(
989 "SUI conservation failed -- {} SUI charged for storage, \
990 {} SUI in storage rebate field of output objects",
991 gas_summary.storage_cost, total_output_rebate
992 )));
993 }
994 }
995 Ok(())
996 }
997
998 pub fn check_sui_conserved_expensive(
1011 &self,
1012 gas_summary: &GasCostSummary,
1013 advance_epoch_gas_summary: Option<(u64, u64)>,
1014 layout_resolver: &mut impl LayoutResolver,
1015 ) -> Result<(), ExecutionError> {
1016 let mut total_input_sui = 0;
1018 let mut total_output_sui = 0;
1020 for (id, input, output) in self.get_modified_objects() {
1021 if let Some(input) = input {
1022 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1023 }
1024 if let Some(object) = output {
1025 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1026 make_invariant_violation!(
1027 "Failed looking up output SUI in SUI conservation checking for \
1028 mutated type {:?}: {e:#?}",
1029 object.struct_tag(),
1030 )
1031 })?;
1032 }
1033 }
1034 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1039 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1040 total_input_sui += epoch_fees;
1041 total_output_sui += epoch_rebates;
1042 }
1043 if total_input_sui != total_output_sui {
1044 return Err(ExecutionError::invariant_violation(format!(
1045 "SUI conservation failed: input={}, output={}, \
1046 this transaction either mints or burns SUI",
1047 total_input_sui, total_output_sui,
1048 )));
1049 }
1050 Ok(())
1051 }
1052}
1053
1054impl ChildObjectResolver for TemporaryStore<'_> {
1055 fn read_child_object(
1056 &self,
1057 parent: &ObjectID,
1058 child: &ObjectID,
1059 child_version_upper_bound: SequenceNumber,
1060 ) -> SuiResult<Option<Object>> {
1061 let obj_opt = self.execution_results.written_objects.get(child);
1062 if obj_opt.is_some() {
1063 Ok(obj_opt.cloned())
1064 } else {
1065 self.store
1066 .read_child_object(parent, child, child_version_upper_bound)
1067 }
1068 }
1069
1070 fn get_object_received_at_version(
1071 &self,
1072 owner: &ObjectID,
1073 receiving_object_id: &ObjectID,
1074 receive_object_at_version: SequenceNumber,
1075 epoch_id: EpochId,
1076 ) -> SuiResult<Option<Object>> {
1077 debug_assert!(!self
1080 .execution_results
1081 .written_objects
1082 .contains_key(receiving_object_id));
1083 debug_assert!(!self
1084 .execution_results
1085 .deleted_object_ids
1086 .contains(receiving_object_id));
1087 self.store.get_object_received_at_version(
1088 owner,
1089 receiving_object_id,
1090 receive_object_at_version,
1091 epoch_id,
1092 )
1093 }
1094}
1095
1096impl Storage for TemporaryStore<'_> {
1097 fn reset(&mut self) {
1098 self.drop_writes();
1099 }
1100
1101 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1102 TemporaryStore::read_object(self, id)
1103 }
1104
1105 fn record_execution_results(
1107 &mut self,
1108 results: ExecutionResults,
1109 ) -> Result<(), ExecutionError> {
1110 let ExecutionResults::V2(results) = results else {
1111 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1112 };
1113 self.execution_results.merge_results(results);
1116 Ok(())
1117 }
1118
1119 fn save_loaded_runtime_objects(
1120 &mut self,
1121 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1122 ) {
1123 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1124 }
1125
1126 fn save_wrapped_object_containers(
1127 &mut self,
1128 _wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1129 ) {
1130 unreachable!("Unused in v1")
1131 }
1132
1133 fn check_coin_deny_list(
1134 &self,
1135 _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1136 ) -> DenyListResult {
1137 unreachable!("Coin denylist v2 is not supported in sui-execution v1");
1138 }
1139
1140 fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1141 unreachable!(
1142 "Generated object IDs are not recorded in ExecutionResults in sui-execution v1"
1143 );
1144 }
1145}
1146
1147impl BackingPackageStore for TemporaryStore<'_> {
1148 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1149 if let Some(obj) = self.read_object(package_id) {
1156 Ok(Some(PackageObject::new(obj.clone())))
1157 } else {
1158 self.store.get_package_object(package_id).inspect(|obj| {
1159 if let Some(v) = obj {
1161 if !self
1162 .runtime_packages_loaded_from_db
1163 .read()
1164 .contains_key(package_id)
1165 {
1166 self.runtime_packages_loaded_from_db
1169 .write()
1170 .insert(*package_id, v.clone());
1171 }
1172 }
1173 })
1174 }
1175 }
1176}
1177
1178impl ParentSync for TemporaryStore<'_> {
1179 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1180 unreachable!("Never called in newer protocol versions")
1181 }
1182}