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