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