sui_rpc_api/grpc/v2/
render.rs1use sui_rpc::{
5 field::FieldMaskTree,
6 merge::Merge,
7 proto::sui::rpc::v2::{Bcs, Event, Object, TransactionEffects, TransactionEvents},
8};
9
10use crate::RpcService;
11
12impl RpcService {
13 pub fn render_object_to_proto(
14 &self,
15 object: &sui_types::object::Object,
16 read_mask: &FieldMaskTree,
17 ) -> Object {
18 let mut message = Object::default();
19
20 if read_mask.contains(Object::JSON_FIELD) {
21 message.json = self.render_object_to_json(object).map(Box::new);
22 }
23
24 message.merge(object, read_mask);
25
26 message
27 }
28
29 fn render_object_to_json(
30 &self,
31 object: &sui_types::object::Object,
32 ) -> Option<prost_types::Value> {
33 let move_object = object.data.try_as_move()?;
34 self.render_json(&move_object.type_().clone().into(), move_object.contents())
35 }
36
37 pub fn render_json(
38 &self,
39 struct_tag: &move_core_types::language_storage::StructTag,
40 contents: &[u8],
41 ) -> Option<prost_types::Value> {
42 let layout = self
43 .reader
44 .inner()
45 .get_struct_layout(struct_tag)
46 .ok()
47 .flatten()?;
48
49 sui_types::proto_value::ProtoVisitor::new(self.config.max_json_move_value_size())
50 .deserialize_value(contents, &layout)
51 .map_err(|e| tracing::debug!("unable to convert move value to JSON: {e}"))
52 .ok()
53 }
54
55 pub fn render_events_to_proto(
56 &self,
57 events: &sui_types::effects::TransactionEvents,
58 mask: &FieldMaskTree,
59 ) -> TransactionEvents {
60 let mut message = TransactionEvents::default();
61
62 if mask.contains(TransactionEvents::BCS_FIELD) {
63 let mut bcs = Bcs::serialize(&events).unwrap();
64 bcs.name = Some("TransactionEvents".to_owned());
65 message.bcs = Some(bcs);
66 }
67
68 if mask.contains(TransactionEvents::DIGEST_FIELD) {
69 message.digest = Some(events.digest().to_string());
70 }
71
72 if let Some(event_mask) = mask.subtree(TransactionEvents::EVENTS_FIELD) {
73 message.events = events
74 .data
75 .iter()
76 .map(|event| self.render_event_to_proto(event, &event_mask))
77 .collect();
78 }
79
80 message
81 }
82
83 pub fn render_event_to_proto(
84 &self,
85 event: &sui_types::event::Event,
86 mask: &FieldMaskTree,
87 ) -> Event {
88 let mut message = Event::default();
89
90 if mask.contains(Event::PACKAGE_ID_FIELD) {
91 message.set_package_id(event.package_id.to_canonical_string(true));
92 }
93
94 if mask.contains(Event::MODULE_FIELD) {
95 message.set_module(event.transaction_module.to_string());
96 }
97
98 if mask.contains(Event::SENDER_FIELD) {
99 message.sender = Some(event.sender.to_string());
100 }
101
102 if mask.contains(Event::EVENT_TYPE_FIELD) {
103 message.event_type = Some(event.type_.to_canonical_string(true));
104 }
105
106 if mask.contains(Event::CONTENTS_FIELD) {
107 let mut bcs = Bcs::from(event.contents.clone());
108 bcs.name = Some(event.type_.to_canonical_string(true));
109 message.contents = Some(bcs);
110 }
111
112 if mask.contains(Event::JSON_FIELD) {
113 message.json = self
114 .render_json(&event.type_, &event.contents)
115 .map(Box::new);
116 }
117
118 message
119 }
120
121 pub fn render_clever_error(&self, effects: &mut TransactionEffects) {
123 use sui_rpc::proto::sui::rpc::v2::CleverError;
124 use sui_rpc::proto::sui::rpc::v2::MoveAbort;
125 use sui_rpc::proto::sui::rpc::v2::clever_error;
126 use sui_rpc::proto::sui::rpc::v2::execution_error::ErrorDetails;
127
128 let Some(move_abort) = effects
129 .status
130 .as_mut()
131 .and_then(|status| status.error.as_mut())
132 .and_then(|error| match &mut error.error_details {
133 Some(ErrorDetails::Abort(move_abort)) => Some(move_abort),
134 _ => None,
135 })
136 else {
137 return;
138 };
139
140 fn render(service: &RpcService, move_abort: &MoveAbort) -> Option<CleverError> {
141 let location = move_abort.location.as_ref()?;
142 let abort_code = move_abort.abort_code();
143 let package_id = location.package().parse::<sui_sdk_types::Address>().ok()?;
144 let module = location.module();
145
146 let package = {
147 let object = service.reader.inner().get_object(&package_id.into())?;
148 sui_package_resolver::Package::read_from_object(&object).ok()?
149 };
150
151 let clever_error = package.resolve_clever_error(module, abort_code)?;
152
153 let mut clever_error_message = CleverError::default();
154
155 match clever_error.error_info {
156 sui_package_resolver::ErrorConstants::None => {}
157 sui_package_resolver::ErrorConstants::Rendered {
158 identifier,
159 constant,
160 } => {
161 clever_error_message.constant_name = Some(identifier);
162 clever_error_message.value = Some(clever_error::Value::Rendered(constant));
163 }
164 sui_package_resolver::ErrorConstants::Raw { identifier, bytes } => {
165 clever_error_message.constant_name = Some(identifier);
166 clever_error_message.value = Some(clever_error::Value::Raw(bytes.into()));
167 }
168 }
169
170 clever_error_message.error_code = clever_error.error_code.map(Into::into);
171 clever_error_message.line_number = Some(clever_error.source_line_number.into());
172
173 Some(clever_error_message)
174 }
175
176 move_abort.clever_error = render(self, move_abort);
177 }
178
179 pub fn render_effects_to_proto<F>(
180 &self,
181 effects: &sui_types::effects::TransactionEffects,
182 unchanged_loaded_runtime_objects: &[sui_types::storage::ObjectKey],
183 object_type_lookup: F,
184 mask: &FieldMaskTree,
185 ) -> TransactionEffects
186 where
187 F: Fn(&sui_types::base_types::ObjectID) -> Option<sui_types::base_types::ObjectType>,
188 {
189 let mut effects = TransactionEffects::merge_from(effects, mask);
191
192 if mask.contains(TransactionEffects::UNCHANGED_LOADED_RUNTIME_OBJECTS_FIELD) {
193 effects.unchanged_loaded_runtime_objects = unchanged_loaded_runtime_objects
194 .iter()
195 .map(Into::into)
196 .collect();
197 }
198
199 if mask.contains(TransactionEffects::CHANGED_OBJECTS_FIELD) {
200 for changed_object in effects.changed_objects.iter_mut() {
201 let Ok(object_id) = changed_object
202 .object_id()
203 .parse::<sui_types::base_types::ObjectID>()
204 else {
205 continue;
206 };
207
208 if let Some(object_type) = object_type_lookup(&object_id) {
209 changed_object.set_object_type(object_type_to_string(object_type));
210 }
211 }
212 }
213
214 if mask.contains(TransactionEffects::UNCHANGED_CONSENSUS_OBJECTS_FIELD) {
215 for unchanged_consensus_object in effects.unchanged_consensus_objects.iter_mut() {
216 let Ok(object_id) = unchanged_consensus_object
217 .object_id()
218 .parse::<sui_types::base_types::ObjectID>()
219 else {
220 continue;
221 };
222
223 if let Some(object_type) = object_type_lookup(&object_id) {
224 unchanged_consensus_object.set_object_type(object_type_to_string(object_type));
225 }
226 }
227 }
228
229 self.render_clever_error(&mut effects);
231
232 effects
233 }
234}
235
236fn object_type_to_string(object_type: sui_types::base_types::ObjectType) -> String {
237 match object_type {
238 sui_types::base_types::ObjectType::Package => "package".to_owned(),
239 sui_types::base_types::ObjectType::Struct(move_object_type) => {
240 move_object_type.to_canonical_string(true)
241 }
242 }
243}