iota_json_rpc/
move_utils.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{collections::BTreeMap, sync::Arc};
6
7use async_trait::async_trait;
8use iota_core::authority::AuthorityState;
9use iota_json_rpc_api::{MoveUtilsOpenRpc, MoveUtilsServer};
10use iota_json_rpc_types::{
11    IotaMoveNormalizedFunction, IotaMoveNormalizedModule, IotaMoveNormalizedStruct,
12    MoveFunctionArgType, ObjectValueKind,
13};
14use iota_open_rpc::Module;
15use iota_types::{
16    base_types::ObjectID,
17    move_package::normalize_modules,
18    object::{Data, ObjectRead},
19};
20use jsonrpsee::{RpcModule, core::RpcResult};
21#[cfg(test)]
22use mockall::automock;
23use move_binary_format::{
24    binary_config::BinaryConfig,
25    normalized::{Module as NormalizedModule, Type},
26};
27use move_core_types::identifier::Identifier;
28use tap::TapFallible;
29use tracing::{error, instrument, warn};
30
31use crate::{
32    IotaRpcModule,
33    authority_state::StateRead,
34    error::{Error, IotaRpcInputError},
35    logger::FutureWithTracing as _,
36};
37
38#[cfg_attr(test, automock)]
39#[async_trait]
40pub trait MoveUtilsInternalTrait {
41    fn get_state(&self) -> &dyn StateRead;
42
43    async fn get_move_module(
44        &self,
45        package: ObjectID,
46        module_name: String,
47    ) -> Result<NormalizedModule, Error>;
48
49    async fn get_move_modules_by_package(
50        &self,
51        package: ObjectID,
52    ) -> Result<BTreeMap<String, NormalizedModule>, Error>;
53
54    fn get_object_read(&self, package: ObjectID) -> Result<ObjectRead, Error>;
55}
56
57pub struct MoveUtilsInternal {
58    state: Arc<dyn StateRead>,
59}
60
61impl MoveUtilsInternal {
62    pub fn new(state: Arc<AuthorityState>) -> Self {
63        Self { state }
64    }
65}
66
67#[async_trait]
68impl MoveUtilsInternalTrait for MoveUtilsInternal {
69    fn get_state(&self) -> &dyn StateRead {
70        Arc::as_ref(&self.state)
71    }
72
73    async fn get_move_module(
74        &self,
75        package: ObjectID,
76        module_name: String,
77    ) -> Result<NormalizedModule, Error> {
78        let normalized = self.get_move_modules_by_package(package).await?;
79        Ok(match normalized.get(&module_name) {
80            Some(module) => Ok(module.clone()),
81            None => Err(IotaRpcInputError::GenericNotFound(format!(
82                "No module found with module name {module_name}"
83            ))),
84        }?)
85    }
86
87    async fn get_move_modules_by_package(
88        &self,
89        package: ObjectID,
90    ) -> Result<BTreeMap<String, NormalizedModule>, Error> {
91        let object_read = self.get_state().get_object_read(&package).tap_err(|_| {
92            warn!("Failed to call get_move_modules_by_package for package: {package:?}");
93        })?;
94
95        match object_read {
96            ObjectRead::Exists(_obj_ref, object, _layout) => {
97                match object.into_inner().data {
98                    Data::Package(p) => {
99                        // we are on the read path - it's OK to use VERSION_MAX of the supported
100                        // Move binary format
101                        let binary_config = BinaryConfig::with_extraneous_bytes_check(false);
102                        normalize_modules(
103                            p.serialized_module_map().values(),
104                            &binary_config,
105                        )
106                        .map_err(|e| {
107                            error!("Failed to call get_move_modules_by_package for package: {package:?}");
108                            Error::from(e)
109                        })
110                    }
111                    _ => Err(IotaRpcInputError::GenericInvalid(format!(
112                        "Object is not a package with ID {package}"
113                    )))?,
114                }
115            }
116            _ => Err(IotaRpcInputError::GenericNotFound(format!(
117                "Package object does not exist with ID {package}"
118            )))?,
119        }
120    }
121
122    fn get_object_read(&self, package: ObjectID) -> Result<ObjectRead, Error> {
123        self.state.get_object_read(&package).map_err(Error::from)
124    }
125}
126
127pub struct MoveUtils {
128    internal: Arc<dyn MoveUtilsInternalTrait + Send + Sync>,
129}
130
131impl MoveUtils {
132    pub fn new(state: Arc<AuthorityState>) -> Self {
133        Self {
134            internal: Arc::new(MoveUtilsInternal::new(state))
135                as Arc<dyn MoveUtilsInternalTrait + Send + Sync>,
136        }
137    }
138}
139
140impl IotaRpcModule for MoveUtils {
141    fn rpc(self) -> RpcModule<Self> {
142        self.into_rpc()
143    }
144
145    fn rpc_doc_module() -> Module {
146        MoveUtilsOpenRpc::module_doc()
147    }
148}
149
150#[async_trait]
151impl MoveUtilsServer for MoveUtils {
152    #[instrument(skip(self))]
153    async fn get_normalized_move_modules_by_package(
154        &self,
155        package: ObjectID,
156    ) -> RpcResult<BTreeMap<String, IotaMoveNormalizedModule>> {
157        async move {
158            let modules = self.internal.get_move_modules_by_package(package).await?;
159            Ok(modules
160                .into_iter()
161                .map(|(name, module)| (name, module.into()))
162                .collect::<BTreeMap<String, IotaMoveNormalizedModule>>())
163        }
164        .trace()
165        .await
166    }
167
168    #[instrument(skip(self))]
169    async fn get_normalized_move_module(
170        &self,
171        package: ObjectID,
172        module_name: String,
173    ) -> RpcResult<IotaMoveNormalizedModule> {
174        async move {
175            let module = self.internal.get_move_module(package, module_name).await?;
176            Ok(module.into())
177        }
178        .trace()
179        .await
180    }
181
182    #[instrument(skip(self))]
183    async fn get_normalized_move_struct(
184        &self,
185        package: ObjectID,
186        module_name: String,
187        struct_name: String,
188    ) -> RpcResult<IotaMoveNormalizedStruct> {
189        async move {
190            let module = self.internal.get_move_module(package, module_name).await?;
191            let structs = module.structs;
192            let identifier = Identifier::new(struct_name.as_str())
193                .map_err(|e| IotaRpcInputError::GenericInvalid(format!("{e}")))?;
194            match structs.get(&identifier) {
195                Some(struct_) => Ok(struct_.clone().into()),
196                None => Err(IotaRpcInputError::GenericNotFound(format!(
197                    "No struct was found with struct name {struct_name}"
198                )))?,
199            }
200        }
201        .trace()
202        .await
203    }
204
205    #[instrument(skip(self))]
206    async fn get_normalized_move_function(
207        &self,
208        package: ObjectID,
209        module_name: String,
210        function_name: String,
211    ) -> RpcResult<IotaMoveNormalizedFunction> {
212        async move {
213            let module = self.internal.get_move_module(package, module_name).await?;
214            let functions = module.functions;
215            let identifier = Identifier::new(function_name.as_str())
216                .map_err(|e| IotaRpcInputError::GenericInvalid(format!("{e}")))?;
217            match functions.get(&identifier) {
218                Some(function) => Ok(function.clone().into()),
219                None => Err(IotaRpcInputError::GenericNotFound(format!(
220                    "No function was found with function name {function_name}",
221                )))?,
222            }
223        }
224        .trace()
225        .await
226    }
227
228    #[instrument(skip(self))]
229    async fn get_move_function_arg_types(
230        &self,
231        package: ObjectID,
232        module: String,
233        function: String,
234    ) -> RpcResult<Vec<MoveFunctionArgType>> {
235        async move {
236            let object_read = self.internal.get_object_read(package)?;
237
238            let normalized = match object_read {
239                ObjectRead::Exists(_obj_ref, object, _layout) => match object.into_inner().data {
240                    Data::Package(p) => {
241                        // we are on the read path - it's OK to use VERSION_MAX of the supported
242                        // Move binary format
243                        let binary_config = BinaryConfig::with_extraneous_bytes_check(false);
244                        normalize_modules(p.serialized_module_map().values(), &binary_config)
245                            .map_err(Error::from)
246                    }
247                    _ => Err(IotaRpcInputError::GenericInvalid(format!(
248                        "Object is not a package with ID {package}",
249                    )))?,
250                },
251                _ => Err(IotaRpcInputError::GenericNotFound(format!(
252                    "Package object does not exist with ID {package}",
253                )))?,
254            }?;
255
256            let identifier = Identifier::new(function.as_str())
257                .map_err(|e| IotaRpcInputError::GenericInvalid(format!("{e}")))?;
258            let parameters = normalized
259                .get(&module)
260                .and_then(|m| m.functions.get(&identifier).map(|f| f.parameters.clone()));
261
262            match parameters {
263                Some(parameters) => Ok(parameters
264                    .iter()
265                    .map(|p| match p {
266                        Type::Struct {
267                            address: _,
268                            module: _,
269                            name: _,
270                            type_arguments: _,
271                        } => MoveFunctionArgType::Object(ObjectValueKind::ByValue),
272                        Type::Reference(_) => {
273                            MoveFunctionArgType::Object(ObjectValueKind::ByImmutableReference)
274                        }
275                        Type::MutableReference(_) => {
276                            MoveFunctionArgType::Object(ObjectValueKind::ByMutableReference)
277                        }
278                        _ => MoveFunctionArgType::Pure,
279                    })
280                    .collect::<Vec<MoveFunctionArgType>>()),
281                None => Err(IotaRpcInputError::GenericNotFound(format!(
282                    "No parameters found for function {function}",
283                )))?,
284            }
285        }
286        .trace()
287        .await
288    }
289}
290
291#[cfg(test)]
292mod tests {
293
294    mod get_normalized_move_module_tests {
295        use move_binary_format::file_format::basic_test_module;
296
297        use super::super::*;
298
299        fn setup() -> (ObjectID, String) {
300            (ObjectID::random(), String::from("test_module"))
301        }
302
303        #[tokio::test]
304        async fn test_success_response() {
305            let (package, module_name) = setup();
306            let mut mock_internal = MockMoveUtilsInternalTrait::new();
307
308            let m = basic_test_module();
309            let normalized_module = NormalizedModule::new(&m);
310            let expected_module: IotaMoveNormalizedModule = normalized_module.clone().into();
311
312            mock_internal
313                .expect_get_move_module()
314                .return_once(move |_package, _module_name| Ok(normalized_module));
315
316            let move_utils = MoveUtils {
317                internal: Arc::new(mock_internal),
318            };
319
320            let response = move_utils
321                .get_normalized_move_module(package, module_name)
322                .await;
323
324            assert!(response.is_ok());
325            let result = response.unwrap();
326            assert_eq!(result, expected_module);
327        }
328
329        #[tokio::test]
330        async fn test_no_module_found() {
331            let (package, module_name) = setup();
332            let mut mock_internal = MockMoveUtilsInternalTrait::new();
333            let error_string = format!("No module found with module name {module_name}");
334            let expected_error =
335                Error::IotaRpcInput(IotaRpcInputError::GenericNotFound(error_string.clone()));
336            mock_internal
337                .expect_get_move_module()
338                .return_once(move |_package, _module_name| Err(expected_error));
339            let move_utils = MoveUtils {
340                internal: Arc::new(mock_internal),
341            };
342
343            let response = move_utils
344                .get_normalized_move_module(package, module_name)
345                .await;
346            let error_result = response.unwrap_err();
347
348            assert_eq!(error_result.code(), -32602);
349            assert_eq!(error_result.message(), &error_string);
350        }
351    }
352}