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(rename = "type")]
74 #[serde_as(as = "Readable<IotaTypeTag, _>")]
75 pub type_tag: TypeTag,
76 #[serde_as(as = "Readable<_, DisplayFromStr>")]
80 pub value: Value,
81}
82
83impl Display for DynamicFieldName {
84 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
85 write!(f, "{}: {}", self.type_tag, self.value)
86 }
87}
88
89#[derive(Copy, Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
90pub enum DynamicFieldType {
91 #[serde(rename_all = "camelCase")]
92 DynamicField,
93 DynamicObject,
94}
95
96impl Display for DynamicFieldType {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 match self {
99 DynamicFieldType::DynamicField => write!(f, "DynamicField"),
100 DynamicFieldType::DynamicObject => write!(f, "DynamicObject"),
101 }
102 }
103}
104
105impl DynamicFieldInfo {
106 pub fn is_dynamic_field(tag: &StructTag) -> bool {
107 tag.is_dynamic_field()
108 }
109
110 pub fn is_dynamic_object_field_wrapper(tag: &StructTag) -> bool {
111 tag.is_dynamic_object_field_wrapper()
112 }
113
114 pub fn dynamic_field_type(key: TypeTag, value: TypeTag) -> StructTag {
115 StructTag::new_dynamic_field(key, value)
116 }
117
118 pub fn dynamic_object_field_wrapper(key: TypeTag) -> StructTag {
119 StructTag::new_dynamic_object_field_wrapper(key)
120 }
121
122 pub fn try_extract_field_name(
123 tag: &StructTag,
124 type_: &DynamicFieldType,
125 ) -> IotaResult<TypeTag> {
126 match (type_, tag.type_params().first()) {
127 (DynamicFieldType::DynamicField, Some(name_type)) => Ok(name_type.clone()),
128 (DynamicFieldType::DynamicObject, Some(TypeTag::Struct(s))) => Ok(s
129 .type_params()
130 .first()
131 .ok_or_else(|| IotaError::ObjectDeserialization {
132 error: format!("Error extracting dynamic object name from object: {tag}"),
133 })?
134 .clone()),
135 _ => Err(IotaError::ObjectDeserialization {
136 error: format!("Error extracting dynamic object name from object: {tag}"),
137 }),
138 }
139 }
140
141 pub fn try_extract_field_value(tag: &StructTag) -> IotaResult<TypeTag> {
142 match tag.type_params().last() {
143 Some(value_type) => Ok(value_type.clone()),
144 None => Err(IotaError::ObjectDeserialization {
145 error: format!("Error extracting dynamic object value from object: {tag}"),
146 }),
147 }
148 }
149
150 pub fn parse_move_object(
151 move_struct: &MoveStruct,
152 ) -> IotaResult<(MoveValue, DynamicFieldType, ObjectId)> {
153 let name = extract_field_from_move_struct(move_struct, "name").ok_or_else(|| {
154 IotaError::ObjectDeserialization {
155 error: "Cannot extract [name] field from iota::dynamic_field::Field".to_string(),
156 }
157 })?;
158
159 let value = extract_field_from_move_struct(move_struct, "value").ok_or_else(|| {
160 IotaError::ObjectDeserialization {
161 error: "Cannot extract [value] field from iota::dynamic_field::Field".to_string(),
162 }
163 })?;
164
165 Ok(if is_dynamic_object(move_struct) {
166 let name = match name {
167 MoveValue::Struct(name_struct) => {
168 extract_field_from_move_struct(name_struct, "name")
169 }
170 _ => None,
171 }
172 .ok_or_else(|| IotaError::ObjectDeserialization {
173 error: "Cannot extract [name] field from iota::dynamic_object_field::Wrapper."
174 .to_string(),
175 })?;
176 let object_id =
178 extract_id_value(value).ok_or_else(|| IotaError::ObjectDeserialization {
179 error: format!(
180 "Cannot extract dynamic object's object id from \
181 iota::dynamic_field::Field, {value:?}"
182 ),
183 })?;
184 (name.clone(), DynamicFieldType::DynamicObject, object_id)
185 } else {
186 let object_id =
188 extract_object_id(move_struct).ok_or_else(|| IotaError::ObjectDeserialization {
189 error: format!(
190 "Cannot extract dynamic object's object id from \
191 iota::dynamic_field::Field, {move_struct:?}",
192 ),
193 })?;
194 (name.clone(), DynamicFieldType::DynamicField, object_id)
195 })
196 }
197}
198
199pub fn extract_field_from_move_struct<'a>(
200 move_struct: &'a MoveStruct,
201 field_name: &str,
202) -> Option<&'a MoveValue> {
203 move_struct.fields.iter().find_map(|(id, value)| {
204 if id.to_string() == field_name {
205 Some(value)
206 } else {
207 None
208 }
209 })
210}
211
212fn extract_object_id(value: &MoveStruct) -> Option<ObjectId> {
213 let uid_value = &value.fields.first()?.1;
215
216 let id_value = match uid_value {
218 MoveValue::Struct(MoveStruct { fields, .. }) => &fields.first()?.1,
219 _ => return None,
220 };
221 extract_id_value(id_value)
222}
223
224pub fn extract_id_value(id_value: &MoveValue) -> Option<ObjectId> {
225 let id_bytes_value = match id_value {
227 MoveValue::Struct(MoveStruct { fields, .. }) => &fields.first()?.1,
228 _ => return None,
229 };
230 match id_bytes_value {
232 MoveValue::Address(addr) => Some(ObjectId::new(addr.into_bytes())),
233 _ => None,
234 }
235}
236
237pub fn is_dynamic_object(move_struct: &MoveStruct) -> bool {
238 matches!(
239 &type_tag_core_to_sdk(&move_struct.type_.type_params[0]),
240 TypeTag::Struct(tag) if DynamicFieldInfo::is_dynamic_object_field_wrapper(tag)
241 )
242}
243
244pub fn derive_dynamic_field_id<T>(
245 parent: T,
246 key_type_tag: &TypeTag,
247 key_bytes: &[u8],
248) -> Result<ObjectId, bcs::Error>
249where
250 T: Into<Address>,
251{
252 let parent: Address = parent.into();
253 let k_tag_bytes = bcs::to_bytes(key_type_tag)?;
254 tracing::trace!(
255 "Deriving dynamic field ID for parent={:?}, key={:?}, key_type_tag={:?}",
256 parent,
257 key_bytes,
258 key_type_tag,
259 );
260
261 let mut hasher = DefaultHash::default();
263 hasher.update([HashingIntentScope::ChildObjectId as u8]);
264 hasher.update(parent);
265 hasher.update((key_bytes.len() as u64).to_le_bytes());
269 hasher.update(key_bytes);
270 hasher.update(k_tag_bytes);
271 let hash = hasher.finalize();
272
273 let id = ObjectId::from_bytes(&hash.as_ref()[0..ObjectId::LENGTH]).unwrap();
277 tracing::trace!("derive_dynamic_field_id result: {:?}", id);
278 Ok(id)
279}
280
281pub fn get_dynamic_field_object_from_store<K>(
290 object_store: &dyn ObjectStore,
291 parent_id: ObjectId,
292 key: &K,
293) -> Result<Object, IotaError>
294where
295 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
296{
297 let id = derive_dynamic_field_id(parent_id, &K::get_type_tag(), &bcs::to_bytes(key).unwrap())
298 .map_err(|err| IotaError::DynamicFieldRead(err.to_string()))?;
299 let object = object_store.try_get_object(&id)?.ok_or_else(|| {
300 let key = format!("{key:?}");
301 IotaError::DynamicFieldNotExists { parent_id, id, key }
302 })?;
303 Ok(object)
304}
305
306pub fn get_dynamic_field_from_store<K, V>(
309 object_store: &dyn ObjectStore,
310 parent_id: ObjectId,
311 key: &K,
312) -> Result<V, IotaError>
313where
314 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
315 V: Serialize + DeserializeOwned,
316{
317 let object = get_dynamic_field_object_from_store(object_store, parent_id, key)?;
318 let move_object = object.data.as_opt_struct().ok_or_else(|| {
319 IotaError::DynamicFieldRead(format!(
320 "Dynamic field {:?} is not a Move object",
321 object.id()
322 ))
323 })?;
324 Ok(bcs::from_bytes::<Field<K, V>>(move_object.contents())
325 .map_err(|err| IotaError::DynamicFieldRead(err.to_string()))?
326 .value)
327}
328
329#[cfg(test)]
330mod tests {
331 use std::collections::BTreeMap;
332
333 use super::*;
334
335 #[test]
336 fn missing_dynamic_field_returns_dynamic_field_not_exists() {
337 let parent_id = ObjectId::random();
338 let key = 42u64;
339 let mut store: BTreeMap<ObjectId, Object> = BTreeMap::new();
340
341 let err = get_dynamic_field_object_from_store(&store, parent_id, &key).unwrap_err();
342 match err {
343 IotaError::DynamicFieldNotExists {
344 parent_id: err_parent_id,
345 id,
346 key: err_key,
347 } => {
348 assert_eq!(err_parent_id, parent_id);
349 assert_eq!(err_key, format!("{key:?}"));
350
351 store.insert(id, Object::with_id_owner_for_testing(id, Address::random()));
355 let object = get_dynamic_field_object_from_store(&store, parent_id, &key).unwrap();
356 assert_eq!(object.id(), id);
357 }
358 other => panic!("expected DynamicFieldNotExists, got: {other:?}"),
359 }
360 }
361}