pub(crate) mod object_store;
use self::object_store::{ChildObjectEffect, ObjectResult};
use super::get_object_id;
use better_any::{Tid, TidAble};
use indexmap::map::IndexMap;
use indexmap::set::IndexSet;
use move_binary_format::errors::{PartialVMError, PartialVMResult};
use move_core_types::{
account_address::AccountAddress,
annotated_value::{MoveTypeLayout, MoveValue},
annotated_visitor as AV,
effects::Op,
language_storage::StructTag,
runtime_value as R,
vm_status::StatusCode,
};
use move_vm_types::{
loaded_data::runtime_types::Type,
values::{GlobalValue, Value},
};
use object_store::ChildObjectStore;
use std::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
};
use sui_protocol_config::{check_limit_by_meter, LimitThresholdCrossed, ProtocolConfig};
use sui_types::{
base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress},
committee::EpochId,
error::{ExecutionError, ExecutionErrorKind, VMMemoryLimitExceededSubStatusCode},
execution::DynamicallyLoadedObjectMetadata,
id::UID,
metrics::LimitsMetrics,
object::{MoveObject, Owner},
storage::ChildObjectResolver,
SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_CLOCK_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID,
SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
};
pub enum ObjectEvent {
Transfer(Owner, MoveObject),
DeleteObjectID(ObjectID),
}
type Set<K> = IndexSet<K>;
#[derive(Default)]
pub(crate) struct TestInventories {
pub(crate) objects: BTreeMap<ObjectID, Value>,
pub(crate) address_inventories: BTreeMap<SuiAddress, BTreeMap<Type, Set<ObjectID>>>,
pub(crate) shared_inventory: BTreeMap<Type, Set<ObjectID>>,
pub(crate) immutable_inventory: BTreeMap<Type, Set<ObjectID>>,
pub(crate) taken_immutable_values: BTreeMap<Type, BTreeMap<ObjectID, Value>>,
pub(crate) taken: BTreeMap<ObjectID, Owner>,
}
pub struct LoadedRuntimeObject {
pub version: SequenceNumber,
pub is_modified: bool,
}
pub struct RuntimeResults {
pub writes: IndexMap<ObjectID, (Owner, Type, Value)>,
pub user_events: Vec<(Type, StructTag, Value)>,
pub loaded_child_objects: BTreeMap<ObjectID, LoadedRuntimeObject>,
pub created_object_ids: Set<ObjectID>,
pub deleted_object_ids: Set<ObjectID>,
}
#[derive(Default)]
pub(crate) struct ObjectRuntimeState {
pub(crate) input_objects: BTreeMap<ObjectID, Owner>,
new_ids: Set<ObjectID>,
deleted_ids: Set<ObjectID>,
transfers: IndexMap<ObjectID, (Owner, Type, Value)>,
events: Vec<(Type, StructTag, Value)>,
total_events_size: u64,
received: IndexMap<ObjectID, DynamicallyLoadedObjectMetadata>,
}
#[derive(Tid)]
pub struct ObjectRuntime<'a> {
child_object_store: ChildObjectStore<'a>,
pub(crate) test_inventories: TestInventories,
pub(crate) state: ObjectRuntimeState,
is_metered: bool,
pub(crate) protocol_config: &'a ProtocolConfig,
pub(crate) metrics: Arc<LimitsMetrics>,
}
pub enum TransferResult {
New,
SameOwner,
OwnerChanged,
}
pub struct InputObject {
pub contained_uids: BTreeSet<ObjectID>,
pub version: SequenceNumber,
pub owner: Owner,
}
impl TestInventories {
fn new() -> Self {
Self::default()
}
}
impl<'a> ObjectRuntime<'a> {
pub fn new(
object_resolver: &'a dyn ChildObjectResolver,
input_objects: BTreeMap<ObjectID, InputObject>,
is_metered: bool,
protocol_config: &'a ProtocolConfig,
metrics: Arc<LimitsMetrics>,
epoch_id: EpochId,
) -> Self {
let mut input_object_owners = BTreeMap::new();
let mut root_version = BTreeMap::new();
let mut wrapped_object_containers = BTreeMap::new();
for (id, input_object) in input_objects {
let InputObject {
contained_uids,
version,
owner,
} = input_object;
input_object_owners.insert(id, owner);
debug_assert!(contained_uids.contains(&id));
for contained_uid in contained_uids {
root_version.insert(contained_uid, version);
if contained_uid != id {
let prev = wrapped_object_containers.insert(contained_uid, id);
debug_assert!(prev.is_none());
}
}
}
Self {
child_object_store: ChildObjectStore::new(
object_resolver,
root_version,
wrapped_object_containers,
is_metered,
protocol_config,
metrics.clone(),
epoch_id,
),
test_inventories: TestInventories::new(),
state: ObjectRuntimeState {
input_objects: input_object_owners,
new_ids: Set::new(),
deleted_ids: Set::new(),
transfers: IndexMap::new(),
events: vec![],
total_events_size: 0,
received: IndexMap::new(),
},
is_metered,
protocol_config,
metrics,
}
}
pub fn new_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
self.is_metered,
self.state.new_ids.len(),
self.protocol_config.max_num_new_move_object_ids(),
self.protocol_config.max_num_new_move_object_ids_system_tx(),
self.metrics.excessive_new_move_object_ids
) {
return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
.with_message(format!("Creating more than {} IDs is not allowed", lim))
.with_sub_status(
VMMemoryLimitExceededSubStatusCode::NEW_ID_COUNT_LIMIT_EXCEEDED as u64,
));
};
let was_present = self.state.deleted_ids.shift_remove(&id);
if !was_present {
self.state.new_ids.insert(id);
}
Ok(())
}
pub fn delete_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
self.is_metered,
self.state.deleted_ids.len(),
self.protocol_config.max_num_deleted_move_object_ids(),
self.protocol_config
.max_num_deleted_move_object_ids_system_tx(),
self.metrics.excessive_deleted_move_object_ids
) {
return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
.with_message(format!("Deleting more than {} IDs is not allowed", lim))
.with_sub_status(
VMMemoryLimitExceededSubStatusCode::DELETED_ID_COUNT_LIMIT_EXCEEDED as u64,
));
};
let was_new = self.state.new_ids.shift_remove(&id);
if !was_new {
self.state.deleted_ids.insert(id);
}
Ok(())
}
pub fn transfer(
&mut self,
owner: Owner,
ty: Type,
obj: Value,
) -> PartialVMResult<TransferResult> {
let id: ObjectID = get_object_id(obj.copy_value()?)?
.value_as::<AccountAddress>()?
.into();
let is_framework_obj = [
SUI_SYSTEM_STATE_OBJECT_ID,
SUI_CLOCK_OBJECT_ID,
SUI_AUTHENTICATOR_STATE_OBJECT_ID,
SUI_RANDOMNESS_STATE_OBJECT_ID,
SUI_DENY_LIST_OBJECT_ID,
]
.contains(&id);
let transfer_result = if self.state.new_ids.contains(&id) {
TransferResult::New
} else if is_framework_obj {
self.state.new_ids.insert(id);
TransferResult::New
} else if let Some(prev_owner) = self.state.input_objects.get(&id) {
match (&owner, prev_owner) {
(Owner::Shared { .. }, Owner::Shared { .. }) => TransferResult::SameOwner,
(new, old) if new == old => TransferResult::SameOwner,
_ => TransferResult::OwnerChanged,
}
} else {
TransferResult::OwnerChanged
};
if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
self.is_metered && !is_framework_obj, self.state.transfers.len(),
self.protocol_config.max_num_transferred_move_object_ids(),
self.protocol_config
.max_num_transferred_move_object_ids_system_tx(),
self.metrics.excessive_transferred_move_object_ids
) {
return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
.with_message(format!("Transferring more than {} IDs is not allowed", lim))
.with_sub_status(
VMMemoryLimitExceededSubStatusCode::TRANSFER_ID_COUNT_LIMIT_EXCEEDED as u64,
));
};
self.state.transfers.insert(id, (owner, ty, obj));
Ok(transfer_result)
}
pub fn emit_event(&mut self, ty: Type, tag: StructTag, event: Value) -> PartialVMResult<()> {
if self.state.events.len() >= (self.protocol_config.max_num_event_emit() as usize) {
return Err(max_event_error(self.protocol_config.max_num_event_emit()));
}
self.state.events.push((ty, tag, event));
Ok(())
}
pub fn take_user_events(&mut self) -> Vec<(Type, StructTag, Value)> {
std::mem::take(&mut self.state.events)
}
pub(crate) fn child_object_exists(
&mut self,
parent: ObjectID,
child: ObjectID,
) -> PartialVMResult<bool> {
self.child_object_store.object_exists(parent, child)
}
pub(crate) fn child_object_exists_and_has_type(
&mut self,
parent: ObjectID,
child: ObjectID,
child_type: &MoveObjectType,
) -> PartialVMResult<bool> {
self.child_object_store
.object_exists_and_has_type(parent, child, child_type)
}
pub(super) fn receive_object(
&mut self,
parent: ObjectID,
child: ObjectID,
child_version: SequenceNumber,
child_ty: &Type,
child_layout: &R::MoveTypeLayout,
child_fully_annotated_layout: &MoveTypeLayout,
child_move_type: MoveObjectType,
) -> PartialVMResult<Option<ObjectResult<Value>>> {
let Some((value, obj_meta)) = self.child_object_store.receive_object(
parent,
child,
child_version,
child_ty,
child_layout,
child_fully_annotated_layout,
child_move_type,
)?
else {
return Ok(None);
};
if self.state.received.insert(child, obj_meta).is_some() {
return Err(
PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(format!(
"Object {child} at version {child_version} already received. This can only happen \
if multiple `Receiving` arguments exist for the same object in the transaction which is impossible."
)),
);
}
Ok(Some(value))
}
pub(crate) fn get_or_fetch_child_object(
&mut self,
parent: ObjectID,
child: ObjectID,
child_ty: &Type,
child_layout: &R::MoveTypeLayout,
child_fully_annotated_layout: &MoveTypeLayout,
child_move_type: MoveObjectType,
) -> PartialVMResult<ObjectResult<&mut GlobalValue>> {
let res = self.child_object_store.get_or_fetch_object(
parent,
child,
child_ty,
child_layout,
child_fully_annotated_layout,
child_move_type,
)?;
Ok(match res {
ObjectResult::MismatchedType => ObjectResult::MismatchedType,
ObjectResult::Loaded(child_object) => ObjectResult::Loaded(&mut child_object.value),
})
}
pub(crate) fn add_child_object(
&mut self,
parent: ObjectID,
child: ObjectID,
child_ty: &Type,
child_move_type: MoveObjectType,
child_value: Value,
) -> PartialVMResult<()> {
self.child_object_store
.add_object(parent, child, child_ty, child_move_type, child_value)
}
pub(crate) fn take_state(&mut self) -> ObjectRuntimeState {
std::mem::take(&mut self.state)
}
pub fn finish(mut self) -> Result<RuntimeResults, ExecutionError> {
let loaded_child_objects = self.loaded_runtime_objects();
let child_effects = self.child_object_store.take_effects();
self.state.finish(loaded_child_objects, child_effects)
}
pub(crate) fn all_active_child_objects(
&self,
) -> impl Iterator<Item = (&ObjectID, &Type, Value)> {
self.child_object_store.all_active_objects()
}
pub fn loaded_runtime_objects(&self) -> BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata> {
debug_assert!(self
.child_object_store
.cached_objects()
.keys()
.all(|id| !self.state.received.contains_key(id)));
self.child_object_store
.cached_objects()
.iter()
.filter_map(|(id, obj_opt)| {
obj_opt.as_ref().map(|obj| {
(
*id,
DynamicallyLoadedObjectMetadata {
version: obj.version(),
digest: obj.digest(),
storage_rebate: obj.storage_rebate,
owner: obj.owner.clone(),
previous_transaction: obj.previous_transaction,
},
)
})
})
.chain(
self.state
.received
.iter()
.map(|(id, meta)| (*id, meta.clone())),
)
.collect()
}
pub fn wrapped_object_containers(&self) -> BTreeMap<ObjectID, ObjectID> {
self.child_object_store.wrapped_object_containers().clone()
}
}
pub fn max_event_error(max_events: u64) -> PartialVMError {
PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
.with_message(format!(
"Emitting more than {} events is not allowed",
max_events
))
.with_sub_status(VMMemoryLimitExceededSubStatusCode::EVENT_COUNT_LIMIT_EXCEEDED as u64)
}
impl ObjectRuntimeState {
pub(crate) fn finish(
mut self,
loaded_child_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
child_object_effects: BTreeMap<ObjectID, ChildObjectEffect>,
) -> Result<RuntimeResults, ExecutionError> {
let mut loaded_child_objects: BTreeMap<_, _> = loaded_child_objects
.into_iter()
.map(|(id, metadata)| {
(
id,
LoadedRuntimeObject {
version: metadata.version,
is_modified: false,
},
)
})
.collect();
for (child, child_object_effect) in child_object_effects {
let ChildObjectEffect {
owner: parent,
ty,
effect,
} = child_object_effect;
if let Some(loaded_child) = loaded_child_objects.get_mut(&child) {
loaded_child.is_modified = true;
}
match effect {
Op::Modify(v) => {
debug_assert!(!self.transfers.contains_key(&child));
debug_assert!(!self.new_ids.contains(&child));
debug_assert!(loaded_child_objects.contains_key(&child));
self.transfers
.insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
}
Op::New(v) => {
debug_assert!(!self.transfers.contains_key(&child));
self.transfers
.insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
}
Op::Delete => {
if self.transfers.contains_key(&child) {
debug_assert!(!self.deleted_ids.contains(&child));
}
if self.deleted_ids.contains(&child) {
debug_assert!(!self.transfers.contains_key(&child));
debug_assert!(!self.new_ids.contains(&child));
}
}
}
}
let ObjectRuntimeState {
input_objects: _,
new_ids,
deleted_ids,
transfers,
events: user_events,
total_events_size: _,
received,
} = self;
check_circular_ownership(
transfers
.iter()
.map(|(id, (owner, _, _))| (*id, owner.clone())),
)?;
let written_objects: IndexMap<_, _> = transfers
.into_iter()
.map(|(id, (owner, type_, value))| {
if let Some(loaded_child) = loaded_child_objects.get_mut(&id) {
loaded_child.is_modified = true;
}
(id, (owner, type_, value))
})
.collect();
for deleted_id in &deleted_ids {
if let Some(loaded_child) = loaded_child_objects.get_mut(deleted_id) {
loaded_child.is_modified = true;
}
}
for (received_object, _) in received.into_iter() {
match loaded_child_objects.get_mut(&received_object) {
Some(loaded_child) => {
loaded_child.is_modified = true;
}
None => {
return Err(ExecutionError::invariant_violation(format!(
"Failed to find received UID {received_object} in loaded child objects."
)))
}
}
}
Ok(RuntimeResults {
writes: written_objects,
user_events,
loaded_child_objects,
created_object_ids: new_ids,
deleted_object_ids: deleted_ids,
})
}
pub fn total_events_size(&self) -> u64 {
self.total_events_size
}
pub fn incr_total_events_size(&mut self, size: u64) {
self.total_events_size += size;
}
}
fn check_circular_ownership(
transfers: impl IntoIterator<Item = (ObjectID, Owner)>,
) -> Result<(), ExecutionError> {
let mut object_owner_map = BTreeMap::new();
for (id, recipient) in transfers {
object_owner_map.remove(&id);
match recipient {
Owner::AddressOwner(_) | Owner::Shared { .. } | Owner::Immutable => (),
Owner::ObjectOwner(new_owner) => {
let new_owner: ObjectID = new_owner.into();
let mut cur = new_owner;
loop {
if cur == id {
return Err(ExecutionError::from_kind(
ExecutionErrorKind::CircularObjectOwnership { object: cur },
));
}
if let Some(parent) = object_owner_map.get(&cur) {
cur = *parent;
} else {
break;
}
}
object_owner_map.insert(id, new_owner);
}
Owner::ConsensusV2 { .. } => {
unimplemented!("ConsensusV2 does not exist for this execution version")
}
}
}
Ok(())
}
pub fn get_all_uids(
fully_annotated_layout: &MoveTypeLayout,
bcs_bytes: &[u8],
) -> Result<BTreeSet<ObjectID>, String> {
let mut ids = BTreeSet::new();
struct UIDTraversal<'i>(&'i mut BTreeSet<ObjectID>);
struct UIDCollector<'i>(&'i mut BTreeSet<ObjectID>);
impl<'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'_> {
type Error = AV::Error;
fn traverse_struct(
&mut self,
driver: &mut AV::StructDriver<'_, 'b, 'l>,
) -> Result<(), Self::Error> {
if driver.struct_layout().type_ == UID::type_() {
while driver.next_field(&mut UIDCollector(self.0))?.is_some() {}
} else {
while driver.next_field(self)?.is_some() {}
}
Ok(())
}
}
impl<'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'_> {
type Error = AV::Error;
fn traverse_address(
&mut self,
_driver: &AV::ValueDriver<'_, 'b, 'l>,
value: AccountAddress,
) -> Result<(), Self::Error> {
self.0.insert(value.into());
Ok(())
}
}
MoveValue::visit_deserialize(
bcs_bytes,
fully_annotated_layout,
&mut UIDTraversal(&mut ids),
)
.map_err(|e| format!("Failed to deserialize. {e:?}"))?;
Ok(ids)
}