1use crate::gas_charger::{GasCharger, PaymentLocation};
5use mysten_common::ZipDebugEqIteratorExt;
6use mysten_metrics::monitored_scope;
7use parking_lot::RwLock;
8use std::collections::{BTreeMap, BTreeSet, HashSet};
9use sui_protocol_config::ProtocolConfig;
10use sui_types::accumulator_event::AccumulatorEvent;
11use sui_types::accumulator_root::AccumulatorObjId;
12use sui_types::base_types::VersionDigest;
13use sui_types::committee::EpochId;
14use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
15use sui_types::effects::{
16 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
17 TransactionEvents,
18};
19use sui_types::execution::{
20 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
21};
22use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
23use sui_types::inner_temporary_store::InnerTemporaryStore;
24use sui_types::layout_resolver::LayoutResolver;
25use sui_types::object::Data;
26use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
27use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
28use sui_types::{
29 SUI_DENY_LIST_OBJECT_ID,
30 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
31 effects::EffectsObjectChange,
32 error::{ExecutionError, SuiResult},
33 gas::GasCostSummary,
34 object::Object,
35 object::Owner,
36 storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
37 transaction::InputObjects,
38};
39use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
40
41pub struct TemporaryStore<'backing> {
42 store: &'backing dyn BackingStore,
48 tx_digest: TransactionDigest,
49 input_objects: BTreeMap<ObjectID, Object>,
50
51 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
54
55 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
56 lamport_timestamp: SequenceNumber,
58 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
61 execution_results: ExecutionResultsV2,
62 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
64 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
66 protocol_config: &'backing ProtocolConfig,
67
68 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
71
72 receiving_objects: Vec<ObjectRef>,
75
76 generated_runtime_ids: BTreeSet<ObjectID>,
80
81 cur_epoch: EpochId,
84
85 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
88}
89
90impl<'backing> TemporaryStore<'backing> {
91 pub fn new(
94 store: &'backing dyn BackingStore,
95 input_objects: InputObjects,
96 receiving_objects: Vec<ObjectRef>,
97 tx_digest: TransactionDigest,
98 protocol_config: &'backing ProtocolConfig,
99 cur_epoch: EpochId,
100 ) -> Self {
101 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
102 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
103
104 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
105 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
106 let objects = input_objects.into_object_map();
107 #[cfg(debug_assertions)]
108 {
109 assert!(
111 objects
112 .keys()
113 .collect::<HashSet<_>>()
114 .intersection(
115 &receiving_objects
116 .iter()
117 .map(|oref| &oref.0)
118 .collect::<HashSet<_>>()
119 )
120 .next()
121 .is_none()
122 );
123 }
124 Self {
125 store,
126 tx_digest,
127 input_objects: objects,
128 non_exclusive_input_original_versions,
129 stream_ended_consensus_objects,
130 lamport_timestamp,
131 mutable_input_refs,
132 execution_results: ExecutionResultsV2::default(),
133 protocol_config,
134 loaded_runtime_objects: BTreeMap::new(),
135 wrapped_object_containers: BTreeMap::new(),
136 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
137 receiving_objects,
138 generated_runtime_ids: BTreeSet::new(),
139 cur_epoch,
140 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
141 }
142 }
143
144 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
146 &self.input_objects
147 }
148
149 pub fn update_object_version_and_prev_tx(&mut self) {
150 self.execution_results.update_version_and_previous_tx(
151 self.lamport_timestamp,
152 self.tx_digest,
153 &self.input_objects,
154 self.protocol_config.reshare_at_same_initial_version(),
155 );
156
157 #[cfg(debug_assertions)]
158 {
159 self.check_invariants();
160 }
161 }
162
163 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
164 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
165 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
166 for event in &self.execution_results.accumulator_events {
167 match &event.write.value {
168 AccumulatorValue::Integer(amount) => match event.write.operation {
169 AccumulatorOperation::Split => {
170 let entry = running_net_withdraws
171 .entry(event.accumulator_obj)
172 .or_default();
173 *entry += *amount as i128;
174 if *entry > 0 {
175 let max_entry = running_max_withdraws
176 .entry(event.accumulator_obj)
177 .or_default();
178 *max_entry = (*max_entry).max(*entry as u128);
179 }
180 }
181 AccumulatorOperation::Merge => {
182 let entry = running_net_withdraws
183 .entry(event.accumulator_obj)
184 .or_default();
185 *entry -= *amount as i128;
186 }
187 },
188 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
189 }
190 }
191 running_max_withdraws
192 }
193
194 fn merge_accumulator_events(&mut self) {
196 self.execution_results.accumulator_events = self
197 .execution_results
198 .accumulator_events
199 .iter()
200 .fold(
201 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
202 |mut map, event| {
203 map.entry(event.accumulator_obj)
204 .or_default()
205 .push(event.write.clone());
206 map
207 },
208 )
209 .into_iter()
210 .map(|(obj_id, writes)| {
211 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
212 })
213 .collect();
214 }
215
216 pub fn into_inner(
218 self,
219 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
220 ) -> InnerTemporaryStore {
221 let results = self.execution_results;
222 InnerTemporaryStore {
223 input_objects: self.input_objects,
224 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
225 mutable_inputs: self.mutable_input_refs,
226 written: results.written_objects,
227 events: TransactionEvents {
228 data: results.user_events,
229 },
230 accumulator_events: results.accumulator_events,
231 loaded_runtime_objects: self.loaded_runtime_objects,
232 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
233 lamport_version: self.lamport_timestamp,
234 binary_config: self.protocol_config.binary_config(None),
235 accumulator_running_max_withdraws,
236 }
237 }
238
239 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
243 let mut to_be_updated = vec![];
244 for id in self.mutable_input_refs.keys() {
246 if !self.execution_results.modified_objects.contains(id) {
247 to_be_updated.push(self.input_objects[id].clone());
251 }
252 }
253 for object in to_be_updated {
254 self.mutate_input_object(object.clone());
256 }
257 }
258
259 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
260 let results = &self.execution_results;
261 let all_ids = results
262 .created_object_ids
263 .iter()
264 .chain(&results.deleted_object_ids)
265 .chain(&results.modified_objects)
266 .chain(results.written_objects.keys())
267 .collect::<BTreeSet<_>>();
268 all_ids
269 .into_iter()
270 .map(|id| {
271 (
272 *id,
273 EffectsObjectChange::new(
274 self.get_object_modified_at(id)
275 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
276 results.written_objects.get(id),
277 results.created_object_ids.contains(id),
278 results.deleted_object_ids.contains(id),
279 ),
280 )
281 })
282 .chain(results.accumulator_events.iter().cloned().map(
283 |AccumulatorEvent {
284 accumulator_obj,
285 write,
286 }| {
287 (
288 *accumulator_obj.inner(),
289 EffectsObjectChange::new_from_accumulator_write(write),
290 )
291 },
292 ))
293 .collect()
294 }
295
296 pub fn into_effects(
297 mut self,
298 shared_object_refs: Vec<SharedInput>,
299 transaction_digest: &TransactionDigest,
300 mut transaction_dependencies: BTreeSet<TransactionDigest>,
301 gas_cost_summary: GasCostSummary,
302 status: ExecutionStatus,
303 gas_charger: &mut GasCharger,
304 epoch: EpochId,
305 ) -> (InnerTemporaryStore, TransactionEffects) {
306 self.update_object_version_and_prev_tx();
307 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
309 self.merge_accumulator_events();
310
311 for (id, expected_version, expected_digest) in &self.receiving_objects {
314 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
318 let loaded_via_receive = obj_meta.version == *expected_version
322 && obj_meta.digest == *expected_digest
323 && obj_meta.owner.is_address_owned();
324 if loaded_via_receive {
325 transaction_dependencies.insert(obj_meta.previous_transaction);
326 }
327 }
328 }
329
330 assert!(self.protocol_config.enable_effects_v2());
331
332 let gas_coin = gas_charger
337 .gas_payment_amount()
338 .and_then(|gp| match gp.location {
339 PaymentLocation::Coin(coin_id) => Some(coin_id),
340 PaymentLocation::AddressBalance(_) => None,
341 });
342
343 let object_changes = self.get_object_changes();
344
345 let lamport_version = self.lamport_timestamp;
346 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
348 let inner = self.into_inner(accumulator_running_max_withdraws);
349
350 let effects = TransactionEffects::new_from_execution_v2(
351 status,
352 epoch,
353 gas_cost_summary,
354 shared_object_refs,
356 loaded_per_epoch_config_objects,
357 *transaction_digest,
358 lamport_version,
359 object_changes,
360 gas_coin,
361 if inner.events.data.is_empty() {
362 None
363 } else {
364 Some(inner.events.digest())
365 },
366 transaction_dependencies.into_iter().collect(),
367 );
368
369 (inner, effects)
370 }
371
372 #[cfg(debug_assertions)]
374 fn check_invariants(&self) {
375 debug_assert!(
377 {
378 self.execution_results
379 .written_objects
380 .keys()
381 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
382 },
383 "Object both written and deleted."
384 );
385
386 debug_assert!(
388 {
389 self.mutable_input_refs
390 .keys()
391 .all(|id| self.execution_results.modified_objects.contains(id))
392 },
393 "Mutable input not modified."
394 );
395
396 debug_assert!(
397 {
398 self.execution_results
399 .written_objects
400 .values()
401 .all(|obj| obj.previous_transaction == self.tx_digest)
402 },
403 "Object previous transaction not properly set",
404 );
405 }
406
407 pub fn mutate_input_object(&mut self, object: Object) {
409 let id = object.id();
410 debug_assert!(self.input_objects.contains_key(&id));
411 debug_assert!(!object.is_immutable());
412 self.execution_results.modified_objects.insert(id);
413 self.execution_results.written_objects.insert(id, object);
414 }
415
416 pub fn mutate_new_or_input_object(&mut self, object: Object) {
417 let id = object.id();
418 debug_assert!(!object.is_immutable());
419 if self.input_objects.contains_key(&id) {
420 self.execution_results.modified_objects.insert(id);
421 }
422 self.execution_results.written_objects.insert(id, object);
423 }
424
425 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
429 let id = new_object.id();
430 let old_ref = old_object.compute_object_reference();
431 debug_assert_eq!(old_ref.0, id);
432 self.loaded_runtime_objects.insert(
433 id,
434 DynamicallyLoadedObjectMetadata {
435 version: old_ref.1,
436 digest: old_ref.2,
437 owner: old_object.owner.clone(),
438 storage_rebate: old_object.storage_rebate,
439 previous_transaction: old_object.previous_transaction,
440 },
441 );
442 self.execution_results.modified_objects.insert(id);
443 self.execution_results
444 .written_objects
445 .insert(id, new_object);
446 }
447
448 pub fn upgrade_system_package(&mut self, package: Object) {
452 let id = package.id();
453 assert!(package.is_package() && is_system_package(id));
454 self.execution_results.modified_objects.insert(id);
455 self.execution_results.written_objects.insert(id, package);
456 }
457
458 pub fn create_object(&mut self, object: Object) {
460 debug_assert!(
465 object.is_immutable() || object.version() == SequenceNumber::MIN,
466 "Created mutable objects should not have a version set",
467 );
468 let id = object.id();
469 self.execution_results.created_object_ids.insert(id);
470 self.execution_results.written_objects.insert(id, object);
471 }
472
473 pub fn delete_input_object(&mut self, id: &ObjectID) {
475 debug_assert!(!self.execution_results.written_objects.contains_key(id));
477 debug_assert!(self.input_objects.contains_key(id));
478 self.execution_results.modified_objects.insert(*id);
479 self.execution_results.deleted_object_ids.insert(*id);
480 }
481
482 pub fn drop_writes(&mut self) {
483 self.execution_results.drop_writes();
484 }
485
486 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
487 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
489 self.execution_results
490 .written_objects
491 .get(id)
492 .or_else(|| self.input_objects.get(id))
493 }
494
495 pub fn save_loaded_runtime_objects(
496 &mut self,
497 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
498 ) {
499 #[cfg(debug_assertions)]
500 {
501 for (id, v1) in &loaded_runtime_objects {
502 if let Some(v2) = self.loaded_runtime_objects.get(id) {
503 assert_eq!(v1, v2);
504 }
505 }
506 for (id, v1) in &self.loaded_runtime_objects {
507 if let Some(v2) = loaded_runtime_objects.get(id) {
508 assert_eq!(v1, v2);
509 }
510 }
511 }
512 self.loaded_runtime_objects.extend(loaded_runtime_objects);
515 }
516
517 pub fn save_wrapped_object_containers(
518 &mut self,
519 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
520 ) {
521 #[cfg(debug_assertions)]
522 {
523 for (id, container1) in &wrapped_object_containers {
524 if let Some(container2) = self.wrapped_object_containers.get(id) {
525 assert_eq!(container1, container2);
526 }
527 }
528 for (id, container1) in &self.wrapped_object_containers {
529 if let Some(container2) = wrapped_object_containers.get(id) {
530 assert_eq!(container1, container2);
531 }
532 }
533 }
534 self.wrapped_object_containers
537 .extend(wrapped_object_containers);
538 }
539
540 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
541 #[cfg(debug_assertions)]
542 {
543 for id in &self.generated_runtime_ids {
544 assert!(!generated_ids.contains(id))
545 }
546 for id in &generated_ids {
547 assert!(!self.generated_runtime_ids.contains(id));
548 }
549 }
550 self.generated_runtime_ids.extend(generated_ids);
551 }
552
553 pub fn estimate_effects_size_upperbound(&self) -> usize {
554 TransactionEffects::estimate_effects_size_upperbound_v2(
555 self.execution_results.written_objects.len(),
556 self.execution_results.modified_objects.len(),
557 self.input_objects.len(),
558 )
559 }
560
561 pub fn written_objects_size(&self) -> usize {
562 self.execution_results
563 .written_objects
564 .values()
565 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
566 }
567
568 pub fn check_gasless_execution_requirements(
574 &self,
575 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
576 ) -> Result<(), String> {
577 if !self.execution_results.written_objects.is_empty() {
578 return Err("Gasless transactions cannot create or mutate objects".to_string());
579 }
580
581 let input_coin_ids: BTreeSet<ObjectID> = self
582 .input_objects
583 .iter()
584 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
585 .map(|(id, _)| *id)
586 .collect();
587 if self.execution_results.deleted_object_ids != input_coin_ids {
588 return Err(format!(
589 "Gasless transaction must destroy exactly its input Coins. \
590 Expected: {input_coin_ids:?}, deleted: {:?}",
591 self.execution_results.deleted_object_ids
592 ));
593 }
594
595 let allowed_types =
596 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
597
598 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
601 &self.execution_results.accumulator_events,
602 )
603 .fold(
604 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
605 |mut totals, (address, token_type, signed_amount)| {
606 *totals.entry((address, token_type)).or_default() += signed_amount;
607 totals
608 },
609 );
610
611 for ((recipient, token_type), net_amount) in &net_totals {
612 if *net_amount <= 0 {
613 continue;
614 }
615 if let Some(&min_amount) = allowed_types.get(token_type)
616 && *net_amount < i128::from(min_amount)
617 {
618 return Err(format!(
619 "Gasless transfer of {net_amount} to {recipient} is below \
620 minimum {min_amount} for token type {token_type}"
621 ));
622 }
623 }
624
625 if let Some(reservations) = withdrawal_reservations {
626 for ((owner, token_type), &reserved) in reservations {
627 let net = net_totals
628 .get(&(*owner, token_type.clone()))
629 .copied()
630 .unwrap_or(0);
631 let remaining = (reserved as i128).saturating_add(net);
632 if remaining > 0
633 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
634 && min_balance_remaining > 0
635 && remaining < min_balance_remaining as i128
636 {
637 return Err(format!(
638 "Gasless withdrawal leaves {remaining} unused for {owner}, \
639 below minimum {min_balance_remaining} for token type {token_type}"
640 ));
641 }
642 }
643 }
644
645 Ok(())
646 }
647
648 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
653 if unmetered_storage_rebate == 0 {
654 return;
658 }
659 tracing::debug!(
660 "Amount of unmetered storage rebate from system tx: {:?}",
661 unmetered_storage_rebate
662 );
663 let mut system_state_wrapper = self
664 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
665 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
666 .clone();
667 assert_eq!(system_state_wrapper.storage_rebate, 0);
670 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
671 self.mutate_input_object(system_state_wrapper);
672 }
673
674 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
676 self.execution_results.accumulator_events.push(event);
677 }
678
679 fn get_object_modified_at(
685 &self,
686 object_id: &ObjectID,
687 ) -> Option<DynamicallyLoadedObjectMetadata> {
688 if self.execution_results.modified_objects.contains(object_id) {
689 Some(
690 self.mutable_input_refs
691 .get(object_id)
692 .map(
693 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
694 version: *version,
695 digest: *digest,
696 owner: owner.clone(),
697 storage_rebate: self.input_objects[object_id].storage_rebate,
699 previous_transaction: self.input_objects[object_id]
700 .previous_transaction,
701 },
702 )
703 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
704 .unwrap_or_else(|| {
705 debug_assert!(is_system_package(*object_id));
706 let package_obj =
707 self.store.get_package_object(object_id).unwrap().unwrap();
708 let obj = package_obj.object();
709 DynamicallyLoadedObjectMetadata {
710 version: obj.version(),
711 digest: obj.digest(),
712 owner: obj.owner.clone(),
713 storage_rebate: obj.storage_rebate,
714 previous_transaction: obj.previous_transaction,
715 }
716 }),
717 )
718 } else {
719 None
720 }
721 }
722
723 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
724 self.protocol_config
725 }
726}
727
728impl TemporaryStore<'_> {
729 pub fn check_ownership_invariants(
732 &self,
733 sender: &SuiAddress,
734 sponsor: &Option<SuiAddress>,
735 gas_charger: &mut GasCharger,
736 mutable_inputs: &HashSet<ObjectID>,
737 is_epoch_change: bool,
738 ) -> SuiResult<()> {
739 let gas_objs: HashSet<&ObjectID> = gas_charger.used_coins().map(|g| &g.0).collect();
740 let gas_owner = sponsor.as_ref().unwrap_or(sender);
741
742 let mut authenticated_for_mutation: HashSet<_> = self
744 .input_objects
745 .iter()
746 .filter_map(|(id, obj)| {
747 match &obj.owner {
748 Owner::AddressOwner(a) => {
749 if gas_objs.contains(id) {
750 assert!(
752 a == gas_owner,
753 "Gas object must be owned by sender or sponsor"
754 );
755 } else {
756 assert!(sender == a, "Input object must be owned by sender");
757 }
758 Some(id)
759 }
760 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => Some(id),
761 Owner::Immutable => {
762 None
773 }
774 Owner::ObjectOwner(_parent) => {
775 unreachable!(
776 "Input objects must be address owned, shared, consensus, or immutable"
777 )
778 }
779 }
780 })
781 .filter(|id| {
782 mutable_inputs.contains(id)
785 })
786 .copied()
787 .chain(self.generated_runtime_ids.iter().copied())
790 .collect();
791
792 authenticated_for_mutation.insert((*sender).into());
794 if let Some(sponsor) = sponsor {
795 authenticated_for_mutation.insert((*sponsor).into());
796 }
797
798 let mut objects_to_authenticate = self
800 .execution_results
801 .modified_objects
802 .iter()
803 .copied()
804 .collect::<Vec<_>>();
805
806 while let Some(to_authenticate) = objects_to_authenticate.pop() {
807 if authenticated_for_mutation.contains(&to_authenticate) {
808 continue;
810 }
811
812 let parent = if let Some(container_id) =
813 self.wrapped_object_containers.get(&to_authenticate)
814 {
815 *container_id
817 } else {
818 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
821 panic!(
822 "Failed to load object {to_authenticate:?}.\n \
823 If it cannot be loaded, we would expect it to be in the wrapped object map: {:#?}",
824 &self.wrapped_object_containers
825 )
826 };
827
828 match &old_obj.owner {
829 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
832 Owner::AddressOwner(parent)
837 | Owner::ConsensusAddressOwner { owner: parent, .. } => {
838 ObjectID::from(*parent)
842 }
843 owner @ Owner::Shared { .. } => {
846 panic!(
847 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
848 Potentially covering objects in: {authenticated_for_mutation:#?}"
849 );
850 }
851 Owner::Immutable => {
852 assert!(
853 is_epoch_change,
854 "Immutable objects cannot be written, except for \
855 Sui Framework/Move stdlib upgrades at epoch change boundaries"
856 );
857 assert!(
861 is_system_package(to_authenticate),
862 "Only system packages can be upgraded"
863 );
864 continue;
865 }
866 }
867 };
868
869 authenticated_for_mutation.insert(to_authenticate);
871 objects_to_authenticate.push(parent);
872 }
873 Ok(())
874 }
875}
876
877impl TemporaryStore<'_> {
878 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
885 let old_storage_rebates: Vec<_> = self
887 .execution_results
888 .written_objects
889 .keys()
890 .map(|object_id| {
891 self.get_object_modified_at(object_id)
892 .map(|metadata| metadata.storage_rebate)
893 .unwrap_or_default()
894 })
895 .collect();
896 for (object, old_storage_rebate) in self
897 .execution_results
898 .written_objects
899 .values_mut()
900 .zip_debug_eq(old_storage_rebates)
901 {
902 let new_object_size = object.object_size_for_gas_metering();
904 let new_storage_rebate = gas_charger.track_storage_mutation(
906 object.id(),
907 new_object_size,
908 old_storage_rebate,
909 );
910 object.storage_rebate = new_storage_rebate;
911 }
912
913 self.collect_rebate(gas_charger);
914 }
915
916 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
917 for object_id in &self.execution_results.modified_objects {
918 if self
919 .execution_results
920 .written_objects
921 .contains_key(object_id)
922 {
923 continue;
924 }
925 let storage_rebate = self
927 .get_object_modified_at(object_id)
928 .unwrap()
930 .storage_rebate;
931 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
932 }
933 }
934
935 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
936 assert_invariant!(
937 self.execution_results
938 .created_object_ids
939 .iter()
940 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
941 && !self.execution_results.modified_objects.contains(id)),
942 "Created object IDs cannot also be deleted or modified"
943 );
944 assert_invariant!(
945 self.execution_results.modified_objects.iter().all(|id| {
946 self.mutable_input_refs.contains_key(id)
947 || self.loaded_runtime_objects.contains_key(id)
948 || is_system_package(*id)
949 }),
950 "A modified object must be either a mutable input, a loaded child object, or a system package"
951 );
952 Ok(())
953 }
954}
955impl TemporaryStore<'_> {
960 pub fn advance_epoch_safe_mode(
961 &mut self,
962 params: &AdvanceEpochParams,
963 protocol_config: &ProtocolConfig,
964 ) {
965 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
966 .expect("System state wrapper object must exist");
967 let (old_object, new_object) =
968 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
969 self.mutate_child_object(old_object, new_object);
970 }
971}
972
973type ModifiedObjectInfo<'a> = (
974 ObjectID,
975 Option<DynamicallyLoadedObjectMetadata>,
977 Option<&'a Object>,
978);
979
980impl TemporaryStore<'_> {
981 fn get_input_sui(
982 &self,
983 id: &ObjectID,
984 expected_version: SequenceNumber,
985 layout_resolver: &mut impl LayoutResolver,
986 ) -> Result<u64, ExecutionError> {
987 if let Some(obj) = self.input_objects.get(id) {
988 if obj.version() != expected_version {
990 invariant_violation!(
991 "Version mismatching when resolving input object to check conservation--\
992 expected {}, got {}",
993 expected_version,
994 obj.version(),
995 );
996 }
997 obj.get_total_sui(layout_resolver).map_err(|e| {
998 make_invariant_violation!(
999 "Failed looking up input SUI in SUI conservation checking for input with \
1000 type {:?}: {e:#?}",
1001 obj.struct_tag(),
1002 )
1003 })
1004 } else {
1005 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
1007 invariant_violation!(
1008 "Failed looking up dynamic field {id} in SUI conservation checking"
1009 );
1010 };
1011 obj.get_total_sui(layout_resolver).map_err(|e| {
1012 make_invariant_violation!(
1013 "Failed looking up input SUI in SUI conservation checking for type \
1014 {:?}: {e:#?}",
1015 obj.struct_tag(),
1016 )
1017 })
1018 }
1019 }
1020
1021 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
1026 self.execution_results
1027 .modified_objects
1028 .iter()
1029 .map(|id| {
1030 let metadata = self.get_object_modified_at(id);
1031 let output = self.execution_results.written_objects.get(id);
1032 (*id, metadata, output)
1033 })
1034 .chain(
1035 self.execution_results
1036 .written_objects
1037 .iter()
1038 .filter_map(|(id, object)| {
1039 if self.execution_results.modified_objects.contains(id) {
1040 None
1041 } else {
1042 Some((*id, None, Some(object)))
1043 }
1044 }),
1045 )
1046 .collect()
1047 }
1048
1049 pub fn check_sui_conserved(
1063 &self,
1064 simple_conservation_checks: bool,
1065 gas_summary: &GasCostSummary,
1066 ) -> Result<(), ExecutionError> {
1067 if !simple_conservation_checks {
1068 return Ok(());
1069 }
1070 let mut total_input_rebate = 0;
1072 let mut total_output_rebate = 0;
1074 for (_id, input, output) in self.get_modified_objects() {
1075 if let Some(input) = input {
1076 total_input_rebate += input.storage_rebate;
1077 }
1078 if let Some(object) = output {
1079 total_output_rebate += object.storage_rebate;
1080 }
1081 }
1082
1083 if gas_summary.storage_cost == 0 {
1084 if total_input_rebate
1096 != total_output_rebate
1097 + gas_summary.storage_rebate
1098 + gas_summary.non_refundable_storage_fee
1099 {
1100 return Err(ExecutionError::invariant_violation(format!(
1101 "SUI conservation failed -- no storage charges in gas summary \
1102 and total storage input rebate {} not equal \
1103 to total storage output rebate {}",
1104 total_input_rebate, total_output_rebate,
1105 )));
1106 }
1107 } else {
1108 if total_input_rebate
1111 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1112 {
1113 return Err(ExecutionError::invariant_violation(format!(
1114 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1115 {} SUI in tx storage rebate or tx non-refundable storage rebate",
1116 total_input_rebate, gas_summary.non_refundable_storage_fee,
1117 )));
1118 }
1119
1120 if gas_summary.storage_cost != total_output_rebate {
1123 return Err(ExecutionError::invariant_violation(format!(
1124 "SUI conservation failed -- {} SUI charged for storage, \
1125 {} SUI in storage rebate field of output objects",
1126 gas_summary.storage_cost, total_output_rebate
1127 )));
1128 }
1129 }
1130 Ok(())
1131 }
1132
1133 pub fn check_sui_conserved_expensive(
1146 &self,
1147 gas_summary: &GasCostSummary,
1148 advance_epoch_gas_summary: Option<(u64, u64)>,
1149 layout_resolver: &mut impl LayoutResolver,
1150 ) -> Result<(), ExecutionError> {
1151 let mut total_input_sui = 0;
1153 let mut total_output_sui = 0;
1155
1156 total_input_sui += self.execution_results.settlement_input_sui;
1160 total_output_sui += self.execution_results.settlement_output_sui;
1161
1162 for (id, input, output) in self.get_modified_objects() {
1163 if let Some(input) = input {
1164 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1165 }
1166 if let Some(object) = output {
1167 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1168 make_invariant_violation!(
1169 "Failed looking up output SUI in SUI conservation checking for \
1170 mutated type {:?}: {e:#?}",
1171 object.struct_tag(),
1172 )
1173 })?;
1174 }
1175 }
1176
1177 for event in &self.execution_results.accumulator_events {
1178 let (input, output) = event.total_sui_in_event();
1179 total_input_sui += input;
1180 total_output_sui += output;
1181 }
1182
1183 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1188 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1189 total_input_sui += epoch_fees;
1190 total_output_sui += epoch_rebates;
1191 }
1192 if total_input_sui != total_output_sui {
1193 return Err(ExecutionError::invariant_violation(format!(
1194 "SUI conservation failed: input={}, output={}, \
1195 this transaction either mints or burns SUI",
1196 total_input_sui, total_output_sui,
1197 )));
1198 }
1199 Ok(())
1200 }
1201}
1202
1203impl ChildObjectResolver for TemporaryStore<'_> {
1204 fn read_child_object(
1205 &self,
1206 parent: &ObjectID,
1207 child: &ObjectID,
1208 child_version_upper_bound: SequenceNumber,
1209 ) -> SuiResult<Option<Object>> {
1210 let obj_opt = self.execution_results.written_objects.get(child);
1211 if obj_opt.is_some() {
1212 Ok(obj_opt.cloned())
1213 } else {
1214 let _scope = monitored_scope("Execution::read_child_object");
1215 self.store
1216 .read_child_object(parent, child, child_version_upper_bound)
1217 }
1218 }
1219
1220 fn get_object_received_at_version(
1221 &self,
1222 owner: &ObjectID,
1223 receiving_object_id: &ObjectID,
1224 receive_object_at_version: SequenceNumber,
1225 epoch_id: EpochId,
1226 ) -> SuiResult<Option<Object>> {
1227 debug_assert!(
1230 !self
1231 .execution_results
1232 .written_objects
1233 .contains_key(receiving_object_id)
1234 );
1235 debug_assert!(
1236 !self
1237 .execution_results
1238 .deleted_object_ids
1239 .contains(receiving_object_id)
1240 );
1241 self.store.get_object_received_at_version(
1242 owner,
1243 receiving_object_id,
1244 receive_object_at_version,
1245 epoch_id,
1246 )
1247 }
1248}
1249
1250fn was_object_mutated(object: &Object, original: &Object) -> bool {
1253 let data_equal = match (&object.data, &original.data) {
1254 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1255 (Data::Package(a), Data::Package(b)) => a == b,
1258 _ => false,
1259 };
1260
1261 let owner_equal = match (&object.owner, &original.owner) {
1262 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1266 (
1267 Owner::ConsensusAddressOwner { owner: a, .. },
1268 Owner::ConsensusAddressOwner { owner: b, .. },
1269 ) => a == b,
1270 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1271 (Owner::Immutable, Owner::Immutable) => true,
1272 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1273
1274 (Owner::AddressOwner(_), _)
1277 | (Owner::Immutable, _)
1278 | (Owner::ObjectOwner(_), _)
1279 | (Owner::Shared { .. }, _)
1280 | (Owner::ConsensusAddressOwner { .. }, _) => false,
1281 };
1282
1283 !data_equal || !owner_equal
1284}
1285
1286impl Storage for TemporaryStore<'_> {
1287 fn reset(&mut self) {
1288 self.drop_writes();
1289 }
1290
1291 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1292 TemporaryStore::read_object(self, id)
1293 }
1294
1295 fn record_execution_results(
1297 &mut self,
1298 results: ExecutionResults,
1299 ) -> Result<(), ExecutionError> {
1300 let ExecutionResults::V2(mut results) = results else {
1301 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1302 };
1303
1304 let mut to_remove = Vec::new();
1306 for (id, original) in &self.non_exclusive_input_original_versions {
1307 if results
1309 .written_objects
1310 .get(id)
1311 .map(|obj| was_object_mutated(obj, original))
1312 .unwrap_or(true)
1313 {
1314 return Err(ExecutionError::new_with_source(
1315 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1316 "Non-exclusive write input object has been modified or deleted",
1317 ));
1318 }
1319 to_remove.push(*id);
1320 }
1321
1322 for id in to_remove {
1323 results.written_objects.remove(&id);
1324 results.modified_objects.remove(&id);
1325 }
1326
1327 self.execution_results.merge_results(
1330 results, true, true,
1331 )?;
1332
1333 Ok(())
1334 }
1335
1336 fn save_loaded_runtime_objects(
1337 &mut self,
1338 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1339 ) {
1340 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1341 }
1342
1343 fn save_wrapped_object_containers(
1344 &mut self,
1345 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1346 ) {
1347 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1348 }
1349
1350 fn check_coin_deny_list(
1351 &self,
1352 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1353 ) -> DenyListResult {
1354 let result = check_coin_deny_list_v2_during_execution(
1355 receiving_funds_type_and_owners,
1356 self.cur_epoch,
1357 self.store.as_object_store(),
1358 );
1359 if result.num_non_gas_coin_owners > 0
1362 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1363 {
1364 self.loaded_per_epoch_config_objects
1365 .write()
1366 .insert(SUI_DENY_LIST_OBJECT_ID);
1367 }
1368 result
1369 }
1370
1371 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1372 TemporaryStore::save_generated_object_ids(self, generated_ids)
1373 }
1374}
1375
1376impl BackingPackageStore for TemporaryStore<'_> {
1377 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1378 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1385 Ok(Some(PackageObject::new(obj.clone())))
1386 } else {
1387 self.store.get_package_object(package_id).inspect(|obj| {
1388 if let Some(v) = obj
1390 && !self
1391 .runtime_packages_loaded_from_db
1392 .read()
1393 .contains_key(package_id)
1394 {
1395 self.runtime_packages_loaded_from_db
1400 .write()
1401 .insert(*package_id, v.clone());
1402 }
1403 })
1404 }
1405 }
1406}
1407
1408impl ParentSync for TemporaryStore<'_> {
1409 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1410 unreachable!("Never called in newer protocol versions")
1411 }
1412}