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