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