Skip to main content

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_sdk_types::{
15    Address, MoveStruct, ObjectDigest, ObjectId, Owner, StructTag, TransactionDigest, TypeTag,
16    Version,
17};
18use iota_types::{
19    dynamic_field::DynamicFieldInfo,
20    execution::DynamicallyLoadedObjectMetadata,
21    id::UID,
22    in_memory_storage::InMemoryStorage,
23    iota_sdk_types_conversions::struct_tag_core_to_sdk,
24    object::{MoveStructExt, Object},
25    storage::{BackingPackageStore, ChildObjectResolver},
26};
27use move_binary_format::errors::{PartialVMError, PartialVMResult};
28use move_core_types::{
29    account_address::AccountAddress,
30    annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout, MoveValue},
31    annotated_visitor as AV,
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, Vector, VectorSpecialization},
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: Version,
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: Version,
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::Address(a) => {
228                inventories
229                    .address_inventories
230                    .entry(a)
231                    .or_default()
232                    .entry(ty)
233                    .or_default()
234                    .insert(id);
235            }
236            Owner::Object(_) => (),
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            _ => unimplemented!("a new Owner enum variant was added and needs to be handled"),
252        }
253    }
254
255    // For any unused allocated tickets, remove them from the store.
256    let store: &&InMemoryTestStore = context.extensions().get()?;
257    for id in unreceived {
258        if store
259            .0
260            .with_borrow_mut(|store| store.remove_object(id).is_none())
261        {
262            return Ok(NativeResult::err(
263                context.gas_used(),
264                E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET,
265            ));
266        }
267    }
268
269    // deletions already handled above, but we drop the delete kind for the effects
270    let mut deleted = vec![];
271    for id in deleted_object_ids {
272        // Mark as "incorrect" if a imm object was deleted. Allow shared objects to be
273        // deleted though.
274        incorrect_shared_or_imm_handling = incorrect_shared_or_imm_handling
275            || taken_shared_or_imm
276                .get(&id)
277                .is_some_and(|owner| matches!(owner, Owner::Immutable));
278        deleted.push(id);
279    }
280    // find all wrapped objects
281    let mut all_wrapped = BTreeSet::new();
282    let object_runtime_ref: &ObjectRuntime = context.extensions().get()?;
283    find_all_wrapped_objects(
284        context,
285        &mut all_wrapped,
286        new_object_values
287            .iter()
288            .map(|(id, (ty, value))| (id, ty, value)),
289    );
290    find_all_wrapped_objects(
291        context,
292        &mut all_wrapped,
293        object_runtime_ref
294            .all_active_child_objects()
295            .filter_map(|child| Some((child.id, child.ty, child.copied_value?))),
296    );
297    // mark as "incorrect" if a shared/imm object was wrapped or is a child object
298    incorrect_shared_or_imm_handling = incorrect_shared_or_imm_handling
299        || taken_shared_or_imm.keys().any(|id| {
300            all_wrapped.contains(id) || all_active_child_objects_with_values.contains(id)
301        });
302    // if incorrect handling, return with an 'abort'
303    if incorrect_shared_or_imm_handling {
304        return Ok(NativeResult::err(
305            legacy_test_cost(),
306            E_INVALID_SHARED_OR_IMMUTABLE_USAGE,
307        ));
308    }
309
310    // mark all wrapped as deleted
311    for wrapped in all_wrapped {
312        deleted.push(wrapped)
313    }
314
315    // new input objects are remaining taken objects not written/deleted
316    let object_runtime_ref: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
317    let mut config_settings = vec![];
318    for child in object_runtime_ref.all_active_child_objects() {
319        let s: StructTag = child.move_type.clone();
320        let is_setting = DynamicFieldInfo::is_dynamic_field(&s)
321            && matches!(&s.type_params()[1], TypeTag::Struct(s) if s.is_config_setting());
322        if is_setting {
323            config_settings.push((
324                *child.owner,
325                *child.id,
326                child.move_type.clone(),
327                child.copied_value,
328            ));
329        }
330    }
331    for (config, setting, ty, value) in config_settings {
332        object_runtime_ref.config_setting_cache_update(config, setting, ty, value)
333    }
334    object_runtime_ref.state.input_objects = object_runtime_ref
335        .test_inventories
336        .taken
337        .iter()
338        .map(|(id, owner)| (*id, *owner))
339        .collect::<BTreeMap<_, _>>();
340    // update inventories
341    // check for bad updates to immutable values
342    for (id, (ty, value)) in new_object_values {
343        debug_assert!(!all_active_child_objects_with_values.contains(&id));
344        if let Some(prev_value) = object_runtime_ref
345            .test_inventories
346            .taken_immutable_values
347            .get(&ty)
348            .and_then(|values| values.get(&id))
349        {
350            if !value.equals(prev_value)? {
351                return Ok(NativeResult::err(
352                    legacy_test_cost(),
353                    E_INVALID_SHARED_OR_IMMUTABLE_USAGE,
354                ));
355            }
356        }
357        object_runtime_ref
358            .test_inventories
359            .objects
360            .insert(id, value);
361    }
362    // remove deleted
363    for id in &deleted {
364        object_runtime_ref.test_inventories.objects.remove(id);
365    }
366    // remove active child objects
367    for id in all_active_child_objects_with_values {
368        object_runtime_ref.test_inventories.objects.remove(&id);
369    }
370
371    let effects = transaction_effects(
372        created
373            .into_iter()
374            .map(|id| AccountAddress::new(id.into_bytes())),
375        written
376            .into_iter()
377            .map(|id| AccountAddress::new(id.into_bytes())),
378        deleted
379            .into_iter()
380            .map(|id| AccountAddress::new(id.into_bytes())),
381        transferred,
382        user_events.len() as u64,
383    );
384    Ok(NativeResult::ok(legacy_test_cost(), smallvec![effects]))
385}
386
387// native fun take_from_address_by_id<T: key>(account: address, id: ID): T;
388pub fn take_from_address_by_id(
389    context: &mut NativeContext,
390    ty_args: Vec<Type>,
391    mut args: VecDeque<Value>,
392) -> PartialVMResult<NativeResult> {
393    let specified_ty = get_specified_ty(ty_args);
394    let id = pop_id(&mut args)?;
395    let account = Address::new(pop_arg!(args, AccountAddress).into_bytes());
396    pop_arg!(args, StructRef);
397    assert!(args.is_empty());
398    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
399    let inventories = &mut object_runtime.test_inventories;
400    let res = take_from_inventory(
401        |x| {
402            inventories
403                .address_inventories
404                .get(&account)
405                .and_then(|inv| inv.get(&specified_ty))
406                .map(|s| s.contains(x))
407                .unwrap_or(false)
408        },
409        &inventories.objects,
410        &mut inventories.taken,
411        &mut object_runtime.state.input_objects,
412        id,
413        Owner::Address(account),
414    );
415    Ok(match res {
416        Ok(value) => NativeResult::ok(legacy_test_cost(), smallvec![value]),
417        Err(native_err) => native_err,
418    })
419}
420
421// native fun ids_for_address<T: key>(account: address): vector<ID>;
422pub fn ids_for_address(
423    context: &mut NativeContext,
424    ty_args: Vec<Type>,
425    mut args: VecDeque<Value>,
426) -> PartialVMResult<NativeResult> {
427    let specified_ty = get_specified_ty(ty_args);
428    let account: Address = Address::new(pop_arg!(args, AccountAddress).into_bytes());
429    assert!(args.is_empty());
430    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
431    let inventories = &mut object_runtime.test_inventories;
432    let ids = inventories
433        .address_inventories
434        .get(&account)
435        .and_then(|inv| inv.get(&specified_ty))
436        .map(|s| {
437            s.iter()
438                .map(|id| pack_id(AccountAddress::new(id.into_bytes())))
439                .collect::<Vec<Value>>()
440        })
441        .unwrap_or_default();
442    let ids_vector = Vector::pack(VectorSpecialization::Container, ids).unwrap();
443    Ok(NativeResult::ok(legacy_test_cost(), smallvec![ids_vector]))
444}
445
446// native fun most_recent_id_for_address<T: key>(account: address): Option<ID>;
447pub fn most_recent_id_for_address(
448    context: &mut NativeContext,
449    ty_args: Vec<Type>,
450    mut args: VecDeque<Value>,
451) -> PartialVMResult<NativeResult> {
452    let specified_ty = get_specified_ty(ty_args);
453    let account: Address = Address::new(pop_arg!(args, AccountAddress).into_bytes());
454    assert!(args.is_empty());
455    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
456    let inventories = &mut object_runtime.test_inventories;
457    let most_recent_id = match inventories.address_inventories.get(&account) {
458        None => pack_option(vector_specialization(&specified_ty), None),
459        Some(inv) => most_recent_at_ty(&inventories.taken, inv, specified_ty),
460    };
461    Ok(NativeResult::ok(
462        legacy_test_cost(),
463        smallvec![most_recent_id],
464    ))
465}
466
467// native fun was_taken_from_address(account: address, id: ID): bool;
468pub fn was_taken_from_address(
469    context: &mut NativeContext,
470    ty_args: Vec<Type>,
471    mut args: VecDeque<Value>,
472) -> PartialVMResult<NativeResult> {
473    assert!(ty_args.is_empty());
474    let id = pop_id(&mut args)?;
475    let account: Address = Address::new(pop_arg!(args, AccountAddress).into_bytes());
476    assert!(args.is_empty());
477    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
478    let inventories = &mut object_runtime.test_inventories;
479    let was_taken = inventories
480        .taken
481        .get(&id)
482        .map(|owner| owner == &Owner::Address(account))
483        .unwrap_or(false);
484    Ok(NativeResult::ok(
485        legacy_test_cost(),
486        smallvec![Value::bool(was_taken)],
487    ))
488}
489
490// native fun take_immutable_by_id<T: key>(id: ID): T;
491pub fn take_immutable_by_id(
492    context: &mut NativeContext,
493    ty_args: Vec<Type>,
494    mut args: VecDeque<Value>,
495) -> PartialVMResult<NativeResult> {
496    let specified_ty = get_specified_ty(ty_args);
497    let id = pop_id(&mut args)?;
498    pop_arg!(args, StructRef);
499    assert!(args.is_empty());
500    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
501    let inventories = &mut object_runtime.test_inventories;
502    let res = take_from_inventory(
503        |x| {
504            inventories
505                .immutable_inventory
506                .get(&specified_ty)
507                .map(|s| s.contains(x))
508                .unwrap_or(false)
509        },
510        &inventories.objects,
511        &mut inventories.taken,
512        &mut object_runtime.state.input_objects,
513        id,
514        Owner::Immutable,
515    );
516    Ok(match res {
517        Ok(value) => {
518            inventories
519                .taken_immutable_values
520                .entry(specified_ty)
521                .or_default()
522                .insert(id, value.copy_value().unwrap());
523            NativeResult::ok(legacy_test_cost(), smallvec![value])
524        }
525        Err(native_err) => native_err,
526    })
527}
528
529// native fun most_recent_immutable_id<T: key>(): Option<ID>;
530pub fn most_recent_immutable_id(
531    context: &mut NativeContext,
532    ty_args: Vec<Type>,
533    args: VecDeque<Value>,
534) -> PartialVMResult<NativeResult> {
535    let specified_ty = get_specified_ty(ty_args);
536    assert!(args.is_empty());
537    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
538    let inventories = &mut object_runtime.test_inventories;
539    let most_recent_id = most_recent_at_ty(
540        &inventories.taken,
541        &inventories.immutable_inventory,
542        specified_ty,
543    );
544    Ok(NativeResult::ok(
545        legacy_test_cost(),
546        smallvec![most_recent_id],
547    ))
548}
549
550// native fun was_taken_immutable(id: ID): bool;
551pub fn was_taken_immutable(
552    context: &mut NativeContext,
553    ty_args: Vec<Type>,
554    mut args: VecDeque<Value>,
555) -> PartialVMResult<NativeResult> {
556    assert!(ty_args.is_empty());
557    let id = pop_id(&mut args)?;
558    assert!(args.is_empty());
559    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
560    let inventories = &mut object_runtime.test_inventories;
561    let was_taken = inventories
562        .taken
563        .get(&id)
564        .map(|owner| owner == &Owner::Immutable)
565        .unwrap_or(false);
566    Ok(NativeResult::ok(
567        legacy_test_cost(),
568        smallvec![Value::bool(was_taken)],
569    ))
570}
571
572// native fun take_shared_by_id<T: key>(id: ID): T;
573pub fn take_shared_by_id(
574    context: &mut NativeContext,
575    ty_args: Vec<Type>,
576    mut args: VecDeque<Value>,
577) -> PartialVMResult<NativeResult> {
578    let specified_ty = get_specified_ty(ty_args);
579    let id = pop_id(&mut args)?;
580    pop_arg!(args, StructRef);
581    assert!(args.is_empty());
582    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
583    let inventories = &mut object_runtime.test_inventories;
584    let res = take_from_inventory(
585        |x| {
586            inventories
587                .shared_inventory
588                .get(&specified_ty)
589                .map(|s| s.contains(x))
590                .unwrap_or(false)
591        },
592        &inventories.objects,
593        &mut inventories.taken,
594        &mut object_runtime.state.input_objects,
595        id,
596        Owner::Shared(Default::default()),
597    );
598    Ok(match res {
599        Ok(value) => NativeResult::ok(legacy_test_cost(), smallvec![value]),
600        Err(native_err) => native_err,
601    })
602}
603
604// native fun most_recent_id_shared<T: key>(): Option<ID>;
605pub fn most_recent_id_shared(
606    context: &mut NativeContext,
607    ty_args: Vec<Type>,
608    args: VecDeque<Value>,
609) -> PartialVMResult<NativeResult> {
610    let specified_ty = get_specified_ty(ty_args);
611    assert!(args.is_empty());
612    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
613    let inventories = &mut object_runtime.test_inventories;
614    let most_recent_id = most_recent_at_ty(
615        &inventories.taken,
616        &inventories.shared_inventory,
617        specified_ty,
618    );
619    Ok(NativeResult::ok(
620        legacy_test_cost(),
621        smallvec![most_recent_id],
622    ))
623}
624
625// native fun was_taken_shared(id: ID): bool;
626pub fn was_taken_shared(
627    context: &mut NativeContext,
628    ty_args: Vec<Type>,
629    mut args: VecDeque<Value>,
630) -> PartialVMResult<NativeResult> {
631    assert!(ty_args.is_empty());
632    let id = pop_id(&mut args)?;
633    assert!(args.is_empty());
634    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
635    let inventories = &mut object_runtime.test_inventories;
636    let was_taken = inventories
637        .taken
638        .get(&id)
639        .map(|owner| matches!(owner, Owner::Shared(_)))
640        .unwrap_or(false);
641    Ok(NativeResult::ok(
642        legacy_test_cost(),
643        smallvec![Value::bool(was_taken)],
644    ))
645}
646
647pub fn allocate_receiving_ticket_for_object(
648    context: &mut NativeContext,
649    ty_args: Vec<Type>,
650    mut args: VecDeque<Value>,
651) -> PartialVMResult<NativeResult> {
652    let ty = get_specified_ty(ty_args);
653    let id = pop_id(&mut args)?;
654
655    let Some((tag, layout, _)) = get_tag_and_layouts(context, &ty)? else {
656        return Ok(NativeResult::err(
657            context.gas_used(),
658            E_UNABLE_TO_ALLOCATE_RECEIVING_TICKET,
659        ));
660    };
661    let tag = struct_tag_core_to_sdk(&tag);
662    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
663    let object_version = Version::default();
664    let inventories = &mut object_runtime.test_inventories;
665    if inventories.allocated_tickets.contains_key(&id) {
666        return Ok(NativeResult::err(
667            context.gas_used(),
668            E_RECEIVING_TICKET_ALREADY_ALLOCATED,
669        ));
670    }
671
672    let obj_value = inventories.objects.remove(&id).unwrap();
673    let Some(bytes) = obj_value.simple_serialize(&layout) else {
674        return Ok(NativeResult::err(
675            context.gas_used(),
676            E_UNABLE_TO_ALLOCATE_RECEIVING_TICKET,
677        ));
678    };
679    let move_struct =
680        MoveStruct::new_from_execution_with_limit(tag, object_version, bytes, 250 * 1024).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: Version::default(),
698                digest: ObjectDigest::MIN,
699                owner: Owner::Address(*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_struct,
709        Owner::Address(*owner),
710        TransactionDigest::default(),
711    );
712
713    // NB: Must be a `&&` reference since the extension stores a static ref to the
714    // object storage.
715    let store: &&InMemoryTestStore = context.extensions().get()?;
716    store.0.with_borrow_mut(|store| store.insert_object(object));
717
718    Ok(NativeResult::ok(
719        legacy_test_cost(),
720        smallvec![Value::u64(object_version.as_u64())],
721    ))
722}
723
724pub fn deallocate_receiving_ticket_for_object(
725    context: &mut NativeContext,
726    _ty_args: Vec<Type>,
727    mut args: VecDeque<Value>,
728) -> PartialVMResult<NativeResult> {
729    let id = pop_id(&mut args)?;
730
731    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
732    let inventories = &mut object_runtime.test_inventories;
733    // Deallocate the ticket -- we should never hit this scenario
734    let Some((_, value)) = inventories.allocated_tickets.remove(&id) else {
735        return Ok(NativeResult::err(
736            context.gas_used(),
737            E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET,
738        ));
739    };
740
741    // Insert the object value that we saved from earlier and put it back into the
742    // object set. This is fine since it can't have been touched.
743    inventories.objects.insert(id, value);
744
745    // Remove the object from storage. We should never hit this scenario either.
746    let store: &&InMemoryTestStore = context.extensions().get()?;
747    if store
748        .0
749        .with_borrow_mut(|store| store.remove_object(id).is_none())
750    {
751        return Ok(NativeResult::err(
752            context.gas_used(),
753            E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET,
754        ));
755    };
756
757    Ok(NativeResult::ok(legacy_test_cost(), smallvec![]))
758}
759
760// impls
761
762fn take_from_inventory(
763    is_in_inventory: impl FnOnce(&ObjectId) -> bool,
764    objects: &BTreeMap<ObjectId, Value>,
765    taken: &mut BTreeMap<ObjectId, Owner>,
766    input_objects: &mut BTreeMap<ObjectId, Owner>,
767    id: ObjectId,
768    owner: Owner,
769) -> Result<Value, NativeResult> {
770    let obj_opt = objects.get(&id);
771    let is_taken = taken.contains_key(&id);
772    if is_taken || !is_in_inventory(&id) || obj_opt.is_none() {
773        return Err(NativeResult::err(
774            legacy_test_cost(),
775            E_OBJECT_NOT_FOUND_CODE,
776        ));
777    }
778    taken.insert(id, owner);
779    input_objects.insert(id, owner);
780    let obj = obj_opt.unwrap();
781    Ok(obj.copy_value().unwrap())
782}
783
784fn vector_specialization(ty: &Type) -> VectorSpecialization {
785    match ty.try_into() {
786        Ok(s) => s,
787        Err(_) => {
788            debug_assert!(false, "Invalid vector specialization");
789            VectorSpecialization::Container
790        }
791    }
792}
793
794fn most_recent_at_ty(
795    taken: &BTreeMap<ObjectId, Owner>,
796    inv: &BTreeMap<Type, Set<ObjectId>>,
797    ty: Type,
798) -> Value {
799    pack_option(
800        vector_specialization(&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<Type, Set<ObjectId>>,
808    ty: Type,
809) -> Option<Value> {
810    let s = inv.get(&ty)?;
811    let most_recent_id = s.iter().rfind(|id| !taken.contains_key(id))?;
812    Some(pack_id(AccountAddress::new(most_recent_id.into_bytes())))
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(ObjectId::new(
831        get_nth_struct_field(v, 0)?
832            .value_as::<AccountAddress>()?
833            .into_bytes(),
834    ))
835}
836
837fn pack_id(a: impl Into<AccountAddress>) -> Value {
838    Value::struct_(values::Struct::pack(vec![Value::address(a.into())]))
839}
840
841fn pack_ids(items: impl IntoIterator<Item = impl Into<AccountAddress>>) -> Value {
842    Vector::pack(
843        VectorSpecialization::Container,
844        items.into_iter().map(pack_id),
845    )
846    .unwrap()
847}
848
849fn pack_vec_map(items: impl IntoIterator<Item = (Value, Value)>) -> Value {
850    Value::struct_(values::Struct::pack(vec![
851        Vector::pack(
852            VectorSpecialization::Container,
853            items
854                .into_iter()
855                .map(|(k, v)| Value::struct_(values::Struct::pack(vec![k, v]))),
856        )
857        .unwrap(),
858    ]))
859}
860
861fn transaction_effects(
862    created: impl IntoIterator<Item = impl Into<AccountAddress>>,
863    written: impl IntoIterator<Item = impl Into<AccountAddress>>,
864    deleted: impl IntoIterator<Item = impl Into<AccountAddress>>,
865    transferred: impl IntoIterator<Item = (ObjectId, Owner)>,
866    num_events: u64,
867) -> Value {
868    let mut transferred_to_account = vec![];
869    let mut transferred_to_object = vec![];
870    let mut shared = vec![];
871    let mut frozen = vec![];
872    for (id, owner) in transferred {
873        match owner {
874            Owner::Address(a) => transferred_to_account.push((
875                pack_id(AccountAddress::new(id.into_bytes())),
876                Value::address(AccountAddress::new(a.into_bytes())),
877            )),
878            Owner::Object(o) => transferred_to_object.push((
879                pack_id(AccountAddress::new(id.into_bytes())),
880                pack_id(AccountAddress::new(o.into_bytes())),
881            )),
882            Owner::Shared(_) => shared.push(AccountAddress::new(id.into_bytes())),
883            Owner::Immutable => frozen.push(AccountAddress::new(id.into_bytes())),
884            _ => unimplemented!("a new Owner enum variant was added and needs to be handled"),
885        }
886    }
887
888    let created_field = pack_ids(created);
889    let written_field = pack_ids(written);
890    let deleted_field = pack_ids(deleted);
891    let transferred_to_account_field = pack_vec_map(transferred_to_account);
892    let transferred_to_object_field = pack_vec_map(transferred_to_object);
893    let shared_field = pack_ids(shared);
894    let frozen_field = pack_ids(frozen);
895    let num_events_field = Value::u64(num_events);
896    Value::struct_(values::Struct::pack(vec![
897        created_field,
898        written_field,
899        deleted_field,
900        transferred_to_account_field,
901        transferred_to_object_field,
902        shared_field,
903        frozen_field,
904        num_events_field,
905    ]))
906}
907
908fn pack_option(specialization: VectorSpecialization, opt: Option<Value>) -> Value {
909    let item = match opt {
910        Some(v) => vec![v],
911        None => vec![],
912    };
913    Value::struct_(values::Struct::pack(vec![
914        Vector::pack(specialization, item).unwrap(),
915    ]))
916}
917
918fn find_all_wrapped_objects<'a, 'i>(
919    context: &NativeContext,
920    ids: &'i mut BTreeSet<ObjectId>,
921    new_object_values: impl IntoIterator<Item = (&'a ObjectId, &'a Type, impl Borrow<Value>)>,
922) {
923    #[derive(Copy, Clone)]
924    enum LookingFor {
925        Wrapped,
926        Uid,
927        Address,
928    }
929
930    struct Traversal<'i, 'u> {
931        state: LookingFor,
932        ids: &'i mut BTreeSet<ObjectId>,
933        uid: &'u MoveStructLayout,
934    }
935
936    impl<'b, 'l> AV::Traversal<'b, 'l> for Traversal<'_, '_> {
937        type Error = AV::Error;
938
939        fn traverse_struct(
940            &mut self,
941            driver: &mut AV::StructDriver<'_, 'b, 'l>,
942        ) -> Result<(), Self::Error> {
943            match self.state {
944                // We're at the top-level of the traversal, looking for an object to recurse into.
945                // We can unconditionally switch to looking for UID fields at the level below,
946                // because we know that all the top-level values are objects.
947                LookingFor::Wrapped => {
948                    while driver
949                        .next_field(&mut Traversal {
950                            state: LookingFor::Uid,
951                            ids: self.ids,
952                            uid: self.uid,
953                        })?
954                        .is_some()
955                    {}
956                }
957
958                // We are looking for UID fields. If we find one (which we confirm by checking its
959                // layout), switch to looking for addresses in its sub-structure.
960                LookingFor::Uid => {
961                    while let Some(MoveFieldLayout { name: _, layout }) = driver.peek_field() {
962                        if matches!(layout, MoveTypeLayout::Struct(s) if s.as_ref() == self.uid) {
963                            driver.next_field(&mut Traversal {
964                                state: LookingFor::Address,
965                                ids: self.ids,
966                                uid: self.uid,
967                            })?;
968                        } else {
969                            driver.next_field(self)?;
970                        }
971                    }
972                }
973
974                // When looking for addresses, recurse through structs, as the address is nested
975                // within the UID.
976                LookingFor::Address => while driver.next_field(self)?.is_some() {},
977            }
978
979            Ok(())
980        }
981
982        fn traverse_address(
983            &mut self,
984            _: &AV::ValueDriver<'_, 'b, 'l>,
985            address: AccountAddress,
986        ) -> Result<(), Self::Error> {
987            // If we're looking for addresses, and we found one, then save it.
988            if matches!(self.state, LookingFor::Address) {
989                self.ids.insert(ObjectId::new(address.into_bytes()));
990            }
991            Ok(())
992        }
993    }
994
995    let uid = UID::layout();
996    for (_id, ty, value) in new_object_values {
997        let Ok(Some(layout)) = context.type_to_type_layout(ty) else {
998            debug_assert!(false);
999            continue;
1000        };
1001
1002        let Ok(Some(annotated_layout)) = context.type_to_fully_annotated_layout(ty) else {
1003            debug_assert!(false);
1004            continue;
1005        };
1006
1007        let blob = value.borrow().simple_serialize(&layout).unwrap();
1008        MoveValue::visit_deserialize(
1009            &blob,
1010            &annotated_layout,
1011            &mut Traversal {
1012                state: LookingFor::Wrapped,
1013                ids,
1014                uid: &uid,
1015            },
1016        )
1017        .unwrap();
1018    }
1019}