1use crate::{
5 get_extension, get_extension_mut, get_nth_struct_field, get_tag_and_layouts, legacy_test_cost,
6 object_runtime::{
7 MoveAccumulatorAction, MoveAccumulatorEvent, MoveAccumulatorValue, ObjectRuntime,
8 RuntimeResults, object_store::ChildObjectEffects,
9 },
10 scratch::ScratchRuntime,
11};
12use better_any::{Tid, TidAble};
13use indexmap::{IndexMap, IndexSet};
14use move_binary_format::errors::{PartialVMError, PartialVMResult};
15use move_binary_format::{safe_assert, safe_unwrap};
16use move_core_types::{
17 account_address::AccountAddress,
18 annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout, MoveValue},
19 annotated_visitor as AV,
20 language_storage::StructTag,
21 u256::U256,
22 vm_status::StatusCode,
23};
24use move_vm_runtime::{
25 execution::values::{Vector, VectorSpecialization},
26 natives::{
27 extensions::NativeExtensionMarker,
28 functions::{NativeContext, NativeResult},
29 },
30};
31use move_vm_runtime::{
32 execution::{
33 Type,
34 values::{self, StructRef, Value},
35 },
36 pop_arg,
37};
38use smallvec::smallvec;
39use std::{
40 borrow::Borrow,
41 cell::RefCell,
42 collections::{BTreeMap, BTreeSet, VecDeque},
43};
44use sui_types::{
45 TypeTag,
46 base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress},
47 config,
48 digests::{ObjectDigest, TransactionDigest},
49 dynamic_field::DynamicFieldInfo,
50 execution::DynamicallyLoadedObjectMetadata,
51 id::UID,
52 in_memory_storage::InMemoryStorage,
53 object::{MoveObject, Object, Owner},
54 storage::{
55 BackingPackageStore, ImplicitSystemObjectResolver, PackageObject, RuntimeObjectResolver,
56 },
57};
58
59const E_COULD_NOT_GENERATE_EFFECTS: u64 = 0;
60const E_INVALID_SHARED_OR_IMMUTABLE_USAGE: u64 = 1;
61const E_OBJECT_NOT_FOUND_CODE: u64 = 4;
62const E_UNABLE_TO_ALLOCATE_RECEIVING_TICKET: u64 = 5;
63const E_RECEIVING_TICKET_ALREADY_ALLOCATED: u64 = 6;
64const E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET: u64 = 7;
65const E_INSUFFICIENT_FUNDS: u64 = 8;
66const E_UNBACKED_WITHDRAWAL: u64 = 11;
67
68type Set<K> = IndexSet<K>;
69
70#[derive(Tid, Default)]
74pub struct InMemoryTestStore {
75 storage: RefCell<InMemoryStorage>,
77 funds: RefCell<BTreeMap<(SuiAddress, TypeTag), u128>>,
81 address_reservations: RefCell<BTreeMap<(SuiAddress, TypeTag), U256>>,
85}
86impl<'a> NativeExtensionMarker<'a> for &'a InMemoryTestStore {}
87
88impl InMemoryTestStore {
89 fn settled_funds(&self, owner: SuiAddress, type_: &TypeTag) -> u128 {
90 self.funds
91 .borrow()
92 .get(&(owner, type_.clone()))
93 .copied()
94 .unwrap_or(0)
95 }
96
97 fn reserve_address_funds(&self, owner: SuiAddress, type_: TypeTag, limit: U256) -> bool {
100 let Some(reserved) = self
101 .address_reservations
102 .borrow()
103 .get(&(owner, type_.clone()))
104 .copied()
105 .unwrap_or_else(U256::zero)
106 .checked_add(limit)
107 else {
108 return false;
109 };
110 if reserved > U256::from(self.settled_funds(owner, &type_)) {
111 return false;
112 }
113 self.address_reservations
114 .borrow_mut()
115 .insert((owner, type_), reserved);
116 true
117 }
118
119 fn settle_funds(&self, accumulator_events: Vec<MoveAccumulatorEvent>) -> bool {
124 self.address_reservations.borrow_mut().clear();
125 let mut changes: BTreeMap<(SuiAddress, TypeTag), (u128, u128)> = BTreeMap::new();
126 for event in accumulator_events {
127 let MoveAccumulatorValue::U64(amount) = event.value else {
128 continue;
129 };
130 let (merged, split) = changes
131 .entry((event.target_addr.into(), event.target_ty))
132 .or_default();
133 match event.action {
134 MoveAccumulatorAction::Merge => *merged += amount as u128,
135 MoveAccumulatorAction::Split => *split += amount as u128,
136 }
137 }
138 let mut funds = self.funds.borrow_mut();
139 for (key, (merged, split)) in changes {
143 let balance = funds.entry(key).or_default();
144 let Some(settled) = (*balance + merged).checked_sub(split) else {
145 return false;
146 };
147 *balance = settled;
148 }
149 true
150 }
151}
152
153impl BackingPackageStore for InMemoryTestStore {
154 fn get_package_object(
155 &self,
156 package_id: &ObjectID,
157 ) -> sui_types::error::SuiResult<Option<PackageObject>> {
158 self.storage.borrow().get_package_object(package_id)
159 }
160}
161
162impl ImplicitSystemObjectResolver for InMemoryTestStore {
163 fn object_available_balance(
164 &self,
165 owner: SuiAddress,
166 type_: &TypeTag,
167 ) -> sui_types::error::SuiResult<u128> {
168 Ok(self.settled_funds(owner, type_))
169 }
170}
171
172impl RuntimeObjectResolver for InMemoryTestStore {
173 fn read_child_object(
174 &self,
175 parent: &ObjectID,
176 child: &ObjectID,
177 child_version_upper_bound: SequenceNumber,
178 ) -> sui_types::error::SuiResult<Option<Object>> {
179 self.storage
180 .borrow()
181 .read_child_object(parent, child, child_version_upper_bound)
182 }
183
184 fn get_object_received_at_version(
185 &self,
186 owner: &ObjectID,
187 receiving_object_id: &ObjectID,
188 receive_object_at_version: SequenceNumber,
189 epoch_id: sui_types::committee::EpochId,
190 ) -> sui_types::error::SuiResult<Option<Object>> {
191 self.storage.borrow().get_object_received_at_version(
192 owner,
193 receiving_object_id,
194 receive_object_at_version,
195 epoch_id,
196 )
197 }
198}
199
200pub fn end_transaction(
204 context: &mut NativeContext,
205 ty_args: Vec<Type>,
206 args: VecDeque<Value>,
207) -> PartialVMResult<NativeResult> {
208 safe_assert!(ty_args.is_empty());
209 safe_assert!(args.is_empty());
210 get_extension_mut!(context, ScratchRuntime)?.clear();
214 let object_runtime_ref: &mut ObjectRuntime = get_extension_mut!(context)?;
215 let taken_shared_or_imm: BTreeMap<_, _> = object_runtime_ref
216 .test_inventories
217 .taken
218 .iter()
219 .filter(|(_id, owner)| matches!(owner, Owner::Shared { .. } | Owner::Immutable))
220 .map(|(id, owner)| (*id, owner.clone()))
221 .collect();
222 let mut incorrect_shared_or_imm_handling = false;
227
228 let allocated_tickets =
235 std::mem::take(&mut object_runtime_ref.test_inventories.allocated_tickets);
236 let mut received = BTreeMap::new();
237 let mut unreceived = BTreeSet::new();
238 let loaded_runtime_objects = object_runtime_ref.loaded_runtime_objects();
239 for (id, (metadata, value)) in allocated_tickets {
240 if loaded_runtime_objects.contains_key(&id) {
241 received.insert(id, metadata);
242 } else {
243 unreceived.insert(id);
244 object_runtime_ref
246 .test_inventories
247 .objects
248 .insert(id, value);
249 }
250 }
251
252 let object_runtime_state = object_runtime_ref.take_state();
253 let results = object_runtime_state.finish(received, ChildObjectEffects::new());
257 let RuntimeResults {
258 writes,
259 user_events,
260 loaded_child_objects: _,
261 created_object_ids,
262 deleted_object_ids,
263 accumulator_events,
264 settlement_input_sui: _,
265 settlement_output_sui: _,
266 } = match results {
267 Ok(res) => res,
268 Err(_) => {
269 return Ok(NativeResult::err(
270 legacy_test_cost(),
271 E_COULD_NOT_GENERATE_EFFECTS,
272 ));
273 }
274 };
275 let object_runtime_ref: &mut ObjectRuntime = get_extension_mut!(context)?;
276 let all_active_child_objects_with_values = object_runtime_ref
277 .all_active_child_objects()
278 .filter(|child| child.copied_value.is_some())
279 .map(|child| *child.id)
280 .collect::<BTreeSet<_>>();
281 let inventories = &mut object_runtime_ref.test_inventories;
282 let mut new_object_values = IndexMap::new();
283 let mut transferred = vec![];
284 for id in deleted_object_ids
290 .iter()
291 .chain(writes.keys())
292 .chain(&all_active_child_objects_with_values)
293 {
294 for addr_inventory in inventories.address_inventories.values_mut() {
295 for s in addr_inventory.values_mut() {
296 s.shift_remove(id);
297 }
298 }
299 for s in &mut inventories.shared_inventory.values_mut() {
300 s.shift_remove(id);
301 }
302 for s in &mut inventories.immutable_inventory.values_mut() {
303 s.shift_remove(id);
304 }
305 inventories.taken.remove(id);
306 }
307
308 let mut created = vec![];
310 let mut written = vec![];
311 for (id, (owner, ty, value)) in writes {
312 new_object_values.insert(id, (ty.clone(), value.copy_value()));
314 transferred.push((id, owner.clone()));
315 incorrect_shared_or_imm_handling = incorrect_shared_or_imm_handling
316 || taken_shared_or_imm
317 .get(&id)
318 .map(|shared_or_imm_owner| shared_or_imm_owner != &owner)
319 .unwrap_or(false);
320 if created_object_ids.contains(&id) {
321 created.push(id);
322 } else {
323 written.push(id);
324 }
325 match owner {
326 Owner::AddressOwner(a) => {
327 inventories
328 .address_inventories
329 .entry(a)
330 .or_default()
331 .entry(ty)
332 .or_default()
333 .insert(id);
334 }
335 Owner::ObjectOwner(_) => (),
336 Owner::Shared { .. } => {
337 inventories
338 .shared_inventory
339 .entry(ty)
340 .or_default()
341 .insert(id);
342 }
343 Owner::Immutable => {
344 inventories
345 .immutable_inventory
346 .entry(ty)
347 .or_default()
348 .insert(id);
349 }
350 Owner::ConsensusAddressOwner { owner, .. } => {
351 inventories
352 .address_inventories
353 .entry(owner)
354 .or_default()
355 .entry(ty)
356 .or_default()
357 .insert(id);
358 }
359 Owner::Party { .. } => {
360 todo!("Party WIP")
362 }
363 }
364 }
365
366 let store: &&InMemoryTestStore = get_extension!(context)?;
368 for id in unreceived {
369 if store.storage.borrow_mut().remove_object(id).is_none() {
370 return Ok(NativeResult::err(
371 context.gas_used(),
372 E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET,
373 ));
374 }
375 }
376 if !store.settle_funds(accumulator_events) {
377 return Ok(NativeResult::err(legacy_test_cost(), E_UNBACKED_WITHDRAWAL));
378 }
379
380 let mut deleted = vec![];
382 for id in deleted_object_ids {
383 incorrect_shared_or_imm_handling = incorrect_shared_or_imm_handling
385 || taken_shared_or_imm
386 .get(&id)
387 .is_some_and(|owner| matches!(owner, Owner::Immutable));
388 deleted.push(id);
389 }
390 let mut all_wrapped = BTreeSet::new();
392 let object_runtime_ref: &ObjectRuntime = get_extension!(context)?;
393 find_all_wrapped_objects(
394 context,
395 &mut all_wrapped,
396 new_object_values
397 .iter()
398 .map(|(id, (ty, value))| (id, ty, value)),
399 )?;
400 find_all_wrapped_objects(
401 context,
402 &mut all_wrapped,
403 object_runtime_ref
404 .all_active_child_objects()
405 .filter_map(|child| Some((child.id, child.ty, child.copied_value?))),
406 )?;
407 incorrect_shared_or_imm_handling = incorrect_shared_or_imm_handling
409 || taken_shared_or_imm.keys().any(|id| {
410 all_wrapped.contains(id) || all_active_child_objects_with_values.contains(id)
411 });
412 if incorrect_shared_or_imm_handling {
414 return Ok(NativeResult::err(
415 legacy_test_cost(),
416 E_INVALID_SHARED_OR_IMMUTABLE_USAGE,
417 ));
418 }
419
420 for wrapped in all_wrapped {
422 deleted.push(wrapped)
423 }
424
425 let object_runtime_ref: &mut ObjectRuntime = get_extension_mut!(context)?;
427 let mut config_settings = vec![];
428 for child in object_runtime_ref.all_active_child_objects() {
429 let s: StructTag = child.ty.clone().into();
430 let is_setting = DynamicFieldInfo::is_dynamic_field(&s)
431 && matches!(&s.type_params[1], TypeTag::Struct(s) if config::is_setting(s));
432 if is_setting {
433 config_settings.push((
434 *child.owner,
435 *child.id,
436 child.ty.clone(),
437 child.copied_value,
438 ));
439 }
440 }
441 for (config, setting, ty, value) in config_settings {
442 object_runtime_ref.config_setting_cache_update(config, setting, ty, value)
443 }
444 object_runtime_ref.state.input_objects = object_runtime_ref
445 .test_inventories
446 .taken
447 .iter()
448 .map(|(id, owner)| (*id, owner.clone()))
449 .collect::<BTreeMap<_, _>>();
450 for (id, (ty, value)) in new_object_values {
453 debug_assert!(!all_active_child_objects_with_values.contains(&id));
454 if let Some(prev_value) = object_runtime_ref
455 .test_inventories
456 .taken_immutable_values
457 .get(&ty)
458 .and_then(|values| values.get(&id))
459 && !value.equals(prev_value)?
460 {
461 return Ok(NativeResult::err(
462 legacy_test_cost(),
463 E_INVALID_SHARED_OR_IMMUTABLE_USAGE,
464 ));
465 }
466 object_runtime_ref
467 .test_inventories
468 .objects
469 .insert(id, value);
470 }
471 for id in &deleted {
473 object_runtime_ref.test_inventories.objects.remove(id);
474 }
475 for id in all_active_child_objects_with_values {
477 object_runtime_ref.test_inventories.objects.remove(&id);
478 }
479
480 let effects = transaction_effects(
481 created,
482 written,
483 deleted,
484 transferred,
485 user_events.len() as u64,
486 )?;
488 Ok(NativeResult::ok(legacy_test_cost(), smallvec![effects]))
489}
490
491pub fn reserve_funds_from_address(
492 context: &mut NativeContext,
493 ty_args: Vec<Type>,
494 mut args: VecDeque<Value>,
495) -> PartialVMResult<NativeResult> {
496 let funds_type = context.type_to_type_tag(&get_specified_ty(ty_args)?)?;
497 let limit = pop_arg!(args, U256);
498 let owner: SuiAddress = pop_arg!(args, AccountAddress).into();
499 safe_assert!(args.is_empty());
500 let store: &&InMemoryTestStore = get_extension!(context)?;
501 if !store.reserve_address_funds(owner, funds_type, limit) {
502 return Ok(NativeResult::err(legacy_test_cost(), E_INSUFFICIENT_FUNDS));
503 }
504 Ok(NativeResult::ok(legacy_test_cost(), smallvec![]))
505}
506
507pub fn settled_funds(
508 context: &mut NativeContext,
509 ty_args: Vec<Type>,
510 mut args: VecDeque<Value>,
511) -> PartialVMResult<NativeResult> {
512 let funds_type = context.type_to_type_tag(&get_specified_ty(ty_args)?)?;
513 let owner: SuiAddress = pop_arg!(args, AccountAddress).into();
514 safe_assert!(args.is_empty());
515 let store: &&InMemoryTestStore = get_extension!(context)?;
516 let value = store.settled_funds(owner, &funds_type);
517 Ok(NativeResult::ok(
518 legacy_test_cost(),
519 smallvec![Value::u128(value)],
520 ))
521}
522
523pub fn take_from_address_by_id(
525 context: &mut NativeContext,
526 ty_args: Vec<Type>,
527 mut args: VecDeque<Value>,
528) -> PartialVMResult<NativeResult> {
529 let specified_ty = get_specified_ty(ty_args)?;
530 let id = pop_id(&mut args)?;
531 let account: SuiAddress = pop_arg!(args, AccountAddress).into();
532 pop_arg!(args, StructRef);
533 safe_assert!(args.is_empty());
534 let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
535 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
536 let inventories = &mut object_runtime.test_inventories;
537 let res = take_from_inventory(
538 |x| {
539 inventories
540 .address_inventories
541 .get(&account)
542 .and_then(|inv| inv.get(&specified_obj_ty))
543 .map(|s| s.contains(x))
544 .unwrap_or(false)
545 },
546 &inventories.objects,
547 &mut inventories.taken,
548 &mut object_runtime.state.input_objects,
549 id,
550 Owner::AddressOwner(account),
551 );
552 Ok(match res {
553 Ok(value) => NativeResult::ok(legacy_test_cost(), smallvec![value]),
554 Err(native_err) => native_err,
555 })
556}
557
558pub fn ids_for_address(
560 context: &mut NativeContext,
561 ty_args: Vec<Type>,
562 mut args: VecDeque<Value>,
563) -> PartialVMResult<NativeResult> {
564 let specified_ty = get_specified_ty(ty_args)?;
565 let account: SuiAddress = pop_arg!(args, AccountAddress).into();
566 safe_assert!(args.is_empty());
567 let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
568 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
569 let inventories = &mut object_runtime.test_inventories;
570 let ids = inventories
571 .address_inventories
572 .get(&account)
573 .and_then(|inv| inv.get(&specified_obj_ty))
574 .map(|s| s.iter().map(|id| pack_id(*id)).collect::<Vec<Value>>())
575 .unwrap_or_default();
576 let ids_vector = safe_unwrap!(Vector::pack(VectorSpecialization::Container, ids));
577 Ok(NativeResult::ok(legacy_test_cost(), smallvec![ids_vector]))
578}
579
580pub fn most_recent_id_for_address(
582 context: &mut NativeContext,
583 ty_args: Vec<Type>,
584 mut args: VecDeque<Value>,
585) -> PartialVMResult<NativeResult> {
586 let specified_ty = get_specified_ty(ty_args)?;
587 let account: SuiAddress = pop_arg!(args, AccountAddress).into();
588 safe_assert!(args.is_empty());
589 let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
590 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
591 let inventories = &mut object_runtime.test_inventories;
592 let most_recent_id = match inventories.address_inventories.get(&account) {
593 None => pack_option(vector_specialization(&specified_ty), None)?,
594 Some(inv) => most_recent_at_ty(&inventories.taken, inv, &specified_ty, specified_obj_ty)?,
595 };
596 Ok(NativeResult::ok(
597 legacy_test_cost(),
598 smallvec![most_recent_id],
599 ))
600}
601
602pub fn was_taken_from_address(
604 context: &mut NativeContext,
605 ty_args: Vec<Type>,
606 mut args: VecDeque<Value>,
607) -> PartialVMResult<NativeResult> {
608 safe_assert!(ty_args.is_empty());
609 let id = pop_id(&mut args)?;
610 let account: SuiAddress = pop_arg!(args, AccountAddress).into();
611 safe_assert!(args.is_empty());
612 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
613 let inventories = &mut object_runtime.test_inventories;
614 let was_taken = inventories
615 .taken
616 .get(&id)
617 .map(|owner| owner == &Owner::AddressOwner(account))
618 .unwrap_or(false);
619 Ok(NativeResult::ok(
620 legacy_test_cost(),
621 smallvec![Value::bool(was_taken)],
622 ))
623}
624
625pub fn take_immutable_by_id(
627 context: &mut NativeContext,
628 ty_args: Vec<Type>,
629 mut args: VecDeque<Value>,
630) -> PartialVMResult<NativeResult> {
631 let specified_ty = get_specified_ty(ty_args)?;
632 let id = pop_id(&mut args)?;
633 pop_arg!(args, StructRef);
634 safe_assert!(args.is_empty());
635 let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
636 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
637 let inventories = &mut object_runtime.test_inventories;
638 let res = take_from_inventory(
639 |x| {
640 inventories
641 .immutable_inventory
642 .get(&specified_obj_ty)
643 .map(|s| s.contains(x))
644 .unwrap_or(false)
645 },
646 &inventories.objects,
647 &mut inventories.taken,
648 &mut object_runtime.state.input_objects,
649 id,
650 Owner::Immutable,
651 );
652 Ok(match res {
653 Ok(value) => {
654 inventories
655 .taken_immutable_values
656 .entry(specified_obj_ty)
657 .or_default()
658 .insert(id, value.copy_value());
659 NativeResult::ok(legacy_test_cost(), smallvec![value])
660 }
661 Err(native_err) => native_err,
662 })
663}
664
665pub fn most_recent_immutable_id(
667 context: &mut NativeContext,
668 ty_args: Vec<Type>,
669 args: VecDeque<Value>,
670) -> PartialVMResult<NativeResult> {
671 let specified_ty = get_specified_ty(ty_args)?;
672 safe_assert!(args.is_empty());
673 let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
674 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
675 let inventories = &mut object_runtime.test_inventories;
676 let most_recent_id = most_recent_at_ty(
677 &inventories.taken,
678 &inventories.immutable_inventory,
679 &specified_ty,
680 specified_obj_ty,
681 )?;
682 Ok(NativeResult::ok(
683 legacy_test_cost(),
684 smallvec![most_recent_id],
685 ))
686}
687
688pub fn was_taken_immutable(
690 context: &mut NativeContext,
691 ty_args: Vec<Type>,
692 mut args: VecDeque<Value>,
693) -> PartialVMResult<NativeResult> {
694 safe_assert!(ty_args.is_empty());
695 let id = pop_id(&mut args)?;
696 safe_assert!(args.is_empty());
697 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
698 let inventories = &mut object_runtime.test_inventories;
699 let was_taken = inventories
700 .taken
701 .get(&id)
702 .map(|owner| owner == &Owner::Immutable)
703 .unwrap_or(false);
704 Ok(NativeResult::ok(
705 legacy_test_cost(),
706 smallvec![Value::bool(was_taken)],
707 ))
708}
709
710pub fn take_shared_by_id(
712 context: &mut NativeContext,
713 ty_args: Vec<Type>,
714 mut args: VecDeque<Value>,
715) -> PartialVMResult<NativeResult> {
716 let specified_ty = get_specified_ty(ty_args)?;
717 let id = pop_id(&mut args)?;
718 pop_arg!(args, StructRef);
719 safe_assert!(args.is_empty());
720 let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
721 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
722 let inventories = &mut object_runtime.test_inventories;
723 let res = take_from_inventory(
724 |x| {
725 inventories
726 .shared_inventory
727 .get(&specified_obj_ty)
728 .map(|s| s.contains(x))
729 .unwrap_or(false)
730 },
731 &inventories.objects,
732 &mut inventories.taken,
733 &mut object_runtime.state.input_objects,
734 id,
735 Owner::Shared { initial_shared_version: SequenceNumber::new() },
736 );
737 Ok(match res {
738 Ok(value) => NativeResult::ok(legacy_test_cost(), smallvec![value]),
739 Err(native_err) => native_err,
740 })
741}
742
743pub fn most_recent_id_shared(
745 context: &mut NativeContext,
746 ty_args: Vec<Type>,
747 args: VecDeque<Value>,
748) -> PartialVMResult<NativeResult> {
749 let specified_ty = get_specified_ty(ty_args)?;
750 safe_assert!(args.is_empty());
751 let specified_obj_ty = object_type_of_type(context, &specified_ty)?;
752 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
753 let inventories = &mut object_runtime.test_inventories;
754 let most_recent_id = most_recent_at_ty(
755 &inventories.taken,
756 &inventories.shared_inventory,
757 &specified_ty,
758 specified_obj_ty,
759 )?;
760 Ok(NativeResult::ok(
761 legacy_test_cost(),
762 smallvec![most_recent_id],
763 ))
764}
765
766pub fn was_taken_shared(
768 context: &mut NativeContext,
769 ty_args: Vec<Type>,
770 mut args: VecDeque<Value>,
771) -> PartialVMResult<NativeResult> {
772 safe_assert!(ty_args.is_empty());
773 let id = pop_id(&mut args)?;
774 safe_assert!(args.is_empty());
775 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
776 let inventories = &mut object_runtime.test_inventories;
777 let was_taken = inventories
778 .taken
779 .get(&id)
780 .map(|owner| matches!(owner, Owner::Shared { .. }))
781 .unwrap_or(false);
782 Ok(NativeResult::ok(
783 legacy_test_cost(),
784 smallvec![Value::bool(was_taken)],
785 ))
786}
787
788pub fn allocate_receiving_ticket_for_object(
789 context: &mut NativeContext,
790 ty_args: Vec<Type>,
791 mut args: VecDeque<Value>,
792) -> PartialVMResult<NativeResult> {
793 let ty = get_specified_ty(ty_args)?;
794 let id = pop_id(&mut args)?;
795
796 let abilities = context.type_to_abilities(&ty)?;
797 let Some((tag, layout, _)) = get_tag_and_layouts(context, &ty)? else {
798 return Ok(NativeResult::err(
799 context.gas_used(),
800 E_UNABLE_TO_ALLOCATE_RECEIVING_TICKET,
801 ));
802 };
803 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
804 let object_version = SequenceNumber::new();
805 let inventories = &mut object_runtime.test_inventories;
806 if inventories.allocated_tickets.contains_key(&id) {
807 return Ok(NativeResult::err(
808 context.gas_used(),
809 E_RECEIVING_TICKET_ALREADY_ALLOCATED,
810 ));
811 }
812
813 let obj_value = safe_unwrap!(inventories.objects.remove(&id));
814 let Some(bytes) = obj_value.typed_serialize(&layout) else {
815 return Ok(NativeResult::err(
816 context.gas_used(),
817 E_UNABLE_TO_ALLOCATE_RECEIVING_TICKET,
818 ));
819 };
820 let has_public_transfer = abilities.has_store();
821 let move_object = safe_unwrap!(unsafe {
822 MoveObject::new_from_execution_with_limit(
823 tag.into(),
824 has_public_transfer,
825 object_version,
826 bytes,
827 250 * 1024,
828 )
829 });
830
831 let Some((owner, _)) = inventories
832 .address_inventories
833 .iter()
834 .find(|(_addr, objs)| objs.iter().any(|(_, ids)| ids.contains(&id)))
835 else {
836 return Ok(NativeResult::err(
837 context.gas_used(),
838 E_OBJECT_NOT_FOUND_CODE,
839 ));
840 };
841
842 inventories.allocated_tickets.insert(
843 id,
844 (
845 DynamicallyLoadedObjectMetadata {
846 version: SequenceNumber::new(),
847 digest: ObjectDigest::MIN,
848 owner: Owner::AddressOwner(*owner),
849 storage_rebate: 0,
850 previous_transaction: TransactionDigest::default(),
851 },
852 obj_value,
853 ),
854 );
855
856 let object = Object::new_move(
857 move_object,
858 Owner::AddressOwner(*owner),
859 TransactionDigest::default(),
860 );
861
862 let store: &&InMemoryTestStore = get_extension!(context)?;
864 store.storage.borrow_mut().insert_object(object);
865
866 Ok(NativeResult::ok(
867 legacy_test_cost(),
868 smallvec![Value::u64(object_version.value())],
869 ))
870}
871
872pub fn deallocate_receiving_ticket_for_object(
873 context: &mut NativeContext,
874 _ty_args: Vec<Type>,
875 mut args: VecDeque<Value>,
876) -> PartialVMResult<NativeResult> {
877 let id = pop_id(&mut args)?;
878
879 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
880 let inventories = &mut object_runtime.test_inventories;
881 let Some((_, value)) = inventories.allocated_tickets.remove(&id) else {
883 return Ok(NativeResult::err(
884 context.gas_used(),
885 E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET,
886 ));
887 };
888
889 inventories.objects.insert(id, value);
892
893 let store: &&InMemoryTestStore = get_extension!(context)?;
895 if store.storage.borrow_mut().remove_object(id).is_none() {
896 return Ok(NativeResult::err(
897 context.gas_used(),
898 E_UNABLE_TO_DEALLOCATE_RECEIVING_TICKET,
899 ));
900 };
901
902 Ok(NativeResult::ok(legacy_test_cost(), smallvec![]))
903}
904
905fn take_from_inventory(
908 is_in_inventory: impl FnOnce(&ObjectID) -> bool,
909 objects: &BTreeMap<ObjectID, Value>,
910 taken: &mut BTreeMap<ObjectID, Owner>,
911 input_objects: &mut BTreeMap<ObjectID, Owner>,
912 id: ObjectID,
913 owner: Owner,
914) -> Result<Value, NativeResult> {
915 let is_taken = taken.contains_key(&id);
916 let obj = match objects.get(&id) {
917 Some(obj) if !is_taken && is_in_inventory(&id) => obj,
918 _ => {
919 return Err(NativeResult::err(
920 legacy_test_cost(),
921 E_OBJECT_NOT_FOUND_CODE,
922 ));
923 }
924 };
925 taken.insert(id, owner.clone());
926 input_objects.insert(id, owner);
927 Ok(obj.copy_value())
928}
929
930fn vector_specialization(ty: &Type) -> VectorSpecialization {
931 match ty.try_into() {
932 Ok(s) => s,
933 Err(_) => {
934 debug_assert!(false, "Invalid vector specialization");
935 VectorSpecialization::Container
936 }
937 }
938}
939
940fn most_recent_at_ty(
941 taken: &BTreeMap<ObjectID, Owner>,
942 inv: &BTreeMap<MoveObjectType, Set<ObjectID>>,
943 runtime_ty: &Type,
944 ty: MoveObjectType,
945) -> PartialVMResult<Value> {
946 pack_option(
947 vector_specialization(runtime_ty),
948 most_recent_at_ty_opt(taken, inv, ty),
949 )
950}
951
952fn most_recent_at_ty_opt(
953 taken: &BTreeMap<ObjectID, Owner>,
954 inv: &BTreeMap<MoveObjectType, Set<ObjectID>>,
955 ty: MoveObjectType,
956) -> Option<Value> {
957 let s = inv.get(&ty)?;
958 let most_recent_id = s.iter().rfind(|id| !taken.contains_key(id))?;
959 Some(pack_id(*most_recent_id))
960}
961
962fn get_specified_ty(mut ty_args: Vec<Type>) -> PartialVMResult<Type> {
963 safe_assert!(ty_args.len() == 1);
964 Ok(safe_unwrap!(ty_args.pop()))
965}
966
967fn pop_id(args: &mut VecDeque<Value>) -> PartialVMResult<ObjectID> {
969 let v = match args.pop_back() {
970 None => {
971 return Err(PartialVMError::new(
972 StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
973 ));
974 }
975 Some(v) => v,
976 };
977 Ok(get_nth_struct_field(v, 0)?
978 .value_as::<AccountAddress>()?
979 .into())
980}
981
982fn pack_id(a: impl Into<AccountAddress>) -> Value {
983 Value::struct_(values::Struct::pack(vec![Value::address(a.into())]))
984}
985
986fn pack_ids(items: impl IntoIterator<Item = impl Into<AccountAddress>>) -> PartialVMResult<Value> {
987 Vector::pack(
988 VectorSpecialization::Container,
989 items.into_iter().map(pack_id),
990 )
991}
992
993fn pack_vec_map(items: impl IntoIterator<Item = (Value, Value)>) -> PartialVMResult<Value> {
994 Ok(Value::struct_(values::Struct::pack(vec![Vector::pack(
995 VectorSpecialization::Container,
996 items
997 .into_iter()
998 .map(|(k, v)| Value::struct_(values::Struct::pack(vec![k, v]))),
999 )?])))
1000}
1001
1002fn transaction_effects(
1003 created: impl IntoIterator<Item = impl Into<AccountAddress>>,
1004 written: impl IntoIterator<Item = impl Into<AccountAddress>>,
1005 deleted: impl IntoIterator<Item = impl Into<AccountAddress>>,
1006 transferred: impl IntoIterator<Item = (ObjectID, Owner)>,
1007 num_events: u64,
1008) -> PartialVMResult<Value> {
1009 let mut transferred_to_account = vec![];
1010 let mut transferred_to_object = vec![];
1011 let mut shared = vec![];
1012 let mut frozen = vec![];
1013 for (id, owner) in transferred {
1014 match owner {
1015 Owner::AddressOwner(a) => {
1016 transferred_to_account.push((pack_id(id), Value::address(a.into())))
1017 }
1018 Owner::ObjectOwner(o) => transferred_to_object.push((pack_id(id), pack_id(o))),
1019 Owner::Shared { .. } => shared.push(id),
1020 Owner::Immutable => frozen.push(id),
1021 Owner::ConsensusAddressOwner { owner, .. } => {
1022 transferred_to_account.push((pack_id(id), Value::address(owner.into())))
1023 }
1024 Owner::Party { .. } => {
1025 todo!("Party WIP")
1027 }
1028 }
1029 }
1030
1031 let created_field = pack_ids(created)?;
1032 let written_field = pack_ids(written)?;
1033 let deleted_field = pack_ids(deleted)?;
1034 let transferred_to_account_field = pack_vec_map(transferred_to_account)?;
1035 let transferred_to_object_field = pack_vec_map(transferred_to_object)?;
1036 let shared_field = pack_ids(shared)?;
1037 let frozen_field = pack_ids(frozen)?;
1038 let num_events_field = Value::u64(num_events);
1039 Ok(Value::struct_(values::Struct::pack(vec![
1040 created_field,
1041 written_field,
1042 deleted_field,
1043 transferred_to_account_field,
1044 transferred_to_object_field,
1045 shared_field,
1046 frozen_field,
1047 num_events_field,
1048 ])))
1049}
1050
1051fn object_type_of_type(context: &NativeContext, ty: &Type) -> PartialVMResult<MoveObjectType> {
1052 let TypeTag::Struct(s_tag) = context.type_to_type_tag(ty)? else {
1053 return Err(PartialVMError::new(
1054 StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
1055 ));
1056 };
1057 Ok(MoveObjectType::from(*s_tag))
1058}
1059
1060fn pack_option(specialization: VectorSpecialization, opt: Option<Value>) -> PartialVMResult<Value> {
1061 let item = match opt {
1062 Some(v) => vec![v],
1063 None => vec![],
1064 };
1065 Ok(Value::struct_(values::Struct::pack(vec![Vector::pack(
1066 specialization,
1067 item,
1068 )?])))
1069}
1070
1071fn find_all_wrapped_objects<'a, 'i>(
1072 context: &NativeContext,
1073 ids: &'i mut BTreeSet<ObjectID>,
1074 new_object_values: impl IntoIterator<Item = (&'a ObjectID, &'a MoveObjectType, impl Borrow<Value>)>,
1075) -> PartialVMResult<()> {
1076 #[derive(Copy, Clone)]
1077 enum LookingFor {
1078 Wrapped,
1079 Uid,
1080 Address,
1081 }
1082
1083 struct Traversal<'i, 'u> {
1084 state: LookingFor,
1085 ids: &'i mut BTreeSet<ObjectID>,
1086 uid: &'u MoveStructLayout,
1087 }
1088
1089 impl<'b, 'l> AV::Traversal<'b, 'l> for Traversal<'_, '_> {
1090 type Error = AV::Error;
1091
1092 fn traverse_struct(
1093 &mut self,
1094 driver: &mut AV::StructDriver<'_, 'b, 'l>,
1095 ) -> Result<(), Self::Error> {
1096 match self.state {
1097 LookingFor::Wrapped => {
1101 while driver
1102 .next_field(&mut Traversal {
1103 state: LookingFor::Uid,
1104 ids: self.ids,
1105 uid: self.uid,
1106 })?
1107 .is_some()
1108 {}
1109 }
1110
1111 LookingFor::Uid => {
1114 while let Some(MoveFieldLayout { name: _, layout }) = driver.peek_field() {
1115 if matches!(layout, MoveTypeLayout::Struct(s) if s.as_ref() == self.uid) {
1116 driver.next_field(&mut Traversal {
1117 state: LookingFor::Address,
1118 ids: self.ids,
1119 uid: self.uid,
1120 })?;
1121 } else {
1122 driver.next_field(self)?;
1123 }
1124 }
1125 }
1126
1127 LookingFor::Address => while driver.next_field(self)?.is_some() {},
1130 }
1131
1132 Ok(())
1133 }
1134
1135 fn traverse_address(
1136 &mut self,
1137 _: &AV::ValueDriver<'_, 'b, 'l>,
1138 address: AccountAddress,
1139 ) -> Result<(), Self::Error> {
1140 if matches!(self.state, LookingFor::Address) {
1142 self.ids.insert(address.into());
1143 }
1144 Ok(())
1145 }
1146 }
1147
1148 let uid = UID::layout();
1149 for (_id, ty, value) in new_object_values {
1150 let type_tag = TypeTag::from(ty.clone());
1151 let Some(layout) = context.type_tag_to_type_layout(&type_tag) else {
1156 debug_assert!(false);
1157 continue;
1158 };
1159
1160 let Some(annotated_layout) = context.type_tag_to_annotated_type_layout(&type_tag) else {
1161 debug_assert!(false);
1162 continue;
1163 };
1164
1165 let blob = safe_unwrap!(value.borrow().typed_serialize(&layout));
1166 safe_unwrap!(MoveValue::visit_deserialize(
1167 &blob,
1168 &annotated_layout,
1169 &mut Traversal {
1170 state: LookingFor::Wrapped,
1171 ids,
1172 uid: &uid,
1173 },
1174 ));
1175 }
1176 Ok(())
1177}