switchboard_evm/
sdk.rs

1use crate::*;
2
3use std::collections::HashMap;
4use std::env;
5use std::str::FromStr;
6use std::sync::Arc;
7
8use base64;
9use base64::engine::general_purpose::STANDARD as BASE64;
10use base64::engine::Engine as _;
11use ethers::prelude::{k256::ecdsa::SigningKey, ContractCall, SignerMiddleware};
12use ethers::providers::{Http, Provider};
13use ethers::signers::{Signer, Wallet};
14use ethers::types::{Address, Bytes, U256};
15use ethers::utils::hex;
16use serde_json;
17use nom::AsBytes;
18
19use crate::bindings::{eip712, switchboard};
20use crate::utils::{generate_signer, load_env_address};
21
22pub type EVMMiddleware<T> = SignerMiddleware<Provider<T>, Wallet<SigningKey>>;
23
24/// EVM specific environment used during a Switchboard function execution
25#[derive(Clone)]
26pub struct EvmFunctionRunner {
27    /// `FUNCTION_KEY`: environemnt variable passed in that denoted what function
28    /// is executing
29    pub function_id: Address,
30    /// This is a keypair generated inside the function execution runtime.
31    /// As long as not explicitly exported, this keypair will never be known
32    /// outside the functions exectuion environment.
33    pub enclave_wallet: Wallet<SigningKey>,
34    pub signer: Address,
35    /// `VERIFYING_CONTRACT`: An environmnet variable denoting the signoff
36    /// callback program ID. On evm chains this is equivalent to the Switchboard
37    /// program address.
38    pub verifying_contract: Address,
39    /// `CHAIN_ID`: The chain ID of the chain this evm function is executing on
40    pub chain_id: u64,
41    /// A list of function parameter based calls to attempt to handle this run.
42    /// Parsing these is up to the function.
43    pub params: Vec<Vec<u8>>,
44    /// `FUNCTION_CALL_IDS`: A list of the UUIDs of all the calls the function
45    /// will be attempting to resolve.
46    /// Routines OR Requests are represented in call_ids
47    pub call_ids: Vec<Address>,
48    /// A derived url based on the CHAIN_ID that can be used for basic querying
49    /// of the target chain through the default public rpc.
50    pub default_provider_url: Option<String>,
51    pub call_id_map: HashMap<Address, usize>,
52    // Map of call_id to error code
53    pub call_id_error_map: HashMap<Address, u8>,
54    // Map of call_id to Transaction and Signature
55    pub call_id_tx_map: HashMap<Address, Vec<ContractCall<EVMMiddleware<Http>, ()>>>,
56}
57
58impl std::fmt::Display for EvmFunctionRunner {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(
61            f,
62            "SwitchboardFunctionRunner: signer: {}, verifying_contract: {}, function_id: {}",
63            self.signer, self.verifying_contract, self.function_id,
64        )
65    }
66}
67
68impl EvmFunctionRunner {
69    pub fn new() -> Result<EvmFunctionRunner, SbError> {
70        let enclave_wallet = generate_signer();
71        let signer = enclave_wallet.address();
72        let chain_id_string = env::var("CHAIN_ID").unwrap();
73        let chain_id: u64 = chain_id_string.parse().unwrap_or(1);
74        let verifying_contract = load_env_address("VERIFYING_CONTRACT")?;
75        let function_id = load_env_address("FUNCTION_KEY")?;
76
77        let params = BASE64.decode(env::var("FUNCTION_PARAMS").unwrap()).unwrap();
78        let params: Vec<String> = serde_json::from_slice(&params).unwrap();
79        let params: Vec<Vec<u8>> = params.iter().map(|p| BASE64.decode(p).unwrap()).collect();
80
81        // get call ids as vec of Addresses
82        let call_ids = BASE64
83            .decode(env::var("FUNCTION_CALL_IDS").unwrap())
84            .unwrap();
85        let call_ids: Vec<String> = serde_json::from_slice(&call_ids).unwrap();
86        let call_ids: Vec<Address> = call_ids
87            .iter()
88            .map(|c| Address::from_str(c.as_str()).unwrap())
89            .collect();
90
91        // get map of call_id to index in calls
92        let call_id_map: std::collections::HashMap<Address, usize> =
93            call_ids.iter().enumerate().map(|(i, c)| (*c, i)).collect();
94
95        let default_provider_url = match chain_id {
96            // CoreDAO
97            1116 => Some("https://rpc.coredao.org".to_string()),
98            1115 => Some("https://rpc.test.btcs.network".to_string()),
99            // Arbitrum
100            42161 => Some("https://arb1.arbitrum.io/rpc".to_string()),
101            421613 => Some("https://goerli-rollup.arbitrum.io/rpc".to_string()),
102            // Base
103            8453 => Some("https://mainnet.base.org".to_string()),
104            84531 => Some("https://goerli.base.org".to_string()),
105            // Optimism
106            10 => Some("https://mainnet.optimism.io".to_string()),
107            420 => Some("https://goerli.optimism.io".to_string()),
108            // Panic
109            _ => None,
110        };
111
112        Ok(Self {
113            function_id,
114            enclave_wallet,
115            signer,
116            verifying_contract,
117            params,
118            call_ids,
119            chain_id,
120            default_provider_url,
121            call_id_map,
122            call_id_error_map: HashMap::new(),
123            call_id_tx_map: HashMap::new(),
124        })
125    }
126
127    /// Creates a provider to communicate with the target chain
128    pub fn get_provider(&self, provider_url: Option<&str>) -> Result<Provider<Http>, SbError> {
129        if let Some(url) = provider_url {
130            return Provider::<Http>::try_from(url).map_err(|e| SbError::CustomError {
131                message: "Failed to create provider".to_string(),
132                source: std::sync::Arc::new(e),
133            });
134        }
135
136        self.get_default_provider()
137    }
138
139    /// Creates a default provider to communicate with the target chain
140    pub fn get_default_provider(&self) -> Result<Provider<Http>, SbError> {
141        if let Some(url) = self.default_provider_url.as_ref() {
142            return Provider::<Http>::try_from(url).map_err(|e| SbError::CustomError {
143                message: "Failed to create provider".to_string(),
144                source: std::sync::Arc::new(e),
145            });
146        }
147
148        Err(SbError::CustomMessage(format!(
149            "No default provider found for chain_id {}",
150            self.chain_id
151        )))
152    }
153
154    /// Creates a rpc client from the provided url and the enclave signer
155    pub async fn get_client(
156        &self,
157        provider_url: Option<&str>,
158    ) -> Result<Arc<EVMMiddleware<Http>>, SbError> {
159        let provider = self.get_provider(provider_url)?;
160
161        let client = SignerMiddleware::new_with_provider_chain(
162            provider.clone(),
163            self.enclave_wallet.clone(),
164        )
165        .await
166        .map_err(|e| SbError::CustomError {
167            message: "Failed to create client".to_string(),
168            source: std::sync::Arc::new(e),
169        })?;
170
171        Ok(Arc::new(client))
172    }
173
174    /// Creates a FunctionResult object as the output of your function run.
175    /// The `calls` passed here will be verified and executed by the switchboard network.
176    pub fn get_result(
177        &self,
178        expiration_time_seconds: U256,
179        gas_limit: U256,
180    ) -> Result<FunctionResult, SwitchboardClientError> {
181        // create a vector of each field to pass to the function result
182        let mut evm_txns: Vec<EvmTransaction> = Vec::new();
183        let mut call_ids: Vec<Address> = Vec::new();
184        let mut signatures: Vec<Bytes> = Vec::new();
185        let mut error_codes: Vec<u8> = Vec::new();
186        let mut checksums: Vec<String> = Vec::new();
187
188        // Fill the runs that were successful
189        for (call_id, call_vec) in self.call_id_tx_map.iter() {
190            // get calls from call_vec and add them to the right fields
191            for call in call_vec.iter() {
192                // get the to address from the call
193                let to_name = call.tx.to().expect("Transaction field `to` must be set");
194                let to_as_address = to_name
195                    .as_address()
196                    .expect("'to' must be an address, ens names not supported");
197
198                // build the switchboard::Transaction for the signature
199                let transaction = switchboard::Transaction {
200                    expiration_time_seconds,
201                    gas_limit,
202                    value: *call.tx.value().unwrap_or(&U256::from(0)),
203                    to: *to_as_address,
204                    from: self.enclave_wallet.address(),
205                    data: call.tx.data().unwrap().clone(),
206                };
207
208                let eip712_hash = eip712::get_transaction_hash(
209                    "Switchboard".to_string(),
210                    "0.0.1".to_string(),
211                    self.chain_id,
212                    self.verifying_contract,
213                    transaction,
214                )
215                .unwrap();
216
217                // build the EvmTransaction for the EvmFunctionResultV1
218                let evm_txn = EvmTransaction {
219                    expiration_time_seconds: expiration_time_seconds.as_u64(),
220                    gas_limit: gas_limit.to_string(),
221                    data: call.tx.data().unwrap_or(&Bytes::new()).as_bytes().into(),
222                    from: self.enclave_wallet.address().as_bytes().to_vec(),
223                    to: to_as_address.as_bytes().to_vec(),
224                    value: call.tx.value().unwrap_or(&U256::from(0)).to_string(),
225                };
226
227                // get the index of the call_id in the call_ids vector so we can grab the params and get the aligned checksum
228                let index = self.call_id_map.get(call_id).unwrap();
229                let checksum = hex::encode(ethers::utils::keccak256(self.params[*index].clone()));
230
231                // push the transaction and signature to the evm_txns and signatures vectors also push call_id to call_ids
232                evm_txns.push(evm_txn);
233                call_ids.push(call_id.clone());
234                signatures.push(Bytes::from(
235                    self.enclave_wallet
236                        .sign_hash(ethers::types::H256::from(eip712_hash))
237                        .unwrap()
238                        .to_vec(),
239                ));
240                error_codes.push(0);
241                checksums.push(checksum);
242            }
243        }
244
245        // Fill the Error Runs
246        for (call_id, error_code) in self.call_id_error_map.iter() {
247            let index = self.call_id_map.get(call_id).unwrap();
248            let checksum = hex::encode(ethers::utils::keccak256(self.params[*index].clone()));
249
250            // create a dummy transaction and signature for the error
251            let transaction = switchboard::Transaction {
252                expiration_time_seconds,
253                gas_limit,
254                value: U256::from(0),
255                to: Address::zero(),
256                from: self.enclave_wallet.address(),
257                data: Bytes::new(),
258            };
259
260            let eip712_hash = eip712::get_transaction_hash(
261                "Switchboard".to_string(),
262                "0.0.1".to_string(),
263                self.chain_id,
264                self.verifying_contract,
265                transaction,
266            )
267            .unwrap();
268
269            let evm_txn = EvmTransaction {
270                expiration_time_seconds: expiration_time_seconds.as_u64(),
271                gas_limit: gas_limit.to_string(),
272                data: vec![],
273                from: self.enclave_wallet.address().as_bytes().to_vec(),
274                to: Address::zero().as_bytes().to_vec(),
275                value: 0u8.to_string(),
276            };
277
278            // push the transaction and signature to the evm_txns and signatures vectors also push call_id to call_ids
279            evm_txns.push(evm_txn);
280            call_ids.push(call_id.clone());
281            signatures.push(Bytes::from(
282                self.enclave_wallet
283                    .sign_hash(ethers::types::H256::from(eip712_hash))
284                    .unwrap()
285                    .to_vec(),
286            ));
287            error_codes.push(*error_code);
288            checksums.push(checksum);
289        }
290
291        let evm_function_result = EvmFunctionResultV1 {
292            function_id: format!("{:?}", self.function_id),
293            signer: format!("{:?}", self.enclave_wallet.address()),
294            txs: evm_txns.clone(),
295            signatures: signatures.iter().map(|s| s.to_string()).collect(),
296            resolved_ids: call_ids.iter().map(|c| format!("{:?}", c)).collect(),
297            checksums,
298            error_codes,
299        };
300
301        let hash = evm_function_result.hash();
302        let chain_result_info = ChainResultInfo::Evm(switchboard_common::EvmFunctionResult::V1(
303            evm_function_result,
304        ));
305
306        let quote_raw =
307            Gramine::generate_quote(self.enclave_wallet.address().as_bytes()).unwrap_or_default();
308
309        if quote_raw.is_empty() {
310            println!(
311                "WARNING: Error generating quote. This is likely due to the enclave not being initialized."
312            )
313        }
314
315        // get hash as [u8; 32] from Vec<u8>
316        let hash: [u8; 32] = hash.as_slice().try_into().unwrap();
317
318        // get signature of hash
319        let signature = self
320            .enclave_wallet
321            .sign_hash(ethers::types::H256::from(hash))
322            .unwrap();
323
324        Ok(switchboard_common::FunctionResult::V1(
325            switchboard_common::FunctionResultV1 {
326                quote: quote_raw,
327                chain_result_info,
328                error_code: 0,
329                signer: self.enclave_wallet.address().as_bytes().to_vec(),
330                signature: signature.into(),
331            },
332        ))
333    }
334
335    /// Emit the function result
336    /// This will trigger the switchboard verifier and trigger the submission of the
337    /// passed in meta-transactions (funded by the switchboard function escrow).
338    pub fn emit(
339        &self,
340        expiration_time_seconds: U256,
341        gas_limit: U256,
342    ) -> Result<(), SwitchboardClientError> {
343        self.get_result(expiration_time_seconds, gas_limit)
344            .map_err(|e| SbError::CustomError {
345                message: "failed to run function verify".to_string(),
346                source: Arc::new(e),
347            })
348            .unwrap()
349            .emit();
350        Ok(())
351    }
352
353    // Emit error for all calls received by the function
354    pub fn emit_error(
355        &self,
356        error_code: u8,
357        expiration_time_seconds: U256,
358        gas_limit: U256,
359    ) -> Result<(), SwitchboardClientError> {
360        let function_result = self
361            .get_result(expiration_time_seconds, gas_limit)
362            .map_err(|e| SwitchboardClientError::CustomError {
363                message: "failed to run function resolve".to_string(),
364                source: Arc::new(e),
365            })
366            .unwrap();
367
368        // get V1 enum variant from FunctionResult
369        let mut function_result_v1 = match function_result {
370            FunctionResult::V1(v1) => v1,
371            _ => {
372                return Err(SwitchboardClientError::CustomMessage(
373                    "Function result must be V1.".to_string(),
374                ));
375            }
376        };
377
378        // set error code (which will represent a function failure, not necessarily a call failure)
379        function_result_v1.error_code = error_code;
380
381        // wrap in FunctionResult enum variant
382        let function_result = FunctionResult::V1(function_result_v1);
383        function_result.emit();
384        Ok(())
385    }
386
387    // assign error code to a call_id
388    pub fn set_error(&mut self, resolved_id: Address, error_code: u8) {
389        self.call_id_error_map.insert(resolved_id, error_code);
390    }
391
392    // assign transactions to a call_id
393    pub fn set_txs(
394        &mut self,
395        resolved_id: Address,
396        transactions: Vec<ContractCall<EVMMiddleware<Http>, ()>>,
397    ) {
398        self.call_id_tx_map.insert(resolved_id, transactions);
399    }
400}
401
402pub async fn fetch_measurements(
403    provider_url: &str,
404    switchboard_address: Address,
405    function_id: Address,
406) -> Result<Vec<[u8; 32]>, SbError> {
407    let provider = Provider::<Http>::try_from(provider_url).map_err(|e| SbError::CustomError {
408        message: "Failed to create provider".to_string(),
409        source: std::sync::Arc::new(e),
410    })?;
411    let client = SignerMiddleware::new_with_provider_chain(provider.clone(), generate_signer())
412        .await
413        .map_err(|e| SbError::CustomError {
414            message: "Failed to create client".to_string(),
415            source: std::sync::Arc::new(e),
416        })?;
417
418    // get switchboard contract
419    let contract = switchboard::Switchboard::new(switchboard_address, Arc::new(client.clone()));
420
421    // get user function
422    let mr_enclaves = contract
423        .get_function_mr_enclaves(function_id)
424        .call()
425        .await
426        .map_err(|e| SbError::CustomError {
427            message: "Failed to get function mr_enclaves".to_string(),
428            source: std::sync::Arc::new(e),
429        })
430        .unwrap_or_default();
431
432    // get enclave's measurement
433    Ok(mr_enclaves)
434}