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