1use 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 let binary_config = BinaryConfig::with_extraneous_bytes_check(false);
102 normalize_modules(
103 pool,
104 p.serialized_module_map().values(),
105 &binary_config,
106 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 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, )
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(false, _) => {
276 MoveFunctionArgType::Object(ObjectValueKind::ByImmutableReference)
277 }
278 Type::Reference(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, );
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, ))
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}