sui_rpc_api/grpc/v2/
render.rs1use futures::FutureExt;
5use prost_types::Struct;
6use sui_rpc::{
7 field::FieldMaskTree,
8 merge::Merge,
9 proto::sui::rpc::v2::{Bcs, Display, Event, Object, TransactionEffects, TransactionEvents},
10};
11use sui_types::full_checkpoint_content::ObjectSet;
12
13use crate::{RpcService, reader::DisplayStore};
14
15impl RpcService {
16 pub fn render_object_to_proto(
17 &self,
18 object: &sui_types::object::Object,
19 read_mask: &FieldMaskTree,
20 output_objects: &ObjectSet,
21 ) -> Object {
22 let mut message = Object::default();
23
24 if read_mask.contains(Object::JSON_FIELD) {
25 let move_object = object.data.try_as_move();
26 message.json = move_object.and_then(|m| {
27 self.render_json(&m.type_().clone().into(), m.contents(), output_objects)
28 .map(Box::new)
29 });
30 }
31
32 if read_mask.contains(Object::DISPLAY_FIELD) {
33 message.display = self.render_object_display(object).map(Box::new);
34 }
35
36 message.merge(object, read_mask);
37
38 message
39 }
40
41 pub fn render_json(
44 &self,
45 struct_tag: &move_core_types::language_storage::StructTag,
46 contents: &[u8],
47 output_objects: &ObjectSet,
48 ) -> Option<prost_types::Value> {
49 let mut budget = self.config.max_json_move_value_size();
50 self.render_json_with_budget(struct_tag, contents, output_objects, &mut budget)
51 }
52
53 pub fn render_json_with_budget(
61 &self,
62 struct_tag: &move_core_types::language_storage::StructTag,
63 contents: &[u8],
64 output_objects: &ObjectSet,
65 size_budget: &mut usize,
66 ) -> Option<prost_types::Value> {
67 let layout = self
68 .reader
69 .inner()
70 .get_struct_layout_with_overlay(struct_tag, output_objects)
71 .ok()
72 .flatten()?;
73
74 sui_types::object::rpc_visitor::proto::ProtoVisitor::deserialize_value_with_budget(
75 contents,
76 &layout,
77 size_budget,
78 )
79 .map_err(|e| tracing::debug!("unable to convert move value to JSON: {e}"))
80 .ok()
81 }
82
83 pub fn render_object_display(&self, object: &sui_types::object::Object) -> Option<Display> {
84 let move_object = object.data.try_as_move()?;
85 let object_type = &move_object.type_().clone().into();
86 let contents = move_object.contents();
87
88 let limits = sui_display::v2::Limits {
89 max_depth: self.config.display().max_field_depth(),
90 max_nodes: self.config.display().max_format_nodes(),
91 max_loads: self.config.display().max_object_loads(),
92 };
93 let display_object = self.reader.get_display_object_v2_by_type(object_type)?;
94 let display_template =
95 sui_display::v2::Display::parse(limits, display_object.fields()).ok()?;
96
97 let layout = self
98 .reader
99 .inner()
100 .get_struct_layout(object_type)
101 .ok()
102 .flatten()?;
103
104 let root = sui_display::v2::OwnedSlice::new(layout, contents.to_owned());
105 let interpreter = sui_display::v2::Interpreter::new(root, DisplayStore::new(&self.reader));
106
107 let mut display = Display::default();
108
109 match display_template
113 .display::<prost_types::Value>(
114 self.config.display().max_move_value_depth(),
115 self.config.display().max_output_size(),
116 &interpreter,
117 )
118 .now_or_never()
119 .unwrap()
120 {
121 Ok(rendered) => {
122 let mut output = Struct::default();
123 let mut errors = Struct::default();
124
125 for (field, result) in rendered {
126 match result {
127 Ok(value) => {
128 output.fields.insert(field, value);
129 }
130 Err(e) => {
131 errors.fields.insert(field, e.to_string().into());
132 }
133 }
134 }
135
136 if !output.fields.is_empty() {
137 display.set_output(output.fields);
138 }
139
140 if !errors.fields.is_empty() {
141 display.set_errors(errors.fields);
142 }
143 }
144 Err(e) => {
145 display.set_errors(e.to_string());
146 }
147 }
148
149 Some(display)
150 }
151
152 pub fn render_events_to_proto(
153 &self,
154 events: &sui_types::effects::TransactionEvents,
155 mask: &FieldMaskTree,
156 output_objects: &ObjectSet,
157 ) -> TransactionEvents {
158 let mut message = TransactionEvents::default();
159
160 if mask.contains(TransactionEvents::BCS_FIELD) {
161 let mut bcs = Bcs::serialize(&events).unwrap();
162 bcs.name = Some("TransactionEvents".to_owned());
163 message.bcs = Some(bcs);
164 }
165
166 if mask.contains(TransactionEvents::DIGEST_FIELD) {
167 message.digest = Some(events.digest().to_string());
168 }
169
170 if let Some(event_mask) = mask.subtree(TransactionEvents::EVENTS_FIELD) {
171 message.events = events
172 .data
173 .iter()
174 .map(|event| self.render_event_to_proto(event, &event_mask, output_objects))
175 .collect();
176 }
177
178 message
179 }
180
181 pub fn render_event_to_proto(
182 &self,
183 event: &sui_types::event::Event,
184 mask: &FieldMaskTree,
185 output_objects: &ObjectSet,
186 ) -> Event {
187 let mut message = Event::default();
188
189 if mask.contains(Event::PACKAGE_ID_FIELD) {
190 message.set_package_id(event.package_id.to_canonical_string(true));
191 }
192
193 if mask.contains(Event::MODULE_FIELD) {
194 message.set_module(event.transaction_module.to_string());
195 }
196
197 if mask.contains(Event::SENDER_FIELD) {
198 message.sender = Some(event.sender.to_string());
199 }
200
201 if mask.contains(Event::EVENT_TYPE_FIELD) {
202 message.event_type = Some(event.type_.to_canonical_string(true));
203 }
204
205 if mask.contains(Event::CONTENTS_FIELD) {
206 let mut bcs = Bcs::from(event.contents.clone());
207 bcs.name = Some(event.type_.to_canonical_string(true));
208 message.contents = Some(bcs);
209 }
210
211 if mask.contains(Event::JSON_FIELD) {
212 message.json = self
213 .render_json(&event.type_, &event.contents, output_objects)
214 .map(Box::new);
215 }
216
217 message
218 }
219
220 pub fn render_clever_error(&self, effects: &mut TransactionEffects) {
222 use sui_rpc::proto::sui::rpc::v2::CleverError;
223 use sui_rpc::proto::sui::rpc::v2::MoveAbort;
224 use sui_rpc::proto::sui::rpc::v2::clever_error;
225 use sui_rpc::proto::sui::rpc::v2::execution_error::ErrorDetails;
226
227 let Some(move_abort) = effects
228 .status
229 .as_mut()
230 .and_then(|status| status.error.as_mut())
231 .and_then(|error| match &mut error.error_details {
232 Some(ErrorDetails::Abort(move_abort)) => Some(move_abort),
233 _ => None,
234 })
235 else {
236 return;
237 };
238
239 fn render(service: &RpcService, move_abort: &MoveAbort) -> Option<CleverError> {
240 let location = move_abort.location.as_ref()?;
241 let abort_code = move_abort.abort_code();
242 let package_id = location.package().parse::<sui_sdk_types::Address>().ok()?;
243 let module = location.module();
244
245 let package = {
246 let object = service.reader.inner().get_object(&package_id.into())?;
247 sui_package_resolver::Package::read_from_object(&object).ok()?
248 };
249
250 let clever_error = package.resolve_clever_error(module, abort_code)?;
251
252 let mut clever_error_message = CleverError::default();
253
254 match clever_error.error_info {
255 sui_package_resolver::ErrorConstants::None => {}
256 sui_package_resolver::ErrorConstants::Rendered {
257 identifier,
258 constant,
259 } => {
260 clever_error_message.constant_name = Some(identifier);
261 clever_error_message.value = Some(clever_error::Value::Rendered(constant));
262 }
263 sui_package_resolver::ErrorConstants::Raw { identifier, bytes } => {
264 clever_error_message.constant_name = Some(identifier);
265 clever_error_message.value = Some(clever_error::Value::Raw(bytes.into()));
266 }
267 }
268
269 clever_error_message.error_code = clever_error.error_code.map(Into::into);
270 clever_error_message.line_number = Some(clever_error.source_line_number.into());
271
272 Some(clever_error_message)
273 }
274
275 move_abort.clever_error = render(self, move_abort);
276 }
277
278 pub fn render_effects_to_proto(
279 &self,
280 effects: &sui_types::effects::TransactionEffects,
281 unchanged_loaded_runtime_objects: &[sui_types::storage::ObjectKey],
282 objects: &ObjectSet,
283 mask: &FieldMaskTree,
284 ) -> TransactionEffects {
285 let mut effects = TransactionEffects::merge_from(effects, mask);
287
288 if mask.contains(TransactionEffects::UNCHANGED_LOADED_RUNTIME_OBJECTS_FIELD) {
289 effects.unchanged_loaded_runtime_objects = unchanged_loaded_runtime_objects
290 .iter()
291 .map(Into::into)
292 .collect();
293 }
294
295 if mask.contains(TransactionEffects::CHANGED_OBJECTS_FIELD) {
296 for changed_object in effects.changed_objects.iter_mut() {
297 let Ok(object_id) = changed_object
298 .object_id()
299 .parse::<sui_types::base_types::ObjectID>()
300 else {
301 continue;
302 };
303
304 if let Some(object) = objects.get(&sui_types::storage::ObjectKey(
305 object_id,
306 changed_object
307 .input_version_opt()
308 .unwrap_or_else(|| changed_object.output_version())
309 .into(),
310 )) {
311 changed_object.set_object_type(object_type_to_string(object.into()));
312 }
313 }
314 }
315
316 if mask.contains(TransactionEffects::UNCHANGED_CONSENSUS_OBJECTS_FIELD) {
317 for unchanged_consensus_object in effects.unchanged_consensus_objects.iter_mut() {
318 let Ok(object_id) = unchanged_consensus_object
319 .object_id()
320 .parse::<sui_types::base_types::ObjectID>()
321 else {
322 continue;
323 };
324
325 if let Some(object) = objects.get(&sui_types::storage::ObjectKey(
326 object_id,
327 unchanged_consensus_object.version().into(),
328 )) {
329 unchanged_consensus_object
330 .set_object_type(object_type_to_string(object.into()));
331 }
332 }
333 }
334
335 self.render_clever_error(&mut effects);
337
338 effects
339 }
340}
341
342fn object_type_to_string(object_type: sui_types::base_types::ObjectType) -> String {
343 match object_type {
344 sui_types::base_types::ObjectType::Package => "package".to_owned(),
345 sui_types::base_types::ObjectType::Struct(move_object_type) => {
346 move_object_type.to_canonical_string(true)
347 }
348 }
349}