1use crate::base_types::{MoveObjectType, ObjectDigest, SuiAddress};
5use crate::crypto::DefaultHash;
6use crate::error::{SuiError, SuiErrorKind, SuiResult};
7use crate::id::UID;
8use crate::object::{MoveObject, Object};
9use crate::storage::{ChildObjectResolver, ObjectStore};
10use crate::sui_serde::Readable;
11use crate::sui_serde::SuiTypeTag;
12use crate::{MoveTypeTagTrait, ObjectID, SUI_FRAMEWORK_ADDRESS, SequenceNumber};
13use fastcrypto::encoding::Base64;
14use fastcrypto::hash::HashFunction;
15use move_core_types::annotated_value::{MoveStruct, MoveValue};
16use move_core_types::ident_str;
17use move_core_types::identifier::IdentStr;
18use move_core_types::language_storage::{StructTag, TypeTag};
19use schemars::JsonSchema;
20use serde::Deserialize;
21use serde::Serialize;
22use serde::de::DeserializeOwned;
23use serde_json::Value;
24use serde_with::DisplayFromStr;
25use serde_with::serde_as;
26use shared_crypto::intent::HashingIntentScope;
27use std::fmt;
28use std::fmt::{Display, Formatter};
29
30pub mod visitor;
31
32pub const DYNAMIC_FIELD_MODULE_NAME: &IdentStr = ident_str!("dynamic_field");
33pub const DYNAMIC_FIELD_FIELD_STRUCT_NAME: &IdentStr = ident_str!("Field");
34
35const DYNAMIC_OBJECT_FIELD_MODULE_NAME: &IdentStr = ident_str!("dynamic_object_field");
36const DYNAMIC_OBJECT_FIELD_WRAPPER_STRUCT_NAME: &IdentStr = ident_str!("Wrapper");
37
38#[derive(Clone, Serialize, Deserialize, Debug)]
40pub struct Field<N, V> {
41 pub id: UID,
42 pub name: N,
43 pub value: V,
44}
45
46#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
48pub struct DOFWrapper<N> {
49 pub name: N,
50}
51
52impl<N> MoveTypeTagTrait for DOFWrapper<N>
53where
54 N: MoveTypeTagTrait,
55{
56 fn get_type_tag() -> TypeTag {
57 TypeTag::Struct(Box::new(DynamicFieldInfo::dynamic_object_field_wrapper(
58 N::get_type_tag(),
59 )))
60 }
61}
62
63#[serde_as]
64#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
65#[serde(rename_all = "camelCase")]
66pub struct DynamicFieldInfo {
67 pub name: DynamicFieldName,
68 #[serde_as(as = "Readable<Base64, _>")]
69 pub bcs_name: Vec<u8>,
70 pub type_: DynamicFieldType,
71 pub object_type: String,
72 pub object_id: ObjectID,
73 pub version: SequenceNumber,
74 pub digest: ObjectDigest,
75}
76
77#[serde_as]
78#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq)]
79#[serde(rename_all = "camelCase")]
80pub struct DynamicFieldName {
81 #[schemars(with = "String")]
82 #[serde_as(as = "Readable<SuiTypeTag, _>")]
83 pub type_: TypeTag,
84 #[schemars(with = "Value")]
87 #[serde_as(as = "Readable<_, DisplayFromStr>")]
88 pub value: Value,
89}
90
91impl Display for DynamicFieldName {
92 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93 write!(f, "{}: {}", self.type_, self.value)
94 }
95}
96
97#[derive(
98 Copy, Clone, Serialize, Deserialize, JsonSchema, Ord, PartialOrd, Eq, PartialEq, Debug,
99)]
100pub enum DynamicFieldType {
101 #[serde(rename_all = "camelCase")]
102 DynamicField,
103 DynamicObject,
104}
105
106impl Display for DynamicFieldType {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 match self {
109 DynamicFieldType::DynamicField => write!(f, "DynamicField"),
110 DynamicFieldType::DynamicObject => write!(f, "DynamicObject"),
111 }
112 }
113}
114
115impl DynamicFieldInfo {
116 pub fn is_dynamic_field(tag: &StructTag) -> bool {
117 tag.address == SUI_FRAMEWORK_ADDRESS
118 && tag.module.as_ident_str() == DYNAMIC_FIELD_MODULE_NAME
119 && tag.name.as_ident_str() == DYNAMIC_FIELD_FIELD_STRUCT_NAME
120 }
121
122 pub fn is_dynamic_object_field_wrapper(tag: &StructTag) -> bool {
123 tag.address == SUI_FRAMEWORK_ADDRESS
124 && tag.module.as_ident_str() == DYNAMIC_OBJECT_FIELD_MODULE_NAME
125 && tag.name.as_ident_str() == DYNAMIC_OBJECT_FIELD_WRAPPER_STRUCT_NAME
126 }
127
128 pub fn dynamic_field_type(key: TypeTag, value: TypeTag) -> StructTag {
129 StructTag {
130 address: SUI_FRAMEWORK_ADDRESS,
131 name: DYNAMIC_FIELD_FIELD_STRUCT_NAME.to_owned(),
132 module: DYNAMIC_FIELD_MODULE_NAME.to_owned(),
133 type_params: vec![key, value],
134 }
135 }
136
137 pub fn dynamic_object_field_wrapper(key: TypeTag) -> StructTag {
138 StructTag {
139 address: SUI_FRAMEWORK_ADDRESS,
140 module: DYNAMIC_OBJECT_FIELD_MODULE_NAME.to_owned(),
141 name: DYNAMIC_OBJECT_FIELD_WRAPPER_STRUCT_NAME.to_owned(),
142 type_params: vec![key],
143 }
144 }
145
146 pub fn try_extract_field_name(tag: &StructTag, type_: &DynamicFieldType) -> SuiResult<TypeTag> {
147 match (type_, tag.type_params.first()) {
148 (DynamicFieldType::DynamicField, Some(name_type)) => Ok(name_type.clone()),
149 (DynamicFieldType::DynamicObject, Some(TypeTag::Struct(s))) => Ok(s
150 .type_params
151 .first()
152 .ok_or_else(|| SuiErrorKind::ObjectDeserializationError {
153 error: format!("Error extracting dynamic object name from object: {tag}"),
154 })?
155 .clone()),
156 _ => Err(SuiErrorKind::ObjectDeserializationError {
157 error: format!("Error extracting dynamic object name from object: {tag}"),
158 }
159 .into()),
160 }
161 }
162
163 pub fn try_extract_field_value(tag: &StructTag) -> SuiResult<TypeTag> {
164 match tag.type_params.last() {
165 Some(value_type) => Ok(value_type.clone()),
166 None => Err(SuiErrorKind::ObjectDeserializationError {
167 error: format!("Error extracting dynamic object value from object: {tag}"),
168 }
169 .into()),
170 }
171 }
172
173 pub fn parse_move_object(
174 move_struct: &MoveStruct,
175 ) -> SuiResult<(MoveValue, DynamicFieldType, ObjectID)> {
176 let name = extract_field_from_move_struct(move_struct, "name").ok_or_else(|| {
177 SuiErrorKind::ObjectDeserializationError {
178 error: "Cannot extract [name] field from sui::dynamic_field::Field".to_string(),
179 }
180 })?;
181
182 let value = extract_field_from_move_struct(move_struct, "value").ok_or_else(|| {
183 SuiErrorKind::ObjectDeserializationError {
184 error: "Cannot extract [value] field from sui::dynamic_field::Field".to_string(),
185 }
186 })?;
187
188 Ok(if is_dynamic_object(move_struct) {
189 let name = match name {
190 MoveValue::Struct(name_struct) => {
191 extract_field_from_move_struct(name_struct, "name")
192 }
193 _ => None,
194 }
195 .ok_or_else(|| SuiErrorKind::ObjectDeserializationError {
196 error: "Cannot extract [name] field from sui::dynamic_object_field::Wrapper."
197 .to_string(),
198 })?;
199 let object_id = extract_id_value(value).ok_or_else(|| {
201 SuiErrorKind::ObjectDeserializationError {
202 error: format!(
203 "Cannot extract dynamic object's object id from \
204 sui::dynamic_field::Field, {value:?}"
205 ),
206 }
207 })?;
208 (name.clone(), DynamicFieldType::DynamicObject, object_id)
209 } else {
210 let object_id = extract_object_id(move_struct).ok_or_else(|| {
212 SuiErrorKind::ObjectDeserializationError {
213 error: format!(
214 "Cannot extract dynamic object's object id from \
215 sui::dynamic_field::Field, {move_struct:?}",
216 ),
217 }
218 })?;
219 (name.clone(), DynamicFieldType::DynamicField, object_id)
220 })
221 }
222}
223
224pub fn extract_field_from_move_struct<'a>(
225 move_struct: &'a MoveStruct,
226 field_name: &str,
227) -> Option<&'a MoveValue> {
228 move_struct.fields.iter().find_map(|(id, value)| {
229 if id.to_string() == field_name {
230 Some(value)
231 } else {
232 None
233 }
234 })
235}
236
237fn extract_object_id(value: &MoveStruct) -> Option<ObjectID> {
238 let uid_value = &value.fields.first()?.1;
240
241 let id_value = match uid_value {
243 MoveValue::Struct(MoveStruct { fields, .. }) => &fields.first()?.1,
244 _ => return None,
245 };
246 extract_id_value(id_value)
247}
248
249pub fn extract_id_value(id_value: &MoveValue) -> Option<ObjectID> {
250 let id_bytes_value = match id_value {
252 MoveValue::Struct(MoveStruct { fields, .. }) => &fields.first()?.1,
253 _ => return None,
254 };
255 match id_bytes_value {
257 MoveValue::Address(addr) => Some(ObjectID::from(*addr)),
258 _ => None,
259 }
260}
261
262pub fn is_dynamic_object(move_struct: &MoveStruct) -> bool {
263 matches!(
264 &move_struct.type_.type_params[0],
265 TypeTag::Struct(tag) if DynamicFieldInfo::is_dynamic_object_field_wrapper(tag)
266 )
267}
268
269pub fn derive_dynamic_field_id<T>(
270 parent: T,
271 key_type_tag: &TypeTag,
272 key_bytes: &[u8],
273) -> Result<ObjectID, bcs::Error>
274where
275 T: Into<SuiAddress>,
276{
277 let parent: SuiAddress = parent.into();
278 tracing::trace!(
279 "Deriving dynamic field ID for parent={:?}, key={:?}, key_type_tag={}",
280 parent,
281 key_bytes,
282 key_type_tag.to_canonical_display(true),
283 );
284
285 let mut hasher = DefaultHash::default();
287 hasher.update([HashingIntentScope::ChildObjectId as u8]);
288 hasher.update(parent);
289 hasher.update(key_bytes.len().to_le_bytes());
290 hasher.update(key_bytes);
291 bcs::serialize_into(&mut hasher, key_type_tag)?;
292 let hash = hasher.finalize();
293
294 let id = ObjectID::try_from(&hash.as_ref()[0..ObjectID::LENGTH]).unwrap();
297 tracing::trace!("derive_dynamic_field_id result: {:?}", id);
298 Ok(id)
299}
300
301pub fn serialize_dynamic_field<K, V>(id: &UID, name: &K, value: V) -> Result<Vec<u8>, SuiError>
302where
303 K: Serialize + Clone,
304 V: Serialize,
305{
306 let field = Field::<K, V> {
307 id: id.clone(),
308 name: name.clone(),
309 value,
310 };
311
312 bcs::to_bytes(&field).map_err(|err| {
313 SuiErrorKind::ObjectSerializationError {
314 error: err.to_string(),
315 }
316 .into()
317 })
318}
319
320pub fn get_dynamic_field_object_from_store<K>(
325 object_store: &dyn ObjectStore,
326 parent_id: ObjectID,
327 key: &K,
328) -> Result<Object, SuiError>
329where
330 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug + Clone,
331{
332 Ok(DynamicFieldKey(parent_id, key.clone(), K::get_type_tag())
333 .into_unbounded_id()?
334 .expect_object(key, object_store)?
335 .into_object())
336}
337
338pub fn get_dynamic_field_from_store<K, V>(
341 object_store: &dyn ObjectStore,
342 parent_id: ObjectID,
343 key: &K,
344) -> Result<V, SuiError>
345where
346 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug + Clone,
347 V: Serialize + DeserializeOwned,
348{
349 DynamicFieldKey(parent_id, key.clone(), K::get_type_tag())
350 .into_unbounded_id()?
351 .expect_object(key, object_store)?
352 .load_value::<V>()
353}
354
355pub struct DynamicFieldKey<ParentID, K>(pub ParentID, pub K, pub TypeTag);
376
377impl<ParentID, K> DynamicFieldKey<ParentID, K>
378where
379 ParentID: Into<SuiAddress> + Into<ObjectID> + Copy,
380 K: Serialize + fmt::Debug,
381{
382 pub fn object_id(&self) -> Result<ObjectID, SuiError> {
384 derive_dynamic_field_id(self.0, &self.2, &bcs::to_bytes(&self.1).unwrap())
385 .map_err(|e| SuiErrorKind::DynamicFieldReadError(e.to_string()).into())
386 }
387
388 pub fn into_unbounded_id(self) -> Result<UnboundedDynamicFieldID<K>, SuiError> {
391 let id = self.object_id()?;
392 Ok(UnboundedDynamicFieldID::<K>::new(self.0.into(), id))
393 }
394
395 pub fn into_id_with_bound(
398 self,
399 parent_version: SequenceNumber,
400 ) -> Result<BoundedDynamicFieldID<K>, SuiError> {
401 let id = self.object_id()?;
402 Ok(BoundedDynamicFieldID::<K>::new(
403 self.0.into(),
404 id,
405 parent_version,
406 ))
407 }
408
409 pub fn into_field<V>(self, value: V) -> Result<DynamicField<K, V>, SuiError>
414 where
415 V: Serialize + DeserializeOwned + MoveTypeTagTrait,
416 {
417 let id = self.object_id()?;
418 let field = Field::<K, V> {
419 id: UID::new(id),
420 name: self.1,
421 value,
422 };
423 let type_tag = TypeTag::Struct(Box::new(DynamicFieldInfo::dynamic_field_type(
424 self.2,
425 V::get_type_tag(),
426 )));
427 Ok(DynamicField(field, type_tag))
428 }
429}
430
431pub struct DynamicField<K, V>(Field<K, V>, TypeTag);
433
434impl<K, V> DynamicField<K, V>
435where
436 K: Serialize,
437 V: Serialize,
438{
439 pub fn into_move_object_unsafe_for_testing(
446 self,
447 version: SequenceNumber,
448 ) -> Result<MoveObject, SuiError> {
449 let field = self.0;
450 let type_tag = self.1;
451 let TypeTag::Struct(struct_tag) = type_tag else {
452 unreachable!()
453 };
454 let move_object_type = MoveObjectType::from(*struct_tag);
456
457 let field_bytes =
458 bcs::to_bytes(&field).map_err(|e| SuiErrorKind::ObjectSerializationError {
459 error: e.to_string(),
460 })?;
461 Ok(unsafe {
462 MoveObject::new_from_execution_with_limit(
463 move_object_type,
464 false, version,
466 field_bytes,
467 512,
468 )
469 }?)
470 }
471
472 pub fn into_inner(self) -> Field<K, V> {
474 self.0
475 }
476}
477
478pub struct UnboundedDynamicFieldID<K: Serialize>(
484 pub ObjectID, pub ObjectID, std::marker::PhantomData<K>,
487);
488
489impl<K> UnboundedDynamicFieldID<K>
490where
491 K: Serialize + std::fmt::Debug,
492{
493 pub fn new(parent: ObjectID, id: ObjectID) -> Self {
495 Self(parent, id, std::marker::PhantomData)
496 }
497
498 pub fn load_object(self, object_store: &dyn ObjectStore) -> Option<DynamicFieldObject<K>> {
500 object_store
501 .get_object(&self.1)
502 .map(DynamicFieldObject::<K>::new)
503 }
504
505 pub fn expect_object(
508 self,
509 key: &K,
510 object_store: &dyn ObjectStore,
511 ) -> Result<DynamicFieldObject<K>, SuiError> {
512 let parent = self.0;
513 let id = self.1;
514 self.load_object(object_store).ok_or_else(|| {
515 {
516 SuiErrorKind::DynamicFieldReadError(format!(
517 "Dynamic field with key={:?} and ID={:?} not found on parent {:?}",
518 key, id, parent
519 ))
520 }
521 .into()
522 })
523 }
524
525 pub fn exists(self, object_store: &dyn ObjectStore) -> bool {
527 self.load_object(object_store).is_some()
528 }
529
530 pub fn with_bound(self, parent_version: SequenceNumber) -> BoundedDynamicFieldID<K> {
533 BoundedDynamicFieldID::new(self.0, self.1, parent_version)
534 }
535
536 pub fn as_object_id(self) -> ObjectID {
538 self.1
539 }
540}
541
542pub struct BoundedDynamicFieldID<K: Serialize>(
549 pub ObjectID, pub ObjectID, pub SequenceNumber, std::marker::PhantomData<K>,
553);
554
555impl<K> BoundedDynamicFieldID<K>
556where
557 K: Serialize,
558{
559 pub fn new(parent_id: ObjectID, child_id: ObjectID, parent_version: SequenceNumber) -> Self {
561 Self(
562 parent_id,
563 child_id,
564 parent_version,
565 std::marker::PhantomData,
566 )
567 }
568
569 pub fn load_object(
572 self,
573 child_object_resolver: &dyn ChildObjectResolver,
574 ) -> Result<Option<DynamicFieldObject<K>>, SuiError> {
575 child_object_resolver
576 .read_child_object(&self.0, &self.1, self.2)
577 .map(|r| r.map(DynamicFieldObject::<K>::new))
578 }
579
580 pub fn exists(self, child_object_resolver: &dyn ChildObjectResolver) -> Result<bool, SuiError> {
582 self.load_object(child_object_resolver).map(|r| r.is_some())
583 }
584}
585
586pub struct DynamicFieldObject<K>(pub Object, std::marker::PhantomData<K>);
588
589impl<K> DynamicFieldObject<K> {
590 pub fn new(object: Object) -> Self {
592 Self(object, std::marker::PhantomData)
593 }
594
595 pub fn into_object(self) -> Object {
597 self.0
598 }
599
600 pub fn as_object(&self) -> &Object {
601 &self.0
602 }
603}
604
605impl<K> DynamicFieldObject<K>
606where
607 K: Serialize + DeserializeOwned,
608{
609 pub fn load_value<V>(self) -> Result<V, SuiError>
611 where
612 V: Serialize + DeserializeOwned,
613 {
614 self.load_field::<V>().map(|f| f.value)
615 }
616
617 pub fn load_field<V>(self) -> Result<Field<K, V>, SuiError>
619 where
620 V: Serialize + DeserializeOwned,
621 {
622 let object = self.0;
623 let move_object = object.data.try_as_move().ok_or_else(|| {
624 SuiErrorKind::DynamicFieldReadError(format!(
625 "Dynamic field {:?} is not a Move object",
626 object.id()
627 ))
628 })?;
629 bcs::from_bytes::<Field<K, V>>(move_object.contents())
630 .map_err(|err| SuiErrorKind::DynamicFieldReadError(err.to_string()).into())
631 }
632}