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