sui_adapter_v0/programmable_transactions/
context.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4pub use checked::*;
5
6#[sui_macros::with_checked_arithmetic]
7mod checked {
8
9    use std::{
10        collections::{BTreeMap, BTreeSet, HashMap},
11        sync::Arc,
12    };
13
14    use crate::adapter::new_native_extensions;
15    use crate::error::convert_vm_error;
16    use crate::execution_mode::ExecutionMode;
17    use crate::execution_value::{
18        CommandKind, ExecutionState, InputObjectMetadata, InputValue, ObjectContents, ObjectValue,
19        RawValueType, ResultValue, TryFromValue, UsageKind, Value,
20    };
21    use crate::gas_charger::GasCharger;
22    use crate::programmable_transactions::linkage_view::{LinkageInfo, LinkageView, SavedLinkage};
23    use crate::type_resolver::TypeTagResolver;
24    use move_binary_format::{
25        errors::{Location, VMError, VMResult},
26        file_format::{CodeOffset, FunctionDefinitionIndex, TypeParameterIndex},
27        CompiledModule,
28    };
29    use move_core_types::{
30        account_address::AccountAddress,
31        language_storage::{ModuleId, StructTag, TypeTag},
32    };
33    use move_vm_runtime::{move_vm::MoveVM, session::Session};
34    use move_vm_types::loaded_data::runtime_types::Type;
35    use sui_move_natives::object_runtime::{
36        self, get_all_uids, max_event_error, ObjectRuntime, RuntimeResults,
37    };
38    use sui_protocol_config::ProtocolConfig;
39    use sui_types::execution_status::CommandArgumentError;
40    use sui_types::storage::PackageObject;
41    use sui_types::{
42        balance::Balance,
43        base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress, TxContext},
44        coin::Coin,
45        error::{command_argument_error, ExecutionError},
46        event::Event,
47        execution::{ExecutionResults, ExecutionResultsV1},
48        execution_status::ExecutionErrorKind,
49        metrics::LimitsMetrics,
50        move_package::MovePackage,
51        object::{Data, MoveObject, Object, ObjectInner, Owner},
52        storage::{
53            BackingPackageStore, ChildObjectResolver, DeleteKind, DeleteKindWithOldVersion,
54            ObjectChange, WriteKind,
55        },
56        transaction::{Argument, CallArg, ObjectArg},
57    };
58
59    /// Maintains all runtime state specific to programmable transactions
60    pub struct ExecutionContext<'vm, 'state, 'a> {
61        /// The protocol config
62        pub protocol_config: &'a ProtocolConfig,
63        /// Metrics for reporting exceeded limits
64        pub metrics: Arc<LimitsMetrics>,
65        /// The MoveVM
66        pub vm: &'vm MoveVM,
67        /// The global state, used for resolving packages
68        pub state_view: &'state dyn ExecutionState,
69        /// A shared transaction context, contains transaction digest information and manages the
70        /// creation of new object IDs
71        pub tx_context: &'a mut TxContext,
72        /// The gas charger used for metering
73        pub gas_charger: &'a mut GasCharger,
74        /// The session used for interacting with Move types and calls
75        pub session: Session<'state, 'vm, LinkageView<'state>>,
76        /// Additional transfers not from the Move runtime
77        additional_transfers: Vec<(/* new owner */ SuiAddress, ObjectValue)>,
78        /// Newly published packages
79        new_packages: Vec<Object>,
80        /// User events are claimed after each Move call
81        user_events: Vec<(ModuleId, StructTag, Vec<u8>)>,
82        // runtime data
83        /// The runtime value for the Gas coin, None if it has been taken/moved
84        gas: InputValue,
85        /// The runtime value for the inputs/call args, None if it has been taken/moved
86        inputs: Vec<InputValue>,
87        /// The results of a given command. For most commands, the inner vector will have length 1.
88        /// It will only not be 1 for Move calls with multiple return values.
89        /// Inner values are None if taken/moved by-value
90        results: Vec<Vec<ResultValue>>,
91        /// Map of arguments that are currently borrowed in this command, true if the borrow is mutable
92        /// This gets cleared out when new results are pushed, i.e. the end of a command
93        borrowed: HashMap<Argument, /* mut */ bool>,
94    }
95
96    /// A write for an object that was generated outside of the Move ObjectRuntime
97    struct AdditionalWrite {
98        /// The new owner of the object
99        recipient: Owner,
100        /// the type of the object,
101        type_: Type,
102        /// if the object has public transfer or not, i.e. if it has store
103        has_public_transfer: bool,
104        /// contents of the object
105        bytes: Vec<u8>,
106    }
107
108    impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> {
109        pub fn new(
110            protocol_config: &'a ProtocolConfig,
111            metrics: Arc<LimitsMetrics>,
112            vm: &'vm MoveVM,
113            state_view: &'state dyn ExecutionState,
114            tx_context: &'a mut TxContext,
115            gas_charger: &'a mut GasCharger,
116            inputs: Vec<CallArg>,
117        ) -> Result<Self, ExecutionError> {
118            let init_linkage = if protocol_config.package_upgrades_supported() {
119                LinkageInfo::Unset
120            } else {
121                LinkageInfo::Universal
122            };
123
124            // we need a new session just for loading types, which is sad
125            // TODO remove this
126            let linkage = LinkageView::new(Box::new(state_view.as_sui_resolver()), init_linkage);
127            let mut tmp_session = new_session(
128                vm,
129                linkage,
130                state_view.as_child_resolver(),
131                BTreeMap::new(),
132                !gas_charger.is_unmetered(),
133                protocol_config,
134                metrics.clone(),
135            );
136            let mut input_object_map = BTreeMap::new();
137            let inputs = inputs
138                .into_iter()
139                .map(|call_arg| {
140                    load_call_arg(
141                        vm,
142                        state_view,
143                        &mut tmp_session,
144                        &mut input_object_map,
145                        call_arg,
146                    )
147                })
148                .collect::<Result<_, ExecutionError>>()?;
149            let gas = if let Some(gas_coin) = gas_charger.gas_coin() {
150                let mut gas = load_object(
151                    vm,
152                    state_view,
153                    &mut tmp_session,
154                    &mut input_object_map,
155                    /* imm override */ false,
156                    gas_coin,
157                )?;
158                // subtract the max gas budget. This amount is off limits in the programmable transaction,
159                // so to mimic this "off limits" behavior, we act as if the coin has less balance than
160                // it really does
161                let Some(Value::Object(ObjectValue {
162                    contents: ObjectContents::Coin(coin),
163                    ..
164                })) = &mut gas.inner.value
165                else {
166                    invariant_violation!("Gas object should be a populated coin")
167                };
168                let max_gas_in_balance = gas_charger.gas_budget();
169                let Some(new_balance) = coin.balance.value().checked_sub(max_gas_in_balance) else {
170                    invariant_violation!(
171                        "Transaction input checker should check that there is enough gas"
172                    );
173                };
174                coin.balance = Balance::new(new_balance);
175                gas
176            } else {
177                InputValue {
178                    object_metadata: None,
179                    inner: ResultValue {
180                        last_usage_kind: None,
181                        value: None,
182                    },
183                }
184            };
185            // the session was just used for ability and layout metadata fetching, no changes should
186            // exist. Plus, Sui Move does not use these changes or events
187            let (res, linkage) = tmp_session.finish();
188            let change_set = res.map_err(|e| crate::error::convert_vm_error(e, vm, &linkage))?;
189            assert_invariant!(change_set.accounts().is_empty(), "Change set must be empty");
190            // make the real session
191            let session = new_session(
192                vm,
193                linkage,
194                state_view.as_child_resolver(),
195                input_object_map,
196                !gas_charger.is_unmetered(),
197                protocol_config,
198                metrics.clone(),
199            );
200
201            Ok(Self {
202                protocol_config,
203                metrics,
204                vm,
205                state_view,
206                tx_context,
207                gas_charger,
208                session,
209                gas,
210                inputs,
211                results: vec![],
212                additional_transfers: vec![],
213                new_packages: vec![],
214                user_events: vec![],
215                borrowed: HashMap::new(),
216            })
217        }
218
219        /// Create a new ID and update the state
220        pub fn fresh_id(&mut self) -> Result<ObjectID, ExecutionError> {
221            let object_id = self.tx_context.fresh_id();
222            let object_runtime: &mut ObjectRuntime = self.session.get_native_extensions().get_mut();
223            object_runtime
224                .new_id(object_id)
225                .map_err(|e| self.convert_vm_error(e.finish(Location::Undefined)))?;
226            Ok(object_id)
227        }
228
229        /// Delete an ID and update the state
230        pub fn delete_id(&mut self, object_id: ObjectID) -> Result<(), ExecutionError> {
231            let object_runtime: &mut ObjectRuntime = self.session.get_native_extensions().get_mut();
232            object_runtime
233                .delete_id(object_id)
234                .map_err(|e| self.convert_vm_error(e.finish(Location::Undefined)))
235        }
236
237        /// Set the link context for the session from the linkage information in the MovePackage found
238        /// at `package_id`.  Returns the runtime ID of the link context package on success.
239        pub fn set_link_context(
240            &mut self,
241            package_id: ObjectID,
242        ) -> Result<AccountAddress, ExecutionError> {
243            let resolver = self.session.get_resolver();
244            if resolver.has_linkage(package_id) {
245                // Setting same context again, can skip.
246                return Ok(resolver.original_package_id().unwrap_or(*package_id));
247            }
248
249            let package = package_for_linkage(&self.session, package_id)
250                .map_err(|e| self.convert_vm_error(e))?;
251
252            set_linkage(&mut self.session, package.move_package())
253        }
254
255        /// Set the link context for the session from the linkage information in the `package`.  Returns
256        /// the runtime ID of the link context package on success.
257        pub fn set_linkage(
258            &mut self,
259            package: &MovePackage,
260        ) -> Result<AccountAddress, ExecutionError> {
261            set_linkage(&mut self.session, package)
262        }
263
264        /// Turn off linkage information, so that the next use of the session will need to set linkage
265        /// information to succeed.
266        pub fn reset_linkage(&mut self) {
267            reset_linkage(&mut self.session);
268        }
269
270        /// Reset the linkage context, and save it (if one exists)
271        pub fn steal_linkage(&mut self) -> Option<SavedLinkage> {
272            steal_linkage(&mut self.session)
273        }
274
275        /// Restore a previously stolen/saved link context.
276        pub fn restore_linkage(
277            &mut self,
278            saved: Option<SavedLinkage>,
279        ) -> Result<(), ExecutionError> {
280            restore_linkage(&mut self.session, saved)
281        }
282
283        /// Load a type using the context's current session.
284        pub fn load_type(&mut self, type_tag: &TypeTag) -> VMResult<Type> {
285            load_type(&mut self.session, type_tag)
286        }
287
288        /// Takes the user events from the runtime and tags them with the Move module of the function
289        /// that was invoked for the command
290        pub fn take_user_events(
291            &mut self,
292            module_id: &ModuleId,
293            function: FunctionDefinitionIndex,
294            last_offset: CodeOffset,
295        ) -> Result<(), ExecutionError> {
296            let object_runtime: &mut ObjectRuntime = self.session.get_native_extensions().get_mut();
297            let events = object_runtime.take_user_events();
298            let num_events = self.user_events.len() + events.len();
299            let max_events = self.protocol_config.max_num_event_emit();
300            if num_events as u64 > max_events {
301                let err = max_event_error(max_events)
302                    .at_code_offset(function, last_offset)
303                    .finish(Location::Module(module_id.clone()));
304                return Err(self.convert_vm_error(err));
305            }
306            let new_events = events
307                .into_iter()
308                .map(|(ty, tag, value)| {
309                    let layout = self
310                        .session
311                        .type_to_type_layout(&ty)
312                        .map_err(|e| self.convert_vm_error(e))?;
313                    let Some(bytes) = value.simple_serialize(&layout) else {
314                        invariant_violation!("Failed to deserialize already serialized Move value");
315                    };
316                    Ok((module_id.clone(), tag, bytes))
317                })
318                .collect::<Result<Vec<_>, ExecutionError>>()?;
319            self.user_events.extend(new_events);
320            Ok(())
321        }
322
323        /// Get the argument value. Cloning the value if it is copyable, and setting its value to None
324        /// if it is not (making it unavailable).
325        /// Errors if out of bounds, if the argument is borrowed, if it is unavailable (already taken),
326        /// or if it is an object that cannot be taken by value (shared or immutable)
327        pub fn by_value_arg<V: TryFromValue>(
328            &mut self,
329            command_kind: CommandKind<'_>,
330            arg_idx: usize,
331            arg: Argument,
332        ) -> Result<V, ExecutionError> {
333            self.by_value_arg_(command_kind, arg)
334                .map_err(|e| command_argument_error(e, arg_idx))
335        }
336        fn by_value_arg_<V: TryFromValue>(
337            &mut self,
338            command_kind: CommandKind<'_>,
339            arg: Argument,
340        ) -> Result<V, CommandArgumentError> {
341            let is_borrowed = self.arg_is_borrowed(&arg);
342            let (input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::ByValue)?;
343            let is_copyable = if let Some(val) = val_opt {
344                val.is_copyable()
345            } else {
346                return Err(CommandArgumentError::InvalidValueUsage);
347            };
348            // If it was taken, we catch this above.
349            // If it was not copyable and was borrowed, error as it creates a dangling reference in
350            // effect.
351            // We allow copyable values to be copied out even if borrowed, as we do not care about
352            // referential transparency at this level.
353            if !is_copyable && is_borrowed {
354                return Err(CommandArgumentError::InvalidValueUsage);
355            }
356            // Gas coin cannot be taken by value, except in TransferObjects
357            if matches!(arg, Argument::GasCoin)
358                && !matches!(command_kind, CommandKind::TransferObjects)
359            {
360                return Err(CommandArgumentError::InvalidGasCoinUsage);
361            }
362            // Immutable objects and shared objects cannot be taken by value
363            if matches!(
364                input_metadata_opt,
365                Some(InputObjectMetadata::InputObject {
366                    owner: Owner::Immutable | Owner::Shared { .. },
367                    ..
368                })
369            ) {
370                return Err(CommandArgumentError::InvalidObjectByValue);
371            }
372            let val = if is_copyable {
373                val_opt.as_ref().unwrap().clone()
374            } else {
375                val_opt.take().unwrap()
376            };
377            V::try_from_value(val)
378        }
379
380        /// Mimic a mutable borrow by taking the argument value, setting its value to None,
381        /// making it unavailable. The value will be marked as borrowed and must be returned with
382        /// restore_arg
383        /// Errors if out of bounds, if the argument is borrowed, if it is unavailable (already taken),
384        /// or if it is an object that cannot be mutably borrowed (immutable)
385        pub fn borrow_arg_mut<V: TryFromValue>(
386            &mut self,
387            arg_idx: usize,
388            arg: Argument,
389        ) -> Result<V, ExecutionError> {
390            self.borrow_arg_mut_(arg)
391                .map_err(|e| command_argument_error(e, arg_idx))
392        }
393        fn borrow_arg_mut_<V: TryFromValue>(
394            &mut self,
395            arg: Argument,
396        ) -> Result<V, CommandArgumentError> {
397            // mutable borrowing requires unique usage
398            if self.arg_is_borrowed(&arg) {
399                return Err(CommandArgumentError::InvalidValueUsage);
400            }
401            self.borrowed.insert(arg, /* is_mut */ true);
402            let (input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::BorrowMut)?;
403            let is_copyable = if let Some(val) = val_opt {
404                val.is_copyable()
405            } else {
406                // error if taken
407                return Err(CommandArgumentError::InvalidValueUsage);
408            };
409            if let Some(InputObjectMetadata::InputObject {
410                is_mutable_input: false,
411                ..
412            }) = input_metadata_opt
413            {
414                return Err(CommandArgumentError::InvalidObjectByMutRef);
415            }
416            // if it is copyable, don't take it as we allow for the value to be copied even if
417            // mutably borrowed
418            let val = if is_copyable {
419                val_opt.as_ref().unwrap().clone()
420            } else {
421                val_opt.take().unwrap()
422            };
423            V::try_from_value(val)
424        }
425
426        /// Mimics an immutable borrow by cloning the argument value without setting its value to None
427        /// Errors if out of bounds, if the argument is mutably borrowed,
428        /// or if it is unavailable (already taken)
429        pub fn borrow_arg<V: TryFromValue>(
430            &mut self,
431            arg_idx: usize,
432            arg: Argument,
433        ) -> Result<V, ExecutionError> {
434            self.borrow_arg_(arg)
435                .map_err(|e| command_argument_error(e, arg_idx))
436        }
437        fn borrow_arg_<V: TryFromValue>(
438            &mut self,
439            arg: Argument,
440        ) -> Result<V, CommandArgumentError> {
441            // immutable borrowing requires the value was not mutably borrowed.
442            // If it was copied, that is okay.
443            // If it was taken/moved, we will find out below
444            if self.arg_is_mut_borrowed(&arg) {
445                return Err(CommandArgumentError::InvalidValueUsage);
446            }
447            self.borrowed.insert(arg, /* is_mut */ false);
448            let (_input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::BorrowImm)?;
449            if val_opt.is_none() {
450                return Err(CommandArgumentError::InvalidValueUsage);
451            }
452            V::try_from_value(val_opt.as_ref().unwrap().clone())
453        }
454
455        /// Restore an argument after being mutably borrowed
456        pub fn restore_arg<Mode: ExecutionMode>(
457            &mut self,
458            updates: &mut Mode::ArgumentUpdates,
459            arg: Argument,
460            value: Value,
461        ) -> Result<(), ExecutionError> {
462            Mode::add_argument_update(self, updates, arg, &value)?;
463            let was_mut_opt = self.borrowed.remove(&arg);
464            assert_invariant!(
465                was_mut_opt.is_some() && was_mut_opt.unwrap(),
466                "Should never restore a non-mut borrowed value. \
467            The take+restore is an implementation detail of mutable references"
468            );
469            // restore is exclusively used for mut
470            let Ok((_, value_opt)) = self.borrow_mut_impl(arg, None) else {
471                invariant_violation!("Should be able to borrow argument to restore it")
472            };
473            let old_value = value_opt.replace(value);
474            assert_invariant!(
475                old_value.is_none() || old_value.unwrap().is_copyable(),
476                "Should never restore a non-taken value, unless it is copyable. \
477            The take+restore is an implementation detail of mutable references"
478            );
479            Ok(())
480        }
481
482        /// Transfer the object to a new owner
483        pub fn transfer_object(
484            &mut self,
485            obj: ObjectValue,
486            addr: SuiAddress,
487        ) -> Result<(), ExecutionError> {
488            self.additional_transfers.push((addr, obj));
489            Ok(())
490        }
491
492        /// Create a new package
493        pub fn new_package<'p>(
494            &self,
495            modules: &[CompiledModule],
496            dependencies: impl IntoIterator<Item = &'p MovePackage>,
497        ) -> Result<Object, ExecutionError> {
498            Object::new_package(
499                modules,
500                self.tx_context.digest(),
501                self.protocol_config,
502                dependencies,
503            )
504        }
505
506        /// Create a package upgrade from `previous_package` with `new_modules` and `dependencies`
507        pub fn upgrade_package<'p>(
508            &self,
509            storage_id: ObjectID,
510            previous_package: &MovePackage,
511            new_modules: &[CompiledModule],
512            dependencies: impl IntoIterator<Item = &'p MovePackage>,
513        ) -> Result<Object, ExecutionError> {
514            Object::new_upgraded_package(
515                previous_package,
516                storage_id,
517                new_modules,
518                self.tx_context.digest(),
519                self.protocol_config,
520                dependencies,
521            )
522        }
523
524        /// Add a newly created package to write as an effect of the transaction
525        pub fn write_package(&mut self, package: Object) -> Result<(), ExecutionError> {
526            assert_invariant!(package.is_package(), "Must be a package");
527            self.new_packages.push(package);
528            Ok(())
529        }
530
531        /// Finish a command: clearing the borrows and adding the results to the result vector
532        pub fn push_command_results(&mut self, results: Vec<Value>) -> Result<(), ExecutionError> {
533            assert_invariant!(
534                self.borrowed.values().all(|is_mut| !is_mut),
535                "all mut borrows should be restored"
536            );
537            // clear borrow state
538            self.borrowed = HashMap::new();
539            self.results
540                .push(results.into_iter().map(ResultValue::new).collect());
541            Ok(())
542        }
543
544        /// Determine the object changes and collect all user events
545        pub fn finish<Mode: ExecutionMode>(self) -> Result<ExecutionResults, ExecutionError> {
546            let Self {
547                protocol_config,
548                metrics,
549                vm,
550                state_view,
551                tx_context,
552                gas_charger,
553                session,
554                additional_transfers,
555                new_packages,
556                gas,
557                inputs,
558                results,
559                user_events,
560                ..
561            } = self;
562            let tx_digest = tx_context.digest();
563            let mut additional_writes = BTreeMap::new();
564            let mut input_object_metadata = BTreeMap::new();
565            // Any object value that has not been taken (still has `Some` for it's value) needs to
566            // written as it's value might have changed (and eventually it's sequence number will need
567            // to increase)
568            let mut by_value_inputs = BTreeSet::new();
569            let mut add_input_object_write = |input| -> Result<(), ExecutionError> {
570                let InputValue {
571                    object_metadata: object_metadata_opt,
572                    inner: ResultValue { value, .. },
573                } = input;
574                let Some(object_metadata) = object_metadata_opt else {
575                    return Ok(());
576                };
577                let InputObjectMetadata::InputObject {
578                    is_mutable_input,
579                    owner,
580                    id,
581                    ..
582                } = &object_metadata
583                else {
584                    unreachable!("Found non-input object metadata for input object when adding writes to input objects -- impossible in v0");
585                };
586                input_object_metadata.insert(object_metadata.id(), object_metadata.clone());
587                let Some(Value::Object(object_value)) = value else {
588                    by_value_inputs.insert(*id);
589                    return Ok(());
590                };
591                if *is_mutable_input {
592                    add_additional_write(&mut additional_writes, owner.clone(), object_value)?;
593                }
594                Ok(())
595            };
596            let gas_id_opt = gas.object_metadata.as_ref().map(|info| info.id());
597            add_input_object_write(gas)?;
598            for input in inputs {
599                add_input_object_write(input)?
600            }
601            // check for unused values
602            // disable this check for dev inspect
603            if !Mode::allow_arbitrary_values() {
604                for (i, command_result) in results.iter().enumerate() {
605                    for (j, result_value) in command_result.iter().enumerate() {
606                        let ResultValue {
607                            last_usage_kind,
608                            value,
609                        } = result_value;
610                        match value {
611                            None => (),
612                            Some(Value::Object(_)) => {
613                                return Err(ExecutionErrorKind::UnusedValueWithoutDrop {
614                                    result_idx: i as u16,
615                                    secondary_idx: j as u16,
616                                }
617                                .into())
618                            }
619                            Some(Value::Raw(RawValueType::Any, _)) => (),
620                            Some(Value::Raw(RawValueType::Loaded { abilities, .. }, _)) => {
621                                // - nothing to check for drop
622                                // - if it does not have drop, but has copy,
623                                //   the last usage must be by value in order to "lie" and say that the
624                                //   last usage is actually a take instead of a clone
625                                // - Otherwise, an error
626                                if abilities.has_drop()
627                                    || (abilities.has_copy()
628                                        && matches!(last_usage_kind, Some(UsageKind::ByValue)))
629                                {
630                                } else {
631                                    let msg = if abilities.has_copy() {
632                                        "The value has copy, but not drop. \
633                                    Its last usage must be by-value so it can be taken."
634                                    } else {
635                                        "Unused value without drop"
636                                    };
637                                    return Err(ExecutionError::new_with_source(
638                                        ExecutionErrorKind::UnusedValueWithoutDrop {
639                                            result_idx: i as u16,
640                                            secondary_idx: j as u16,
641                                        },
642                                        msg,
643                                    ));
644                                }
645                            }
646                            Some(Value::Receiving(_, _, _)) => {
647                                unreachable!("Impossible to hit Receiving in v0")
648                            }
649                        }
650                    }
651                }
652            }
653            // add transfers from TransferObjects command
654            for (recipient, object_value) in additional_transfers {
655                let owner = Owner::AddressOwner(recipient);
656                add_additional_write(&mut additional_writes, owner, object_value)?;
657            }
658            // Refund unused gas
659            if let Some(gas_id) = gas_id_opt {
660                refund_max_gas_budget(&mut additional_writes, gas_charger, gas_id)?;
661            }
662
663            let (res, linkage) = session.finish_with_extensions();
664            let (_, mut native_context_extensions) =
665                res.map_err(|e| convert_vm_error(e, vm, &linkage))?;
666            let object_runtime: ObjectRuntime = native_context_extensions.remove();
667            let new_ids = object_runtime.new_ids().clone();
668            // tell the object runtime what input objects were taken and which were transferred
669            let external_transfers = additional_writes.keys().copied().collect();
670            let RuntimeResults {
671                writes,
672                deletions,
673                user_events: remaining_events,
674                loaded_child_objects,
675            } = object_runtime.finish(by_value_inputs, external_transfers)?;
676            assert_invariant!(
677                remaining_events.is_empty(),
678                "Events should be taken after every Move call"
679            );
680            let mut object_changes = BTreeMap::new();
681            for package in new_packages {
682                let id = package.id();
683                let change = ObjectChange::Write(package, WriteKind::Create);
684                object_changes.insert(id, change);
685            }
686            // we need a new session just for deserializing and fetching abilities. Which is sad
687            // TODO remove this
688            let tmp_session = new_session(
689                vm,
690                linkage,
691                state_view.as_child_resolver(),
692                BTreeMap::new(),
693                !gas_charger.is_unmetered(),
694                protocol_config,
695                metrics,
696            );
697            for (id, additional_write) in additional_writes {
698                let AdditionalWrite {
699                    recipient,
700                    type_,
701                    has_public_transfer,
702                    bytes,
703                } = additional_write;
704                let write_kind = if input_object_metadata.contains_key(&id)
705                    || loaded_child_objects.contains_key(&id)
706                {
707                    assert_invariant!(
708                        !new_ids.contains_key(&id),
709                        "new id should not be in mutations"
710                    );
711                    WriteKind::Mutate
712                } else if new_ids.contains_key(&id) {
713                    WriteKind::Create
714                } else {
715                    WriteKind::Unwrap
716                };
717                // safe given the invariant that the runtime correctly propagates has_public_transfer
718                let move_object = unsafe {
719                    create_written_object(
720                        vm,
721                        &tmp_session,
722                        protocol_config,
723                        &input_object_metadata,
724                        &loaded_child_objects,
725                        id,
726                        type_,
727                        has_public_transfer,
728                        bytes,
729                        write_kind,
730                    )?
731                };
732                let object = Object::new_move(move_object, recipient, tx_digest);
733                let change = ObjectChange::Write(object, write_kind);
734                object_changes.insert(id, change);
735            }
736
737            for (id, (write_kind, recipient, ty, value)) in writes {
738                let abilities = tmp_session
739                    .get_type_abilities(&ty)
740                    .map_err(|e| convert_vm_error(e, vm, tmp_session.get_resolver()))?;
741                let has_public_transfer = abilities.has_store();
742                let layout = tmp_session
743                    .type_to_type_layout(&ty)
744                    .map_err(|e| convert_vm_error(e, vm, tmp_session.get_resolver()))?;
745                let Some(bytes) = value.simple_serialize(&layout) else {
746                    invariant_violation!("Failed to deserialize already serialized Move value");
747                };
748                // safe because has_public_transfer has been determined by the abilities
749                let move_object = unsafe {
750                    create_written_object(
751                        vm,
752                        &tmp_session,
753                        protocol_config,
754                        &input_object_metadata,
755                        &loaded_child_objects,
756                        id,
757                        ty,
758                        has_public_transfer,
759                        bytes,
760                        write_kind,
761                    )?
762                };
763                let object = Object::new_move(move_object, recipient, tx_digest);
764                let change = ObjectChange::Write(object, write_kind);
765                object_changes.insert(id, change);
766            }
767            for (id, delete_kind) in deletions {
768                // For deleted and wrapped objects, the object must exist either in the input or was
769                // loaded as child object. We can read them to get the previous version.
770                // For unwrap_then_delete, in older protocol versions, we must consult the object store
771                // to see if there exists a tombstone, and if so we include it otherwise we skip it.
772                // In newer protocol versions, we can just skip it.
773                let delete_kind_with_seq = match delete_kind {
774                    DeleteKind::Normal | DeleteKind::Wrap => {
775                        let old_version = match input_object_metadata.get(&id) {
776                        Some(metadata) => {
777                            assert_invariant!(
778                                !matches!(metadata, InputObjectMetadata::InputObject { owner: Owner::Immutable, .. }),
779                                "Attempting to delete immutable object {id} via delete kind {delete_kind}"
780                            );
781                            metadata.version()
782                        }
783                        None => {
784                            match loaded_child_objects.get(&id) {
785                                Some(version) => *version,
786                                None => invariant_violation!("Deleted/wrapped object {id} must be either in input or loaded child objects")
787                            }
788                        }
789                    };
790                        if delete_kind == DeleteKind::Normal {
791                            DeleteKindWithOldVersion::Normal(old_version)
792                        } else {
793                            DeleteKindWithOldVersion::Wrap(old_version)
794                        }
795                    }
796                    DeleteKind::UnwrapThenDelete => {
797                        if protocol_config.simplified_unwrap_then_delete() {
798                            DeleteKindWithOldVersion::UnwrapThenDelete
799                        } else {
800                            let old_version =
801                                match state_view.get_latest_parent_entry_ref_deprecated(id) {
802                                    Some((_, previous_version, _)) => previous_version,
803                                    // This object was not created this transaction but has never existed in
804                                    // storage, skip it.
805                                    None => continue,
806                                };
807                            DeleteKindWithOldVersion::UnwrapThenDeleteDEPRECATED(old_version)
808                        }
809                    }
810                };
811                object_changes.insert(id, ObjectChange::Delete(delete_kind_with_seq));
812            }
813
814            let (res, linkage) = tmp_session.finish();
815            let change_set = res.map_err(|e| convert_vm_error(e, vm, &linkage))?;
816
817            // the session was just used for ability and layout metadata fetching, no changes should
818            // exist. Plus, Sui Move does not use these changes or events
819            assert_invariant!(change_set.accounts().is_empty(), "Change set must be empty");
820
821            Ok(ExecutionResults::V1(ExecutionResultsV1 {
822                object_changes,
823                user_events: user_events
824                    .into_iter()
825                    .map(|(module_id, tag, contents)| {
826                        Event::new(
827                            module_id.address(),
828                            module_id.name(),
829                            tx_context.sender(),
830                            tag,
831                            contents,
832                        )
833                    })
834                    .collect(),
835            }))
836        }
837
838        /// Convert a VM Error to an execution one
839        pub fn convert_vm_error(&self, error: VMError) -> ExecutionError {
840            crate::error::convert_vm_error(error, self.vm, self.session.get_resolver())
841        }
842
843        /// Special case errors for type arguments to Move functions
844        pub fn convert_type_argument_error(&self, idx: usize, error: VMError) -> ExecutionError {
845            use move_core_types::vm_status::StatusCode;
846            use sui_types::execution_status::TypeArgumentError;
847            match error.major_status() {
848                StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH => {
849                    ExecutionErrorKind::TypeArityMismatch.into()
850                }
851                StatusCode::TYPE_RESOLUTION_FAILURE => ExecutionErrorKind::TypeArgumentError {
852                    argument_idx: idx as TypeParameterIndex,
853                    kind: TypeArgumentError::TypeNotFound,
854                }
855                .into(),
856                StatusCode::CONSTRAINT_NOT_SATISFIED => ExecutionErrorKind::TypeArgumentError {
857                    argument_idx: idx as TypeParameterIndex,
858                    kind: TypeArgumentError::ConstraintNotSatisfied,
859                }
860                .into(),
861                _ => self.convert_vm_error(error),
862            }
863        }
864
865        /// Returns true if the value at the argument's location is borrowed, mutably or immutably
866        fn arg_is_borrowed(&self, arg: &Argument) -> bool {
867            self.borrowed.contains_key(arg)
868        }
869
870        /// Returns true if the value at the argument's location is mutably borrowed
871        fn arg_is_mut_borrowed(&self, arg: &Argument) -> bool {
872            matches!(self.borrowed.get(arg), Some(/* mut */ true))
873        }
874
875        /// Internal helper to borrow the value for an argument and update the most recent usage
876        fn borrow_mut(
877            &mut self,
878            arg: Argument,
879            usage: UsageKind,
880        ) -> Result<(Option<&InputObjectMetadata>, &mut Option<Value>), CommandArgumentError>
881        {
882            self.borrow_mut_impl(arg, Some(usage))
883        }
884
885        /// Internal helper to borrow the value for an argument
886        /// Updates the most recent usage if specified
887        fn borrow_mut_impl(
888            &mut self,
889            arg: Argument,
890            update_last_usage: Option<UsageKind>,
891        ) -> Result<(Option<&InputObjectMetadata>, &mut Option<Value>), CommandArgumentError>
892        {
893            let (metadata, result_value) = match arg {
894                Argument::GasCoin => (self.gas.object_metadata.as_ref(), &mut self.gas.inner),
895                Argument::Input(i) => {
896                    let Some(input_value) = self.inputs.get_mut(i as usize) else {
897                        return Err(CommandArgumentError::IndexOutOfBounds { idx: i });
898                    };
899                    (input_value.object_metadata.as_ref(), &mut input_value.inner)
900                }
901                Argument::Result(i) => {
902                    let Some(command_result) = self.results.get_mut(i as usize) else {
903                        return Err(CommandArgumentError::IndexOutOfBounds { idx: i });
904                    };
905                    if command_result.len() != 1 {
906                        return Err(CommandArgumentError::InvalidResultArity { result_idx: i });
907                    }
908                    (None, &mut command_result[0])
909                }
910                Argument::NestedResult(i, j) => {
911                    let Some(command_result) = self.results.get_mut(i as usize) else {
912                        return Err(CommandArgumentError::IndexOutOfBounds { idx: i });
913                    };
914                    let Some(result_value) = command_result.get_mut(j as usize) else {
915                        return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
916                            result_idx: i,
917                            secondary_idx: j,
918                        });
919                    };
920                    (None, result_value)
921                }
922            };
923            if let Some(usage) = update_last_usage {
924                result_value.last_usage_kind = Some(usage);
925            }
926            Ok((metadata, &mut result_value.value))
927        }
928    }
929
930    impl TypeTagResolver for ExecutionContext<'_, '_, '_> {
931        fn get_type_tag(&self, type_: &Type) -> Result<TypeTag, ExecutionError> {
932            self.session
933                .get_type_tag(type_)
934                .map_err(|e| self.convert_vm_error(e))
935        }
936    }
937
938    pub(crate) fn new_session<'state, 'vm>(
939        vm: &'vm MoveVM,
940        linkage: LinkageView<'state>,
941        child_resolver: &'state dyn ChildObjectResolver,
942        input_objects: BTreeMap<ObjectID, object_runtime::InputObject>,
943        is_metered: bool,
944        protocol_config: &ProtocolConfig,
945        metrics: Arc<LimitsMetrics>,
946    ) -> Session<'state, 'vm, LinkageView<'state>> {
947        vm.new_session_with_extensions(
948            linkage,
949            new_native_extensions(
950                child_resolver,
951                input_objects,
952                is_metered,
953                protocol_config,
954                metrics,
955            ),
956        )
957    }
958
959    // Create a new Session suitable for resolving type and type operations rather than execution
960    pub(crate) fn new_session_for_linkage<'vm, 'state>(
961        vm: &'vm MoveVM,
962        linkage: LinkageView<'state>,
963    ) -> Session<'state, 'vm, LinkageView<'state>> {
964        vm.new_session(linkage)
965    }
966
967    /// Set the link context for the session from the linkage information in the `package`.
968    pub fn set_linkage(
969        session: &mut Session<LinkageView>,
970        linkage: &MovePackage,
971    ) -> Result<AccountAddress, ExecutionError> {
972        session.get_resolver_mut().set_linkage(linkage)
973    }
974
975    /// Turn off linkage information, so that the next use of the session will need to set linkage
976    /// information to succeed.
977    pub fn reset_linkage(session: &mut Session<LinkageView>) {
978        session.get_resolver_mut().reset_linkage();
979    }
980
981    pub fn steal_linkage(session: &mut Session<LinkageView>) -> Option<SavedLinkage> {
982        session.get_resolver_mut().steal_linkage()
983    }
984
985    pub fn restore_linkage(
986        session: &mut Session<LinkageView>,
987        saved: Option<SavedLinkage>,
988    ) -> Result<(), ExecutionError> {
989        session.get_resolver_mut().restore_linkage(saved)
990    }
991
992    /// Fetch the package at `package_id` with a view to using it as a link context.  Produces an error
993    /// if the object at that ID does not exist, or is not a package.
994    fn package_for_linkage(
995        session: &Session<LinkageView>,
996        package_id: ObjectID,
997    ) -> VMResult<PackageObject> {
998        use move_binary_format::errors::PartialVMError;
999        use move_core_types::vm_status::StatusCode;
1000
1001        match session.get_resolver().get_package_object(&package_id) {
1002            Ok(Some(package)) => Ok(package),
1003            Ok(None) => Err(PartialVMError::new(StatusCode::LINKER_ERROR)
1004                .with_message(format!("Cannot find link context {package_id} in store"))
1005                .finish(Location::Undefined)),
1006            Err(err) => Err(PartialVMError::new(StatusCode::LINKER_ERROR)
1007                .with_message(format!(
1008                    "Error loading link context {package_id} from store: {err}"
1009                ))
1010                .finish(Location::Undefined)),
1011        }
1012    }
1013
1014    /// Load `type_tag` to get a `Type` in the provided `session`.  `session`'s linkage context may be
1015    /// reset after this operation, because during the operation, it may change when loading a struct.
1016    pub fn load_type(session: &mut Session<LinkageView>, type_tag: &TypeTag) -> VMResult<Type> {
1017        use move_binary_format::errors::PartialVMError;
1018        use move_core_types::vm_status::StatusCode;
1019
1020        fn verification_error<T>(code: StatusCode) -> VMResult<T> {
1021            Err(PartialVMError::new(code).finish(Location::Undefined))
1022        }
1023
1024        Ok(match type_tag {
1025            TypeTag::Bool => Type::Bool,
1026            TypeTag::U8 => Type::U8,
1027            TypeTag::U16 => Type::U16,
1028            TypeTag::U32 => Type::U32,
1029            TypeTag::U64 => Type::U64,
1030            TypeTag::U128 => Type::U128,
1031            TypeTag::U256 => Type::U256,
1032            TypeTag::Address => Type::Address,
1033            TypeTag::Signer => Type::Signer,
1034
1035            TypeTag::Vector(inner) => Type::Vector(Box::new(load_type(session, inner)?)),
1036            TypeTag::Struct(struct_tag) => {
1037                let StructTag {
1038                    address,
1039                    module,
1040                    name,
1041                    type_params,
1042                } = struct_tag.as_ref();
1043
1044                // Load the package that the struct is defined in, in storage
1045                let defining_id = ObjectID::from_address(*address);
1046                let package = package_for_linkage(session, defining_id)?;
1047
1048                // Set the defining package as the link context on the session while loading the
1049                // struct
1050                let original_address =
1051                    set_linkage(session, package.move_package()).map_err(|e| {
1052                        PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
1053                            .with_message(e.to_string())
1054                            .finish(Location::Undefined)
1055                    })?;
1056
1057                let runtime_id = ModuleId::new(original_address, module.clone());
1058                let res = session.load_struct(&runtime_id, name);
1059                reset_linkage(session);
1060                let (idx, struct_type) = res?;
1061
1062                // Recursively load type parameters, if necessary
1063                let type_param_constraints = struct_type.type_param_constraints();
1064                if type_param_constraints.len() != type_params.len() {
1065                    return verification_error(StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH);
1066                }
1067
1068                if type_params.is_empty() {
1069                    Type::Datatype(idx)
1070                } else {
1071                    let loaded_type_params = type_params
1072                        .iter()
1073                        .map(|type_param| load_type(session, type_param))
1074                        .collect::<VMResult<Vec<_>>>()?;
1075
1076                    // Verify that the type parameter constraints on the struct are met
1077                    for (constraint, param) in type_param_constraints.zip(&loaded_type_params) {
1078                        let abilities = session.get_type_abilities(param)?;
1079                        if !constraint.is_subset(abilities) {
1080                            return verification_error(StatusCode::CONSTRAINT_NOT_SATISFIED);
1081                        }
1082                    }
1083
1084                    Type::DatatypeInstantiation(Box::new((idx, loaded_type_params)))
1085                }
1086            }
1087        })
1088    }
1089
1090    pub(crate) fn make_object_value<'vm, 'state>(
1091        vm: &'vm MoveVM,
1092        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1093        type_: MoveObjectType,
1094        has_public_transfer: bool,
1095        used_in_non_entry_move_call: bool,
1096        contents: &[u8],
1097    ) -> Result<ObjectValue, ExecutionError> {
1098        let contents = if type_.is_coin() {
1099            let Ok(coin) = Coin::from_bcs_bytes(contents) else {
1100                invariant_violation!("Could not deserialize a coin")
1101            };
1102            ObjectContents::Coin(coin)
1103        } else {
1104            ObjectContents::Raw(contents.to_vec())
1105        };
1106
1107        let tag: StructTag = type_.into();
1108        let type_ = load_type(session, &TypeTag::Struct(Box::new(tag)))
1109            .map_err(|e| crate::error::convert_vm_error(e, vm, session.get_resolver()))?;
1110        Ok(ObjectValue {
1111            type_,
1112            has_public_transfer,
1113            used_in_non_entry_move_call,
1114            contents,
1115        })
1116    }
1117
1118    pub(crate) fn value_from_object<'vm, 'state>(
1119        vm: &'vm MoveVM,
1120        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1121        object: &Object,
1122    ) -> Result<ObjectValue, ExecutionError> {
1123        let ObjectInner {
1124            data: Data::Move(object),
1125            ..
1126        } = object.as_inner()
1127        else {
1128            invariant_violation!("Expected a Move object");
1129        };
1130
1131        let used_in_non_entry_move_call = false;
1132        make_object_value(
1133            vm,
1134            session,
1135            object.type_().clone(),
1136            object.has_public_transfer(),
1137            used_in_non_entry_move_call,
1138            object.contents(),
1139        )
1140    }
1141
1142    /// Load an input object from the state_view
1143    fn load_object<'vm, 'state>(
1144        vm: &'vm MoveVM,
1145        state_view: &'state dyn ExecutionState,
1146        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1147        input_object_map: &mut BTreeMap<ObjectID, object_runtime::InputObject>,
1148        override_as_immutable: bool,
1149        id: ObjectID,
1150    ) -> Result<InputValue, ExecutionError> {
1151        let Some(obj) = state_view.read_object(&id) else {
1152            // protected by transaction input checker
1153            invariant_violation!("Object {} does not exist yet", id);
1154        };
1155        // override_as_immutable ==> Owner::Shared
1156        assert_invariant!(
1157            !override_as_immutable || matches!(obj.owner, Owner::Shared { .. }),
1158            "override_as_immutable should only be set for shared objects"
1159        );
1160        let is_mutable_input = match obj.owner {
1161            Owner::AddressOwner(_) => true,
1162            Owner::Shared { .. } => !override_as_immutable,
1163            Owner::Immutable => false,
1164            Owner::ObjectOwner(_) => {
1165                // protected by transaction input checker
1166                invariant_violation!("ObjectOwner objects cannot be input")
1167            }
1168            Owner::ConsensusAddressOwner { .. } => {
1169                unimplemented!("ConsensusAddressOwner does not exist for this execution version")
1170            }
1171        };
1172        let owner = obj.owner.clone();
1173        let version = obj.version();
1174        let object_metadata = InputObjectMetadata::InputObject {
1175            id,
1176            is_mutable_input,
1177            owner: owner.clone(),
1178            version,
1179        };
1180        let obj_value = value_from_object(vm, session, obj)?;
1181        let contained_uids = {
1182            let fully_annotated_layout =
1183                session
1184                    .type_to_fully_annotated_layout(&obj_value.type_)
1185                    .map_err(|e| convert_vm_error(e, vm, session.get_resolver()))?;
1186            let mut bytes = vec![];
1187            obj_value.write_bcs_bytes(&mut bytes);
1188            match get_all_uids(&fully_annotated_layout, &bytes) {
1189                Err(e) => {
1190                    invariant_violation!("Unable to retrieve UIDs for object. Got error: {e}")
1191                }
1192                Ok(uids) => uids,
1193            }
1194        };
1195        let runtime_input = object_runtime::InputObject {
1196            contained_uids,
1197            owner,
1198            version,
1199        };
1200        let prev = input_object_map.insert(id, runtime_input);
1201        // protected by transaction input checker
1202        assert_invariant!(prev.is_none(), "Duplicate input object {}", id);
1203        Ok(InputValue::new_object(object_metadata, obj_value))
1204    }
1205
1206    /// Load a CallArg, either an object or a raw set of BCS bytes
1207    fn load_call_arg<'vm, 'state>(
1208        vm: &'vm MoveVM,
1209        state_view: &'state dyn ExecutionState,
1210        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1211        input_object_map: &mut BTreeMap<ObjectID, object_runtime::InputObject>,
1212        call_arg: CallArg,
1213    ) -> Result<InputValue, ExecutionError> {
1214        Ok(match call_arg {
1215            CallArg::Pure(bytes) => InputValue::new_raw(RawValueType::Any, bytes),
1216            CallArg::Object(obj_arg) => {
1217                load_object_arg(vm, state_view, session, input_object_map, obj_arg)?
1218            }
1219            CallArg::FundsWithdrawal(_) => unreachable!("Impossible to hit BalanceWithdraw in v0"),
1220        })
1221    }
1222
1223    /// Load an ObjectArg from state view, marking if it can be treated as mutable or not
1224    fn load_object_arg<'vm, 'state>(
1225        vm: &'vm MoveVM,
1226        state_view: &'state dyn ExecutionState,
1227        session: &mut Session<'state, 'vm, LinkageView<'state>>,
1228        input_object_map: &mut BTreeMap<ObjectID, object_runtime::InputObject>,
1229        obj_arg: ObjectArg,
1230    ) -> Result<InputValue, ExecutionError> {
1231        match obj_arg {
1232            ObjectArg::ImmOrOwnedObject((id, _, _)) => load_object(
1233                vm,
1234                state_view,
1235                session,
1236                input_object_map,
1237                /* imm override */ false,
1238                id,
1239            ),
1240            ObjectArg::SharedObject { id, mutability, .. } => load_object(
1241                vm,
1242                state_view,
1243                session,
1244                input_object_map,
1245                /* imm override */ !mutability.is_exclusive(),
1246                id,
1247            ),
1248            ObjectArg::Receiving(_) => unreachable!("Impossible to hit Receiving in v0"),
1249        }
1250    }
1251
1252    /// Generate an additional write for an ObjectValue
1253    fn add_additional_write(
1254        additional_writes: &mut BTreeMap<ObjectID, AdditionalWrite>,
1255        owner: Owner,
1256        object_value: ObjectValue,
1257    ) -> Result<(), ExecutionError> {
1258        let ObjectValue {
1259            type_,
1260            has_public_transfer,
1261            contents,
1262            ..
1263        } = object_value;
1264        let bytes = match contents {
1265            ObjectContents::Coin(coin) => coin.to_bcs_bytes(),
1266            ObjectContents::Raw(bytes) => bytes,
1267        };
1268        let object_id = MoveObject::id_opt(&bytes).map_err(|e| {
1269            ExecutionError::invariant_violation(format!("No id for Raw object bytes. {e}"))
1270        })?;
1271        let additional_write = AdditionalWrite {
1272            recipient: owner,
1273            type_,
1274            has_public_transfer,
1275            bytes,
1276        };
1277        additional_writes.insert(object_id, additional_write);
1278        Ok(())
1279    }
1280
1281    /// The max budget was deducted from the gas coin at the beginning of the transaction,
1282    /// now we return exactly that amount. Gas will be charged by the execution engine
1283    fn refund_max_gas_budget(
1284        additional_writes: &mut BTreeMap<ObjectID, AdditionalWrite>,
1285        gas_charger: &mut GasCharger,
1286        gas_id: ObjectID,
1287    ) -> Result<(), ExecutionError> {
1288        let Some(AdditionalWrite { bytes, .. }) = additional_writes.get_mut(&gas_id) else {
1289            invariant_violation!("Gas object cannot be wrapped or destroyed")
1290        };
1291        let Ok(mut coin) = Coin::from_bcs_bytes(bytes) else {
1292            invariant_violation!("Gas object must be a coin")
1293        };
1294        let Some(new_balance) = coin.balance.value().checked_add(gas_charger.gas_budget()) else {
1295            return Err(ExecutionError::new_with_source(
1296                ExecutionErrorKind::CoinBalanceOverflow,
1297                "Gas coin too large after returning the max gas budget",
1298            ));
1299        };
1300        coin.balance = Balance::new(new_balance);
1301        *bytes = coin.to_bcs_bytes();
1302        Ok(())
1303    }
1304
1305    /// Generate an MoveObject given an updated/written object
1306    /// # Safety
1307    ///
1308    /// This function assumes proper generation of has_public_transfer, either from the abilities of
1309    /// the StructTag, or from the runtime correctly propagating from the inputs
1310    unsafe fn create_written_object<'vm, 'state>(
1311        vm: &'vm MoveVM,
1312        session: &Session<'state, 'vm, LinkageView<'state>>,
1313        protocol_config: &ProtocolConfig,
1314        input_object_metadata: &BTreeMap<ObjectID, InputObjectMetadata>,
1315        loaded_child_objects: &BTreeMap<ObjectID, SequenceNumber>,
1316        id: ObjectID,
1317        type_: Type,
1318        has_public_transfer: bool,
1319        contents: Vec<u8>,
1320        write_kind: WriteKind,
1321    ) -> Result<MoveObject, ExecutionError> {
1322        debug_assert_eq!(
1323            id,
1324            MoveObject::id_opt(&contents).expect("object contents should start with an id")
1325        );
1326        let metadata_opt = input_object_metadata.get(&id);
1327        let loaded_child_version_opt = loaded_child_objects.get(&id);
1328        assert_invariant!(
1329            metadata_opt.is_none() || loaded_child_version_opt.is_none(),
1330            "Loaded {id} as a child, but that object was an input object",
1331        );
1332
1333        let old_obj_ver = metadata_opt
1334            .map(|metadata| metadata.version())
1335            .or_else(|| loaded_child_version_opt.copied());
1336
1337        debug_assert!(
1338            (write_kind == WriteKind::Mutate) == old_obj_ver.is_some(),
1339            "Inconsistent state: write_kind: {write_kind:?}, old ver: {old_obj_ver:?}"
1340        );
1341
1342        let type_tag = session
1343            .get_type_tag(&type_)
1344            .map_err(|e| crate::error::convert_vm_error(e, vm, session.get_resolver()))?;
1345
1346        let struct_tag = match type_tag {
1347            TypeTag::Struct(inner) => *inner,
1348            _ => invariant_violation!("Non struct type for object"),
1349        };
1350        MoveObject::new_from_execution(
1351            struct_tag.into(),
1352            has_public_transfer,
1353            old_obj_ver.unwrap_or_default(),
1354            contents,
1355            protocol_config,
1356            /* system_mutation */ false,
1357        )
1358    }
1359}