1use crate::execution_mode::ExecutionMode;
5use crate::gas_charger::{GasCharger, PaymentLocation};
6use mysten_common::{ZipDebugEqIteratorExt, debug_fatal};
7use mysten_metrics::monitored_scope;
8use parking_lot::RwLock;
9use std::collections::{BTreeMap, BTreeSet, HashSet};
10use sui_protocol_config::ProtocolConfig;
11use sui_types::accumulator_event::AccumulatorEvent;
12use sui_types::accumulator_root::AccumulatorObjId;
13use sui_types::base_types::VersionDigest;
14use sui_types::committee::EpochId;
15use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
16use sui_types::effects::{
17 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
18 TransactionEvents,
19};
20use sui_types::execution::{
21 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
22};
23use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
24use sui_types::inner_temporary_store::InnerTemporaryStore;
25use sui_types::layout_resolver::LayoutResolver;
26use sui_types::object::Data;
27use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
28use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
29use sui_types::{
30 SUI_DENY_LIST_OBJECT_ID,
31 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
32 effects::EffectsObjectChange,
33 error::{ExecutionError, SuiResult},
34 gas::GasCostSummary,
35 object::Object,
36 object::Owner,
37 storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
38 transaction::InputObjects,
39};
40use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
41
42pub struct TemporaryStore<'backing> {
43 store: &'backing dyn BackingStore,
49 tx_digest: TransactionDigest,
50 input_objects: BTreeMap<ObjectID, Object>,
51
52 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
55
56 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
57 lamport_timestamp: SequenceNumber,
59 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
62 execution_results: ExecutionResultsV2,
63 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
65 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
67 protocol_config: &'backing ProtocolConfig,
68
69 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
72
73 receiving_objects: Vec<ObjectRef>,
76
77 generated_runtime_ids: BTreeSet<ObjectID>,
81
82 cur_epoch: EpochId,
85
86 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
89
90 ptb_emitted_accumulator_event_ranges: Vec<std::ops::Range<usize>>,
99}
100
101impl<'backing> TemporaryStore<'backing> {
102 pub fn new(
105 store: &'backing dyn BackingStore,
106 input_objects: InputObjects,
107 receiving_objects: Vec<ObjectRef>,
108 tx_digest: TransactionDigest,
109 protocol_config: &'backing ProtocolConfig,
110 cur_epoch: EpochId,
111 ) -> Self {
112 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
113 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
114
115 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
116 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
117 let objects = input_objects.into_object_map();
118 #[cfg(debug_assertions)]
119 {
120 assert!(
122 objects
123 .keys()
124 .collect::<HashSet<_>>()
125 .intersection(
126 &receiving_objects
127 .iter()
128 .map(|oref| &oref.0)
129 .collect::<HashSet<_>>()
130 )
131 .next()
132 .is_none()
133 );
134 }
135 Self {
136 store,
137 tx_digest,
138 input_objects: objects,
139 non_exclusive_input_original_versions,
140 stream_ended_consensus_objects,
141 lamport_timestamp,
142 mutable_input_refs,
143 execution_results: ExecutionResultsV2::default(),
144 protocol_config,
145 loaded_runtime_objects: BTreeMap::new(),
146 wrapped_object_containers: BTreeMap::new(),
147 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
148 receiving_objects,
149 generated_runtime_ids: BTreeSet::new(),
150 cur_epoch,
151 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
152 ptb_emitted_accumulator_event_ranges: Vec::new(),
153 }
154 }
155
156 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
158 &self.input_objects
159 }
160
161 pub fn update_object_version_and_prev_tx(&mut self) {
162 self.execution_results.update_version_and_previous_tx(
163 self.lamport_timestamp,
164 self.tx_digest,
165 &self.input_objects,
166 self.protocol_config.reshare_at_same_initial_version(),
167 );
168
169 #[cfg(debug_assertions)]
170 {
171 self.check_invariants();
172 }
173 }
174
175 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
176 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
177 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
178 for event in &self.execution_results.accumulator_events {
179 match &event.write.value {
180 AccumulatorValue::Integer(amount) => match event.write.operation {
181 AccumulatorOperation::Split => {
182 let entry = running_net_withdraws
183 .entry(event.accumulator_obj)
184 .or_default();
185 *entry += *amount as i128;
186 if *entry > 0 {
187 let max_entry = running_max_withdraws
188 .entry(event.accumulator_obj)
189 .or_default();
190 *max_entry = (*max_entry).max(*entry as u128);
191 }
192 }
193 AccumulatorOperation::Merge => {
194 let entry = running_net_withdraws
195 .entry(event.accumulator_obj)
196 .or_default();
197 *entry -= *amount as i128;
198 }
199 },
200 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
201 }
202 }
203 running_max_withdraws
204 }
205
206 fn merge_accumulator_events(&mut self) {
208 self.execution_results.accumulator_events = self
209 .execution_results
210 .accumulator_events
211 .iter()
212 .fold(
213 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
214 |mut map, event| {
215 map.entry(event.accumulator_obj)
216 .or_default()
217 .push(event.write.clone());
218 map
219 },
220 )
221 .into_iter()
222 .map(|(obj_id, writes)| {
223 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
224 })
225 .collect();
226 }
227
228 pub fn into_inner(
230 self,
231 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
232 ) -> InnerTemporaryStore {
233 let results = self.execution_results;
234 InnerTemporaryStore {
235 input_objects: self.input_objects,
236 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
237 mutable_inputs: self.mutable_input_refs,
238 written: results.written_objects,
239 events: TransactionEvents {
240 data: results.user_events,
241 },
242 accumulator_events: results.accumulator_events,
243 loaded_runtime_objects: self.loaded_runtime_objects,
244 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
245 lamport_version: self.lamport_timestamp,
246 binary_config: self.protocol_config.binary_config(None),
247 accumulator_running_max_withdraws,
248 }
249 }
250
251 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
255 let mut to_be_updated = vec![];
256 for id in self.mutable_input_refs.keys() {
258 if !self.execution_results.modified_objects.contains(id) {
259 to_be_updated.push(self.input_objects[id].clone());
263 }
264 }
265 for object in to_be_updated {
266 self.mutate_input_object(object.clone());
268 }
269 }
270
271 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
272 let results = &self.execution_results;
273 let all_ids = results
274 .created_object_ids
275 .iter()
276 .chain(&results.deleted_object_ids)
277 .chain(&results.modified_objects)
278 .chain(results.written_objects.keys())
279 .collect::<BTreeSet<_>>();
280 all_ids
281 .into_iter()
282 .map(|id| {
283 (
284 *id,
285 EffectsObjectChange::new(
286 self.get_object_modified_at(id)
287 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
288 results.written_objects.get(id),
289 results.created_object_ids.contains(id),
290 results.deleted_object_ids.contains(id),
291 ),
292 )
293 })
294 .chain(results.accumulator_events.iter().cloned().map(
295 |AccumulatorEvent {
296 accumulator_obj,
297 write,
298 }| {
299 (
300 *accumulator_obj.inner(),
301 EffectsObjectChange::new_from_accumulator_write(write),
302 )
303 },
304 ))
305 .collect()
306 }
307
308 pub fn into_effects(
309 mut self,
310 shared_object_refs: Vec<SharedInput>,
311 transaction_digest: &TransactionDigest,
312 mut transaction_dependencies: BTreeSet<TransactionDigest>,
313 gas_cost_summary: GasCostSummary,
314 status: ExecutionStatus,
315 gas_charger: &mut GasCharger,
316 epoch: EpochId,
317 ) -> (InnerTemporaryStore, TransactionEffects) {
318 self.update_object_version_and_prev_tx();
319 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
321 self.merge_accumulator_events();
322
323 for (id, expected_version, expected_digest) in &self.receiving_objects {
326 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
330 let loaded_via_receive = obj_meta.version == *expected_version
334 && obj_meta.digest == *expected_digest
335 && obj_meta.owner.is_address_owned();
336 if loaded_via_receive {
337 transaction_dependencies.insert(obj_meta.previous_transaction);
338 }
339 }
340 }
341
342 assert!(self.protocol_config.enable_effects_v2());
343
344 let gas_coin = gas_charger
349 .gas_payment_amount()
350 .and_then(|gp| match gp.location {
351 PaymentLocation::Coin(coin_id) => Some(coin_id),
352 PaymentLocation::AddressBalance(_) => None,
353 });
354
355 let object_changes = self.get_object_changes();
356
357 let lamport_version = self.lamport_timestamp;
358 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
360 let inner = self.into_inner(accumulator_running_max_withdraws);
361
362 let effects = TransactionEffects::new_from_execution_v2(
363 status,
364 epoch,
365 gas_cost_summary,
366 shared_object_refs,
368 loaded_per_epoch_config_objects,
369 *transaction_digest,
370 lamport_version,
371 object_changes,
372 gas_coin,
373 if inner.events.data.is_empty() {
374 None
375 } else {
376 Some(inner.events.digest())
377 },
378 transaction_dependencies.into_iter().collect(),
379 );
380
381 (inner, effects)
382 }
383
384 #[cfg(debug_assertions)]
386 fn check_invariants(&self) {
387 debug_assert!(
389 {
390 self.execution_results
391 .written_objects
392 .keys()
393 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
394 },
395 "Object both written and deleted."
396 );
397
398 debug_assert!(
400 {
401 self.mutable_input_refs
402 .keys()
403 .all(|id| self.execution_results.modified_objects.contains(id))
404 },
405 "Mutable input not modified."
406 );
407
408 debug_assert!(
409 {
410 self.execution_results
411 .written_objects
412 .values()
413 .all(|obj| obj.previous_transaction == self.tx_digest)
414 },
415 "Object previous transaction not properly set",
416 );
417 }
418
419 pub fn mutate_input_object(&mut self, object: Object) {
421 let id = object.id();
422 debug_assert!(self.input_objects.contains_key(&id));
423 debug_assert!(!object.is_immutable());
424 self.execution_results.modified_objects.insert(id);
425 self.execution_results.written_objects.insert(id, object);
426 }
427
428 pub fn mutate_new_or_input_object(&mut self, object: Object) {
429 let id = object.id();
430 debug_assert!(!object.is_immutable());
431 if self.input_objects.contains_key(&id) {
432 self.execution_results.modified_objects.insert(id);
433 }
434 self.execution_results.written_objects.insert(id, object);
435 }
436
437 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
441 let id = new_object.id();
442 let old_ref = old_object.compute_object_reference();
443 debug_assert_eq!(old_ref.0, id);
444 self.loaded_runtime_objects.insert(
445 id,
446 DynamicallyLoadedObjectMetadata {
447 version: old_ref.1,
448 digest: old_ref.2,
449 owner: old_object.owner.clone(),
450 storage_rebate: old_object.storage_rebate,
451 previous_transaction: old_object.previous_transaction,
452 },
453 );
454 self.execution_results.modified_objects.insert(id);
455 self.execution_results
456 .written_objects
457 .insert(id, new_object);
458 }
459
460 pub fn upgrade_system_package(&mut self, package: Object) {
464 let id = package.id();
465 assert!(package.is_package() && is_system_package(id));
466 self.execution_results.modified_objects.insert(id);
467 self.execution_results.written_objects.insert(id, package);
468 }
469
470 pub fn create_object(&mut self, object: Object) {
472 debug_assert!(
477 object.is_immutable() || object.version() == SequenceNumber::MIN,
478 "Created mutable objects should not have a version set",
479 );
480 let id = object.id();
481 self.execution_results.created_object_ids.insert(id);
482 self.execution_results.written_objects.insert(id, object);
483 }
484
485 pub fn delete_input_object(&mut self, id: &ObjectID) {
487 debug_assert!(!self.execution_results.written_objects.contains_key(id));
489 debug_assert!(self.input_objects.contains_key(id));
490 self.execution_results.modified_objects.insert(*id);
491 self.execution_results.deleted_object_ids.insert(*id);
492 }
493
494 pub fn drop_writes(&mut self) {
495 self.execution_results.drop_writes();
496 self.ptb_emitted_accumulator_event_ranges.clear();
498 }
499
500 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
501 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
503 self.execution_results
504 .written_objects
505 .get(id)
506 .or_else(|| self.input_objects.get(id))
507 }
508
509 pub fn save_loaded_runtime_objects(
510 &mut self,
511 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
512 ) {
513 #[cfg(debug_assertions)]
514 {
515 for (id, v1) in &loaded_runtime_objects {
516 if let Some(v2) = self.loaded_runtime_objects.get(id) {
517 assert_eq!(v1, v2);
518 }
519 }
520 for (id, v1) in &self.loaded_runtime_objects {
521 if let Some(v2) = loaded_runtime_objects.get(id) {
522 assert_eq!(v1, v2);
523 }
524 }
525 }
526 self.loaded_runtime_objects.extend(loaded_runtime_objects);
529 }
530
531 pub fn save_wrapped_object_containers(
532 &mut self,
533 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
534 ) {
535 #[cfg(debug_assertions)]
536 {
537 for (id, container1) in &wrapped_object_containers {
538 if let Some(container2) = self.wrapped_object_containers.get(id) {
539 assert_eq!(container1, container2);
540 }
541 }
542 for (id, container1) in &self.wrapped_object_containers {
543 if let Some(container2) = wrapped_object_containers.get(id) {
544 assert_eq!(container1, container2);
545 }
546 }
547 }
548 self.wrapped_object_containers
551 .extend(wrapped_object_containers);
552 }
553
554 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
555 #[cfg(debug_assertions)]
556 {
557 for id in &self.generated_runtime_ids {
558 assert!(!generated_ids.contains(id))
559 }
560 for id in &generated_ids {
561 assert!(!self.generated_runtime_ids.contains(id));
562 }
563 }
564 self.generated_runtime_ids.extend(generated_ids);
565 }
566
567 pub fn estimate_effects_size_upperbound(&self) -> usize {
568 TransactionEffects::estimate_effects_size_upperbound_v2(
569 self.execution_results.written_objects.len(),
570 self.execution_results.modified_objects.len(),
571 self.input_objects.len(),
572 )
573 }
574
575 pub fn written_objects_size(&self) -> usize {
576 self.execution_results
577 .written_objects
578 .values()
579 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
580 }
581
582 pub fn check_gasless_execution_requirements(
588 &self,
589 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
590 ) -> Result<(), String> {
591 if !self.execution_results.written_objects.is_empty() {
592 return Err("Gasless transactions cannot create or mutate objects".to_string());
593 }
594
595 let input_coin_ids: BTreeSet<ObjectID> = self
596 .input_objects
597 .iter()
598 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
599 .map(|(id, _)| *id)
600 .collect();
601 if self.execution_results.deleted_object_ids != input_coin_ids {
602 return Err(format!(
603 "Gasless transaction must destroy exactly its input Coins. \
604 Expected: {input_coin_ids:?}, deleted: {:?}",
605 self.execution_results.deleted_object_ids
606 ));
607 }
608
609 let allowed_types =
610 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
611
612 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
615 &self.execution_results.accumulator_events,
616 )
617 .fold(
618 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
619 |mut totals, (address, token_type, signed_amount)| {
620 *totals.entry((address, token_type)).or_default() += signed_amount;
621 totals
622 },
623 );
624
625 for ((recipient, token_type), net_amount) in &net_totals {
626 if *net_amount <= 0 {
627 continue;
628 }
629 if let Some(&min_amount) = allowed_types.get(token_type)
630 && *net_amount < i128::from(min_amount)
631 {
632 return Err(format!(
633 "Gasless transfer of {net_amount} to {recipient} is below \
634 minimum {min_amount} for token type {token_type}"
635 ));
636 }
637 }
638
639 if let Some(reservations) = withdrawal_reservations {
640 for ((owner, token_type), &reserved) in reservations {
641 let net = net_totals
642 .get(&(*owner, token_type.clone()))
643 .copied()
644 .unwrap_or(0);
645 let remaining = (reserved as i128).saturating_add(net);
646 if remaining > 0
647 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
648 && min_balance_remaining > 0
649 && remaining < min_balance_remaining as i128
650 {
651 return Err(format!(
652 "Gasless withdrawal leaves {remaining} unused for {owner}, \
653 below minimum {min_balance_remaining} for token type {token_type}"
654 ));
655 }
656 }
657 }
658
659 Ok(())
660 }
661
662 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
667 if unmetered_storage_rebate == 0 {
668 return;
672 }
673 tracing::debug!(
674 "Amount of unmetered storage rebate from system tx: {:?}",
675 unmetered_storage_rebate
676 );
677 let mut system_state_wrapper = self
678 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
679 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
680 .clone();
681 assert_eq!(system_state_wrapper.storage_rebate, 0);
684 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
685 self.mutate_input_object(system_state_wrapper);
686 }
687
688 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
690 self.execution_results.accumulator_events.push(event);
691 }
692
693 fn get_object_modified_at(
699 &self,
700 object_id: &ObjectID,
701 ) -> Option<DynamicallyLoadedObjectMetadata> {
702 if self.execution_results.modified_objects.contains(object_id) {
703 Some(
704 self.mutable_input_refs
705 .get(object_id)
706 .map(
707 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
708 version: *version,
709 digest: *digest,
710 owner: owner.clone(),
711 storage_rebate: self.input_objects[object_id].storage_rebate,
713 previous_transaction: self.input_objects[object_id]
714 .previous_transaction,
715 },
716 )
717 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
718 .unwrap_or_else(|| {
719 debug_assert!(is_system_package(*object_id));
720 let package_obj =
721 self.store.get_package_object(object_id).unwrap().unwrap();
722 let obj = package_obj.object();
723 DynamicallyLoadedObjectMetadata {
724 version: obj.version(),
725 digest: obj.digest(),
726 owner: obj.owner.clone(),
727 storage_rebate: obj.storage_rebate,
728 previous_transaction: obj.previous_transaction,
729 }
730 }),
731 )
732 } else {
733 None
734 }
735 }
736
737 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
738 self.protocol_config
739 }
740}
741
742impl TemporaryStore<'_> {
743 pub fn check_ownership_invariants(
746 &self,
747 sender: &SuiAddress,
748 sponsor: &Option<SuiAddress>,
749 gas_charger: &GasCharger,
750 mutable_inputs: &HashSet<ObjectID>,
751 input_reservations: &BTreeMap<(SuiAddress, TypeTag), u64>,
752 is_epoch_change: bool,
753 ) -> SuiResult<()> {
754 let gas_objs: HashSet<&ObjectID> = gas_charger.used_coins().map(|g| &g.0).collect();
755 let gas_owner = sponsor.as_ref().unwrap_or(sender);
756
757 let objects_authenticated_for_mutation: HashSet<SuiAddress> = self
759 .input_objects
760 .iter()
761 .filter_map(|(id, obj)| {
762 match &obj.owner {
763 Owner::AddressOwner(a) => {
764 if gas_objs.contains(id) {
765 assert!(
767 a == gas_owner,
768 "Gas object must be owned by sender or sponsor"
769 );
770 } else {
771 assert!(sender == a, "Input object must be owned by sender");
772 }
773 Some(id)
774 }
775 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => Some(id),
776 Owner::Immutable => {
777 None
788 }
789 Owner::ObjectOwner(_parent) => {
790 unreachable!(
791 "Input objects must be address owned, shared, consensus, or immutable"
792 )
793 }
794 }
795 })
796 .filter(|id| {
797 mutable_inputs.contains(id)
800 })
801 .copied()
802 .chain(self.generated_runtime_ids.iter().copied())
805 .map(SuiAddress::from)
806 .collect();
807
808 let mut authenticated_for_mutation = {
810 assert!(
811 !objects_authenticated_for_mutation.contains(sender),
812 "Sender cannot be an object"
813 );
814 assert!(
815 sponsor
816 .is_none_or(|sponsor| !objects_authenticated_for_mutation.contains(&sponsor)),
817 "Sponsor cannot be an object"
818 );
819 let mut s = objects_authenticated_for_mutation.clone();
820 s.insert(*sender);
821 if let Some(sponsor) = sponsor {
822 s.insert(*sponsor);
823 }
824 s
825 };
826
827 let mut objects_to_authenticate = self
829 .execution_results
830 .modified_objects
831 .iter()
832 .copied()
833 .collect::<Vec<_>>();
834
835 while let Some(to_authenticate) = objects_to_authenticate.pop() {
836 if authenticated_for_mutation.contains(&to_authenticate.into()) {
837 continue;
839 }
840
841 let parent = if let Some(container_id) =
842 self.wrapped_object_containers.get(&to_authenticate)
843 {
844 *container_id
846 } else {
847 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
850 panic!(
851 "Failed to load object {to_authenticate:?}.\n \
852 If it cannot be loaded, we would expect it to be in the wrapped object map: {:#?}",
853 &self.wrapped_object_containers
854 )
855 };
856
857 match &old_obj.owner {
858 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
861 Owner::AddressOwner(parent)
866 | Owner::ConsensusAddressOwner { owner: parent, .. } => {
867 ObjectID::from(*parent)
871 }
872 owner @ Owner::Shared { .. } => {
875 panic!(
876 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
877 Potentially covering objects in: {authenticated_for_mutation:#?}"
878 );
879 }
880 Owner::Immutable => {
881 assert!(
882 is_epoch_change,
883 "Immutable objects cannot be written, except for \
884 Sui Framework/Move stdlib upgrades at epoch change boundaries"
885 );
886 assert!(
890 is_system_package(to_authenticate),
891 "Only system packages can be upgraded"
892 );
893 continue;
894 }
895 }
896 };
897
898 authenticated_for_mutation.insert(to_authenticate.into());
900 objects_to_authenticate.push(parent);
901 }
902
903 let sui_balance_type =
905 sui_types::balance::Balance::type_tag(sui_types::gas_coin::GAS::type_tag());
906 let gas_payment_address_balance =
907 gas_charger
908 .gas_payment_location()
909 .and_then(|location| match location {
910 PaymentLocation::Coin(_) => None,
911 PaymentLocation::AddressBalance(address) => Some(address),
912 });
913 let mut funds_net_changes: BTreeMap<(SuiAddress, TypeTag), i128> = BTreeMap::new();
914 for event in self.execution_results.accumulator_events.iter() {
915 let amount = match event.write.value {
916 AccumulatorValue::Integer(a) => a as i128,
917 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {
918 assert!(
919 !sui_types::balance::Balance::is_balance_type(&event.write.address.ty),
920 "Non-integer accumulator changes should not be balances"
921 );
922 continue;
923 }
924 };
925 let signed = match event.write.operation {
926 AccumulatorOperation::Split => -amount,
927 AccumulatorOperation::Merge => amount,
928 };
929 let address = event.write.address.address;
930 let type_tag = &event.write.address.ty;
931 let key = (address, type_tag.clone());
932 *funds_net_changes.entry(key.clone()).or_insert(0) += signed;
933 let authorized = match event.write.operation {
940 AccumulatorOperation::Merge => true,
941 AccumulatorOperation::Split => {
942 input_reservations.contains_key(&key)
943 || objects_authenticated_for_mutation.contains(&address)
944 || (*type_tag == sui_balance_type
945 && gas_payment_address_balance
946 .is_some_and(|gas_addr| gas_addr == address))
947 }
948 };
949 assert!(
950 authorized,
951 "Unauthenticated funds-accumulator Split at address {address} for type \
952 {type_tag}: no input reservation, address is not an authenticated object, and \
953 it is not the final gas payment address balance"
954 );
955 }
956
957 for (key, change) in funds_net_changes {
962 if change >= 0 || objects_authenticated_for_mutation.contains(&key.0) {
964 continue;
965 }
966 let reservation = input_reservations.get(&key).copied().unwrap_or(0) as u128;
967 let withdrawn = change.unsigned_abs();
968 assert!(
969 withdrawn <= reservation,
970 "Net withdrawal of {withdrawn} for {key:?} exceeds input reservation of \
971 {reservation}"
972 );
973 }
974
975 Ok(())
976 }
977}
978
979impl TemporaryStore<'_> {
980 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
987 let old_storage_rebates: Vec<_> = self
989 .execution_results
990 .written_objects
991 .keys()
992 .map(|object_id| {
993 self.get_object_modified_at(object_id)
994 .map(|metadata| metadata.storage_rebate)
995 .unwrap_or_default()
996 })
997 .collect();
998 for (object, old_storage_rebate) in self
999 .execution_results
1000 .written_objects
1001 .values_mut()
1002 .zip_debug_eq(old_storage_rebates)
1003 {
1004 let new_object_size = object.object_size_for_gas_metering();
1006 let new_storage_rebate = gas_charger.track_storage_mutation(
1008 object.id(),
1009 new_object_size,
1010 old_storage_rebate,
1011 );
1012 object.storage_rebate = new_storage_rebate;
1013 }
1014
1015 self.collect_rebate(gas_charger);
1016 }
1017
1018 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
1019 for object_id in &self.execution_results.modified_objects {
1020 if self
1021 .execution_results
1022 .written_objects
1023 .contains_key(object_id)
1024 {
1025 continue;
1026 }
1027 let storage_rebate = self
1029 .get_object_modified_at(object_id)
1030 .unwrap()
1032 .storage_rebate;
1033 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
1034 }
1035 }
1036
1037 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1038 &self,
1039 ) -> Result<(), Mode::Error> {
1040 assert_invariant!(
1041 self.execution_results
1042 .created_object_ids
1043 .iter()
1044 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1045 && !self.execution_results.modified_objects.contains(id)),
1046 "Created object IDs cannot also be deleted or modified"
1047 );
1048 assert_invariant!(
1049 self.execution_results.modified_objects.iter().all(|id| {
1050 self.mutable_input_refs.contains_key(id)
1051 || self.loaded_runtime_objects.contains_key(id)
1052 || is_system_package(*id)
1053 }),
1054 "A modified object must be either a mutable input, a loaded child object, or a system package"
1055 );
1056 Ok(())
1057 }
1058}
1059impl TemporaryStore<'_> {
1064 pub fn advance_epoch_safe_mode(
1065 &mut self,
1066 params: &AdvanceEpochParams,
1067 protocol_config: &ProtocolConfig,
1068 ) {
1069 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
1070 .expect("System state wrapper object must exist");
1071 let (old_object, new_object) =
1072 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
1073 self.mutate_child_object(old_object, new_object);
1074 }
1075}
1076
1077type ModifiedObjectInfo<'a> = (
1078 ObjectID,
1079 Option<DynamicallyLoadedObjectMetadata>,
1081 Option<&'a Object>,
1082);
1083
1084impl TemporaryStore<'_> {
1085 fn get_input_sui(
1086 &self,
1087 id: &ObjectID,
1088 expected_version: SequenceNumber,
1089 layout_resolver: &mut impl LayoutResolver,
1090 ) -> Result<u64, ExecutionError> {
1091 if let Some(obj) = self.input_objects.get(id) {
1092 if obj.version() != expected_version {
1094 invariant_violation!(
1095 "Version mismatching when resolving input object to check conservation--\
1096 expected {}, got {}",
1097 expected_version,
1098 obj.version(),
1099 );
1100 }
1101 obj.get_total_sui(layout_resolver).map_err(|e| {
1102 make_invariant_violation!(
1103 "Failed looking up input SUI in SUI conservation checking for input with \
1104 type {:?}: {e:#?}",
1105 obj.struct_tag(),
1106 )
1107 })
1108 } else {
1109 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
1111 invariant_violation!(
1112 "Failed looking up dynamic field {id} in SUI conservation checking"
1113 );
1114 };
1115 obj.get_total_sui(layout_resolver).map_err(|e| {
1116 make_invariant_violation!(
1117 "Failed looking up input SUI in SUI conservation checking for type \
1118 {:?}: {e:#?}",
1119 obj.struct_tag(),
1120 )
1121 })
1122 }
1123 }
1124
1125 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
1130 self.execution_results
1131 .modified_objects
1132 .iter()
1133 .map(|id| {
1134 let metadata = self.get_object_modified_at(id);
1135 let output = self.execution_results.written_objects.get(id);
1136 (*id, metadata, output)
1137 })
1138 .chain(
1139 self.execution_results
1140 .written_objects
1141 .iter()
1142 .filter_map(|(id, object)| {
1143 if self.execution_results.modified_objects.contains(id) {
1144 None
1145 } else {
1146 Some((*id, None, Some(object)))
1147 }
1148 }),
1149 )
1150 .collect()
1151 }
1152
1153 pub fn check_sui_conserved(
1167 &self,
1168 simple_conservation_checks: bool,
1169 gas_summary: &GasCostSummary,
1170 ) -> Result<(), ExecutionError> {
1171 if !simple_conservation_checks {
1172 return Ok(());
1173 }
1174 let mut total_input_rebate = 0;
1176 let mut total_output_rebate = 0;
1178 for (_id, input, output) in self.get_modified_objects() {
1179 if let Some(input) = input {
1180 total_input_rebate += input.storage_rebate;
1181 }
1182 if let Some(object) = output {
1183 total_output_rebate += object.storage_rebate;
1184 }
1185 }
1186
1187 if gas_summary.storage_cost == 0 {
1188 if total_input_rebate
1200 != total_output_rebate
1201 + gas_summary.storage_rebate
1202 + gas_summary.non_refundable_storage_fee
1203 {
1204 return Err(ExecutionError::invariant_violation(format!(
1205 "SUI conservation failed -- no storage charges in gas summary \
1206 and total storage input rebate {} not equal \
1207 to total storage output rebate {}",
1208 total_input_rebate, total_output_rebate,
1209 )));
1210 }
1211 } else {
1212 if total_input_rebate
1215 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1216 {
1217 return Err(ExecutionError::invariant_violation(format!(
1218 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1219 {} SUI in tx storage rebate or tx non-refundable storage rebate",
1220 total_input_rebate, gas_summary.non_refundable_storage_fee,
1221 )));
1222 }
1223
1224 if gas_summary.storage_cost != total_output_rebate {
1227 return Err(ExecutionError::invariant_violation(format!(
1228 "SUI conservation failed -- {} SUI charged for storage, \
1229 {} SUI in storage rebate field of output objects",
1230 gas_summary.storage_cost, total_output_rebate
1231 )));
1232 }
1233 }
1234 Ok(())
1235 }
1236
1237 pub fn check_address_balance_changes(
1257 &self,
1258 protocol_config: &ProtocolConfig,
1259 input_reservations: &BTreeMap<(SuiAddress, TypeTag), u64>,
1260 ) -> Result<(), ExecutionError> {
1261 let result = self.check_address_balance_changes_impl(input_reservations);
1262 if protocol_config.enforce_address_balance_change_invariant() {
1263 result
1264 } else {
1265 if let Err(e) = result {
1266 panic!("address-balance-change invariant violated pre-flag: {e}");
1267 }
1268 Ok(())
1269 }
1270 }
1271
1272 fn check_address_balance_changes_impl(
1273 &self,
1274 input_reservations: &BTreeMap<(SuiAddress, TypeTag), u64>,
1275 ) -> Result<(), ExecutionError> {
1276 use sui_types::balance::Balance;
1277
1278 let mut actual_changes: BTreeMap<(SuiAddress, TypeTag), i128> = BTreeMap::new();
1279 let mut ptb_changes: BTreeMap<(SuiAddress, TypeTag), i128> = BTreeMap::new();
1280 for (idx, event) in self.execution_results.accumulator_events.iter().enumerate() {
1281 let amount = match event.write.value {
1287 AccumulatorValue::Integer(amount) => amount as i128,
1288 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {
1289 assert_invariant!(
1290 !sui_types::balance::Balance::is_balance_type(&event.write.address.ty),
1291 "Non-integer accumulator changes should not be balances"
1292 );
1293 continue;
1294 }
1295 };
1296 if !Balance::is_balance_type(&event.write.address.ty) {
1297 debug_fatal!(
1298 "Integer accumulator value at non-Balance type: {:?}",
1299 event.write.address.ty
1300 );
1301 continue;
1302 }
1303 let is_ptb_emitted = self
1304 .ptb_emitted_accumulator_event_ranges
1305 .iter()
1306 .any(|range| range.contains(&idx));
1307 let key = (event.write.address.address, event.write.address.ty.clone());
1308 let change = match event.write.operation {
1309 AccumulatorOperation::Split => -amount,
1310 AccumulatorOperation::Merge => amount,
1311 };
1312 *actual_changes.entry(key.clone()).or_insert(0) += change;
1313 if is_ptb_emitted {
1314 *ptb_changes.entry(key).or_insert(0) += change;
1315 }
1316 }
1317
1318 for (key, actual) in actual_changes {
1319 let (address, type_tag) = &key;
1320 if let Some(budget) = input_reservations.get(&key).copied() {
1321 let net_withdrawn = -actual.min(0) as u128;
1322 assert_invariant!(
1323 net_withdrawn <= budget as u128,
1324 "Balance accumulator withdrawal exceeds reservation budget at address \
1325 {address} for type {type_tag}: net Split {net_withdrawn}, budget {budget}"
1326 );
1327 } else if let Some(ptb_change) = ptb_changes.get(&key).copied() {
1328 assert_invariant!(
1333 actual >= ptb_change.min(0),
1334 "PTB-emitted Balance accumulator events do not cover runtime withdrawals \
1335 at address {address} for type {type_tag}: PTB change {ptb_change}, net \
1336 change {actual}"
1337 );
1338 } else {
1339 invariant_violation!(
1340 "Unauthorized runtime Balance accumulator event at address {address} for \
1341 type {type_tag}: net change {actual} (no input reservation, no PTB-emitted \
1342 events)"
1343 );
1344 }
1345 }
1346
1347 Ok(())
1348 }
1349
1350 pub fn check_sui_conserved_expensive(
1363 &self,
1364 gas_summary: &GasCostSummary,
1365 advance_epoch_gas_summary: Option<(u64, u64)>,
1366 layout_resolver: &mut impl LayoutResolver,
1367 ) -> Result<(), ExecutionError> {
1368 let mut total_input_sui = 0;
1370 let mut total_output_sui = 0;
1372
1373 total_input_sui += self.execution_results.settlement_input_sui;
1377 total_output_sui += self.execution_results.settlement_output_sui;
1378
1379 for (id, input, output) in self.get_modified_objects() {
1380 if let Some(input) = input {
1381 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1382 }
1383 if let Some(object) = output {
1384 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1385 make_invariant_violation!(
1386 "Failed looking up output SUI in SUI conservation checking for \
1387 mutated type {:?}: {e:#?}",
1388 object.struct_tag(),
1389 )
1390 })?;
1391 }
1392 }
1393
1394 for event in &self.execution_results.accumulator_events {
1395 let (input, output) = event.total_sui_in_event();
1396 total_input_sui += input;
1397 total_output_sui += output;
1398 }
1399
1400 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1405 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1406 total_input_sui += epoch_fees;
1407 total_output_sui += epoch_rebates;
1408 }
1409 if total_input_sui != total_output_sui {
1410 return Err(ExecutionError::invariant_violation(format!(
1411 "SUI conservation failed: input={}, output={}, \
1412 this transaction either mints or burns SUI",
1413 total_input_sui, total_output_sui,
1414 )));
1415 }
1416 Ok(())
1417 }
1418}
1419
1420impl ChildObjectResolver for TemporaryStore<'_> {
1421 fn read_child_object(
1422 &self,
1423 parent: &ObjectID,
1424 child: &ObjectID,
1425 child_version_upper_bound: SequenceNumber,
1426 ) -> SuiResult<Option<Object>> {
1427 let obj_opt = self.execution_results.written_objects.get(child);
1428 if obj_opt.is_some() {
1429 Ok(obj_opt.cloned())
1430 } else {
1431 let _scope = monitored_scope("Execution::read_child_object");
1432 self.store
1433 .read_child_object(parent, child, child_version_upper_bound)
1434 }
1435 }
1436
1437 fn get_object_received_at_version(
1438 &self,
1439 owner: &ObjectID,
1440 receiving_object_id: &ObjectID,
1441 receive_object_at_version: SequenceNumber,
1442 epoch_id: EpochId,
1443 ) -> SuiResult<Option<Object>> {
1444 debug_assert!(
1447 !self
1448 .execution_results
1449 .written_objects
1450 .contains_key(receiving_object_id)
1451 );
1452 debug_assert!(
1453 !self
1454 .execution_results
1455 .deleted_object_ids
1456 .contains(receiving_object_id)
1457 );
1458 self.store.get_object_received_at_version(
1459 owner,
1460 receiving_object_id,
1461 receive_object_at_version,
1462 epoch_id,
1463 )
1464 }
1465}
1466
1467fn was_object_mutated(object: &Object, original: &Object) -> bool {
1470 let data_equal = match (&object.data, &original.data) {
1471 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1472 (Data::Package(a), Data::Package(b)) => a == b,
1475 _ => false,
1476 };
1477
1478 let owner_equal = match (&object.owner, &original.owner) {
1479 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1483 (
1484 Owner::ConsensusAddressOwner { owner: a, .. },
1485 Owner::ConsensusAddressOwner { owner: b, .. },
1486 ) => a == b,
1487 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1488 (Owner::Immutable, Owner::Immutable) => true,
1489 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1490
1491 (Owner::AddressOwner(_), _)
1494 | (Owner::Immutable, _)
1495 | (Owner::ObjectOwner(_), _)
1496 | (Owner::Shared { .. }, _)
1497 | (Owner::ConsensusAddressOwner { .. }, _) => false,
1498 };
1499
1500 !data_equal || !owner_equal
1501}
1502
1503impl Storage for TemporaryStore<'_> {
1504 fn reset(&mut self) {
1505 self.drop_writes();
1506 }
1507
1508 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1509 TemporaryStore::read_object(self, id)
1510 }
1511
1512 fn record_execution_results(
1514 &mut self,
1515 results: ExecutionResults,
1516 ) -> Result<(), ExecutionError> {
1517 let ExecutionResults::V2(mut results) = results else {
1518 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1519 };
1520
1521 let mut to_remove = Vec::new();
1523 for (id, original) in &self.non_exclusive_input_original_versions {
1524 if results
1526 .written_objects
1527 .get(id)
1528 .map(|obj| was_object_mutated(obj, original))
1529 .unwrap_or(true)
1530 {
1531 return Err(ExecutionError::new_with_source(
1532 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1533 "Non-exclusive write input object has been modified or deleted",
1534 ));
1535 }
1536 to_remove.push(*id);
1537 }
1538
1539 for id in to_remove {
1540 results.written_objects.remove(&id);
1541 results.modified_objects.remove(&id);
1542 }
1543
1544 let event_start = self.execution_results.accumulator_events.len();
1550 self.execution_results.merge_results(
1551 results, true, true,
1552 )?;
1553 let event_end = self.execution_results.accumulator_events.len();
1554 debug_assert!(
1555 event_start <= event_end,
1556 "merge_results should not shrink accumulator_events"
1557 );
1558 let (event_start, event_end) = (event_start.min(event_end), event_start.max(event_end));
1559 let range = event_start..event_end;
1560 match self.ptb_emitted_accumulator_event_ranges.last_mut() {
1561 Some(last) if last.end == range.start => last.end = range.end,
1563 _ => self.ptb_emitted_accumulator_event_ranges.push(range),
1564 }
1565
1566 Ok(())
1567 }
1568
1569 fn save_loaded_runtime_objects(
1570 &mut self,
1571 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1572 ) {
1573 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1574 }
1575
1576 fn save_wrapped_object_containers(
1577 &mut self,
1578 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1579 ) {
1580 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1581 }
1582
1583 fn check_coin_deny_list(
1584 &self,
1585 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1586 ) -> DenyListResult {
1587 let result = check_coin_deny_list_v2_during_execution(
1588 receiving_funds_type_and_owners,
1589 self.cur_epoch,
1590 self.store.as_object_store(),
1591 );
1592 if result.num_non_gas_coin_owners > 0
1595 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1596 {
1597 self.loaded_per_epoch_config_objects
1598 .write()
1599 .insert(SUI_DENY_LIST_OBJECT_ID);
1600 }
1601 result
1602 }
1603
1604 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1605 TemporaryStore::save_generated_object_ids(self, generated_ids)
1606 }
1607}
1608
1609impl BackingPackageStore for TemporaryStore<'_> {
1610 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1611 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1618 Ok(Some(PackageObject::new(obj.clone())))
1619 } else {
1620 self.store.get_package_object(package_id).inspect(|obj| {
1621 if let Some(v) = obj
1623 && !self
1624 .runtime_packages_loaded_from_db
1625 .read()
1626 .contains_key(package_id)
1627 {
1628 self.runtime_packages_loaded_from_db
1633 .write()
1634 .insert(*package_id, v.clone());
1635 }
1636 })
1637 }
1638 }
1639}
1640
1641impl ParentSync for TemporaryStore<'_> {
1642 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1643 unreachable!("Never called in newer protocol versions")
1644 }
1645}