Skip to main content

thruput_view/
lib.rs

1#![allow(non_snake_case)]
2#![allow(unused_imports)]
3
4use thruput_xdr::auth_claim::*;
5use thruput_xdr::auth_token::*;
6use thruput_xdr::contract::*;
7use thruput_xdr::event::*;
8use thruput_xdr::invoke_flag::*;
9use thruput_xdr::network::*;
10use thruput_xdr::syscall_error::*;
11use thruput_xdr::transaction::*;
12use thruput_xdr::types::*;
13use thruput_xdr::view::*;
14
15#[rustfmt::skip]
16pub mod view_gateway;
17pub use view_gateway::*;
18
19#[cfg(test)]
20mod tests {
21    use super::*;
22    use std::sync::{Arc, Mutex};
23    use thruput_rpc_api::{
24        ProgVers, ProgramError, RpcError, RpcResponse, RpcSender,
25    };
26    use xdr_common::{BytesM, StringM, VecM};
27
28    const VIEW_GATEWAY_PROG_VERS: ProgVers = ProgVers::new(0x56475759, 1);
29
30    /// Stand-in protocol token. Fixed width, so it has to fill the array.
31    const TEST_PROTOCOL: ProtocolCommit = *b"hogp/0123456789abcde";
32
33    struct ScriptedViewGateway;
34
35    #[allow(refining_impl_trait)]
36    impl ViewGateway_ViewGateway1 for ScriptedViewGateway {
37        async fn viewgateway_view_batch(
38            &self,
39            request: ViewGatewayBatchQuery,
40        ) -> Result<ViewGatewayBatchResult, ProgramError> {
41            assert_eq!(request.calls.0, vec![query(), query()]);
42            assert_eq!(request.min_height, Some(BlockHeight::from(42)));
43            assert_eq!(request.budget_millis, 1000);
44            assert_eq!(request.max_response_bytes, 1_048_576);
45            Ok(ViewGatewayBatchResult::ViewGatewayBatchOk {
46                payload: ViewGatewayBatchPayload {
47                    height: BlockHeight::from(42),
48                    results: VecM(vec![
49                        TransactionViewResult {
50                            gas_consumed: 17,
51                            output: InvocationFrameOutput::Return {
52                                return_buffer: BytesM(vec![1]),
53                            },
54                            events: VecM(vec![]),
55                        },
56                        TransactionViewResult {
57                            gas_consumed: 23,
58                            output: InvocationFrameOutput::Fault,
59                            events: VecM(vec![]),
60                        },
61                    ]),
62                },
63            })
64        }
65
66        async fn viewgateway_intro(
67            &self,
68            protocol: ProtocolCommit,
69        ) -> Result<ViewGatewayIntroOutcome, ProgramError> {
70            assert_eq!(protocol, TEST_PROTOCOL);
71            Ok(ViewGatewayIntroOutcome::ViewGatewayIntroSuccess)
72        }
73
74        async fn viewgateway_view(
75            &self,
76            query: TransactionViewQuery,
77        ) -> Result<ViewGatewayResult, ProgramError> {
78            assert_eq!(
79                query.invocation.invoked_address,
80                ContractAddress::from([7u8; 32])
81            );
82            assert_eq!(*query.invocation.entrypoint, [8u8; 8]);
83            assert_eq!(query.invocation.gas_limit, 123);
84            assert_eq!(query.invocation.calldata.0, vec![9, 10]);
85            assert_eq!(
86                query.parent_address,
87                Some(ContractAddress::from([6u8; 32]))
88            );
89            assert_eq!(query.forwarded_tokens.0.len(), 0);
90            assert_eq!(
91                query.invoke_flags,
92                InvokeFlag::ReadOnly.0.cast_unsigned()
93            );
94
95            Ok(ok(InvocationFrameOutput::Return {
96                return_buffer: BytesM(vec![1, 2, 3]),
97            }))
98        }
99    }
100
101    struct LocalSender {
102        handler: Arc<dyn thruput_rpc_api::RpcHandler>,
103        calls: Arc<Mutex<Vec<(ProgVers, u32)>>>,
104    }
105
106    #[async_trait::async_trait]
107    impl RpcSender for LocalSender {
108        fn call_buf(&self) -> thruput_rpc_api::CallBuf {
109            thruput_rpc_api::CallBuf::new()
110        }
111
112        async fn send_call(
113            &self,
114            progvers: ProgVers,
115            proc: u32,
116            call: thruput_rpc_api::CallBuf,
117        ) -> Result<RpcResponse, RpcError> {
118            self.calls
119                .lock()
120                .expect("calls lock poisoned")
121                .push((progvers, proc));
122            if progvers != self.handler.prog_vers() {
123                return Err(RpcError::ProgUnavail);
124            }
125            let payload = self.handler.dispatch(proc, call.payload()).await?;
126            Ok(RpcResponse::from_payload(payload))
127        }
128    }
129
130    fn round_trip(result: ViewGatewayResult) {
131        let encoded =
132            xdrs::txs::encode(&result).expect("encode ViewGatewayResult");
133        let decoded: ViewGatewayResult =
134            xdrs::txs::decode(&encoded).expect("decode ViewGatewayResult");
135        assert_eq!(decoded, result);
136    }
137
138    fn ok(output: InvocationFrameOutput) -> ViewGatewayResult {
139        ViewGatewayResult::ViewGatewayOk {
140            payload: ViewGatewayPayload {
141                height: BlockHeight::from(42),
142                output,
143                gas_consumed: 77,
144                events: VecM(vec![]),
145            },
146        }
147    }
148
149    fn query() -> TransactionViewQuery {
150        TransactionViewQuery {
151            invocation: Invocation {
152                invoked_address: ContractAddress::from([7u8; 32]),
153                entrypoint: ContractEntrypointID::from([8u8; 8]),
154                gas_limit: 123,
155                calldata: BytesM(vec![9, 10]),
156            },
157            parent_address: Some(ContractAddress::from([6u8; 32])),
158            auth_claim: AuthClaim::kSourceAddressAuthData {
159                source_address_claim: AuthClaim_source_address_claim {
160                    allowed_keys: 0,
161                    required_weight: 0,
162                },
163            },
164            forwarded_tokens: VecM(vec![]),
165            invoke_flags: InvokeFlag::ReadOnly.0.cast_unsigned(),
166        }
167    }
168
169    #[test]
170    fn view_gateway_result_round_trips_return_abort_and_fault() {
171        round_trip(ok(InvocationFrameOutput::Return {
172            return_buffer: BytesM(vec![1, 2, 3]),
173        }));
174        round_trip(ok(InvocationFrameOutput::Abort {
175            abort_buffer: BytesM(vec![4, 5, 6]),
176        }));
177        round_trip(ok(InvocationFrameOutput::Fault));
178    }
179
180    #[test]
181    fn view_gateway_result_round_trips_service_failures() {
182        round_trip(ViewGatewayResult::ViewGatewayAbandoned);
183        round_trip(ViewGatewayResult::ViewGatewayInternalError);
184    }
185
186    #[test]
187    fn view_gateway_result_round_trips_unknown_default_outcome() {
188        round_trip(ViewGatewayResult::Default { outcome: 99 });
189    }
190
191    #[test]
192    fn view_gateway_types_have_json_schema() {
193        let _ = schemars::schema_for!(ViewGatewayPayload);
194        let _ = schemars::schema_for!(ViewGatewayOutcome);
195        let _ = schemars::schema_for!(ViewGatewayResult);
196        let _ = schemars::schema_for!(ViewGatewayIntroOutcome);
197        let _ = schemars::schema_for!(ViewGatewayBatchQuery);
198        let _ = schemars::schema_for!(ViewGatewayBatchResult);
199    }
200
201    #[cfg(feature = "serde")]
202    #[test]
203    fn view_gateway_result_serde_round_trips() {
204        let result = ok(InvocationFrameOutput::Return {
205            return_buffer: BytesM(vec![1, 2, 3]),
206        });
207        let json = serde_json::to_value(&result)
208            .expect("serialize ViewGatewayResult");
209        let decoded: ViewGatewayResult = serde_json::from_value(json)
210            .expect("deserialize ViewGatewayResult");
211        assert_eq!(decoded, result);
212    }
213
214    #[tokio::test]
215    async fn generated_client_dispatches_intro_view_and_batch() {
216        let calls = Arc::new(Mutex::new(Vec::new()));
217        let handler = Arc::new(ViewGateway_ViewGateway1_Dispatcher::new(
218            Box::new(ScriptedViewGateway),
219        ));
220        let client =
221            ViewGateway_ViewGateway1_Client::new(Arc::new(LocalSender {
222                handler,
223                calls: calls.clone(),
224            }));
225
226        let intro = client
227            .viewgateway_intro(&TEST_PROTOCOL)
228            .await
229            .expect("intro should round-trip through dispatcher");
230        assert_eq!(intro, ViewGatewayIntroOutcome::ViewGatewayIntroSuccess);
231
232        let result = client
233            .viewgateway_view(&query())
234            .await
235            .expect("view should round-trip through dispatcher");
236
237        match result {
238            ViewGatewayResult::ViewGatewayOk { payload } => {
239                assert_eq!(*payload.height, 42);
240                assert_eq!(
241                    payload.output,
242                    InvocationFrameOutput::Return {
243                        return_buffer: BytesM(vec![1, 2, 3]),
244                    }
245                );
246            }
247            other => panic!("unexpected view result: {other:?}"),
248        }
249
250        let batch = ViewGatewayBatchQuery {
251            calls: VecM(vec![query(), query()]),
252            min_height: Some(BlockHeight::from(42)),
253            budget_millis: 1000,
254            max_response_bytes: 1_048_576,
255        };
256        let result = client
257            .viewgateway_view_batch(&batch)
258            .await
259            .expect("batch should round-trip through dispatcher");
260        assert_eq!(
261            result,
262            ScriptedViewGateway
263                .viewgateway_view_batch(batch)
264                .await
265                .unwrap()
266        );
267        assert_eq!(
268            *calls.lock().expect("calls lock poisoned"),
269            vec![
270                (VIEW_GATEWAY_PROG_VERS, 0),
271                (VIEW_GATEWAY_PROG_VERS, 1),
272                (VIEW_GATEWAY_PROG_VERS, 2)
273            ]
274        );
275    }
276
277    #[test]
278    fn batch_failures_preserve_height_details_and_unknown_outcomes() {
279        let mut failures =
280            vec![ViewGatewayBatchResult::ViewGatewayBatchHeightUnavailable {
281                height_unavailable: ViewGatewayHeightUnavailable {
282                    requested: BlockHeight::from(50),
283                    observed: BlockHeight::from(42),
284                },
285            }];
286        failures.extend(
287            [1, 3, 4, 5, 6, 99]
288                .map(|outcome| ViewGatewayBatchResult::Default { outcome }),
289        );
290        for result in failures {
291            let encoded = xdrs::txs::encode(&result).unwrap();
292            let decoded: ViewGatewayBatchResult =
293                xdrs::txs::decode(&encoded).unwrap();
294            assert_eq!(decoded, result);
295        }
296    }
297}