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