1use std::{
6 fmt,
7 fmt::{Display, Formatter},
8};
9
10use fastcrypto::{encoding::Base64, hash::HashFunction};
11use iota_sdk_move_types::iota_framework::dynamic_field::Field;
12use iota_sdk_types::{Address, ObjectDigest, StructTag, TypeTag, crypto::HashingIntentScope};
13use move_core_types::annotated_value::{MoveStruct, MoveValue};
14use serde::{Deserialize, Serialize, de::DeserializeOwned};
15use serde_json::Value;
16use serde_with::{DisplayFromStr, serde_as};
17
18use crate::{
19 MoveTypeTagTrait, ObjectId, Version,
20 crypto::DefaultHash,
21 error::{IotaError, IotaResult},
22 iota_sdk_types_conversions::type_tag_core_to_sdk,
23 iota_serde::{IotaTypeTag, Readable},
24 object::Object,
25 storage::ObjectStore,
26};
27
28pub mod visitor;
29
30#[serde_as]
31#[derive(Clone, Serialize, Deserialize, Debug)]
32#[serde(rename_all = "camelCase")]
33pub struct DynamicFieldInfo {
34 pub name: DynamicFieldName,
35 #[serde_as(as = "Readable<Base64, _>")]
36 pub bcs_name: Vec<u8>,
37 pub type_: DynamicFieldType,
38 pub object_type: String,
39 pub object_id: ObjectId,
40 pub version: Version,
41 pub digest: ObjectDigest,
42}
43
44#[serde_as]
45#[derive(Clone, Serialize, Deserialize, Debug)]
46#[serde(rename_all = "camelCase")]
47pub struct DynamicFieldName {
48 #[serde(rename = "type")]
49 #[serde_as(as = "Readable<IotaTypeTag, _>")]
50 pub type_tag: TypeTag,
51 #[serde_as(as = "Readable<_, DisplayFromStr>")]
55 pub value: Value,
56}
57
58impl Display for DynamicFieldName {
59 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
60 write!(f, "{}: {}", self.type_tag, self.value)
61 }
62}
63
64#[derive(Copy, Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
65pub enum DynamicFieldType {
66 #[serde(rename_all = "camelCase")]
67 DynamicField,
68 DynamicObject,
69}
70
71impl Display for DynamicFieldType {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 match self {
74 DynamicFieldType::DynamicField => write!(f, "DynamicField"),
75 DynamicFieldType::DynamicObject => write!(f, "DynamicObject"),
76 }
77 }
78}
79
80impl DynamicFieldInfo {
81 pub fn is_dynamic_field(tag: &StructTag) -> bool {
82 tag.is_dynamic_field()
83 }
84
85 pub fn is_dynamic_object_field_wrapper(tag: &StructTag) -> bool {
86 tag.is_dynamic_object_field_wrapper()
87 }
88
89 pub fn dynamic_field_type(key: TypeTag, value: TypeTag) -> StructTag {
90 StructTag::new_dynamic_field(key, value)
91 }
92
93 pub fn dynamic_object_field_wrapper(key: TypeTag) -> StructTag {
94 StructTag::new_dynamic_object_field_wrapper(key)
95 }
96
97 pub fn try_extract_field_name(
98 tag: &StructTag,
99 type_: &DynamicFieldType,
100 ) -> IotaResult<TypeTag> {
101 match (type_, tag.type_params().first()) {
102 (DynamicFieldType::DynamicField, Some(name_type)) => Ok(name_type.clone()),
103 (DynamicFieldType::DynamicObject, Some(TypeTag::Struct(s))) => Ok(s
104 .type_params()
105 .first()
106 .ok_or_else(|| IotaError::ObjectDeserialization {
107 error: format!("Error extracting dynamic object name from object: {tag}"),
108 })?
109 .clone()),
110 _ => Err(IotaError::ObjectDeserialization {
111 error: format!("Error extracting dynamic object name from object: {tag}"),
112 }),
113 }
114 }
115
116 pub fn try_extract_field_value(tag: &StructTag) -> IotaResult<TypeTag> {
117 match tag.type_params().last() {
118 Some(value_type) => Ok(value_type.clone()),
119 None => Err(IotaError::ObjectDeserialization {
120 error: format!("Error extracting dynamic object value from object: {tag}"),
121 }),
122 }
123 }
124
125 pub fn parse_move_object(
126 move_struct: &MoveStruct,
127 ) -> IotaResult<(MoveValue, DynamicFieldType, ObjectId)> {
128 let name = extract_field_from_move_struct(move_struct, "name").ok_or_else(|| {
129 IotaError::ObjectDeserialization {
130 error: "Cannot extract [name] field from iota::dynamic_field::Field".to_string(),
131 }
132 })?;
133
134 let value = extract_field_from_move_struct(move_struct, "value").ok_or_else(|| {
135 IotaError::ObjectDeserialization {
136 error: "Cannot extract [value] field from iota::dynamic_field::Field".to_string(),
137 }
138 })?;
139
140 Ok(if is_dynamic_object(move_struct) {
141 let name = match name {
142 MoveValue::Struct(name_struct) => {
143 extract_field_from_move_struct(name_struct, "name")
144 }
145 _ => None,
146 }
147 .ok_or_else(|| IotaError::ObjectDeserialization {
148 error: "Cannot extract [name] field from iota::dynamic_object_field::Wrapper."
149 .to_string(),
150 })?;
151 let object_id =
153 extract_id_value(value).ok_or_else(|| IotaError::ObjectDeserialization {
154 error: format!(
155 "Cannot extract dynamic object's object id from \
156 iota::dynamic_field::Field, {value:?}"
157 ),
158 })?;
159 (name.clone(), DynamicFieldType::DynamicObject, object_id)
160 } else {
161 let object_id =
163 extract_object_id(move_struct).ok_or_else(|| IotaError::ObjectDeserialization {
164 error: format!(
165 "Cannot extract dynamic object's object id from \
166 iota::dynamic_field::Field, {move_struct:?}",
167 ),
168 })?;
169 (name.clone(), DynamicFieldType::DynamicField, object_id)
170 })
171 }
172}
173
174pub fn extract_field_from_move_struct<'a>(
175 move_struct: &'a MoveStruct,
176 field_name: &str,
177) -> Option<&'a MoveValue> {
178 move_struct.fields.iter().find_map(|(id, value)| {
179 if id.to_string() == field_name {
180 Some(value)
181 } else {
182 None
183 }
184 })
185}
186
187fn extract_object_id(value: &MoveStruct) -> Option<ObjectId> {
188 let uid_value = &value.fields.first()?.1;
190
191 let id_value = match uid_value {
193 MoveValue::Struct(MoveStruct { fields, .. }) => &fields.first()?.1,
194 _ => return None,
195 };
196 extract_id_value(id_value)
197}
198
199pub fn extract_id_value(id_value: &MoveValue) -> Option<ObjectId> {
200 let id_bytes_value = match id_value {
202 MoveValue::Struct(MoveStruct { fields, .. }) => &fields.first()?.1,
203 _ => return None,
204 };
205 match id_bytes_value {
207 MoveValue::Address(addr) => Some(ObjectId::new(addr.into_bytes())),
208 _ => None,
209 }
210}
211
212pub fn is_dynamic_object(move_struct: &MoveStruct) -> bool {
213 matches!(
214 &type_tag_core_to_sdk(&move_struct.type_.type_params[0]),
215 TypeTag::Struct(tag) if DynamicFieldInfo::is_dynamic_object_field_wrapper(tag)
216 )
217}
218
219pub fn derive_dynamic_field_id<T>(
220 parent: T,
221 key_type_tag: &TypeTag,
222 key_bytes: &[u8],
223) -> Result<ObjectId, bcs::Error>
224where
225 T: Into<Address>,
226{
227 let parent: Address = parent.into();
228 let k_tag_bytes = bcs::to_bytes(key_type_tag)?;
229 tracing::trace!(
230 "Deriving dynamic field ID for parent={:?}, key={:?}, key_type_tag={:?}",
231 parent,
232 key_bytes,
233 key_type_tag,
234 );
235
236 let mut hasher = DefaultHash::default();
238 hasher.update([HashingIntentScope::ChildObjectId as u8]);
239 hasher.update(parent);
240 hasher.update((key_bytes.len() as u64).to_le_bytes());
244 hasher.update(key_bytes);
245 hasher.update(k_tag_bytes);
246 let hash = hasher.finalize();
247
248 let id = ObjectId::from_bytes(&hash.as_ref()[0..ObjectId::LENGTH]).unwrap();
252 tracing::trace!("derive_dynamic_field_id result: {:?}", id);
253 Ok(id)
254}
255
256pub fn get_dynamic_field_object_from_store<K>(
265 object_store: &dyn ObjectStore,
266 parent_id: ObjectId,
267 key: &K,
268) -> Result<Object, IotaError>
269where
270 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
271{
272 let id = derive_dynamic_field_id(parent_id, &K::get_type_tag(), &bcs::to_bytes(key).unwrap())
273 .map_err(|err| IotaError::DynamicFieldRead(err.to_string()))?;
274 let object = object_store.try_get_object(&id)?.ok_or_else(|| {
275 let key = format!("{key:?}");
276 IotaError::DynamicFieldNotExists { parent_id, id, key }
277 })?;
278 Ok(object)
279}
280
281pub fn get_dynamic_field_from_store<K, V>(
284 object_store: &dyn ObjectStore,
285 parent_id: ObjectId,
286 key: &K,
287) -> Result<V, IotaError>
288where
289 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
290 V: Serialize + DeserializeOwned,
291{
292 let object = get_dynamic_field_object_from_store(object_store, parent_id, key)?;
293 let move_object = object.data.as_opt_struct().ok_or_else(|| {
294 IotaError::DynamicFieldRead(format!(
295 "Dynamic field {:?} is not a Move object",
296 object.id()
297 ))
298 })?;
299 Ok(bcs::from_bytes::<Field<K, V>>(move_object.contents())
300 .map_err(|err| IotaError::DynamicFieldRead(err.to_string()))?
301 .value)
302}
303
304#[cfg(test)]
305mod tests {
306 use std::collections::BTreeMap;
307
308 use super::*;
309
310 #[test]
311 fn missing_dynamic_field_returns_dynamic_field_not_exists() {
312 let parent_id = ObjectId::random();
313 let key = 42u64;
314 let mut store: BTreeMap<ObjectId, Object> = BTreeMap::new();
315
316 let err = get_dynamic_field_object_from_store(&store, parent_id, &key).unwrap_err();
317 match err {
318 IotaError::DynamicFieldNotExists {
319 parent_id: err_parent_id,
320 id,
321 key: err_key,
322 } => {
323 assert_eq!(err_parent_id, parent_id);
324 assert_eq!(err_key, format!("{key:?}"));
325
326 store.insert(id, Object::with_id_owner_for_testing(id, Address::random()));
330 let object = get_dynamic_field_object_from_store(&store, parent_id, &key).unwrap();
331 assert_eq!(object.id(), id);
332 }
333 other => panic!("expected DynamicFieldNotExists, got: {other:?}"),
334 }
335 }
336}