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)]
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)]
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 let k_tag_bytes = bcs::to_bytes(key_type_tag)?;
279 tracing::trace!(
280 "Deriving dynamic field ID for parent={:?}, key={:?}, key_type_tag={:?}",
281 parent,
282 key_bytes,
283 key_type_tag,
284 );
285
286 let mut hasher = DefaultHash::default();
288 hasher.update([HashingIntentScope::ChildObjectId as u8]);
289 hasher.update(parent);
290 hasher.update(key_bytes.len().to_le_bytes());
291 hasher.update(key_bytes);
292 hasher.update(k_tag_bytes);
293 let hash = hasher.finalize();
294
295 let id = ObjectID::try_from(&hash.as_ref()[0..ObjectID::LENGTH]).unwrap();
298 tracing::trace!("derive_dynamic_field_id result: {:?}", id);
299 Ok(id)
300}
301
302pub fn serialize_dynamic_field<K, V>(id: &UID, name: &K, value: V) -> Result<Vec<u8>, SuiError>
303where
304 K: Serialize + Clone,
305 V: Serialize,
306{
307 let field = Field::<K, V> {
308 id: id.clone(),
309 name: name.clone(),
310 value,
311 };
312
313 bcs::to_bytes(&field).map_err(|err| {
314 SuiErrorKind::ObjectSerializationError {
315 error: err.to_string(),
316 }
317 .into()
318 })
319}
320
321pub fn get_dynamic_field_object_from_store<K>(
326 object_store: &dyn ObjectStore,
327 parent_id: ObjectID,
328 key: &K,
329) -> Result<Object, SuiError>
330where
331 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug + Clone,
332{
333 Ok(DynamicFieldKey(parent_id, key.clone(), K::get_type_tag())
334 .into_unbounded_id()?
335 .expect_object(key, object_store)?
336 .as_object())
337}
338
339pub fn get_dynamic_field_from_store<K, V>(
342 object_store: &dyn ObjectStore,
343 parent_id: ObjectID,
344 key: &K,
345) -> Result<V, SuiError>
346where
347 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug + Clone,
348 V: Serialize + DeserializeOwned,
349{
350 DynamicFieldKey(parent_id, key.clone(), K::get_type_tag())
351 .into_unbounded_id()?
352 .expect_object(key, object_store)?
353 .load_value::<V>()
354}
355
356pub struct DynamicFieldKey<ParentID, K>(pub ParentID, pub K, pub TypeTag);
377
378impl<ParentID, K> DynamicFieldKey<ParentID, K>
379where
380 ParentID: Into<SuiAddress> + Into<ObjectID> + Copy,
381 K: Serialize + fmt::Debug,
382{
383 pub fn object_id(&self) -> Result<ObjectID, SuiError> {
385 derive_dynamic_field_id(self.0, &self.2, &bcs::to_bytes(&self.1).unwrap())
386 .map_err(|e| SuiErrorKind::DynamicFieldReadError(e.to_string()).into())
387 }
388
389 pub fn into_unbounded_id(self) -> Result<UnboundedDynamicFieldID<K>, SuiError> {
392 let id = self.object_id()?;
393 Ok(UnboundedDynamicFieldID::<K>::new(self.0.into(), id))
394 }
395
396 pub fn into_id_with_bound(
399 self,
400 parent_version: SequenceNumber,
401 ) -> Result<BoundedDynamicFieldID<K>, SuiError> {
402 let id = self.object_id()?;
403 Ok(BoundedDynamicFieldID::<K>::new(
404 self.0.into(),
405 id,
406 parent_version,
407 ))
408 }
409
410 pub fn into_field<V>(self, value: V) -> Result<DynamicField<K, V>, SuiError>
415 where
416 V: Serialize + DeserializeOwned + MoveTypeTagTrait,
417 {
418 let id = self.object_id()?;
419 let field = Field::<K, V> {
420 id: UID::new(id),
421 name: self.1,
422 value,
423 };
424 let type_tag = TypeTag::Struct(Box::new(DynamicFieldInfo::dynamic_field_type(
425 self.2,
426 V::get_type_tag(),
427 )));
428 Ok(DynamicField(field, type_tag))
429 }
430}
431
432pub struct DynamicField<K, V>(Field<K, V>, TypeTag);
434
435impl<K, V> DynamicField<K, V>
436where
437 K: Serialize,
438 V: Serialize,
439{
440 pub fn into_move_object_unsafe_for_testing(
447 self,
448 version: SequenceNumber,
449 ) -> Result<MoveObject, SuiError> {
450 let field = self.0;
451 let type_tag = self.1;
452 let TypeTag::Struct(struct_tag) = type_tag else {
453 unreachable!()
454 };
455 let move_object_type = MoveObjectType::from(*struct_tag);
457
458 let field_bytes =
459 bcs::to_bytes(&field).map_err(|e| SuiErrorKind::ObjectSerializationError {
460 error: e.to_string(),
461 })?;
462 Ok(unsafe {
463 MoveObject::new_from_execution_with_limit(
464 move_object_type,
465 false, version,
467 field_bytes,
468 512,
469 )
470 }?)
471 }
472
473 pub fn into_inner(self) -> Field<K, V> {
475 self.0
476 }
477}
478
479pub struct UnboundedDynamicFieldID<K: Serialize>(
485 pub ObjectID, pub ObjectID, std::marker::PhantomData<K>,
488);
489
490impl<K> UnboundedDynamicFieldID<K>
491where
492 K: Serialize + std::fmt::Debug,
493{
494 pub fn new(parent: ObjectID, id: ObjectID) -> Self {
496 Self(parent, id, std::marker::PhantomData)
497 }
498
499 pub fn load_object(self, object_store: &dyn ObjectStore) -> Option<DynamicFieldObject<K>> {
501 object_store
502 .get_object(&self.1)
503 .map(DynamicFieldObject::<K>::new)
504 }
505
506 pub fn expect_object(
509 self,
510 key: &K,
511 object_store: &dyn ObjectStore,
512 ) -> Result<DynamicFieldObject<K>, SuiError> {
513 let parent = self.0;
514 let id = self.1;
515 self.load_object(object_store).ok_or_else(|| {
516 {
517 SuiErrorKind::DynamicFieldReadError(format!(
518 "Dynamic field with key={:?} and ID={:?} not found on parent {:?}",
519 key, id, parent
520 ))
521 }
522 .into()
523 })
524 }
525
526 pub fn exists(self, object_store: &dyn ObjectStore) -> bool {
528 self.load_object(object_store).is_some()
529 }
530
531 pub fn with_bound(self, parent_version: SequenceNumber) -> BoundedDynamicFieldID<K> {
534 BoundedDynamicFieldID::new(self.0, self.1, parent_version)
535 }
536
537 pub fn as_object_id(self) -> ObjectID {
539 self.1
540 }
541}
542
543pub struct BoundedDynamicFieldID<K: Serialize>(
550 pub ObjectID, pub ObjectID, pub SequenceNumber, std::marker::PhantomData<K>,
554);
555
556impl<K> BoundedDynamicFieldID<K>
557where
558 K: Serialize,
559{
560 pub fn new(parent_id: ObjectID, child_id: ObjectID, parent_version: SequenceNumber) -> Self {
562 Self(
563 parent_id,
564 child_id,
565 parent_version,
566 std::marker::PhantomData,
567 )
568 }
569
570 pub fn load_object(
573 self,
574 child_object_resolver: &dyn ChildObjectResolver,
575 ) -> Result<Option<DynamicFieldObject<K>>, SuiError> {
576 child_object_resolver
577 .read_child_object(&self.0, &self.1, self.2)
578 .map(|r| r.map(DynamicFieldObject::<K>::new))
579 }
580
581 pub fn exists(self, child_object_resolver: &dyn ChildObjectResolver) -> Result<bool, SuiError> {
583 self.load_object(child_object_resolver).map(|r| r.is_some())
584 }
585}
586
587pub struct DynamicFieldObject<K>(pub Object, std::marker::PhantomData<K>);
589
590impl<K> DynamicFieldObject<K> {
591 pub fn new(object: Object) -> Self {
593 Self(object, std::marker::PhantomData)
594 }
595
596 pub fn as_object(self) -> Object {
598 self.0
599 }
600}
601
602impl<K> DynamicFieldObject<K>
603where
604 K: Serialize + DeserializeOwned,
605{
606 pub fn load_value<V>(self) -> Result<V, SuiError>
608 where
609 V: Serialize + DeserializeOwned,
610 {
611 self.load_field::<V>().map(|f| f.value)
612 }
613
614 pub fn load_field<V>(self) -> Result<Field<K, V>, SuiError>
616 where
617 V: Serialize + DeserializeOwned,
618 {
619 let object = self.0;
620 let move_object = object.data.try_as_move().ok_or_else(|| {
621 SuiErrorKind::DynamicFieldReadError(format!(
622 "Dynamic field {:?} is not a Move object",
623 object.id()
624 ))
625 })?;
626 bcs::from_bytes::<Field<K, V>>(move_object.contents())
627 .map_err(|err| SuiErrorKind::DynamicFieldReadError(err.to_string()).into())
628 }
629}