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_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 = self
281 .store
282 .load_implicitly_read_system_object(object_id, version)?;
285 self.loaded_system_objects
288 .borrow_mut()
289 .insert(*object_id, (object.version(), object.digest()));
290 Some(object)
291 }
292
293 pub fn unsettled_object_funds(&self) -> &dyn UnsettledObjectFundsRead {
294 self.unsettled_object_funds
295 }
296
297 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
299 &self.input_objects
300 }
301
302 pub fn update_object_version_and_prev_tx(&mut self) {
303 self.execution_results.update_version_and_previous_tx(
304 self.lamport_timestamp,
305 self.tx_digest,
306 &self.input_objects,
307 self.protocol_config.reshare_at_same_initial_version(),
308 );
309
310 #[cfg(debug_assertions)]
311 {
312 self.check_invariants();
313 }
314 }
315
316 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
317 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
318 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
319 for event in &self.execution_results.accumulator_events {
320 match &event.write.value {
321 AccumulatorValue::Integer(amount) => match event.write.operation {
322 AccumulatorOperation::Split => {
323 let entry = running_net_withdraws
324 .entry(event.accumulator_obj)
325 .or_default();
326 *entry += *amount as i128;
327 if *entry > 0 {
328 let max_entry = running_max_withdraws
329 .entry(event.accumulator_obj)
330 .or_default();
331 *max_entry = (*max_entry).max(*entry as u128);
332 }
333 }
334 AccumulatorOperation::Merge => {
335 let entry = running_net_withdraws
336 .entry(event.accumulator_obj)
337 .or_default();
338 *entry -= *amount as i128;
339 }
340 },
341 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
342 }
343 }
344 running_max_withdraws
345 }
346
347 pub(crate) fn check_accumulator_amounts_representable(&self) -> Result<(), ExecutionError> {
375 let supply = sui_types::gas_coin::TOTAL_SUPPLY_MIST as u128;
376 let mut merge_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
377 let mut split_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
378 let mut total_sui_split: u128 = 0;
380 for event in &self.execution_results.accumulator_events {
381 let AccumulatorValue::Integer(amount) = event.write.value else {
382 continue;
383 };
384 let amount = amount as u128;
385 let is_sui = sui_types::gas_coin::GasCoin::is_gas_balance_type(&event.write.address.ty);
388 let limit = if is_sui { supply } else { u64::MAX as u128 };
389 let total = match event.write.operation {
390 AccumulatorOperation::Merge => {
391 merge_totals.entry(event.accumulator_obj).or_default()
392 }
393 AccumulatorOperation::Split => {
394 split_totals.entry(event.accumulator_obj).or_default()
395 }
396 };
397 *total += amount;
398 if *total > limit {
399 return Err(ExecutionError::new_with_source(
400 ExecutionErrorKind::CoinBalanceOverflow,
401 format!(
402 "accumulator balance change for {:?} exceeds the representable limit \
403 (gross total {}, limit {})",
404 event.accumulator_obj, *total, limit
405 ),
406 ));
407 }
408 if is_sui && matches!(event.write.operation, AccumulatorOperation::Split) {
409 total_sui_split += amount;
410 if total_sui_split > supply {
411 return Err(ExecutionError::new_with_source(
412 ExecutionErrorKind::CoinBalanceOverflow,
413 format!(
414 "total SUI withdrawn across all accumulators ({total_sui_split}) \
415 exceeds the total supply ({supply})"
416 ),
417 ));
418 }
419 }
420 }
421 Ok(())
422 }
423
424 fn merge_accumulator_events(&mut self) {
426 self.execution_results.accumulator_events = self
427 .execution_results
428 .accumulator_events
429 .iter()
430 .fold(
431 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
432 |mut map, event| {
433 map.entry(event.accumulator_obj)
434 .or_default()
435 .push(event.write.clone());
436 map
437 },
438 )
439 .into_iter()
440 .map(|(obj_id, writes)| {
441 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
442 })
443 .collect();
444 }
445
446 pub fn into_inner(
448 self,
449 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
450 ) -> InnerTemporaryStore {
451 let results = self.execution_results;
452 InnerTemporaryStore {
453 input_objects: self.input_objects,
454 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
455 mutable_inputs: self.mutable_input_refs,
456 written: results.written_objects,
457 events: TransactionEvents {
458 data: results.user_events,
459 },
460 accumulator_events: results.accumulator_events,
461 loaded_runtime_objects: self.loaded_runtime_objects,
462 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
463 lamport_version: self.lamport_timestamp,
464 binary_config: self.protocol_config.binary_config(None),
465 accumulator_running_max_withdraws,
466 }
467 }
468
469 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
473 let mut to_be_updated = vec![];
474 for id in self.mutable_input_refs.keys() {
476 if !self.execution_results.modified_objects.contains(id) {
477 to_be_updated.push(self.input_objects[id].clone());
481 }
482 }
483 for object in to_be_updated {
484 self.mutate_input_object(object.clone());
486 }
487 }
488
489 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
490 let results = &self.execution_results;
491 let all_ids = results
492 .created_object_ids
493 .iter()
494 .chain(&results.deleted_object_ids)
495 .chain(&results.modified_objects)
496 .chain(results.written_objects.keys())
497 .collect::<BTreeSet<_>>();
498 all_ids
499 .into_iter()
500 .map(|id| {
501 (
502 *id,
503 EffectsObjectChange::new(
504 self.get_object_modified_at(id)
505 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
506 results.written_objects.get(id),
507 results.created_object_ids.contains(id),
508 results.deleted_object_ids.contains(id),
509 ),
510 )
511 })
512 .chain(results.accumulator_events.iter().cloned().map(
513 |AccumulatorEvent {
514 accumulator_obj,
515 write,
516 }| {
517 (
518 *accumulator_obj.inner(),
519 EffectsObjectChange::new_from_accumulator_write(write),
520 )
521 },
522 ))
523 .collect()
524 }
525
526 pub fn into_effects(
527 mut self,
528 shared_object_refs: Vec<SharedInput>,
529 transaction_digest: &TransactionDigest,
530 mut transaction_dependencies: BTreeSet<TransactionDigest>,
531 gas_cost_summary: GasCostSummary,
532 status: ExecutionStatus,
533 gas_coin: Option<ObjectID>,
534 epoch: EpochId,
535 ) -> (InnerTemporaryStore, TransactionEffects) {
536 for (id, obj) in &self.execution_results.written_objects {
539 assert!(
540 !matches!(obj.owner, Owner::Party { .. }),
541 "Party-owned objects are not yet supported (object {id})"
542 );
543 }
544
545 self.update_object_version_and_prev_tx();
546 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
548 self.merge_accumulator_events();
549
550 if !self.protocol_config.disable_effects_tx_dependencies() {
551 for (id, expected_version, expected_digest) in &self.receiving_objects {
553 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
554 let loaded_via_receive = obj_meta.version == *expected_version
557 && obj_meta.digest == *expected_digest
558 && obj_meta.owner.is_address_owned();
559 if loaded_via_receive {
560 transaction_dependencies.insert(obj_meta.previous_transaction);
561 }
562 }
563 }
564 }
565
566 assert!(self.protocol_config.enable_effects_v2());
567
568 let object_changes = self.get_object_changes();
569
570 let lamport_version = self.lamport_timestamp;
571 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
573 let loaded_system_objects = self.loaded_system_objects.borrow().clone();
574 let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
575 shared_object_refs,
576 loaded_per_epoch_config_objects,
577 &object_changes,
578 loaded_system_objects,
579 );
580 let inner = self.into_inner(accumulator_running_max_withdraws);
581
582 let effects = TransactionEffects::new_from_execution_v2(
583 status,
584 epoch,
585 gas_cost_summary,
586 unchanged_consensus_objects,
587 *transaction_digest,
588 lamport_version,
589 object_changes,
590 gas_coin,
591 if inner.events.data.is_empty() {
592 None
593 } else {
594 Some(inner.events.digest())
595 },
596 transaction_dependencies.into_iter().collect(),
597 );
598
599 (inner, effects)
600 }
601
602 #[cfg(debug_assertions)]
604 fn check_invariants(&self) {
605 debug_assert!(
607 {
608 self.execution_results
609 .written_objects
610 .keys()
611 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
612 },
613 "Object both written and deleted."
614 );
615
616 debug_assert!(
618 {
619 self.mutable_input_refs
620 .keys()
621 .all(|id| self.execution_results.modified_objects.contains(id))
622 },
623 "Mutable input not modified."
624 );
625
626 debug_assert!(
627 {
628 self.execution_results
629 .written_objects
630 .values()
631 .all(|obj| obj.previous_transaction == self.tx_digest)
632 },
633 "Object previous transaction not properly set",
634 );
635 }
636
637 pub fn mutate_input_object(&mut self, object: Object) {
639 let id = object.id();
640 debug_assert!(self.input_objects.contains_key(&id));
641 debug_assert!(!object.is_immutable());
642 self.execution_results.modified_objects.insert(id);
643 self.execution_results.written_objects.insert(id, object);
644 }
645
646 pub fn mutate_new_or_input_object(&mut self, object: Object) {
647 let id = object.id();
648 debug_assert!(!object.is_immutable());
649 if self.input_objects.contains_key(&id) {
650 self.execution_results.modified_objects.insert(id);
651 }
652 self.execution_results.written_objects.insert(id, object);
653 }
654
655 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
659 let id = new_object.id();
660 let old_ref = old_object.compute_object_reference();
661 debug_assert_eq!(old_ref.0, id);
662 self.loaded_runtime_objects.insert(
663 id,
664 DynamicallyLoadedObjectMetadata {
665 version: old_ref.1,
666 digest: old_ref.2,
667 owner: old_object.owner.clone(),
668 storage_rebate: old_object.storage_rebate,
669 previous_transaction: old_object.previous_transaction,
670 },
671 );
672 self.execution_results.modified_objects.insert(id);
673 self.execution_results
674 .written_objects
675 .insert(id, new_object);
676 }
677
678 pub fn upgrade_system_package(&mut self, package: Object) {
682 let id = package.id();
683 assert!(package.is_package() && is_system_package(id));
684 self.execution_results.modified_objects.insert(id);
685 self.execution_results.written_objects.insert(id, package);
686 }
687
688 pub fn create_object(&mut self, object: Object) {
690 debug_assert!(
695 object.is_immutable() || object.version() == SequenceNumber::MIN,
696 "Created mutable objects should not have a version set",
697 );
698 let id = object.id();
699 self.execution_results.created_object_ids.insert(id);
700 self.execution_results.written_objects.insert(id, object);
701 }
702
703 pub fn delete_input_object(&mut self, id: &ObjectID) {
705 debug_assert!(!self.execution_results.written_objects.contains_key(id));
707 debug_assert!(self.input_objects.contains_key(id));
708 self.execution_results.modified_objects.insert(*id);
709 self.execution_results.deleted_object_ids.insert(*id);
710 }
711
712 pub fn drop_writes(&mut self) {
713 self.execution_results.drop_writes();
714 self.invariants = InvariantChecker::default();
715 }
716
717 pub(crate) fn into_bump_only(self) -> Self {
722 let Self {
723 store,
725 tx_digest,
726 input_objects,
727 non_exclusive_input_original_versions,
728 stream_ended_consensus_objects,
729 lamport_timestamp,
730 mutable_input_refs,
731 receiving_objects,
732 cur_epoch,
733 protocol_config,
734 post_execution_check_inputs,
735 system_object_versions,
736 loaded_runtime_objects,
738 runtime_packages_loaded_from_db,
739 loaded_per_epoch_config_objects,
740 loaded_system_objects,
741 unsettled_object_funds,
742 execution_results: _,
744 invariants: _,
745 } = self;
746 let mut bump_only = Self {
747 store,
748 tx_digest,
749 input_objects,
750 non_exclusive_input_original_versions,
751 stream_ended_consensus_objects,
752 lamport_timestamp,
753 mutable_input_refs,
754 receiving_objects,
755 cur_epoch,
756 protocol_config,
757 loaded_runtime_objects,
758 runtime_packages_loaded_from_db,
759 loaded_per_epoch_config_objects,
760 post_execution_check_inputs,
761 system_object_versions,
762 loaded_system_objects,
763 unsettled_object_funds,
764 execution_results: ExecutionResultsV2::default(),
765 invariants: InvariantChecker::default(),
766 };
767 bump_only.ensure_active_inputs_mutated();
769 bump_only
770 }
771
772 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
773 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
775 self.execution_results
776 .written_objects
777 .get(id)
778 .or_else(|| self.input_objects.get(id))
779 }
780
781 pub fn save_loaded_runtime_objects(
782 &mut self,
783 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
784 ) {
785 #[cfg(debug_assertions)]
786 {
787 for (id, v1) in &loaded_runtime_objects {
788 if let Some(v2) = self.loaded_runtime_objects.get(id) {
789 assert_eq!(v1, v2);
790 }
791 }
792 for (id, v1) in &self.loaded_runtime_objects {
793 if let Some(v2) = loaded_runtime_objects.get(id) {
794 assert_eq!(v1, v2);
795 }
796 }
797 }
798 self.loaded_runtime_objects.extend(loaded_runtime_objects);
801 }
802
803 pub fn save_wrapped_object_containers(
804 &mut self,
805 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
806 ) {
807 self.invariants
808 .save_wrapped_object_containers(wrapped_object_containers);
809 }
810
811 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
812 self.invariants.save_generated_object_ids(generated_ids);
813 }
814
815 pub fn estimate_effects_size_upperbound(&self) -> usize {
816 TransactionEffects::estimate_effects_size_upperbound_v2(
817 self.execution_results.written_objects.len(),
818 self.execution_results.modified_objects.len(),
819 self.input_objects.len(),
820 )
821 }
822
823 pub fn written_objects_size(&self) -> usize {
824 self.execution_results
825 .written_objects
826 .values()
827 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
828 }
829
830 pub(crate) fn check_gasless_execution_requirements(&self) -> Result<(), String> {
833 use sui_types::balance::Balance;
834
835 let withdrawal_reservations = self
838 .post_execution_check_inputs
839 .input_reservations
840 .iter()
841 .filter_map(|((owner, ty), amount)| {
842 Balance::maybe_get_balance_type_param(ty)
843 .map(|coin_type| ((*owner, coin_type), *amount))
844 })
845 .collect();
846 self.check_gasless_execution_requirements_with_reservations(Some(&withdrawal_reservations))
847 }
848
849 pub(crate) fn check_gasless_execution_requirements_with_reservations(
860 &self,
861 withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
862 ) -> Result<(), String> {
863 if !self.execution_results.written_objects.is_empty() {
864 return Err("Gasless transactions cannot create or mutate objects".to_string());
865 }
866
867 let input_coin_ids: BTreeSet<ObjectID> = self
868 .input_objects
869 .iter()
870 .filter(|(_, obj)| obj.coin_type_maybe().is_some())
871 .map(|(id, _)| *id)
872 .collect();
873 if self.execution_results.deleted_object_ids != input_coin_ids {
874 return Err(format!(
875 "Gasless transaction must destroy exactly its input Coins. \
876 Expected: {input_coin_ids:?}, deleted: {:?}",
877 self.execution_results.deleted_object_ids
878 ));
879 }
880
881 let allowed_types =
882 sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
883
884 let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
887 &self.execution_results.accumulator_events,
888 )
889 .fold(
890 BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
891 |mut totals, (address, token_type, signed_amount)| {
892 *totals.entry((address, token_type)).or_default() += signed_amount;
893 totals
894 },
895 );
896
897 for ((recipient, token_type), net_amount) in &net_totals {
898 if *net_amount <= 0 {
899 continue;
900 }
901 if let Some(&min_amount) = allowed_types.get(token_type)
902 && *net_amount < i128::from(min_amount)
903 {
904 return Err(format!(
905 "Gasless transfer of {net_amount} to {recipient} is below \
906 minimum {min_amount} for token type {token_type}"
907 ));
908 }
909 }
910
911 if let Some(reservations) = withdrawal_reservations {
912 for ((owner, token_type), &reserved) in reservations {
913 let net = net_totals
914 .get(&(*owner, token_type.clone()))
915 .copied()
916 .unwrap_or(0);
917 let remaining = (reserved as i128).saturating_add(net);
918 if remaining > 0
919 && let Some(&min_balance_remaining) = allowed_types.get(token_type)
920 && min_balance_remaining > 0
921 && remaining < min_balance_remaining as i128
922 {
923 return Err(format!(
924 "Gasless withdrawal leaves {remaining} unused for {owner}, \
925 below minimum {min_balance_remaining} for token type {token_type}"
926 ));
927 }
928 }
929 }
930
931 Ok(())
932 }
933
934 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
939 if unmetered_storage_rebate == 0 {
940 return;
944 }
945 tracing::debug!(
946 "Amount of unmetered storage rebate from system tx: {:?}",
947 unmetered_storage_rebate
948 );
949 let mut system_state_wrapper = self
950 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
951 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
952 .clone();
953 assert_eq!(system_state_wrapper.storage_rebate, 0);
956 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
957 self.mutate_input_object(system_state_wrapper);
958 }
959
960 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
962 self.execution_results.accumulator_events.push(event);
963 }
964
965 fn get_object_modified_at(
971 &self,
972 object_id: &ObjectID,
973 ) -> Option<DynamicallyLoadedObjectMetadata> {
974 if self.execution_results.modified_objects.contains(object_id) {
975 Some(
976 self.mutable_input_refs
977 .get(object_id)
978 .map(
979 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
980 version: *version,
981 digest: *digest,
982 owner: owner.clone(),
983 storage_rebate: self.input_objects[object_id].storage_rebate,
985 previous_transaction: self.input_objects[object_id]
986 .previous_transaction,
987 },
988 )
989 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
990 .unwrap_or_else(|| {
991 debug_assert!(is_system_package(*object_id));
992 let package_obj =
993 self.store.get_package_object(object_id).unwrap().unwrap();
994 let obj = package_obj.object();
995 DynamicallyLoadedObjectMetadata {
996 version: obj.version(),
997 digest: obj.digest(),
998 owner: obj.owner.clone(),
999 storage_rebate: obj.storage_rebate,
1000 previous_transaction: obj.previous_transaction,
1001 }
1002 }),
1003 )
1004 } else {
1005 None
1006 }
1007 }
1008
1009 pub fn protocol_config(&self) -> &'backing ProtocolConfig {
1010 self.protocol_config
1011 }
1012
1013 pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
1016 &self,
1017 move_vm: &Arc<MoveRuntime>,
1018 enable_expensive_checks: bool,
1019 cost_summary: &GasCostSummary,
1020 ) -> Result<(), ExecutionError> {
1021 self.invariants.check_conservation_invariants::<Mode>(
1022 self,
1023 move_vm,
1024 enable_expensive_checks,
1025 cost_summary,
1026 )
1027 }
1028
1029 pub(crate) fn check_published_packages(&self) -> Result<(), ExecutionError> {
1033 self.invariants.check_published_packages(self)
1034 }
1035
1036 pub(crate) fn check_ownership_invariants(
1037 &self,
1038 sender: &SuiAddress,
1039 sponsor: &Option<SuiAddress>,
1040 gas_charger: &GasCharger,
1041 is_epoch_change: bool,
1042 ) -> SuiResult<()> {
1043 self.invariants.check_ownership_invariants(
1044 self,
1045 sender,
1046 sponsor,
1047 gas_charger,
1048 is_epoch_change,
1049 )
1050 }
1051}
1052
1053impl TemporaryStore<'_> {
1054 pub(crate) fn collect_storage_and_rebate(
1061 &mut self,
1062 gas_charger: &mut GasCharger,
1063 ) -> Result<(), ExecutionError> {
1064 let old_storage_rebates: Vec<_> = self
1066 .execution_results
1067 .written_objects
1068 .keys()
1069 .map(|object_id| {
1070 self.get_object_modified_at(object_id)
1071 .map(|metadata| metadata.storage_rebate)
1072 .unwrap_or_default()
1073 })
1074 .collect();
1075 for (object, old_storage_rebate) in self
1076 .execution_results
1077 .written_objects
1078 .values_mut()
1079 .zip_debug_eq(old_storage_rebates)
1080 {
1081 let new_object_size = object.object_size_for_gas_metering();
1083 let new_storage_rebate = gas_charger
1085 .track_storage_mutation(object.id(), new_object_size, old_storage_rebate)
1086 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1087 object.storage_rebate = new_storage_rebate;
1088 }
1089
1090 self.collect_rebate(gas_charger)
1091 }
1092
1093 pub(crate) fn collect_rebate(
1094 &self,
1095 gas_charger: &mut GasCharger,
1096 ) -> Result<(), ExecutionError> {
1097 for object_id in &self.execution_results.modified_objects {
1098 if self
1099 .execution_results
1100 .written_objects
1101 .contains_key(object_id)
1102 {
1103 continue;
1104 }
1105 let storage_rebate = self
1107 .get_object_modified_at(object_id)
1108 .unwrap()
1110 .storage_rebate;
1111 gas_charger
1112 .track_storage_mutation(*object_id, 0, storage_rebate)
1113 .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1114 }
1115 Ok(())
1116 }
1117
1118 pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1119 &self,
1120 ) -> Result<(), Mode::Error> {
1121 assert_invariant!(
1122 self.execution_results
1123 .created_object_ids
1124 .iter()
1125 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1126 && !self.execution_results.modified_objects.contains(id)),
1127 "Created object IDs cannot also be deleted or modified"
1128 );
1129 assert_invariant!(
1130 self.execution_results.modified_objects.iter().all(|id| {
1131 self.mutable_input_refs.contains_key(id)
1132 || self.loaded_runtime_objects.contains_key(id)
1133 || is_system_package(*id)
1134 }),
1135 "A modified object must be either a mutable input, a loaded child object, or a system package"
1136 );
1137 Ok(())
1138 }
1139}
1140impl TemporaryStore<'_> {
1145 pub fn advance_epoch_safe_mode(
1146 &mut self,
1147 params: &AdvanceEpochParams,
1148 protocol_config: &ProtocolConfig,
1149 ) {
1150 let wrapper = get_sui_system_state_wrapper(self.store)
1151 .expect("System state wrapper object must exist");
1152 let (old_object, new_object) =
1153 wrapper.advance_epoch_safe_mode(params, self.store, protocol_config);
1154 self.mutate_child_object(old_object, new_object);
1155 }
1156}
1157
1158impl RuntimeObjectResolver for TemporaryStore<'_> {
1159 fn read_child_object(
1160 &self,
1161 parent: &ObjectID,
1162 child: &ObjectID,
1163 child_version_upper_bound: SequenceNumber,
1164 ) -> SuiResult<Option<Object>> {
1165 let obj_opt = self.execution_results.written_objects.get(child);
1166 if obj_opt.is_some() {
1167 Ok(obj_opt.cloned())
1168 } else {
1169 let _scope = monitored_scope("Execution::read_child_object");
1170 self.store
1171 .read_child_object(parent, child, child_version_upper_bound)
1172 }
1173 }
1174
1175 fn get_object_received_at_version(
1176 &self,
1177 owner: &ObjectID,
1178 receiving_object_id: &ObjectID,
1179 receive_object_at_version: SequenceNumber,
1180 epoch_id: EpochId,
1181 ) -> SuiResult<Option<Object>> {
1182 debug_assert!(
1185 !self
1186 .execution_results
1187 .written_objects
1188 .contains_key(receiving_object_id)
1189 );
1190 debug_assert!(
1191 !self
1192 .execution_results
1193 .deleted_object_ids
1194 .contains(receiving_object_id)
1195 );
1196 self.store.get_object_received_at_version(
1197 owner,
1198 receiving_object_id,
1199 receive_object_at_version,
1200 epoch_id,
1201 )
1202 }
1203}
1204
1205impl ImplicitSystemObjectResolver for TemporaryStore<'_> {
1206 fn object_available_balance(&self, owner: SuiAddress, type_: &TypeTag) -> SuiResult<u128> {
1210 let required_version = self
1211 .load_implicitly_read_system_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1212 .ok_or(SuiErrorKind::ExecutionInvariantViolation)?
1213 .version();
1214
1215 let settled = AccumulatorRootValue::load(self, Some(required_version), owner, type_)?
1216 .and_then(|value| value.as_u128())
1217 .unwrap_or(0);
1218
1219 let unsettled = self.unsettled_object_funds.get_unsettled_object_withdraw(
1220 &AccumulatorRootValue::get_field_id(owner, type_)?,
1221 required_version,
1222 );
1223 settled
1224 .checked_sub(unsettled)
1225 .ok_or_else(|| SuiErrorKind::ExecutionInvariantViolation.into())
1226 }
1227}
1228
1229fn compute_input_reservations(
1238 transaction_kind: &TransactionKind,
1239 gas_data: &GasData,
1240 transaction_signer: SuiAddress,
1241 enable_gasless: bool,
1242) -> (BTreeMap<(SuiAddress, TypeTag), u64>, AllowanceIds) {
1243 use sui_types::balance::Balance;
1244 use sui_types::gas_coin::GAS;
1245 use sui_types::transaction::{Reservation, WithdrawFrom, is_gas_paid_from_address_balance};
1246
1247 let is_gasless = enable_gasless && is_gasless_transaction(gas_data, transaction_kind);
1248 let mut reservations: BTreeMap<(SuiAddress, TypeTag), u64> = BTreeMap::new();
1249 let mut allowance_ids = AllowanceIds::new();
1250 let sui_balance_type = Balance::type_tag(GAS::type_tag());
1251
1252 for arg in transaction_kind.get_funds_withdrawals() {
1253 let ty = arg.type_arg.to_type_tag();
1254 let owner = match arg.withdraw_from {
1255 WithdrawFrom::Sender => transaction_signer,
1256 WithdrawFrom::Sponsor => gas_data.owner,
1257 WithdrawFrom::SenderAllowance { funder, allowance } => {
1260 allowance_ids
1261 .entry((funder, ty.clone()))
1262 .or_default()
1263 .push(allowance);
1264 funder
1265 }
1266 };
1267 let Reservation::MaxAmountU64(reservation) = arg.reservation;
1268 let entry = reservations.entry((owner, ty)).or_insert(0);
1269 *entry = entry.saturating_add(reservation);
1270 }
1271
1272 if !is_gasless && is_gas_paid_from_address_balance(gas_data, transaction_kind) {
1275 let entry = reservations
1276 .entry((gas_data.owner, sui_balance_type.clone()))
1277 .or_insert(0);
1278 *entry = entry.saturating_add(gas_data.budget);
1279 }
1280
1281 for entry in &gas_data.payment {
1282 if let Ok(parsed) = ParsedDigest::try_from(entry.2) {
1283 let entry = reservations
1284 .entry((gas_data.owner, sui_balance_type.clone()))
1285 .or_insert(0);
1286 *entry = entry.saturating_add(parsed.reservation_amount());
1287 }
1288 }
1289
1290 (reservations, allowance_ids)
1291}
1292
1293fn declared_packages(
1296 transaction_kind: &TransactionKind,
1297) -> Option<Vec<(usize, BTreeSet<ObjectID>)>> {
1298 let TransactionKind::ProgrammableTransaction(pt) = transaction_kind else {
1299 return None;
1300 };
1301 Some(
1302 pt.commands
1303 .iter()
1304 .filter_map(|command| match command {
1305 Command::Publish(modules, dep_ids) | Command::Upgrade(modules, dep_ids, _, _) => {
1306 Some((modules.len(), dep_ids.iter().copied().collect()))
1307 }
1308 _ => None,
1309 })
1310 .collect(),
1311 )
1312}
1313
1314fn was_object_mutated(object: &Object, original: &Object) -> bool {
1317 let data_equal = match (&object.data, &original.data) {
1318 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1319 (Data::Package(a), Data::Package(b)) => a == b,
1322 _ => false,
1323 };
1324
1325 let owner_equal = match (&object.owner, &original.owner) {
1326 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1330 (
1331 Owner::ConsensusAddressOwner { owner: a, .. },
1332 Owner::ConsensusAddressOwner { owner: b, .. },
1333 ) => a == b,
1334 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1335 (Owner::Immutable, Owner::Immutable) => true,
1336 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1337 (
1338 Owner::Party {
1339 permissions: a,
1340 start_version: _,
1341 },
1342 Owner::Party {
1343 permissions: b,
1344 start_version: _,
1345 },
1346 ) => a == b,
1347
1348 (Owner::AddressOwner(_), _)
1351 | (Owner::Immutable, _)
1352 | (Owner::ObjectOwner(_), _)
1353 | (Owner::Shared { .. }, _)
1354 | (Owner::ConsensusAddressOwner { .. }, _)
1355 | (Owner::Party { .. }, _) => false,
1356 };
1357
1358 !data_equal || !owner_equal
1359}
1360
1361impl Storage for TemporaryStore<'_> {
1362 fn reset(&mut self) {
1363 self.drop_writes();
1364 }
1365
1366 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1367 TemporaryStore::read_object(self, id)
1368 }
1369
1370 fn record_execution_results(
1372 &mut self,
1373 results: ExecutionResults,
1374 ) -> Result<(), ExecutionError> {
1375 let ExecutionResults::V2(mut results) = results else {
1376 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1377 };
1378
1379 let mut to_remove = Vec::new();
1381 for (id, original) in &self.non_exclusive_input_original_versions {
1382 if results
1384 .written_objects
1385 .get(id)
1386 .map(|obj| was_object_mutated(obj, original))
1387 .unwrap_or(true)
1388 {
1389 return Err(ExecutionError::new_with_source(
1390 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1391 "Non-exclusive write input object has been modified or deleted",
1392 ));
1393 }
1394 to_remove.push(*id);
1395 }
1396
1397 for id in to_remove {
1398 results.written_objects.remove(&id);
1399 results.modified_objects.remove(&id);
1400 }
1401
1402 let event_start = self.execution_results.accumulator_events.len();
1408 self.execution_results.merge_results(
1409 results, true, true,
1410 )?;
1411 let event_end = self.execution_results.accumulator_events.len();
1412 self.invariants
1413 .record_ptb_event_range(event_start, event_end);
1414
1415 Ok(())
1416 }
1417
1418 fn save_loaded_runtime_objects(
1419 &mut self,
1420 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1421 ) {
1422 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1423 }
1424
1425 fn save_wrapped_object_containers(
1426 &mut self,
1427 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1428 ) {
1429 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1430 }
1431
1432 fn check_coin_deny_list(
1433 &self,
1434 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1435 ) -> DenyListResult {
1436 let result = check_coin_deny_list_v2_during_execution(
1437 receiving_funds_type_and_owners,
1438 self.cur_epoch,
1439 self.store,
1440 );
1441 if result.num_non_gas_coin_owners > 0
1444 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1445 {
1446 self.loaded_per_epoch_config_objects
1447 .write()
1448 .insert(SUI_DENY_LIST_OBJECT_ID);
1449 }
1450 result
1451 }
1452
1453 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1454 TemporaryStore::save_generated_object_ids(self, generated_ids)
1455 }
1456}
1457
1458impl BackingPackageStore for TemporaryStore<'_> {
1459 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1460 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1467 Ok(Some(PackageObject::new(obj.clone())))
1468 } else {
1469 self.store.get_package_object(package_id).inspect(|obj| {
1470 if let Some(v) = obj
1472 && !self
1473 .runtime_packages_loaded_from_db
1474 .read()
1475 .contains_key(package_id)
1476 {
1477 self.runtime_packages_loaded_from_db
1482 .write()
1483 .insert(*package_id, v.clone());
1484 }
1485 })
1486 }
1487 }
1488}