1use std::{
6 fmt,
7 fmt::{Display, Formatter},
8};
9
10use fastcrypto::{encoding::Base64, hash::HashFunction};
11use move_core_types::{
12 annotated_value::{MoveStruct, MoveValue},
13 ident_str,
14 identifier::IdentStr,
15 language_storage::{StructTag, TypeTag},
16};
17use schemars::JsonSchema;
18use serde::{Deserialize, Serialize, de::DeserializeOwned};
19use serde_json::Value;
20use serde_with::{DisplayFromStr, serde_as};
21use shared_crypto::intent::HashingIntentScope;
22
23use crate::{
24 IOTA_FRAMEWORK_ADDRESS, MoveTypeTagTrait, ObjectID, SequenceNumber,
25 base_types::{IotaAddress, ObjectDigest},
26 crypto::DefaultHash,
27 error::{IotaError, IotaResult},
28 id::UID,
29 iota_serde::{IotaTypeTag, Readable},
30 object::Object,
31 storage::ObjectStore,
32};
33
34pub mod visitor;
35
36const DYNAMIC_FIELD_MODULE_NAME: &IdentStr = ident_str!("dynamic_field");
37const DYNAMIC_FIELD_FIELD_STRUCT_NAME: &IdentStr = ident_str!("Field");
38
39const DYNAMIC_OBJECT_FIELD_MODULE_NAME: &IdentStr = ident_str!("dynamic_object_field");
40const DYNAMIC_OBJECT_FIELD_WRAPPER_STRUCT_NAME: &IdentStr = ident_str!("Wrapper");
41
42#[derive(Clone, Serialize, Deserialize, Debug)]
44pub struct Field<N, V> {
45 pub id: UID,
46 pub name: N,
47 pub value: V,
48}
49
50#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
52pub struct DOFWrapper<N> {
53 pub name: N,
54}
55
56impl<N> MoveTypeTagTrait for DOFWrapper<N>
57where
58 N: MoveTypeTagTrait,
59{
60 fn get_type_tag() -> TypeTag {
61 TypeTag::Struct(Box::new(DynamicFieldInfo::dynamic_object_field_wrapper(
62 N::get_type_tag(),
63 )))
64 }
65}
66
67#[serde_as]
68#[derive(Clone, Serialize, Deserialize, Debug)]
69#[serde(rename_all = "camelCase")]
70pub struct DynamicFieldInfo {
71 pub name: DynamicFieldName,
72 #[serde_as(as = "Readable<Base64, _>")]
73 pub bcs_name: Vec<u8>,
74 pub type_: DynamicFieldType,
75 pub object_type: String,
76 pub object_id: ObjectID,
77 pub version: SequenceNumber,
78 pub digest: ObjectDigest,
79}
80
81#[serde_as]
82#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
83#[serde(rename_all = "camelCase")]
84pub struct DynamicFieldName {
85 #[schemars(with = "String")]
86 #[serde_as(as = "Readable<IotaTypeTag, _>")]
87 pub type_: TypeTag,
88 #[schemars(with = "Value")]
92 #[serde_as(as = "Readable<_, DisplayFromStr>")]
93 pub value: Value,
94}
95
96impl Display for DynamicFieldName {
97 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
98 write!(f, "{}: {}", self.type_, self.value)
99 }
100}
101
102#[derive(
103 Copy, Clone, Serialize, Deserialize, JsonSchema, Ord, PartialOrd, Eq, PartialEq, Debug,
104)]
105pub enum DynamicFieldType {
106 #[serde(rename_all = "camelCase")]
107 DynamicField,
108 DynamicObject,
109}
110
111impl Display for DynamicFieldType {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 match self {
114 DynamicFieldType::DynamicField => write!(f, "DynamicField"),
115 DynamicFieldType::DynamicObject => write!(f, "DynamicObject"),
116 }
117 }
118}
119
120impl DynamicFieldInfo {
121 pub fn is_dynamic_field(tag: &StructTag) -> bool {
122 tag.address == IOTA_FRAMEWORK_ADDRESS
123 && tag.module.as_ident_str() == DYNAMIC_FIELD_MODULE_NAME
124 && tag.name.as_ident_str() == DYNAMIC_FIELD_FIELD_STRUCT_NAME
125 }
126
127 pub fn is_dynamic_object_field_wrapper(tag: &StructTag) -> bool {
128 tag.address == IOTA_FRAMEWORK_ADDRESS
129 && tag.module.as_ident_str() == DYNAMIC_OBJECT_FIELD_MODULE_NAME
130 && tag.name.as_ident_str() == DYNAMIC_OBJECT_FIELD_WRAPPER_STRUCT_NAME
131 }
132
133 pub fn dynamic_field_type(key: TypeTag, value: TypeTag) -> StructTag {
134 StructTag {
135 address: IOTA_FRAMEWORK_ADDRESS,
136 name: DYNAMIC_FIELD_FIELD_STRUCT_NAME.to_owned(),
137 module: DYNAMIC_FIELD_MODULE_NAME.to_owned(),
138 type_params: vec![key, value],
139 }
140 }
141
142 pub fn dynamic_object_field_wrapper(key: TypeTag) -> StructTag {
143 StructTag {
144 address: IOTA_FRAMEWORK_ADDRESS,
145 module: DYNAMIC_OBJECT_FIELD_MODULE_NAME.to_owned(),
146 name: DYNAMIC_OBJECT_FIELD_WRAPPER_STRUCT_NAME.to_owned(),
147 type_params: vec![key],
148 }
149 }
150
151 pub fn try_extract_field_name(
152 tag: &StructTag,
153 type_: &DynamicFieldType,
154 ) -> IotaResult<TypeTag> {
155 match (type_, tag.type_params.first()) {
156 (DynamicFieldType::DynamicField, Some(name_type)) => Ok(name_type.clone()),
157 (DynamicFieldType::DynamicObject, Some(TypeTag::Struct(s))) => Ok(s
158 .type_params
159 .first()
160 .ok_or_else(|| IotaError::ObjectDeserialization {
161 error: format!("Error extracting dynamic object name from object: {tag}"),
162 })?
163 .clone()),
164 _ => Err(IotaError::ObjectDeserialization {
165 error: format!("Error extracting dynamic object name from object: {tag}"),
166 }),
167 }
168 }
169
170 pub fn try_extract_field_value(tag: &StructTag) -> IotaResult<TypeTag> {
171 match tag.type_params.last() {
172 Some(value_type) => Ok(value_type.clone()),
173 None => Err(IotaError::ObjectDeserialization {
174 error: format!("Error extracting dynamic object value from object: {tag}"),
175 }),
176 }
177 }
178
179 pub fn parse_move_object(
180 move_struct: &MoveStruct,
181 ) -> IotaResult<(MoveValue, DynamicFieldType, ObjectID)> {
182 let name = extract_field_from_move_struct(move_struct, "name").ok_or_else(|| {
183 IotaError::ObjectDeserialization {
184 error: "Cannot extract [name] field from iota::dynamic_field::Field".to_string(),
185 }
186 })?;
187
188 let value = extract_field_from_move_struct(move_struct, "value").ok_or_else(|| {
189 IotaError::ObjectDeserialization {
190 error: "Cannot extract [value] field from iota::dynamic_field::Field".to_string(),
191 }
192 })?;
193
194 Ok(if is_dynamic_object(move_struct) {
195 let name = match name {
196 MoveValue::Struct(name_struct) => {
197 extract_field_from_move_struct(name_struct, "name")
198 }
199 _ => None,
200 }
201 .ok_or_else(|| IotaError::ObjectDeserialization {
202 error: "Cannot extract [name] field from iota::dynamic_object_field::Wrapper."
203 .to_string(),
204 })?;
205 let object_id =
207 extract_id_value(value).ok_or_else(|| IotaError::ObjectDeserialization {
208 error: format!(
209 "Cannot extract dynamic object's object id from \
210 iota::dynamic_field::Field, {value:?}"
211 ),
212 })?;
213 (name.clone(), DynamicFieldType::DynamicObject, object_id)
214 } else {
215 let object_id =
217 extract_object_id(move_struct).ok_or_else(|| IotaError::ObjectDeserialization {
218 error: format!(
219 "Cannot extract dynamic object's object id from \
220 iota::dynamic_field::Field, {move_struct:?}",
221 ),
222 })?;
223 (name.clone(), DynamicFieldType::DynamicField, object_id)
224 })
225 }
226}
227
228pub fn extract_field_from_move_struct<'a>(
229 move_struct: &'a MoveStruct,
230 field_name: &str,
231) -> Option<&'a MoveValue> {
232 move_struct.fields.iter().find_map(|(id, value)| {
233 if id.to_string() == field_name {
234 Some(value)
235 } else {
236 None
237 }
238 })
239}
240
241fn extract_object_id(value: &MoveStruct) -> Option<ObjectID> {
242 let uid_value = &value.fields.first()?.1;
244
245 let id_value = match uid_value {
247 MoveValue::Struct(MoveStruct { fields, .. }) => &fields.first()?.1,
248 _ => return None,
249 };
250 extract_id_value(id_value)
251}
252
253pub fn extract_id_value(id_value: &MoveValue) -> Option<ObjectID> {
254 let id_bytes_value = match id_value {
256 MoveValue::Struct(MoveStruct { fields, .. }) => &fields.first()?.1,
257 _ => return None,
258 };
259 match id_bytes_value {
261 MoveValue::Address(addr) => Some(ObjectID::from(*addr)),
262 _ => None,
263 }
264}
265
266pub fn is_dynamic_object(move_struct: &MoveStruct) -> bool {
267 matches!(
268 &move_struct.type_.type_params[0],
269 TypeTag::Struct(tag) if DynamicFieldInfo::is_dynamic_object_field_wrapper(tag)
270 )
271}
272
273pub fn derive_dynamic_field_id<T>(
274 parent: T,
275 key_type_tag: &TypeTag,
276 key_bytes: &[u8],
277) -> Result<ObjectID, bcs::Error>
278where
279 T: Into<IotaAddress>,
280{
281 let parent: IotaAddress = parent.into();
282 let k_tag_bytes = bcs::to_bytes(key_type_tag)?;
283 tracing::trace!(
284 "Deriving dynamic field ID for parent={:?}, key={:?}, key_type_tag={:?}",
285 parent,
286 key_bytes,
287 key_type_tag,
288 );
289
290 let mut hasher = DefaultHash::default();
292 hasher.update([HashingIntentScope::ChildObjectId as u8]);
293 hasher.update(parent);
294 hasher.update(key_bytes.len().to_le_bytes());
295 hasher.update(key_bytes);
296 hasher.update(k_tag_bytes);
297 let hash = hasher.finalize();
298
299 let id = ObjectID::try_from(&hash.as_ref()[0..ObjectID::LENGTH]).unwrap();
303 tracing::trace!("derive_dynamic_field_id result: {:?}", id);
304 Ok(id)
305}
306
307pub fn get_dynamic_field_object_from_store<K>(
313 object_store: &dyn ObjectStore,
314 parent_id: ObjectID,
315 key: &K,
316) -> Result<Object, IotaError>
317where
318 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
319{
320 let id = derive_dynamic_field_id(parent_id, &K::get_type_tag(), &bcs::to_bytes(key).unwrap())
321 .map_err(|err| IotaError::DynamicFieldRead(err.to_string()))?;
322 let object = object_store.get_object(&id)?.ok_or_else(|| {
323 IotaError::DynamicFieldRead(format!(
324 "Dynamic field with key={:?} and ID={:?} not found on parent {:?}",
325 key, id, parent_id
326 ))
327 })?;
328 Ok(object)
329}
330
331pub fn get_dynamic_field_from_store<K, V>(
334 object_store: &dyn ObjectStore,
335 parent_id: ObjectID,
336 key: &K,
337) -> Result<V, IotaError>
338where
339 K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
340 V: Serialize + DeserializeOwned,
341{
342 let object = get_dynamic_field_object_from_store(object_store, parent_id, key)?;
343 let move_object = object.data.try_as_move().ok_or_else(|| {
344 IotaError::DynamicFieldRead(format!(
345 "Dynamic field {:?} is not a Move object",
346 object.id()
347 ))
348 })?;
349 Ok(bcs::from_bytes::<Field<K, V>>(move_object.contents())
350 .map_err(|err| IotaError::DynamicFieldRead(err.to_string()))?
351 .value)
352}