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