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