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