Skip to main content

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