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::{binary_config::BinaryConfig, normalized};
24use move_core_types::identifier::Identifier;
25use tap::TapFallible;
26use tracing::{error, instrument, warn};
27
28use crate::{
29    IotaRpcModule,
30    authority_state::StateRead,
31    error::{Error, IotaRpcInputError},
32    logger::FutureWithTracing as _,
33};
34
35type NormalizedModule = normalized::Module<normalized::RcIdentifier>;
36type Type = normalized::Type<normalized::RcIdentifier>;
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 mut normalized = self.get_move_modules_by_package(package).await?;
79        Ok(match normalized.remove(&module_name) {
80            Some(module) => Ok(module),
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        let pool = &mut normalized::RcPool::new();
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                            pool,
104                            p.serialized_module_map().values(),
105                            &binary_config,
106                            /* include code */ false,
107                        )
108                        .map_err(|e| {
109                            error!("failed to call get_move_modules_by_package for package: {package:?}");
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))]
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))]
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))]
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))]
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))]
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                    Data::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}