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