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