iota_types/
derived_object.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use move_core_types::{
6    ident_str,
7    identifier::IdentStr,
8    language_storage::{StructTag, TypeTag},
9};
10
11use crate::{
12    IOTA_FRAMEWORK_ADDRESS,
13    base_types::{IotaAddress, ObjectID},
14    dynamic_field,
15};
16
17pub const DERIVED_OBJECT_MODULE_NAME: &IdentStr = ident_str!("derived_object");
18pub const DERIVED_OBJECT_STRUCT_NAME: &IdentStr = ident_str!("DerivedObjectKey");
19
20/// Using a parent object, a type tag and the bcs bytes of the key,
21/// compute the derived object address.
22///
23/// Note: Namespace is hidden so struct `T` should be passed in as is.
24pub fn derive_object_id<T>(
25    parent: T,
26    key_type_tag: &TypeTag,
27    key_bytes: &[u8],
28) -> Result<ObjectID, bcs::Error>
29where
30    T: Into<IotaAddress>,
31{
32    let parent_address = parent.into();
33
34    // Wrap `T` into `DerivedObjectKey<T>` type (to preserve on-chain namespacing)
35    let wrapper_type_tag = TypeTag::Struct(Box::new(StructTag {
36        address: IOTA_FRAMEWORK_ADDRESS,
37        module: DERIVED_OBJECT_MODULE_NAME.into(),
38        name: DERIVED_OBJECT_STRUCT_NAME.into(),
39        type_params: vec![key_type_tag.clone()],
40    }));
41
42    dynamic_field::derive_dynamic_field_id(parent_address, &wrapper_type_tag, key_bytes)
43}
44
45#[cfg(test)]
46mod tests {
47    use std::str::FromStr;
48
49    use move_core_types::identifier::Identifier;
50    use serde::Serialize;
51
52    use super::*;
53
54    #[derive(Serialize)]
55    struct DemoStruct {
56        value: u64,
57    }
58
59    #[derive(Serialize)]
60    struct GenericStruct<T> {
61        value: T,
62    }
63
64    // Snapshot tests that match the on-chain `derive_address` logic.
65    // These snapshots can also be found in `derived_object_tests.move` unit tests.
66    #[test]
67    fn test_derive_object_snapshot() {
68        // Our key is `UID, Vec<u8>, b"foo"`
69        let key_bytes = bcs::to_bytes("foo".as_bytes()).unwrap();
70        let key_type_tag = TypeTag::Vector(Box::new(TypeTag::U8));
71
72        let id = derive_object_id(
73            ObjectID::from_str("0x2").unwrap(),
74            &key_type_tag,
75            &key_bytes,
76        )
77        .unwrap();
78
79        assert_eq!(
80            id,
81            ObjectID::from_str(
82                "0xa2b411aa9588c398d8e3bc97dddbdd430b5ded7f81545d05e33916c3ca0f30c3"
83            )
84            .unwrap()
85        );
86    }
87
88    #[test]
89    fn test_derive_object_with_struct_key_snapshot() {
90        let key = DemoStruct { value: 1 };
91        let key_value = bcs::to_bytes(&key).unwrap();
92
93        let id = derive_object_id(
94            ObjectID::from_str("0x2").unwrap(),
95            &TypeTag::Struct(Box::new(StructTag {
96                address: IOTA_FRAMEWORK_ADDRESS,
97                module: Identifier::new("derived_object_tests").unwrap(),
98                name: Identifier::new("DemoStruct").unwrap(),
99                type_params: vec![],
100            })),
101            &key_value,
102        )
103        .unwrap();
104
105        assert_eq!(
106            id,
107            ObjectID::from_str(
108                "0x20c58d8790a5d2214c159c23f18a5fdc347211e511186353e785ad543abcea6b"
109            )
110            .unwrap()
111        );
112    }
113
114    #[test]
115    fn test_derive_object_with_generic_struct_key_snapshot() {
116        let key = GenericStruct::<u64> { value: 1 };
117        let key_value = bcs::to_bytes(&key).unwrap();
118
119        let id = derive_object_id(
120            ObjectID::from_str("0x2").unwrap(),
121            &TypeTag::Struct(Box::new(StructTag {
122                address: IOTA_FRAMEWORK_ADDRESS,
123                module: Identifier::new("derived_object_tests").unwrap(),
124                name: Identifier::new("GenericStruct").unwrap(),
125                type_params: vec![TypeTag::U64],
126            })),
127            &key_value,
128        )
129        .unwrap();
130
131        assert_eq!(
132            id,
133            ObjectID::from_str(
134                "0xb497b8dcf1e297ae5fa69c040e4a08ef8240d5373bbc9d6b686ffbd7dfe04cbe"
135            )
136            .unwrap()
137        );
138    }
139}