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::{
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 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 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}