sui_rpc_api/grpc/v2/
render.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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    // Renders clever error information in-place
122    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        // TODO consider inlining this function here to avoid needing to do the extra parsing below
190        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        // Try to render clever error info
230        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}