iota_move_natives_latest/
test_scenario.rs

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