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