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