iota_move_natives_latest/
test_scenario.rs

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