switchboard_starknet_sdk/
lib.rs

1pub mod sdk;
2pub use sdk::*;
3
4pub mod utils;
5pub use utils::*;
6
7use std::env;
8use std::sync::Arc;
9pub use switchboard_common::{
10    ChainResultInfo, 
11    StarknetCall, 
12    StarknetFunctionResult, 
13    StarknetFunctionResultV0,
14    FunctionResult, 
15    Gramine,
16    FunctionResultV1,
17    StarknetFunctionRequestType,
18    SbError as SwitchboardClientError,
19};
20use starknet_crypto::poseidon_hash_many;
21use starknet::{
22    core::types::FieldElement,
23    accounts::Call as NativeCall,
24    signers::{LocalWallet, SigningKey},
25};
26use crate::utils::{generate_signer, load_env_felt};
27
28pub trait SbFunctionParameters {
29    fn decode(data: &[u8]) -> Option<Self>
30    where
31        Self: Sized;
32}
33
34#[derive(Clone, Copy, Debug, Default)]
35pub struct NoParams;
36impl SbFunctionParameters for NoParams {
37    fn decode(_data: &[u8]) -> Option<Self>
38    where
39        Self: Sized,
40    {
41        Some(Self {})
42    }
43}
44
45
46#[derive(Clone)]
47pub struct StarknetFunctionRunner {
48    pub function_id: FieldElement,
49    pub enclave_key: SigningKey,
50    pub enclave_wallet: LocalWallet,
51    pub signer: FieldElement,
52    pub verifying_contract: FieldElement,
53    pub chain_id: u64,
54    pub call_id: FieldElement,
55    pub params: Vec<FieldElement>,
56    pub is_routine: bool,
57    pub call_data: Vec<u8>,
58}
59
60impl std::fmt::Display for StarknetFunctionRunner {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(
63            f,
64            "SwitchboardFunctionRunner: signer: {}, verifying_contract: {}, function_id: {}",
65            self.signer,
66            self.verifying_contract.to_string(),
67            self.function_id.to_string(),
68        )
69    }
70}
71
72impl StarknetFunctionRunner {
73    pub fn new() -> Result<StarknetFunctionRunner, SwitchboardClientError> {
74        let enclave_key = generate_signer();
75        let enclave_wallet = LocalWallet::from_signing_key(enclave_key.clone());
76        let signer = enclave_key.verifying_key().scalar();
77        let chain_id = env::var("CHAIN_ID").unwrap();
78        let verifying_contract = load_env_felt("VERIFYING_CONTRACT")?;
79        let function_id = load_env_felt("FUNCTION_KEY")?;
80
81        let mut is_routine: bool = true;
82        let params: Vec<FieldElement>;
83        let data: Vec<u8>;
84
85        let call_id = if let Some(key) = load_env_felt("FUNCTION_ROUTINE_KEY").ok() {
86            // this is a routine, so we can deserialize it
87            let routine_data = env::var("FUNCTION_ROUTINE_DATA").unwrap();
88            data = routine_data.clone().into_bytes().to_vec();
89            let routine: ViewRoutine = routine_data.try_into()?; 
90            params = routine.params;
91            key
92        } else {
93            is_routine = false;
94            let request_data = env::var("FUNCTION_REQUEST_DATA").unwrap();
95            data = request_data.clone().into_bytes().to_vec();
96            let request: ViewRequest = request_data.try_into()?;
97            params = request.params;
98            load_env_felt("FUNCTION_REQUEST_KEY").expect(
99              "FUNCTION_REQUEST_KEY or FUNCTION_ROUTINE_KEY must be set"
100            )
101        };
102
103        Ok(Self {
104                function_id,
105                enclave_key,
106                enclave_wallet,
107                signer,
108                verifying_contract,
109                params,
110                call_id,
111                chain_id: chain_id.parse().unwrap_or(1),
112                is_routine,
113                call_data: data,
114        })
115    }
116    pub fn get_result(
117        &self,
118        calls: Vec<NativeCall>,
119    ) -> Result<FunctionResult, SwitchboardClientError> {
120        let txs: Vec<StarknetCall> = calls
121            .into_iter()
122            .map(|call| {
123                let txn = StarknetCall {
124                    to: call.to.to_bytes_be().to_vec(),
125                    selector: call.selector.to_bytes_be().to_vec(),
126                    calldata: call.calldata.iter().map(|p| p.to_bytes_be().to_vec()).collect(),
127                };
128                txn
129            })
130            .collect();
131
132        let chain_result_info = ChainResultInfo::Starknet(
133            StarknetFunctionResult::V0(
134                StarknetFunctionResultV0 {
135                    txs,
136                    request_type: if self.is_routine { 
137                        StarknetFunctionRequestType::Routine(
138                            self.call_data.clone()
139                        ) 
140                    } else { 
141                        StarknetFunctionRequestType::Request(
142                            self.call_data.clone()
143                        ) 
144                    },
145                    function_id: self.function_id.to_bytes_be().to_vec(),
146                    function_request_id: self.call_id.to_bytes_be().to_vec(),
147                },
148            ),
149        );
150
151        let quote_raw =
152            Gramine::generate_quote(&self.enclave_key.verifying_key().scalar().to_bytes_be())
153                .unwrap_or_default();
154
155        if quote_raw.len() == 0 {
156            println!(
157                "WARNING: Error generating quote. This is likely due to the enclave not being initialized."
158            )
159        }
160
161        Ok(FunctionResult::V1(FunctionResultV1 {
162            quote: quote_raw,
163            signer: self
164                .enclave_key
165                .verifying_key()
166                .scalar()
167                .to_bytes_be()
168                .to_vec(),
169            signature: format!(
170                "{}",
171                self.enclave_key
172                    .sign(&hash(&chain_result_info))
173                    .unwrap()
174            ).into_bytes().to_vec(),
175            chain_result_info,
176            error_code: 0,
177        }))
178    }
179
180    // Emit the function result
181    // This will trigger the switchboard verifier and trigger the submission of the
182    // passed in meta-transactions (funded by the switchboard function escrow).
183    pub fn emit(
184        &self,
185        calls: Vec<NativeCall>, // vector of instructions to call
186    ) -> Result<(), SwitchboardClientError> {
187        self.get_result(calls)
188            .map_err(|e| SwitchboardClientError::CustomError {
189                message: "failed to run function verify".to_string(),
190                source: Arc::new(e),
191            })
192            .unwrap()
193            .emit();
194        Ok(())
195    }
196
197    // Emit an error from the function
198    pub fn emit_error(
199        &self,
200        error_code: u8,
201    ) -> Result<(), SwitchboardClientError> {
202
203        let chain_result_info = ChainResultInfo::Starknet(
204            StarknetFunctionResult::V0(
205                StarknetFunctionResultV0 {
206                    txs: vec![],
207                    request_type: if self.is_routine { 
208                        StarknetFunctionRequestType::Routine(
209                            self.call_data.clone()
210                        ) 
211                    } else { 
212                        StarknetFunctionRequestType::Request(
213                            self.call_data.clone()
214                        ) 
215                    },
216                    function_id: self.function_id.to_bytes_be().to_vec(),
217                    function_request_id: self.call_id.to_bytes_be().to_vec(),
218                },
219            ),
220        );
221        let quote_raw =
222            Gramine::generate_quote(&self.enclave_key.verifying_key().scalar().to_bytes_be())
223                .unwrap_or_default();
224
225        if quote_raw.len() == 0 {
226            println!(
227                "WARNING: Error generating quote. This is likely due to the enclave not being initialized."
228            )
229        }
230
231        FunctionResult::V1(FunctionResultV1 {
232            quote: quote_raw,
233            signer: self
234                .enclave_key
235                .verifying_key()
236                .scalar()
237                .to_bytes_be()
238                .to_vec(),
239            signature: format!(
240                "{}",
241                self.enclave_key
242                    .sign(&hash(&chain_result_info))
243                    .unwrap()
244            ).into_bytes().to_vec(),
245            chain_result_info,
246            error_code,
247        }).emit();
248
249        Ok(())
250    }
251}
252
253fn hash(chain_result_info: &ChainResultInfo) -> FieldElement {
254    let chain_result_info_json = serde_json::to_string(chain_result_info).unwrap();
255    poseidon_hash_many(&str_to_cairo_long_string(&chain_result_info_json))
256}