1use crate::execution_mode::ExecutionMode;
5use crate::gas_charger::GasCharger;
6use move_vm_runtime::runtime::MoveRuntime;
7use mysten_common::{ZipDebugEqIteratorExt, debug_fatal};
8use mysten_metrics::monitored_scope;
9use parking_lot::RwLock;
10use std::cell::RefCell;
11use std::collections::{BTreeMap, BTreeSet, HashSet};
12use std::sync::Arc;
13use sui_protocol_config::ProtocolConfig;
14use sui_types::accumulator_event::AccumulatorEvent;
15use sui_types::accumulator_root::{
16 AccumulatorObjId, AccumulatorValue as AccumulatorRootValue, EmptyUnsettledObjectFunds,
17 UnsettledObjectFundsRead,
18};
19use sui_types::base_types::{SystemObjectVersions, VersionDigest};
20use sui_types::coin_reservation::ParsedDigest;
21use sui_types::committee::EpochId;
22use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
23use sui_types::effects::{
24 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
25 TransactionEffectsV2, TransactionEvents,
26};
27use sui_types::error::SuiErrorKind;
28use sui_types::execution::{
29 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
30};
31use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
32use sui_types::inner_temporary_store::InnerTemporaryStore;
33use sui_types::object::Data;
34use sui_types::storage::{
35 BackingStore, DenyListResult, ImplicitSystemObjectResolver, PackageObject,
36};
37use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
38use sui_types::transaction::{Command, GasData, TransactionKind, is_gasless_transaction};
39use sui_types::{
40 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_CLOCK_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID,
41 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
42 digests::ObjectDigest,
43 effects::EffectsObjectChange,
44 error::{ExecutionError, SuiResult},
45 gas::GasCostSummary,
46 object::Object,
47 object::Owner,
48 storage::{BackingPackageStore, RuntimeObjectResolver, Storage},
49 transaction::InputObjects,
50};
51use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
52
53pub(crate) mod invariants;
54use invariants::InvariantChecker;
55
56type AllowanceIds = BTreeMap<(SuiAddress, TypeTag), Vec<ObjectID>>;
58
59#[derive(Default)]
60struct PostExecutionCheckInputs {
61 input_reservations: BTreeMap<(SuiAddress, TypeTag), u64>,
64 allowance_ids: AllowanceIds,
67 advance_epoch_gas_summary: Option<(u64, u64)>,
70 is_genesis: bool,
72 declared_packages: Option<Vec<(usize, BTreeSet<ObjectID>)>>,
75}
76
77impl PostExecutionCheckInputs {
78 fn new(transaction: (&TransactionKind, &GasData, SuiAddress), enable_gasless: bool) -> Self {
79 let (transaction_kind, gas_data, transaction_signer) = transaction;
80 let (input_reservations, allowance_ids) = compute_input_reservations(
81 transaction_kind,
82 gas_data,
83 transaction_signer,
84 enable_gasless,
85 );
86 Self {
87 input_reservations,
88 allowance_ids,
89 advance_epoch_gas_summary: transaction_kind.get_advance_epoch_tx_gas_summary(),
90 is_genesis: matches!(transaction_kind, TransactionKind::Genesis(_)),
91 declared_packages: declared_packages(transaction_kind),
92 }
93 }
94}
95
96pub struct TemporaryStore<'backing> {
97 store: &'backing dyn BackingStore,
103 tx_digest: TransactionDigest,
104 input_objects: BTreeMap<ObjectID, Object>,
105 post_execution_check_inputs: PostExecutionCheckInputs,
108
109 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
112
113 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
114 lamport_timestamp: SequenceNumber,
116 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
119 execution_results: ExecutionResultsV2,
120 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
122 protocol_config: &'backing ProtocolConfig,
123
124 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
127
128 receiving_objects: Vec<ObjectRef>,
131
132 cur_epoch: EpochId,
135
136 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
139
140 invariants: InvariantChecker,
142
143 system_object_versions: SystemObjectVersions,
145
146 loaded_system_objects: RefCell<BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>>,
150
151 unsettled_object_funds: &'backing dyn UnsettledObjectFundsRead,
152}
153
154impl<'backing> TemporaryStore<'backing> {
155 #[allow(clippy::too_many_arguments)]
158 pub(crate) fn new(
159 store: &'backing dyn BackingStore,
160 input_objects: InputObjects,
161 receiving_objects: Vec<ObjectRef>,
162 tx_digest: TransactionDigest,
163 protocol_config: &'backing ProtocolConfig,
164 cur_epoch: EpochId,
165 system_object_versions: SystemObjectVersions,
166 transaction: (&TransactionKind, &GasData, SuiAddress),
167 unsettled_object_funds: &'backing dyn UnsettledObjectFundsRead,
168 ) -> Self {
169 let post_execution_check_inputs =
170 PostExecutionCheckInputs::new(transaction, protocol_config.enable_gasless());
171 Self::new_with_input_objects(
172 store,
173 input_objects,
174 receiving_objects,
175 tx_digest,
176 protocol_config,
177 cur_epoch,
178 system_object_versions,
179 post_execution_check_inputs,
180 unsettled_object_funds,
181 )
182 }
183
184 pub(crate) fn new_for_genesis_state_update(
185 store: &'backing dyn BackingStore,
186 tx_digest: TransactionDigest,
187 protocol_config: &'backing ProtocolConfig,
188 ) -> Self {
189 Self::new_with_input_objects(
190 store,
191 InputObjects::new(vec![]),
192 vec![],
193 tx_digest,
194 protocol_config,
195 0,
196 SystemObjectVersions::empty(),
197 PostExecutionCheckInputs {
198 is_genesis: true,
199 ..Default::default()
200 },
201 &EmptyUnsettledObjectFunds,
204 )
205 }
206
207 fn new_with_input_objects(
208 store: &'backing dyn BackingStore,
209 input_objects: InputObjects,
210 receiving_objects: Vec<ObjectRef>,
211 tx_digest: TransactionDigest,
212 protocol_config: &'backing ProtocolConfig,
213 cur_epoch: EpochId,
214 system_object_versions: SystemObjectVersions,
215 post_execution_check_inputs: PostExecutionCheckInputs,
216 unsettled_object_funds: &'backing dyn UnsettledObjectFundsRead,
217 ) -> Self {
218 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
219 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
220
221 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
222 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
223 let objects = input_objects.into_object_map();
224 #[cfg(debug_assertions)]
225 {
226 assert!(
228 objects
229 .keys()
230 .collect::<HashSet<_>>()
231 .intersection(
232 &receiving_objects
233 .iter()
234 .map(|oref| &oref.0)
235 .collect::<HashSet<_>>()
236 )
237 .next()
238 .is_none()
239 );
240 }
241 Self {
242 store,
243 tx_digest,
244 input_objects: objects,
245 non_exclusive_input_original_versions,
246 stream_ended_consensus_objects,
247 lamport_timestamp,
248 mutable_input_refs,
249 execution_results: ExecutionResultsV2::default(),
250 protocol_config,
251 loaded_runtime_objects: BTreeMap::new(),
252 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
253 receiving_objects,
254 cur_epoch,
255 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
256 post_execution_check_inputs,
257 invariants: InvariantChecker::default(),
258 system_object_versions,
259 loaded_system_objects: RefCell::new(BTreeMap::new()),
260 unsettled_object_funds,
261 }
262 }
263
264 pub fn load_implicitly_read_system_object(&self, object_id: &ObjectID) -> Option<Object> {
270 let version = match self.system_object_versions.get(object_id) {
271 Some(version) => version,
272 None => {
273 debug_fatal!(
274 "system_object_versions must contain entry for object_id: {:?}",
275 object_id
276 );
277 return None;
278 }
279 };
280 let object = match self.input_objects.get(object_id) {
281 Some(object) => {
284 if object.version() != version.version {
285 debug_fatal!(
286 "declared input {object_id} at version {:?} disagrees with its implicit \
287 read version {:?}",
288 object.version(),
289 version.version
290 );
291 return None;
292 }
293 object.clone()
294 }
295 None => self
296 .store
297 .load_implicitly_read_system_object(object_id, version)?,
300 };
301 self.loaded_system_objects
304 .borrow_mut()
305 .insert(*object_id, (object.version(), object.digest()));
306 Some(object)
307 }
308
309 pub fn unsettled_object_funds(&self) -> &dyn UnsettledObjectFundsRead {
310 self.unsettled_object_funds
311 }
312
313 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
315 &self.input_objects
316 }
317
318 pub fn update_object_version_and_prev_tx(&mut self) {
319 self.execution_results.update_version_and_previous_tx(
320 self.lamport_timestamp,
321 self.tx_digest,
322 &self.input_objects,
323 self.protocol_config.reshare_at_same_initial_version(),
324 );
325
326 #[cfg(debug_assertions)]
327 {
328 self.check_invariants();
329 }
330 }
331
332 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
333 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
334 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
335 for event in &self.execution_results.accumulator_events {
336 match &event.write.value {
337 AccumulatorValue::Integer(amount) => match event.write.operation {
338 AccumulatorOperation::Split => {
339 let entry = running_net_withdraws
340 .entry(event.accumulator_obj)
341 .or_default();
342 *entry += *amount as i128;
343 if *entry > 0 {
344 let max_entry = running_max_withdraws
345 .entry(event.accumulator_obj)
346 .or_default();
347 *max_entry = (*max_entry).max(*entry as u128);
348 }
349 }
350 AccumulatorOperation::Merge => {
351 let entry = running_net_withdraws
352 .entry(event.accumulator_obj)
353 .or_default();
354 *entry -= *amount as i128;
355 }
356 },
357 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
358 }
359 }
360 running_max_withdraws
361 }
362
363 pub(crate) fn check_accumulator_amounts_representable(&self) -> Result<(), ExecutionError> {
391 let supply = sui_types::gas_coin::TOTAL_SUPPLY_MIST as u128;
392 let mut merge_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
393 let mut split_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
394 let mut total_sui_split: u128 = 0;
396 for event in &self.execution_results.accumulator_events {
397 let AccumulatorValue::Integer(amount) = event.write.value else {
398 continue;
399 };
400 let amount = amount as u128;
401 let is_sui = sui_types::gas_coin::GasCoin::is_gas_balance_type(&event.write.address.ty);
404 let limit = if is_sui { supply } else { u64::MAX as u128 };
405 let total = match event.write.operation {
406 AccumulatorOperation::Merge => {
407 merge_totals.entry(event.accumulator_obj).or_default()
408 }
409 AccumulatorOperation::Split => {
410 split_totals.entry(event.accumulator_obj).or_default()
411 }
412 };
413 *total += amount;
414 if *total > limit {
415 return Err(ExecutionError::new_with_source(
416 ExecutionErrorKind::CoinBalanceOverflow,
417 format!(
418 "accumulator balance change for {:?} exceeds the representable limit \
419 (gross total {}, limit {})",
420 event.accumulator_obj, *total, limit
421 ),
422 ));
423 }
424 if is_sui && matches!(event.write.operation, AccumulatorOperation::Split) {
425 total_sui_split += amount;
426 if total_sui_split > supply {
427 return Err(ExecutionError::new_with_source(
428 ExecutionErrorKind::CoinBalanceOverflow,
429 format!(
430 "total SUI withdrawn across all accumulators ({total_sui_split}) \
431 exceeds the total supply ({supply})"
432 ),
433 ));
434 }
435 }
436 }
437 Ok(())
438 }
439
440 fn merge_accumulator_events(&mut self) {
442 self.execution_results.accumulator_events = self
443 .execution_results
444 .accumulator_events
445 .iter()
446 .fold(
447 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
448 |mut map, event| {
449 map.entry(event.accumulator_obj)
450 .or_default()
451 .push(event.write.clone());
452 map
453 },
454 )
455 .into_iter()
456 .map(|(obj_id, writes)| {
457 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
458 })
459 .collect();
460 }
461
462 pub fn into_inner(
464 self,
465 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
466 ) -> InnerTemporaryStore {
467 let results = self.execution_results;
468 InnerTemporaryStore {
469 input_objects: self.input_objects,
470 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
471 mutable_inputs: self.mutable_input_refs,
472 written: results.written_objects,
473 events: TransactionEvents {
474 data: results.user_events,
475 },
476 accumulator_events: results.accumulator_events,
477 loaded_runtime_objects: self.loaded_runtime_objects,
478 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
479 lamport_version: self.lamport_timestamp,
480 binary_config: self.protocol_config.binary_config(None),
481 accumulator_running_max_withdraws,
482 }
483 }
484
485 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
489 let mut to_be_updated = vec![];
490 for id in self.mutable_input_refs.keys() {
492 if !self.execution_results.modified_objects.contains(id) {
493 to_be_updated.push(self.input_objects[id].clone());
497 }
498 }
499 for object in to_be_updated {
500 self.mutate_input_object(object.clone());
502 }
503 }
504
505 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
506 let results = &self.execution_results;
507 let all_ids = results
508 .created_object_ids
509 .iter()
510 .chain(&results.deleted_object_ids)
511 .chain(&results.modified_objects)
512 .chain(results.written_objects.keys())
513 .collect::<BTreeSet<_>>();
514 all_ids
515 .into_iter()
516 .map(|id| {
517 (
518 *id,
519 EffectsObjectChange::new(
520 self.get_object_modified_at(id)
521 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
522 results.written_objects.get(id),
523 results.created_object_ids.contains(id),
524 results.deleted_object_ids.contains(id),
525 ),
526 )
527 })
528 .chain(results.accumulator_events.iter().cloned().map(
529 |AccumulatorEvent {
530 accumulator_obj,
531 write,
532 }| {
533 (
534 *accumulator_obj.inner(),
535 EffectsObjectChange::new_from_accumulator_write(write),
536 )
537 },
538 ))
539 .collect()
540 }
541
542 pub fn into_effects(
543 mut self,
544 shared_object_refs: Vec<SharedInput>,
545 transaction_digest: &TransactionDigest,
546 mut transaction_dependencies: BTreeSet<TransactionDigest>,
547 gas_cost_summary: GasCostSummary,
548 status: ExecutionStatus,
549 gas_coin: Option<ObjectID>,
550 epoch: EpochId,
551 ) -> (InnerTemporaryStore, TransactionEffects) {
552 for (id, obj) in &self.execution_results.written_objects {
555 assert!(
556 !matches!(obj.owner, Owner::Party { .. }),
557 "Party-owned objects are not yet supported (object {id})"
558 );
559 }
560
561 self.update_object_version_and_prev_tx();
562 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
564 self.merge_accumulator_events();
565
566 if !self.protocol_config.disable_effects_tx_dependencies() {
567 for (id, expected_version, expected_digest) in &self.receiving_objects {
569 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
570 let loaded_via_receive = obj_meta.version == *expected_version
573 && obj_meta.digest == *expected_digest
574 && obj_meta.owner.is_address_owned();
575 if loaded_via_receive {
576 transaction_dependencies.insert(obj_meta.previous_transaction);
577 }
578 }
579 }
580 }
581
582 assert!(self.protocol_config.enable_effects_v2());
583
584 let object_changes = self.get_object_changes();
585
586 let lamport_version = self.lamport_timestamp;
587 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
589 let loaded_system_objects = self.loaded_system_objects.borrow().clone();
590 let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
591 shared_object_refs,
592 loaded_per_epoch_config_objects,
593 &object_changes,
594 loaded_system_objects,
595 );
596 let inner = self.into_inner(accumulator_running_max_withdraws);
597
598 let effects = TransactionEffects::new_from_execution_v2(
599 status,
600 epoch,
601 gas_cost_summary,
602 unchanged_consensus_objects,
603 *transaction_digest,
604 lamport_version,
605 object_changes,
606 gas_coin,
607 if inner.events.data.is_empty() {
608 None
609 } else {
610 Some(inner.events.digest())
611 },
612 transaction_dependencies.into_iter().collect(),
613 );
614
615 (inner, effects)
616 }
617
618 #[cfg(debug_assertions)]
620 fn check_invariants(&self) {
621 debug_assert!(
623 {
624 self.execution_results
625 .written_objects
626 .keys()
627 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
628 },
629 "Object both written and deleted."
630 );
631
632 debug_assert!(
634 {
635 self.mutable_input_refs
636 .keys()
637 .all(|id| self.execution_results.modified_objects.contains(id))
638 },
639 "Mutable input not modified."
640 );
641
642 debug_assert!(
643 {
644 self.execution_results
645 .written_objects
646 .values()
647 .all(|obj| obj.previous_transaction == self.tx_digest)
648 },
649 "Object previous transaction not properly set",
650 );
651 }
652
653 pub fn mutate_input_object(&mut self, object: Object) {
655 let id = object.id();
656 debug_assert!(self.input_objects.contains_key(&id));
657 debug_assert!(!object.is_immutable());
658 self.execution_results.modified_objects.insert(id);
659 self.execution_results.written_objects.insert(id, object);
660 }
661
662 pub fn mutate_new_or_input_object(&mut self, object: Object) {
663 let id = object.id();
664 debug_assert!(!object.is_immutable());
665 if self.input_objects.contains_key(&id) {
666 self.execution_results.modified_objects.insert(id);
667 }
668 self.execution_results.written_objects.insert(id, object);
669 }
670
671 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
675 let id = new_object.id();
676 let old_ref = old_object.compute_object_reference();
677 debug_assert_eq!(old_ref.0, id);
678 self.loaded_runtime_objects.insert(
679 id,
680 DynamicallyLoadedObjectMetadata {
681 version: old_ref.1,
682 digest: old_ref.2,
683 owner: old_object.owner.clone(),
684 storage_rebate: old_object.storage_rebate,
685 previous_transaction: old_object.previous_transaction,
686 },
687 );
688 self.execution_results.modified_objects.insert(id);
689 self.execution_results
690 .written_objects
691 .insert(id, new_object);
692 }
693
694 pub fn upgrade_system_package(&mut self, package: Object) {
698 let id = package.id();
699 assert!(package.is_package() && is_system_package(id));
700 self.execution_results.modified_objects.insert(id);
701 self.execution_results.written_objects.insert(id, package);
702 }
703
704 pub fn create_object(&mut self, object: Object) {
706 debug_assert!(
711 object.is_immutable() || object.version() == SequenceNumber::MIN,
712 "Created mutable objects should not have a version set",
713 );
714 let id = object.id();
715 self.execution_results.created_object_ids.insert(id);
716 self.execution_results.written_objects.insert(id, object);
717 }
718
719 pub fn delete_input_object(&mut self, id: &ObjectID) {
721 debug_assert!(!self.execution_results.written_objects.contains_key(id));
723 debug_assert!(self.input_objects.contains_key(id));
724 self.execution_results.modified_objects.insert(*id);
725 self.execution_results.deleted_object_ids.insert(*id);
726 }
727
728 pub fn drop_writes(&mut self) {
729 self.execution_results.drop_writes();
730 self.invariants = InvariantChecker::default();
731 }
732
733 pub(crate) fn into_bump_only(self) -> Self {
738 let Self {
739 store,
741 tx_digest,
742 input_objects,
743 non_exclusive_input_original_versions,
744 stream_ended_consensus_objects,
745 lamport_timestamp,
746 mutable_input_refs,
747 receiving_objects,
748 cur_epoch,
749 protocol_config,
750 post_execution_check_inputs,
751 system_object_versions,
752 loaded_runtime_objects,
754 runtime_packages_loaded_from_db,
755 loaded_per_epoch_config_objects,
756 loaded_system_objects,
757 unsettled_object_funds,
758 execution_results: _,
760 invariants: _,
761 } = self;
762 let mut bump_only = Self {
763 store,
764 tx_digest,
765 input_objects,
766 non_exclusive_input_original_versions,
767 stream_ended_consensus_objects,
768 lamport_timestamp,
769 mutable_input_refs,
770 receiving_objects,
771 cur_epoch,
772 protocol_config,
773 loaded_runtime_objects,
774 runtime_packages_loaded_from_db,
775 loaded_per_epoch_config_objects,
776 post_execution_check_inputs,
777 system_object_versions,
778 loaded_system_objects,
779 unsettled_object_funds,
780 execution_results: ExecutionResultsV2::default(),
781 invariants: InvariantChecker::default(),
782 };
783 bump_only.ensure_active_inputs_mutated();
785 bump_only
786 }
787
788 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
789 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
791 self.execution_results
792 .written_objects
793 .get(id)
794 .or_else(|| self.input_objects.get(id))
795 }
796
797 pub fn save_loaded_runtime_objects(
798 &mut self,
799 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
800 ) {
801 #[cfg(debug_assertions)]
802 {
803 for (id, v1) in &loaded_runtime_objects {
804 if let Some(v2) = self.loaded_runtime_objects.get(id) {
805 assert_eq!(v1, v2);
806 }
807 }
808 for (id, v1) in &self.loaded_runtime_objects {
809 if let Some(v2) = loaded_runtime_objects.get(id) {
810 assert_eq!(v1, v2);
811 }
812 }
813 }
814 self.loaded_runtime_objects.extend(loaded_runtime_objects);
817 }
818
819 pub fn save_wrapped_object_containers(
820 &mut self,
821 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
822 ) {
823 self.invariants
824 .save_wrapped_object_containers(wrapped_object_containers);
825 }
826
827 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
828 self.invariants.save_generated_object_ids(generated_ids);
829 }
830
831 pub fn estimate_effects_size_upperbound(&self) -> usize {
832 TransactionEffects::estimate_effects_size_upperbound_v2(
833 self.execution_results.written_objects.len(),
834 self.execution_results.modified_objects.len(),
835 self.input_objects.len(),
836 )
837 }
838
839 pub fn written_objects_size(&self) -> usize {
840 self.execution_results
841 .written_objects
842 .values()
843 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
844 }
845
846 pub(crate) fn check_gasless_execution_requirements(&self) -> Result<(), String> {
849 use sui_types::balance::Balance;
850
851 let withdrawal_reservations = self
854 .post_execution_check_inputs
855 .input_reservations
856 .iter()
857 .filter_map(|((owner, ty), amount)| {
858 Balance::maybe_get_balance_type_param(ty)
859 .map(|coin_type| ((*owner, coin_type), *amount))
860 })
861 .collect();
862 self.check_gasless_execution_requirements_with_reservations(Some(&withdrawal_reservations))
863 }
864
865 pub(crate) fn check_gasless_execution_requirements_with_reservations(
876 &self,
877 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
878 ) -> Result<(), String> {
879 if !self.execution_results.written_objects.is_empty() {
880 return Err("Gasless transactions cannot create or mutate objects".to_string());
881 }
882
883 let input_coin_ids: BTreeSet<ObjectID> = self
884 .input_objects
885 .iter()
886 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
887 .map(|(id, _)| *id)
888 .collect();
889 if self.execution_results.deleted_object_ids != input_coin_ids {
890 return Err(format!(
891 "Gasless transaction must destroy exactly its input Coins. \
892 Expected: {input_coin_ids:?}, deleted: {:?}",
893 self.execution_results.deleted_object_ids
894 ));
895 }
896
897 let allowed_types =
898 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
899
900 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
903 &self.execution_results.accumulator_events,
904 )
905 .fold(
906 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
907 |mut totals, (address, token_type, signed_amount)| {
908 *totals.entry((address, token_type)).or_default() += signed_amount;
909 totals
910 },
911 );
912
913 for ((recipient, token_type), net_amount) in &net_totals {
914 if *net_amount <= 0 {
915 continue;
916 }
917 if let Some(&min_amount) = allowed_types.get(token_type)
918 && *net_amount < i128::from(min_amount)
919 {
920 return Err(format!(
921 "Gasless transfer of {net_amount} to {recipient} is below \
922 minimum {min_amount} for token type {token_type}"
923 ));
924 }
925 }
926
927 if let Some(reservations) = withdrawal_reservations {
928 for ((owner, token_type), &reserved) in reservations {
929 let net = net_totals
930 .get(&(*owner, token_type.clone()))
931 .copied()
932 .unwrap_or(0);
933 let remaining = (reserved as i128).saturating_add(net);
934 if remaining > 0
935 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
936 && min_balance_remaining > 0
937 && remaining < min_balance_remaining as i128
938 {
939 return Err(format!(
940 "Gasless withdrawal leaves {remaining} unused for {owner}, \
941 below minimum {min_balance_remaining} for token type {token_type}"
942 ));
943 }
944 }
945 }
946
947 Ok(())
948 }
949
950 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
955 if unmetered_storage_rebate == 0 {
956 return;
960 }
961 tracing::debug!(
962 "Amount of unmetered storage rebate from system tx: {:?}",
963 unmetered_storage_rebate
964 );
965 let mut system_state_wrapper = self
966 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
967 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
968 .clone();
969 assert_eq!(system_state_wrapper.storage_rebate, 0);
972 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
973 self.mutate_input_object(system_state_wrapper);
974 }
975
976 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
978 self.execution_results.accumulator_events.push(event);
979 }
980
981 fn get_object_modified_at(
987 &self,
988 object_id: &ObjectID,
989 ) -> Option<DynamicallyLoadedObjectMetadata> {
990 if self.execution_results.modified_objects.contains(object_id) {
991 Some(
992 self.mutable_input_refs
993 .get(object_id)
994 .map(
995 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
996 version: *version,
997 digest: *digest,
998 owner: owner.clone(),
999 storage_rebate: self.input_objects[object_id].storage_rebate,
1001 previous_transaction: self.input_objects[object_id]
1002 .previous_transaction,
1003 },
1004 )
1005 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
1006 .unwrap_or_else(|| {
1007 debug_assert!(is_system_package(*object_id));
1008 let package_obj =
1009 self.store.get_package_object(object_id).unwrap().unwrap();
1010 let obj = package_obj.object();
1011 DynamicallyLoadedObjectMetadata {
1012 version: obj.version(),
1013 digest: obj.digest(),
1014 owner: obj.owner.clone(),
1015 storage_rebate: obj.storage_rebate,
1016 previous_transaction: obj.previous_transaction,
1017 }
1018 }),
1019 )
1020 } else {
1021 None
1022 }
1023 }
1024
1025 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
1026 self.protocol_config
1027 }
1028
1029 pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
1032 &self,
1033 move_vm: &Arc<MoveRuntime>,
1034 enable_expensive_checks: bool,
1035 cost_summary: &GasCostSummary,
1036 ) -> Result<(), ExecutionError> {
1037 self.invariants.check_conservation_invariants::<Mode>(
1038 self,
1039 move_vm,
1040 enable_expensive_checks,
1041 cost_summary,
1042 )
1043 }
1044
1045 pub(crate) fn check_published_packages(&self) -> Result<(), ExecutionError> {
1049 self.invariants.check_published_packages(self)
1050 }
1051
1052 pub(crate) fn check_ownership_invariants(
1053 &self,
1054 sender: &SuiAddress,
1055 sponsor: &Option<SuiAddress>,
1056 gas_charger: &GasCharger,
1057 is_epoch_change: bool,
1058 ) -> SuiResult<()> {
1059 self.invariants.check_ownership_invariants(
1060 self,
1061 sender,
1062 sponsor,
1063 gas_charger,
1064 is_epoch_change,
1065 )
1066 }
1067}
1068
1069impl TemporaryStore<'_> {
1070 pub(crate) fn collect_storage_and_rebate(
1077 &mut self,
1078 gas_charger: &mut GasCharger,
1079 ) -> Result<(), ExecutionError> {
1080 let old_storage_rebates: Vec<_> = self
1082 .execution_results
1083 .written_objects
1084 .keys()
1085 .map(|object_id| {
1086 self.get_object_modified_at(object_id)
1087 .map(|metadata| metadata.storage_rebate)
1088 .unwrap_or_default()
1089 })
1090 .collect();
1091 for (object, old_storage_rebate) in self
1092 .execution_results
1093 .written_objects
1094 .values_mut()
1095 .zip_debug_eq(old_storage_rebates)
1096 {
1097 let new_object_size = object.object_size_for_gas_metering();
1099 let new_storage_rebate = gas_charger
1101 .track_storage_mutation(object.id(), new_object_size, old_storage_rebate)
1102 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1103 object.storage_rebate = new_storage_rebate;
1104 }
1105
1106 self.collect_rebate(gas_charger)
1107 }
1108
1109 pub(crate) fn collect_rebate(
1110 &self,
1111 gas_charger: &mut GasCharger,
1112 ) -> Result<(), ExecutionError> {
1113 for object_id in &self.execution_results.modified_objects {
1114 if self
1115 .execution_results
1116 .written_objects
1117 .contains_key(object_id)
1118 {
1119 continue;
1120 }
1121 let storage_rebate = self
1123 .get_object_modified_at(object_id)
1124 .unwrap()
1126 .storage_rebate;
1127 gas_charger
1128 .track_storage_mutation(*object_id, 0, storage_rebate)
1129 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1130 }
1131 Ok(())
1132 }
1133
1134 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1135 &self,
1136 ) -> Result<(), Mode::Error> {
1137 assert_invariant!(
1138 self.execution_results
1139 .created_object_ids
1140 .iter()
1141 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1142 && !self.execution_results.modified_objects.contains(id)),
1143 "Created object IDs cannot also be deleted or modified"
1144 );
1145 assert_invariant!(
1146 self.execution_results.modified_objects.iter().all(|id| {
1147 self.mutable_input_refs.contains_key(id)
1148 || self.loaded_runtime_objects.contains_key(id)
1149 || is_system_package(*id)
1150 }),
1151 "A modified object must be either a mutable input, a loaded child object, or a system package"
1152 );
1153 Ok(())
1154 }
1155}
1156impl TemporaryStore<'_> {
1161 pub fn advance_epoch_safe_mode(
1162 &mut self,
1163 params: &AdvanceEpochParams,
1164 protocol_config: &ProtocolConfig,
1165 ) {
1166 let wrapper = get_sui_system_state_wrapper(self.store)
1167 .expect("System state wrapper object must exist");
1168 let (old_object, new_object) =
1169 wrapper.advance_epoch_safe_mode(params, self.store, protocol_config);
1170 self.mutate_child_object(old_object, new_object);
1171 }
1172}
1173
1174impl RuntimeObjectResolver for TemporaryStore<'_> {
1175 fn read_child_object(
1176 &self,
1177 parent: &ObjectID,
1178 child: &ObjectID,
1179 child_version_upper_bound: SequenceNumber,
1180 ) -> SuiResult<Option<Object>> {
1181 let obj_opt = self.execution_results.written_objects.get(child);
1182 if obj_opt.is_some() {
1183 Ok(obj_opt.cloned())
1184 } else {
1185 let _scope = monitored_scope("Execution::read_child_object");
1186 self.store
1187 .read_child_object(parent, child, child_version_upper_bound)
1188 }
1189 }
1190
1191 fn get_object_received_at_version(
1192 &self,
1193 owner: &ObjectID,
1194 receiving_object_id: &ObjectID,
1195 receive_object_at_version: SequenceNumber,
1196 epoch_id: EpochId,
1197 ) -> SuiResult<Option<Object>> {
1198 debug_assert!(
1201 !self
1202 .execution_results
1203 .written_objects
1204 .contains_key(receiving_object_id)
1205 );
1206 debug_assert!(
1207 !self
1208 .execution_results
1209 .deleted_object_ids
1210 .contains(receiving_object_id)
1211 );
1212 self.store.get_object_received_at_version(
1213 owner,
1214 receiving_object_id,
1215 receive_object_at_version,
1216 epoch_id,
1217 )
1218 }
1219}
1220
1221impl ImplicitSystemObjectResolver for TemporaryStore<'_> {
1222 fn object_available_balance(&self, owner: SuiAddress, type_: &TypeTag) -> SuiResult<u128> {
1226 let required_version = self
1227 .load_implicitly_read_system_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1228 .ok_or(SuiErrorKind::ExecutionInvariantViolation)?
1229 .version();
1230
1231 let settled = AccumulatorRootValue::load(self, Some(required_version), owner, type_)?
1232 .and_then(|value| value.as_u128())
1233 .unwrap_or(0);
1234
1235 let unsettled = self.unsettled_object_funds.get_unsettled_object_withdraw(
1236 &AccumulatorRootValue::get_field_id(owner, type_)?,
1237 required_version,
1238 );
1239 settled
1240 .checked_sub(unsettled)
1241 .ok_or_else(|| SuiErrorKind::ExecutionInvariantViolation.into())
1242 }
1243
1244 fn clock(&self) -> SuiResult<Object> {
1246 self.load_implicitly_read_system_object(&SUI_CLOCK_OBJECT_ID)
1247 .ok_or_else(|| SuiErrorKind::ExecutionInvariantViolation.into())
1248 }
1249}
1250
1251fn compute_input_reservations(
1260 transaction_kind: &TransactionKind,
1261 gas_data: &GasData,
1262 transaction_signer: SuiAddress,
1263 enable_gasless: bool,
1264) -> (BTreeMap<(SuiAddress, TypeTag), u64>, AllowanceIds) {
1265 use sui_types::balance::Balance;
1266 use sui_types::gas_coin::GAS;
1267 use sui_types::transaction::{Reservation, WithdrawFrom, is_gas_paid_from_address_balance};
1268
1269 let is_gasless = enable_gasless && is_gasless_transaction(gas_data, transaction_kind);
1270 let mut reservations: BTreeMap<(SuiAddress, TypeTag), u64> = BTreeMap::new();
1271 let mut allowance_ids = AllowanceIds::new();
1272 let sui_balance_type = Balance::type_tag(GAS::type_tag());
1273
1274 for arg in transaction_kind.get_funds_withdrawals() {
1275 let ty = arg.type_arg.to_type_tag();
1276 let owner = match arg.withdraw_from {
1277 WithdrawFrom::Sender => transaction_signer,
1278 WithdrawFrom::Sponsor => gas_data.owner,
1279 WithdrawFrom::SenderAllowance { funder, allowance } => {
1282 allowance_ids
1283 .entry((funder, ty.clone()))
1284 .or_default()
1285 .push(allowance);
1286 funder
1287 }
1288 };
1289 let Reservation::MaxAmountU64(reservation) = arg.reservation;
1290 let entry = reservations.entry((owner, ty)).or_insert(0);
1291 *entry = entry.saturating_add(reservation);
1292 }
1293
1294 if !is_gasless && is_gas_paid_from_address_balance(gas_data, transaction_kind) {
1297 let entry = reservations
1298 .entry((gas_data.owner, sui_balance_type.clone()))
1299 .or_insert(0);
1300 *entry = entry.saturating_add(gas_data.budget);
1301 }
1302
1303 for entry in &gas_data.payment {
1304 if let Ok(parsed) = ParsedDigest::try_from(entry.2) {
1305 let entry = reservations
1306 .entry((gas_data.owner, sui_balance_type.clone()))
1307 .or_insert(0);
1308 *entry = entry.saturating_add(parsed.reservation_amount());
1309 }
1310 }
1311
1312 (reservations, allowance_ids)
1313}
1314
1315fn declared_packages(
1318 transaction_kind: &TransactionKind,
1319) -> Option<Vec<(usize, BTreeSet<ObjectID>)>> {
1320 let TransactionKind::ProgrammableTransaction(pt) = transaction_kind else {
1321 return None;
1322 };
1323 Some(
1324 pt.commands
1325 .iter()
1326 .filter_map(|command| match command {
1327 Command::Publish(modules, dep_ids) | Command::Upgrade(modules, dep_ids, _, _) => {
1328 Some((modules.len(), dep_ids.iter().copied().collect()))
1329 }
1330 _ => None,
1331 })
1332 .collect(),
1333 )
1334}
1335
1336fn was_object_mutated(object: &Object, original: &Object) -> bool {
1339 let data_equal = match (&object.data, &original.data) {
1340 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1341 (Data::Package(a), Data::Package(b)) => a == b,
1344 _ => false,
1345 };
1346
1347 let owner_equal = match (&object.owner, &original.owner) {
1348 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1352 (
1353 Owner::ConsensusAddressOwner { owner: a, .. },
1354 Owner::ConsensusAddressOwner { owner: b, .. },
1355 ) => a == b,
1356 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1357 (Owner::Immutable, Owner::Immutable) => true,
1358 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1359 (
1360 Owner::Party {
1361 permissions: a,
1362 start_version: _,
1363 },
1364 Owner::Party {
1365 permissions: b,
1366 start_version: _,
1367 },
1368 ) => a == b,
1369
1370 (Owner::AddressOwner(_), _)
1373 | (Owner::Immutable, _)
1374 | (Owner::ObjectOwner(_), _)
1375 | (Owner::Shared { .. }, _)
1376 | (Owner::ConsensusAddressOwner { .. }, _)
1377 | (Owner::Party { .. }, _) => false,
1378 };
1379
1380 !data_equal || !owner_equal
1381}
1382
1383impl Storage for TemporaryStore<'_> {
1384 fn reset(&mut self) {
1385 self.drop_writes();
1386 }
1387
1388 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1389 TemporaryStore::read_object(self, id)
1390 }
1391
1392 fn record_execution_results(
1394 &mut self,
1395 results: ExecutionResults,
1396 ) -> Result<(), ExecutionError> {
1397 let ExecutionResults::V2(mut results) = results else {
1398 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1399 };
1400
1401 let mut to_remove = Vec::new();
1403 for (id, original) in &self.non_exclusive_input_original_versions {
1404 if results
1406 .written_objects
1407 .get(id)
1408 .map(|obj| was_object_mutated(obj, original))
1409 .unwrap_or(true)
1410 {
1411 return Err(ExecutionError::new_with_source(
1412 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1413 "Non-exclusive write input object has been modified or deleted",
1414 ));
1415 }
1416 to_remove.push(*id);
1417 }
1418
1419 for id in to_remove {
1420 results.written_objects.remove(&id);
1421 results.modified_objects.remove(&id);
1422 }
1423
1424 let event_start = self.execution_results.accumulator_events.len();
1430 self.execution_results.merge_results(
1431 results, true, true,
1432 )?;
1433 let event_end = self.execution_results.accumulator_events.len();
1434 self.invariants
1435 .record_ptb_event_range(event_start, event_end);
1436
1437 Ok(())
1438 }
1439
1440 fn save_loaded_runtime_objects(
1441 &mut self,
1442 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1443 ) {
1444 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1445 }
1446
1447 fn save_wrapped_object_containers(
1448 &mut self,
1449 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1450 ) {
1451 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1452 }
1453
1454 fn check_coin_deny_list(
1455 &self,
1456 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1457 ) -> DenyListResult {
1458 let result = check_coin_deny_list_v2_during_execution(
1459 receiving_funds_type_and_owners,
1460 self.cur_epoch,
1461 self.store,
1462 );
1463 if result.num_non_gas_coin_owners > 0
1466 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1467 {
1468 self.loaded_per_epoch_config_objects
1469 .write()
1470 .insert(SUI_DENY_LIST_OBJECT_ID);
1471 }
1472 result
1473 }
1474
1475 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1476 TemporaryStore::save_generated_object_ids(self, generated_ids)
1477 }
1478}
1479
1480impl BackingPackageStore for TemporaryStore<'_> {
1481 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1482 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1489 Ok(Some(PackageObject::new(obj.clone())))
1490 } else {
1491 self.store.get_package_object(package_id).inspect(|obj| {
1492 if let Some(v) = obj
1494 && !self
1495 .runtime_packages_loaded_from_db
1496 .read()
1497 .contains_key(package_id)
1498 {
1499 self.runtime_packages_loaded_from_db
1504 .write()
1505 .insert(*package_id, v.clone());
1506 }
1507 })
1508 }
1509 }
1510}