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