Skip to main content

sui_move_natives_latest/
test_scenario.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    get_extension, get_extension_mut, get_nth_struct_field, get_tag_and_layouts, legacy_test_cost,
6    object_runtime::{
7        MoveAccumulatorAction, MoveAccumulatorEvent, MoveAccumulatorValue, ObjectRuntime,
8        RuntimeResults, object_store::ChildObjectEffects,
9    },
10    scratch::ScratchRuntime,
11};
12use better_any::{Tid, TidAble};
13use indexmap::{IndexMap, IndexSet};
14use move_binary_format::errors::{PartialVMError, PartialVMResult};
15use move_binary_format::{safe_assert, safe_unwrap};
16use move_core_types::{
17    account_address::AccountAddress,
18    annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout, MoveValue},
19    annotated_visitor as AV,
20    language_storage::StructTag,
21    u256::U256,
22    vm_status::StatusCode,
23};
24use move_vm_runtime::{
25    execution::values::{Vector, VectorSpecialization},
26    natives::{
27        extensions::NativeExtensionMarker,
28        functions::{NativeContext, NativeResult},
29    },
30};
31use move_vm_runtime::{
32    execution::{
33        Type,
34        values::{self, StructRef, Value},
35    },
36    pop_arg,
37};
38use smallvec::smallvec;
39use std::{
40    borrow::Borrow,
41    cell::RefCell,
42    collections::{BTreeMap, BTreeSet, VecDeque},
43};
44use sui_types::{
45    TypeTag,
46    base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress},
47    config,
48    digests::{ObjectDigest, TransactionDigest},
49    dynamic_field::DynamicFieldInfo,
50    execution::DynamicallyLoadedObjectMetadata,
51    id::UID,
52    in_memory_storage::InMemoryStorage,
53    object::{MoveObject, Object, Owner},
54    storage::{
55        BackingPackageStore, ImplicitSystemObjectResolver, PackageObject, RuntimeObjectResolver,
56    },
57};
58
59const E_COULD_NOT_GENERATE_EFFECTS: u64 = 0;
60const E_INVALID_SHARED_OR_IMMUTABLE_USAGE: u64 = 1;
61const E_OBJECT_NOT_FOUND_CODE: u64 = 4;
62const E_UNABLE_TO_ALLOCATE_RECEIVING_TICKET: u64 = 5;
63const E_RECEIVING_TICKET_ALREADY_ALLOCATED: u64 = 6;
64const E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET: u64 = 7;
65const E_INSUFFICIENT_FUNDS: u64 = 8;
66const E_UNBACKED_WITHDRAWAL: u64 = 11;
67
68type Set<K> = IndexSet<K>;
69
70/// An in-memory test store is a thin wrapper around the in-memory storage and funds in a mutex.
71/// The mutex allows this to be used by both the object runtime (for reading) and the test
72/// scenario (for writing) while hiding mutability.
73#[derive(Tid, Default)]
74pub struct InMemoryTestStore {
75    /// Objects and packages visible to the object runtime.
76    storage: RefCell<InMemoryStorage>,
77    /// Settled funds balance per owner address (address or object) and funds type (e.g.
78    /// `Balance<T>`). Updated from each transaction's accumulator events when the transaction
79    /// ends; within a transaction, the object runtime tracks its own deposits and withdrawals.
80    funds: RefCell<BTreeMap<(SuiAddress, TypeTag), u128>>,
81    /// Funds reserved so far in the current transaction by address withdrawals, which stand in
82    /// for a transaction's funds withdrawal inputs. Object withdrawals are reserved by the object
83    /// runtime instead, under different rules.
84    address_reservations: RefCell<BTreeMap<(SuiAddress, TypeTag), U256>>,
85}
86impl<'a> NativeExtensionMarker<'a> for &'a InMemoryTestStore {}
87
88impl InMemoryTestStore {
89    fn settled_funds(&self, owner: SuiAddress, type_: &TypeTag) -> u128 {
90        self.funds
91            .borrow()
92            .get(&(owner, type_.clone()))
93            .copied()
94            .unwrap_or(0)
95    }
96
97    /// Reserves `limit` of `type_` from `owner`'s settled funds, on top of what this transaction
98    /// has reserved from it already. Returns false if that exceeds the settled funds.
99    fn reserve_address_funds(&self, owner: SuiAddress, type_: TypeTag, limit: U256) -> bool {
100        let Some(reserved) = self
101            .address_reservations
102            .borrow()
103            .get(&(owner, type_.clone()))
104            .copied()
105            .unwrap_or_else(U256::zero)
106            .checked_add(limit)
107        else {
108            return false;
109        };
110        if reserved > U256::from(self.settled_funds(owner, &type_)) {
111            return false;
112        }
113        self.address_reservations
114            .borrow_mut()
115            .insert((owner, type_), reserved);
116        true
117    }
118
119    /// Applies the ending transaction's accumulator events to the settled balances, and drops
120    /// the reservations it took, which only cover that transaction. Returns false if the
121    /// transaction withdrew more from an owner than it had, which only a withdrawal that was not
122    /// reserved against these balances (e.g. one kept from an earlier transaction) can do.
123    fn settle_funds(&self, accumulator_events: Vec<MoveAccumulatorEvent>) -> bool {
124        self.address_reservations.borrow_mut().clear();
125        let mut changes: BTreeMap<(SuiAddress, TypeTag), (u128, u128)> = BTreeMap::new();
126        for event in accumulator_events {
127            let MoveAccumulatorValue::U64(amount) = event.value else {
128                continue;
129            };
130            let (merged, split) = changes
131                .entry((event.target_addr.into(), event.target_ty))
132                .or_default();
133            match event.action {
134                MoveAccumulatorAction::Merge => *merged += amount as u128,
135                MoveAccumulatorAction::Split => *split += amount as u128,
136            }
137        }
138        let mut funds = self.funds.borrow_mut();
139        // TODO: also write these balances into the accumulator root's fields, and refresh the
140        // object runtime's cached copies of them, so that `balance::settled_funds_value` reads
141        // them in test_scenario. Today it always returns zero there.
142        for (key, (merged, split)) in changes {
143            let balance = funds.entry(key).or_default();
144            let Some(settled) = (*balance + merged).checked_sub(split) else {
145                return false;
146            };
147            *balance = settled;
148        }
149        true
150    }
151}
152
153impl BackingPackageStore for InMemoryTestStore {
154    fn get_package_object(
155        &self,
156        package_id: &ObjectID,
157    ) -> sui_types::error::SuiResult<Option<PackageObject>> {
158        self.storage.borrow().get_package_object(package_id)
159    }
160}
161
162impl ImplicitSystemObjectResolver for InMemoryTestStore {
163    fn object_available_balance(
164        &self,
165        owner: SuiAddress,
166        type_: &TypeTag,
167    ) -> sui_types::error::SuiResult<u128> {
168        Ok(self.settled_funds(owner, type_))
169    }
170}
171
172impl RuntimeObjectResolver for InMemoryTestStore {
173    fn read_child_object(
174        &self,
175        parent: &ObjectID,
176        child: &ObjectID,
177        child_version_upper_bound: SequenceNumber,
178    ) -> sui_types::error::SuiResult<Option<Object>> {
179        self.storage
180            .borrow()
181            .read_child_object(parent, child, child_version_upper_bound)
182    }
183
184    fn get_object_received_at_version(
185        &self,
186        owner: &ObjectID,
187        receiving_object_id: &ObjectID,
188        receive_object_at_version: SequenceNumber,
189        epoch_id: sui_types::committee::EpochId,
190    ) -> sui_types::error::SuiResult<Option<Object>> {
191        self.storage.borrow().get_object_received_at_version(
192            owner,
193            receiving_object_id,
194            receive_object_at_version,
195            epoch_id,
196        )
197    }
198}
199
200// This function updates the inventories based on the transfers and deletes that occurred in the
201// transaction
202// native fun end_transaction(): TransactionResult;
203pub fn end_transaction(
204    context: &mut NativeContext,
205    ty_args: Vec<Type>,
206    args: VecDeque<Value>,
207) -> PartialVMResult<NativeResult> {
208    safe_assert!(ty_args.is_empty());
209    safe_assert!(args.is_empty());
210    // scratch is per-transaction. A single set of native extensions is reused across the
211    // transactions simulated by `test_scenario`, so clear the store here to mirror the fresh
212    // `ScratchRuntime` that real execution installs per transaction.
213    get_extension_mut!(context, ScratchRuntime)?.clear();
214    let object_runtime_ref: &mut ObjectRuntime = get_extension_mut!(context)?;
215    let taken_shared_or_imm: BTreeMap<_, _> = object_runtime_ref
216        .test_inventories
217        .taken
218        .iter()
219        .filter(|(_id, owner)| matches!(owner, Owner::Shared { .. } | Owner::Immutable))
220        .map(|(id, owner)| (*id, owner.clone()))
221        .collect();
222    // set to true if a shared or imm object was:
223    // - transferred in a way that changes it from its original shared/imm state
224    // - wraps the object
225    // if true, we will "abort"
226    let mut incorrect_shared_or_imm_handling = false;
227
228    // Handle the allocated tickets:
229    // * Remove all allocated_tickets in the test inventories.
230    // * For each allocated ticket, if the ticket's object ID is loaded, move it to `received`.
231    // * Otherwise re-insert the allocated ticket into the objects inventory, and mark it to be
232    //   removed from the backing storage (deferred due to needing to have access to `context` which
233    //   has outstanding references at this point).
234    let allocated_tickets =
235        std::mem::take(&mut object_runtime_ref.test_inventories.allocated_tickets);
236    let mut received = BTreeMap::new();
237    let mut unreceived = BTreeSet::new();
238    let loaded_runtime_objects = object_runtime_ref.loaded_runtime_objects();
239    for (id, (metadata, value)) in allocated_tickets {
240        if loaded_runtime_objects.contains_key(&id) {
241            received.insert(id, metadata);
242        } else {
243            unreceived.insert(id);
244            // This must be untouched since the allocated ticket is still live, so ok to re-insert.
245            object_runtime_ref
246                .test_inventories
247                .objects
248                .insert(id, value);
249        }
250    }
251
252    let object_runtime_state = object_runtime_ref.take_state();
253    // Determine writes and deletes
254    // We pass the received objects since they should be viewed as "loaded" for the purposes of
255    // calculating the effects of the transaction.
256    let results = object_runtime_state.finish(received, ChildObjectEffects::new());
257    let RuntimeResults {
258        writes,
259        user_events,
260        loaded_child_objects: _,
261        created_object_ids,
262        deleted_object_ids,
263        accumulator_events,
264        settlement_input_sui: _,
265        settlement_output_sui: _,
266    } = match results {
267        Ok(res) => res,
268        Err(_) => {
269            return Ok(NativeResult::err(
270                legacy_test_cost(),
271                E_COULD_NOT_GENERATE_EFFECTS,
272            ));
273        }
274    };
275    let object_runtime_ref: &mut ObjectRuntime = get_extension_mut!(context)?;
276    let all_active_child_objects_with_values = object_runtime_ref
277        .all_active_child_objects()
278        .filter(|child| child.copied_value.is_some())
279        .map(|child| *child.id)
280        .collect::<BTreeSet<_>>();
281    let inventories = &mut object_runtime_ref.test_inventories;
282    let mut new_object_values = IndexMap::new();
283    let mut transferred = vec![];
284    // cleanup inventories
285    // we will remove all changed objects
286    // - deleted objects need to be removed to mark deletions
287    // - written objects are removed and later replaced to mark new values and new owners
288    // - child objects will not be reflected in transfers, but need to be no longer retrievable
289    for id in deleted_object_ids
290        .iter()
291        .chain(writes.keys())
292        .chain(&all_active_child_objects_with_values)
293    {
294        for addr_inventory in inventories.address_inventories.values_mut() {
295            for s in addr_inventory.values_mut() {
296                s.shift_remove(id);
297            }
298        }
299        for s in &mut inventories.shared_inventory.values_mut() {
300            s.shift_remove(id);
301        }
302        for s in &mut inventories.immutable_inventory.values_mut() {
303            s.shift_remove(id);
304        }
305        inventories.taken.remove(id);
306    }
307
308    // handle transfers, inserting transferred/written objects into their respective inventory
309    let mut created = vec![];
310    let mut written = vec![];
311    for (id, (owner, ty, value)) in writes {
312        // write configs to cache
313        new_object_values.insert(id, (ty.clone(), value.copy_value()));
314        transferred.push((id, owner.clone()));
315        incorrect_shared_or_imm_handling = incorrect_shared_or_imm_handling
316            || taken_shared_or_imm
317                .get(&id)
318                .map(|shared_or_imm_owner| shared_or_imm_owner != &owner)
319                .unwrap_or(/* not incorrect */ false);
320        if created_object_ids.contains(&id) {
321            created.push(id);
322        } else {
323            written.push(id);
324        }
325        match owner {
326            Owner::AddressOwner(a) => {
327                inventories
328                    .address_inventories
329                    .entry(a)
330                    .or_default()
331                    .entry(ty)
332                    .or_default()
333                    .insert(id);
334            }
335            Owner::ObjectOwner(_) => (),
336            Owner::Shared { .. } => {
337                inventories
338                    .shared_inventory
339                    .entry(ty)
340                    .or_default()
341                    .insert(id);
342            }
343            Owner::Immutable => {
344                inventories
345                    .immutable_inventory
346                    .entry(ty)
347                    .or_default()
348                    .insert(id);
349            }
350            Owner::ConsensusAddressOwner { owner, .. } => {
351                inventories
352                    .address_inventories
353                    .entry(owner)
354                    .or_default()
355                    .entry(ty)
356                    .or_default()
357                    .insert(id);
358            }
359            Owner::Party { .. } => {
360                // TODO(Party WIP)
361                todo!("Party WIP")
362            }
363        }
364    }
365
366    // For any unused allocated tickets, remove them from the store.
367    let store: &&InMemoryTestStore = get_extension!(context)?;
368    for id in unreceived {
369        if store.storage.borrow_mut().remove_object(id).is_none() {
370            return Ok(NativeResult::err(
371                context.gas_used(),
372                E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET,
373            ));
374        }
375    }
376    if !store.settle_funds(accumulator_events) {
377        return Ok(NativeResult::err(legacy_test_cost(), E_UNBACKED_WITHDRAWAL));
378    }
379
380    // deletions already handled above, but we drop the delete kind for the effects
381    let mut deleted = vec![];
382    for id in deleted_object_ids {
383        // Mark as "incorrect" if a imm object was deleted. Allow shared objects to be deleted though.
384        incorrect_shared_or_imm_handling = incorrect_shared_or_imm_handling
385            || taken_shared_or_imm
386                .get(&id)
387                .is_some_and(|owner| matches!(owner, Owner::Immutable));
388        deleted.push(id);
389    }
390    // find all wrapped objects
391    let mut all_wrapped = BTreeSet::new();
392    let object_runtime_ref: &ObjectRuntime = get_extension!(context)?;
393    find_all_wrapped_objects(
394        context,
395        &mut all_wrapped,
396        new_object_values
397            .iter()
398            .map(|(id, (ty, value))| (id, ty, value)),
399    )?;
400    find_all_wrapped_objects(
401        context,
402        &mut all_wrapped,
403        object_runtime_ref
404            .all_active_child_objects()
405            .filter_map(|child| Some((child.id, child.ty, child.copied_value?))),
406    )?;
407    // mark as "incorrect" if a shared/imm object was wrapped or is a child object
408    incorrect_shared_or_imm_handling = incorrect_shared_or_imm_handling
409        || taken_shared_or_imm.keys().any(|id| {
410            all_wrapped.contains(id) || all_active_child_objects_with_values.contains(id)
411        });
412    // if incorrect handling, return with an 'abort'
413    if incorrect_shared_or_imm_handling {
414        return Ok(NativeResult::err(
415            legacy_test_cost(),
416            E_INVALID_SHARED_OR_IMMUTABLE_USAGE,
417        ));
418    }
419
420    // mark all wrapped as deleted
421    for wrapped in all_wrapped {
422        deleted.push(wrapped)
423    }
424
425    // new input objects are remaining taken objects not written/deleted
426    let object_runtime_ref: &mut ObjectRuntime = get_extension_mut!(context)?;
427    let mut config_settings = vec![];
428    for child in object_runtime_ref.all_active_child_objects() {
429        let s: StructTag = child.ty.clone().into();
430        let is_setting = DynamicFieldInfo::is_dynamic_field(&s)
431            && matches!(&s.type_params[1], TypeTag::Struct(s) if config::is_setting(s));
432        if is_setting {
433            config_settings.push((
434                *child.owner,
435                *child.id,
436                child.ty.clone(),
437                child.copied_value,
438            ));
439        }
440    }
441    for (config, setting, ty, value) in config_settings {
442        object_runtime_ref.config_setting_cache_update(config, setting, ty, value)
443    }
444    object_runtime_ref.state.input_objects = object_runtime_ref
445        .test_inventories
446        .taken
447        .iter()
448        .map(|(id, owner)| (*id, owner.clone()))
449        .collect::<BTreeMap<_, _>>();
450    // update inventories
451    // check for bad updates to immutable values
452    for (id, (ty, value)) in new_object_values {
453        debug_assert!(!all_active_child_objects_with_values.contains(&id));
454        if let Some(prev_value) = object_runtime_ref
455            .test_inventories
456            .taken_immutable_values
457            .get(&ty)
458            .and_then(|values| values.get(&id))
459            && !value.equals(prev_value)?
460        {
461            return Ok(NativeResult::err(
462                legacy_test_cost(),
463                E_INVALID_SHARED_OR_IMMUTABLE_USAGE,
464            ));
465        }
466        object_runtime_ref
467            .test_inventories
468            .objects
469            .insert(id, value);
470    }
471    // remove deleted
472    for id in &deleted {
473        object_runtime_ref.test_inventories.objects.remove(id);
474    }
475    // remove active child objects
476    for id in all_active_child_objects_with_values {
477        object_runtime_ref.test_inventories.objects.remove(&id);
478    }
479
480    let effects = transaction_effects(
481        created,
482        written,
483        deleted,
484        transferred,
485        user_events.len() as u64,
486        // TODO: do we need accumulator events here?
487    )?;
488    Ok(NativeResult::ok(legacy_test_cost(), smallvec![effects]))
489}
490
491pub fn reserve_funds_from_address(
492    context: &mut NativeContext,
493    ty_args: Vec<Type>,
494    mut args: VecDeque<Value>,
495) -> PartialVMResult<NativeResult> {
496    let funds_type = context.type_to_type_tag(&get_specified_ty(ty_args)?)?;
497    let limit = pop_arg!(args, U256);
498    let owner: SuiAddress = pop_arg!(args, AccountAddress).into();
499    safe_assert!(args.is_empty());
500    let store: &&InMemoryTestStore = get_extension!(context)?;
501    if !store.reserve_address_funds(owner, funds_type, limit) {
502        return Ok(NativeResult::err(legacy_test_cost(), E_INSUFFICIENT_FUNDS));
503    }
504    Ok(NativeResult::ok(legacy_test_cost(), smallvec![]))
505}
506
507pub fn settled_funds(
508    context: &mut NativeContext,
509    ty_args: Vec<Type>,
510    mut args: VecDeque<Value>,
511) -> PartialVMResult<NativeResult> {
512    let funds_type = context.type_to_type_tag(&get_specified_ty(ty_args)?)?;
513    let owner: SuiAddress = pop_arg!(args, AccountAddress).into();
514    safe_assert!(args.is_empty());
515    let store: &&InMemoryTestStore = get_extension!(context)?;
516    let value = store.settled_funds(owner, &funds_type);
517    Ok(NativeResult::ok(
518        legacy_test_cost(),
519        smallvec![Value::u128(value)],
520    ))
521}
522
523// native fun take_from_address_by_id<T: key>(account: address, id: ID): T;
524pub fn take_from_address_by_id(
525    context: &mut NativeContext,
526    ty_args: Vec<Type>,
527    mut args: VecDeque<Value>,
528) -> PartialVMResult<NativeResult> {
529    let specified_ty = get_specified_ty(ty_args)?;
530    let id = pop_id(&mut args)?;
531    let account: SuiAddress = pop_arg!(args, AccountAddress).into();
532    pop_arg!(args, StructRef);
533    safe_assert!(args.is_empty());
534    let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
535    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
536    let inventories = &mut object_runtime.test_inventories;
537    let res = take_from_inventory(
538        |x| {
539            inventories
540                .address_inventories
541                .get(&account)
542                .and_then(|inv| inv.get(&specified_obj_ty))
543                .map(|s| s.contains(x))
544                .unwrap_or(false)
545        },
546        &inventories.objects,
547        &mut inventories.taken,
548        &mut object_runtime.state.input_objects,
549        id,
550        Owner::AddressOwner(account),
551    );
552    Ok(match res {
553        Ok(value) => NativeResult::ok(legacy_test_cost(), smallvec![value]),
554        Err(native_err) => native_err,
555    })
556}
557
558// native fun ids_for_address<T: key>(account: address): vector<ID>;
559pub fn ids_for_address(
560    context: &mut NativeContext,
561    ty_args: Vec<Type>,
562    mut args: VecDeque<Value>,
563) -> PartialVMResult<NativeResult> {
564    let specified_ty = get_specified_ty(ty_args)?;
565    let account: SuiAddress = pop_arg!(args, AccountAddress).into();
566    safe_assert!(args.is_empty());
567    let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
568    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
569    let inventories = &mut object_runtime.test_inventories;
570    let ids = inventories
571        .address_inventories
572        .get(&account)
573        .and_then(|inv| inv.get(&specified_obj_ty))
574        .map(|s| s.iter().map(|id| pack_id(*id)).collect::<Vec<Value>>())
575        .unwrap_or_default();
576    let ids_vector = safe_unwrap!(Vector::pack(VectorSpecialization::Container, ids));
577    Ok(NativeResult::ok(legacy_test_cost(), smallvec![ids_vector]))
578}
579
580// native fun most_recent_id_for_address<T: key>(account: address): Option<ID>;
581pub fn most_recent_id_for_address(
582    context: &mut NativeContext,
583    ty_args: Vec<Type>,
584    mut args: VecDeque<Value>,
585) -> PartialVMResult<NativeResult> {
586    let specified_ty = get_specified_ty(ty_args)?;
587    let account: SuiAddress = pop_arg!(args, AccountAddress).into();
588    safe_assert!(args.is_empty());
589    let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
590    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
591    let inventories = &mut object_runtime.test_inventories;
592    let most_recent_id = match inventories.address_inventories.get(&account) {
593        None => pack_option(vector_specialization(&specified_ty), None)?,
594        Some(inv) => most_recent_at_ty(&inventories.taken, inv, &specified_ty, specified_obj_ty)?,
595    };
596    Ok(NativeResult::ok(
597        legacy_test_cost(),
598        smallvec![most_recent_id],
599    ))
600}
601
602// native fun was_taken_from_address(account: address, id: ID): bool;
603pub fn was_taken_from_address(
604    context: &mut NativeContext,
605    ty_args: Vec<Type>,
606    mut args: VecDeque<Value>,
607) -> PartialVMResult<NativeResult> {
608    safe_assert!(ty_args.is_empty());
609    let id = pop_id(&mut args)?;
610    let account: SuiAddress = pop_arg!(args, AccountAddress).into();
611    safe_assert!(args.is_empty());
612    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
613    let inventories = &mut object_runtime.test_inventories;
614    let was_taken = inventories
615        .taken
616        .get(&id)
617        .map(|owner| owner == &Owner::AddressOwner(account))
618        .unwrap_or(false);
619    Ok(NativeResult::ok(
620        legacy_test_cost(),
621        smallvec![Value::bool(was_taken)],
622    ))
623}
624
625// native fun take_immutable_by_id<T: key>(id: ID): T;
626pub fn take_immutable_by_id(
627    context: &mut NativeContext,
628    ty_args: Vec<Type>,
629    mut args: VecDeque<Value>,
630) -> PartialVMResult<NativeResult> {
631    let specified_ty = get_specified_ty(ty_args)?;
632    let id = pop_id(&mut args)?;
633    pop_arg!(args, StructRef);
634    safe_assert!(args.is_empty());
635    let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
636    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
637    let inventories = &mut object_runtime.test_inventories;
638    let res = take_from_inventory(
639        |x| {
640            inventories
641                .immutable_inventory
642                .get(&specified_obj_ty)
643                .map(|s| s.contains(x))
644                .unwrap_or(false)
645        },
646        &inventories.objects,
647        &mut inventories.taken,
648        &mut object_runtime.state.input_objects,
649        id,
650        Owner::Immutable,
651    );
652    Ok(match res {
653        Ok(value) => {
654            inventories
655                .taken_immutable_values
656                .entry(specified_obj_ty)
657                .or_default()
658                .insert(id, value.copy_value());
659            NativeResult::ok(legacy_test_cost(), smallvec![value])
660        }
661        Err(native_err) => native_err,
662    })
663}
664
665// native fun most_recent_immutable_id<T: key>(): Option<ID>;
666pub fn most_recent_immutable_id(
667    context: &mut NativeContext,
668    ty_args: Vec<Type>,
669    args: VecDeque<Value>,
670) -> PartialVMResult<NativeResult> {
671    let specified_ty = get_specified_ty(ty_args)?;
672    safe_assert!(args.is_empty());
673    let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
674    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
675    let inventories = &mut object_runtime.test_inventories;
676    let most_recent_id = most_recent_at_ty(
677        &inventories.taken,
678        &inventories.immutable_inventory,
679        &specified_ty,
680        specified_obj_ty,
681    )?;
682    Ok(NativeResult::ok(
683        legacy_test_cost(),
684        smallvec![most_recent_id],
685    ))
686}
687
688// native fun was_taken_immutable(id: ID): bool;
689pub fn was_taken_immutable(
690    context: &mut NativeContext,
691    ty_args: Vec<Type>,
692    mut args: VecDeque<Value>,
693) -> PartialVMResult<NativeResult> {
694    safe_assert!(ty_args.is_empty());
695    let id = pop_id(&mut args)?;
696    safe_assert!(args.is_empty());
697    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
698    let inventories = &mut object_runtime.test_inventories;
699    let was_taken = inventories
700        .taken
701        .get(&id)
702        .map(|owner| owner == &Owner::Immutable)
703        .unwrap_or(false);
704    Ok(NativeResult::ok(
705        legacy_test_cost(),
706        smallvec![Value::bool(was_taken)],
707    ))
708}
709
710// native fun take_shared_by_id<T: key>(id: ID): T;
711pub fn take_shared_by_id(
712    context: &mut NativeContext,
713    ty_args: Vec<Type>,
714    mut args: VecDeque<Value>,
715) -> PartialVMResult<NativeResult> {
716    let specified_ty = get_specified_ty(ty_args)?;
717    let id = pop_id(&mut args)?;
718    pop_arg!(args, StructRef);
719    safe_assert!(args.is_empty());
720    let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
721    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
722    let inventories = &mut object_runtime.test_inventories;
723    let res = take_from_inventory(
724        |x| {
725            inventories
726                .shared_inventory
727                .get(&specified_obj_ty)
728                .map(|s| s.contains(x))
729                .unwrap_or(false)
730        },
731        &inventories.objects,
732        &mut inventories.taken,
733        &mut object_runtime.state.input_objects,
734        id,
735        Owner::Shared { initial_shared_version: /* dummy */ SequenceNumber::new() },
736    );
737    Ok(match res {
738        Ok(value) => NativeResult::ok(legacy_test_cost(), smallvec![value]),
739        Err(native_err) => native_err,
740    })
741}
742
743// native fun most_recent_id_shared<T: key>(): Option<ID>;
744pub fn most_recent_id_shared(
745    context: &mut NativeContext,
746    ty_args: Vec<Type>,
747    args: VecDeque<Value>,
748) -> PartialVMResult<NativeResult> {
749    let specified_ty = get_specified_ty(ty_args)?;
750    safe_assert!(args.is_empty());
751    let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
752    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
753    let inventories = &mut object_runtime.test_inventories;
754    let most_recent_id = most_recent_at_ty(
755        &inventories.taken,
756        &inventories.shared_inventory,
757        &specified_ty,
758        specified_obj_ty,
759    )?;
760    Ok(NativeResult::ok(
761        legacy_test_cost(),
762        smallvec![most_recent_id],
763    ))
764}
765
766// native fun was_taken_shared(id: ID): bool;
767pub fn was_taken_shared(
768    context: &mut NativeContext,
769    ty_args: Vec<Type>,
770    mut args: VecDeque<Value>,
771) -> PartialVMResult<NativeResult> {
772    safe_assert!(ty_args.is_empty());
773    let id = pop_id(&mut args)?;
774    safe_assert!(args.is_empty());
775    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
776    let inventories = &mut object_runtime.test_inventories;
777    let was_taken = inventories
778        .taken
779        .get(&id)
780        .map(|owner| matches!(owner, Owner::Shared { .. }))
781        .unwrap_or(false);
782    Ok(NativeResult::ok(
783        legacy_test_cost(),
784        smallvec![Value::bool(was_taken)],
785    ))
786}
787
788pub fn allocate_receiving_ticket_for_object(
789    context: &mut NativeContext,
790    ty_args: Vec<Type>,
791    mut args: VecDeque<Value>,
792) -> PartialVMResult<NativeResult> {
793    let ty = get_specified_ty(ty_args)?;
794    let id = pop_id(&mut args)?;
795
796    let abilities = context.type_to_abilities(&ty)?;
797    let Some((tag, layout, _)) = get_tag_and_layouts(context, &ty)? else {
798        return Ok(NativeResult::err(
799            context.gas_used(),
800            E_UNABLE_TO_ALLOCATE_RECEIVING_TICKET,
801        ));
802    };
803    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
804    let object_version = SequenceNumber::new();
805    let inventories = &mut object_runtime.test_inventories;
806    if inventories.allocated_tickets.contains_key(&id) {
807        return Ok(NativeResult::err(
808            context.gas_used(),
809            E_RECEIVING_TICKET_ALREADY_ALLOCATED,
810        ));
811    }
812
813    let obj_value = safe_unwrap!(inventories.objects.remove(&id));
814    let Some(bytes) = obj_value.typed_serialize(&layout) else {
815        return Ok(NativeResult::err(
816            context.gas_used(),
817            E_UNABLE_TO_ALLOCATE_RECEIVING_TICKET,
818        ));
819    };
820    let has_public_transfer = abilities.has_store();
821    let move_object = safe_unwrap!(unsafe {
822        MoveObject::new_from_execution_with_limit(
823            tag.into(),
824            has_public_transfer,
825            object_version,
826            bytes,
827            250 * 1024,
828        )
829    });
830
831    let Some((owner, _)) = inventories
832        .address_inventories
833        .iter()
834        .find(|(_addr, objs)| objs.iter().any(|(_, ids)| ids.contains(&id)))
835    else {
836        return Ok(NativeResult::err(
837            context.gas_used(),
838            E_OBJECT_NOT_FOUND_CODE,
839        ));
840    };
841
842    inventories.allocated_tickets.insert(
843        id,
844        (
845            DynamicallyLoadedObjectMetadata {
846                version: SequenceNumber::new(),
847                digest: ObjectDigest::MIN,
848                owner: Owner::AddressOwner(*owner),
849                storage_rebate: 0,
850                previous_transaction: TransactionDigest::default(),
851            },
852            obj_value,
853        ),
854    );
855
856    let object = Object::new_move(
857        move_object,
858        Owner::AddressOwner(*owner),
859        TransactionDigest::default(),
860    );
861
862    // NB: Must be a `&&` reference since the extension stores a static ref to the object storage.
863    let store: &&InMemoryTestStore = get_extension!(context)?;
864    store.storage.borrow_mut().insert_object(object);
865
866    Ok(NativeResult::ok(
867        legacy_test_cost(),
868        smallvec![Value::u64(object_version.value())],
869    ))
870}
871
872pub fn deallocate_receiving_ticket_for_object(
873    context: &mut NativeContext,
874    _ty_args: Vec<Type>,
875    mut args: VecDeque<Value>,
876) -> PartialVMResult<NativeResult> {
877    let id = pop_id(&mut args)?;
878
879    let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
880    let inventories = &mut object_runtime.test_inventories;
881    // Deallocate the ticket -- we should never hit this scenario
882    let Some((_, value)) = inventories.allocated_tickets.remove(&id) else {
883        return Ok(NativeResult::err(
884            context.gas_used(),
885            E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET,
886        ));
887    };
888
889    // Insert the object value that we saved from earlier and put it back into the object set.
890    // This is fine since it can't have been touched.
891    inventories.objects.insert(id, value);
892
893    // Remove the object from storage. We should never hit this scenario either.
894    let store: &&InMemoryTestStore = get_extension!(context)?;
895    if store.storage.borrow_mut().remove_object(id).is_none() {
896        return Ok(NativeResult::err(
897            context.gas_used(),
898            E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET,
899        ));
900    };
901
902    Ok(NativeResult::ok(legacy_test_cost(), smallvec![]))
903}
904
905// impls
906
907fn take_from_inventory(
908    is_in_inventory: impl FnOnce(&ObjectID) -> bool,
909    objects: &BTreeMap<ObjectID, Value>,
910    taken: &mut BTreeMap<ObjectID, Owner>,
911    input_objects: &mut BTreeMap<ObjectID, Owner>,
912    id: ObjectID,
913    owner: Owner,
914) -> Result<Value, NativeResult> {
915    let is_taken = taken.contains_key(&id);
916    let obj = match objects.get(&id) {
917        Some(obj) if !is_taken && is_in_inventory(&id) => obj,
918        _ => {
919            return Err(NativeResult::err(
920                legacy_test_cost(),
921                E_OBJECT_NOT_FOUND_CODE,
922            ));
923        }
924    };
925    taken.insert(id, owner.clone());
926    input_objects.insert(id, owner);
927    Ok(obj.copy_value())
928}
929
930fn vector_specialization(ty: &Type) -> VectorSpecialization {
931    match ty.try_into() {
932        Ok(s) => s,
933        Err(_) => {
934            debug_assert!(false, "Invalid vector specialization");
935            VectorSpecialization::Container
936        }
937    }
938}
939
940fn most_recent_at_ty(
941    taken: &BTreeMap<ObjectID, Owner>,
942    inv: &BTreeMap<MoveObjectType, Set<ObjectID>>,
943    runtime_ty: &Type,
944    ty: MoveObjectType,
945) -> PartialVMResult<Value> {
946    pack_option(
947        vector_specialization(runtime_ty),
948        most_recent_at_ty_opt(taken, inv, ty),
949    )
950}
951
952fn most_recent_at_ty_opt(
953    taken: &BTreeMap<ObjectID, Owner>,
954    inv: &BTreeMap<MoveObjectType, Set<ObjectID>>,
955    ty: MoveObjectType,
956) -> Option<Value> {
957    let s = inv.get(&ty)?;
958    let most_recent_id = s.iter().rfind(|id| !taken.contains_key(id))?;
959    Some(pack_id(*most_recent_id))
960}
961
962fn get_specified_ty(mut ty_args: Vec<Type>) -> PartialVMResult<Type> {
963    safe_assert!(ty_args.len() == 1);
964    Ok(safe_unwrap!(ty_args.pop()))
965}
966
967// helpers
968fn pop_id(args: &mut VecDeque<Value>) -> PartialVMResult<ObjectID> {
969    let v = match args.pop_back() {
970        None => {
971            return Err(PartialVMError::new(
972                StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
973            ));
974        }
975        Some(v) => v,
976    };
977    Ok(get_nth_struct_field(v, 0)?
978        .value_as::<AccountAddress>()?
979        .into())
980}
981
982fn pack_id(a: impl Into<AccountAddress>) -> Value {
983    Value::struct_(values::Struct::pack(vec![Value::address(a.into())]))
984}
985
986fn pack_ids(items: impl IntoIterator<Item = impl Into<AccountAddress>>) -> PartialVMResult<Value> {
987    Vector::pack(
988        VectorSpecialization::Container,
989        items.into_iter().map(pack_id),
990    )
991}
992
993fn pack_vec_map(items: impl IntoIterator<Item = (Value, Value)>) -> PartialVMResult<Value> {
994    Ok(Value::struct_(values::Struct::pack(vec![Vector::pack(
995        VectorSpecialization::Container,
996        items
997            .into_iter()
998            .map(|(k, v)| Value::struct_(values::Struct::pack(vec![k, v]))),
999    )?])))
1000}
1001
1002fn transaction_effects(
1003    created: impl IntoIterator<Item = impl Into<AccountAddress>>,
1004    written: impl IntoIterator<Item = impl Into<AccountAddress>>,
1005    deleted: impl IntoIterator<Item = impl Into<AccountAddress>>,
1006    transferred: impl IntoIterator<Item = (ObjectID, Owner)>,
1007    num_events: u64,
1008) -> PartialVMResult<Value> {
1009    let mut transferred_to_account = vec![];
1010    let mut transferred_to_object = vec![];
1011    let mut shared = vec![];
1012    let mut frozen = vec![];
1013    for (id, owner) in transferred {
1014        match owner {
1015            Owner::AddressOwner(a) => {
1016                transferred_to_account.push((pack_id(id), Value::address(a.into())))
1017            }
1018            Owner::ObjectOwner(o) => transferred_to_object.push((pack_id(id), pack_id(o))),
1019            Owner::Shared { .. } => shared.push(id),
1020            Owner::Immutable => frozen.push(id),
1021            Owner::ConsensusAddressOwner { owner, .. } => {
1022                transferred_to_account.push((pack_id(id), Value::address(owner.into())))
1023            }
1024            Owner::Party { .. } => {
1025                // TODO(Party WIP)
1026                todo!("Party WIP")
1027            }
1028        }
1029    }
1030
1031    let created_field = pack_ids(created)?;
1032    let written_field = pack_ids(written)?;
1033    let deleted_field = pack_ids(deleted)?;
1034    let transferred_to_account_field = pack_vec_map(transferred_to_account)?;
1035    let transferred_to_object_field = pack_vec_map(transferred_to_object)?;
1036    let shared_field = pack_ids(shared)?;
1037    let frozen_field = pack_ids(frozen)?;
1038    let num_events_field = Value::u64(num_events);
1039    Ok(Value::struct_(values::Struct::pack(vec![
1040        created_field,
1041        written_field,
1042        deleted_field,
1043        transferred_to_account_field,
1044        transferred_to_object_field,
1045        shared_field,
1046        frozen_field,
1047        num_events_field,
1048    ])))
1049}
1050
1051fn object_type_of_type(context: &NativeContext, ty: &Type) -> PartialVMResult<MoveObjectType> {
1052    let TypeTag::Struct(s_tag) = context.type_to_type_tag(ty)? else {
1053        return Err(PartialVMError::new(
1054            StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
1055        ));
1056    };
1057    Ok(MoveObjectType::from(*s_tag))
1058}
1059
1060fn pack_option(specialization: VectorSpecialization, opt: Option<Value>) -> PartialVMResult<Value> {
1061    let item = match opt {
1062        Some(v) => vec![v],
1063        None => vec![],
1064    };
1065    Ok(Value::struct_(values::Struct::pack(vec![Vector::pack(
1066        specialization,
1067        item,
1068    )?])))
1069}
1070
1071fn find_all_wrapped_objects<'a, 'i>(
1072    context: &NativeContext,
1073    ids: &'i mut BTreeSet<ObjectID>,
1074    new_object_values: impl IntoIterator<Item = (&'a ObjectID, &'a MoveObjectType, impl Borrow<Value>)>,
1075) -> PartialVMResult<()> {
1076    #[derive(Copy, Clone)]
1077    enum LookingFor {
1078        Wrapped,
1079        Uid,
1080        Address,
1081    }
1082
1083    struct Traversal<'i, 'u> {
1084        state: LookingFor,
1085        ids: &'i mut BTreeSet<ObjectID>,
1086        uid: &'u MoveStructLayout,
1087    }
1088
1089    impl<'b, 'l> AV::Traversal<'b, 'l> for Traversal<'_, '_> {
1090        type Error = AV::Error;
1091
1092        fn traverse_struct(
1093            &mut self,
1094            driver: &mut AV::StructDriver<'_, 'b, 'l>,
1095        ) -> Result<(), Self::Error> {
1096            match self.state {
1097                // We're at the top-level of the traversal, looking for an object to recurse into.
1098                // We can unconditionally switch to looking for UID fields at the level below,
1099                // because we know that all the top-level values are objects.
1100                LookingFor::Wrapped => {
1101                    while driver
1102                        .next_field(&mut Traversal {
1103                            state: LookingFor::Uid,
1104                            ids: self.ids,
1105                            uid: self.uid,
1106                        })?
1107                        .is_some()
1108                    {}
1109                }
1110
1111                // We are looking for UID fields. If we find one (which we confirm by checking its
1112                // layout), switch to looking for addresses in its sub-structure.
1113                LookingFor::Uid => {
1114                    while let Some(MoveFieldLayout { name: _, layout }) = driver.peek_field() {
1115                        if matches!(layout, MoveTypeLayout::Struct(s) if s.as_ref() == self.uid) {
1116                            driver.next_field(&mut Traversal {
1117                                state: LookingFor::Address,
1118                                ids: self.ids,
1119                                uid: self.uid,
1120                            })?;
1121                        } else {
1122                            driver.next_field(self)?;
1123                        }
1124                    }
1125                }
1126
1127                // When looking for addresses, recurse through structs, as the address is nested
1128                // within the UID.
1129                LookingFor::Address => while driver.next_field(self)?.is_some() {},
1130            }
1131
1132            Ok(())
1133        }
1134
1135        fn traverse_address(
1136            &mut self,
1137            _: &AV::ValueDriver<'_, 'b, 'l>,
1138            address: AccountAddress,
1139        ) -> Result<(), Self::Error> {
1140            // If we're looking for addresses, and we found one, then save it.
1141            if matches!(self.state, LookingFor::Address) {
1142                self.ids.insert(address.into());
1143            }
1144            Ok(())
1145        }
1146    }
1147
1148    let uid = UID::layout();
1149    for (_id, ty, value) in new_object_values {
1150        let type_tag = TypeTag::from(ty.clone());
1151        // NB: We can get the layout from the VM's cache since the types and modules
1152        // associated with all of these types must be in the type/module cache in the VM -- THIS IS
1153        // BECAUSE WE ARE IN TEST SCENARIO ONLY AND THIS MAY NOT GENERALLY HOLD IN A
1154        // MULTI-TRANSACTION SETTING.
1155        let Some(layout) = context.type_tag_to_type_layout(&type_tag) else {
1156            debug_assert!(false);
1157            continue;
1158        };
1159
1160        let Some(annotated_layout) = context.type_tag_to_annotated_type_layout(&type_tag) else {
1161            debug_assert!(false);
1162            continue;
1163        };
1164
1165        let blob = safe_unwrap!(value.borrow().typed_serialize(&layout));
1166        safe_unwrap!(MoveValue::visit_deserialize(
1167            &blob,
1168            &annotated_layout,
1169            &mut Traversal {
1170                state: LookingFor::Wrapped,
1171                ids,
1172                uid: &uid,
1173            },
1174        ));
1175    }
1176    Ok(())
1177}