ethers_ccip_read/
middleware.rs

1use std::iter::successors;
2use std::str::FromStr;
3use std::time::Duration;
4
5use async_recursion::async_recursion;
6use async_trait::async_trait;
7use ethers_core::abi::Detokenize;
8use ethers_core::types::{Address, BlockNumber, Selector, TransactionRequest, H160, U256};
9use ethers_core::{
10    abi::{self, ParamType, Token},
11    types::{transaction::eip2718::TypedTransaction, BlockId, Bytes, NameOrAddress},
12    utils::hex,
13};
14use ethers_providers::{ens, erc, Middleware, MiddlewareError};
15use futures_util::try_join;
16use hex::FromHex;
17use reqwest::Url;
18use serde_json::Value;
19
20use crate::ccip::handle_ccip;
21use crate::utils::{build_reqwest, decode_bytes, dns_encode};
22use crate::{CCIPReadMiddlewareError, CCIPRequest};
23
24#[derive(Debug, Clone)]
25pub struct CCIPReadMiddleware<M> {
26    provider: M,
27    ens: Address,
28    reqwest_client: reqwest::Client,
29    max_redirect_attempt: u8,
30}
31
32pub struct CCIPReadMiddlewareBuilder<M> {
33    provider: Option<M>,
34    ens: Option<Address>,
35    timeout: Option<Duration>,
36    max_redirect_attempt: Option<u8>,
37}
38
39impl<M> Default for CCIPReadMiddlewareBuilder<M> {
40    fn default() -> Self {
41        CCIPReadMiddlewareBuilder {
42            provider: None,
43            ens: None,
44            timeout: None,
45            max_redirect_attempt: None,
46        }
47    }
48}
49
50impl<M: Middleware> CCIPReadMiddlewareBuilder<M> {
51    pub fn with_provider(mut self, provider: M) -> Self {
52        self.provider = Some(provider);
53        self
54    }
55
56    pub fn with_timeout(mut self, timeout: Duration) -> Self {
57        self.timeout = Some(timeout);
58        self
59    }
60
61    pub fn with_max_redirect_attempt(mut self, max_redirect_attempt: u8) -> Self {
62        self.max_redirect_attempt = Some(max_redirect_attempt);
63        self
64    }
65
66    pub fn build(self) -> Result<CCIPReadMiddleware<M>, String> {
67        Ok(CCIPReadMiddleware {
68            provider: self.provider.ok_or("provider is required".to_string())?,
69            ens: self.ens.unwrap_or(ens::ENS_ADDRESS),
70            reqwest_client: build_reqwest(self.timeout.unwrap_or(Duration::from_secs(10))),
71            max_redirect_attempt: self.max_redirect_attempt.unwrap_or(10),
72        })
73    }
74}
75
76static OFFCHAIN_LOOKUP_SELECTOR: &[u8] = &[0x55, 0x6f, 0x18, 0x30];
77
78impl<M: Middleware> CCIPReadMiddleware<M> {
79    /// Creates an instance of CCIPReadMiddleware
80    /// `ìnner` the inner Middleware
81    pub fn new(inner: M) -> Self {
82        Self::builder().with_provider(inner).build().unwrap()
83    }
84
85    pub fn builder() -> CCIPReadMiddlewareBuilder<M> {
86        CCIPReadMiddlewareBuilder::default()
87    }
88
89    /// The supports_wildcard checks if a given resolver supports the wildcard resolution by calling
90    /// its `supportsInterface` function with the `resolve(bytes,bytes)` selector.
91    ///
92    /// # Arguments
93    ///
94    /// * `resolver_address`: The resolver's address.
95    ///
96    /// # Returns
97    ///
98    /// A `Result` with either a `bool` value indicating if the resolver supports wildcard
99    /// resolution or a `ProviderError`.
100    pub async fn supports_wildcard(
101        &self,
102        resolver_address: H160,
103    ) -> Result<bool, CCIPReadMiddlewareError<M>> {
104        // Prepare the data for the `supportsInterface` call, providing the selector for
105        // the "resolve(bytes,bytes)" function
106        let data = Some(
107            "0x01ffc9a79061b92300000000000000000000000000000000000000000000000000000000"
108                .parse()
109                .unwrap(),
110        );
111
112        let _tx_request = TransactionRequest {
113            data,
114            to: Some(NameOrAddress::Address(resolver_address)),
115            ..Default::default()
116        };
117
118        let _tx_result: Result<Bytes, _> = self.call(&_tx_request.into(), None).await;
119        let _tx = match _tx_result {
120            Ok(_tx) => _tx,
121            Err(_error) => {
122                println!("Error calling: {:?}", _error);
123                Bytes::from([])
124            }
125        };
126
127        // If the response is empty, the resolver does not support wildcard resolution
128        if _tx.0.is_empty() {
129            return Ok(false);
130        }
131
132        // Decode the result
133        let data: U256 = decode_bytes(ParamType::Uint(256), &_tx.0)?;
134
135        // If the result is one, the resolver supports wildcard resolution; otherwise, it does not
136        Ok(data == U256::one())
137    }
138
139    async fn query_resolver<T: Detokenize>(
140        &self,
141        param: ParamType,
142        ens_name: &str,
143        selector: Selector,
144    ) -> Result<T, CCIPReadMiddlewareError<M>> {
145        self.query_resolver_parameters(param, ens_name, selector, None)
146            .await
147    }
148
149    async fn query_resolver_parameters<T: Detokenize>(
150        &self,
151        param: ParamType,
152        ens_name: &str,
153        selector: Selector,
154        parameters: Option<&[u8]>,
155    ) -> Result<T, CCIPReadMiddlewareError<M>> {
156        let resolver_address = self.get_resolver(ens_name).await?;
157
158        let mut tx: TypedTransaction =
159            ens::resolve(resolver_address, selector, ens_name, parameters).into();
160
161        let mut parse_bytes = false;
162        if self.supports_wildcard(resolver_address).await? {
163            parse_bytes = true;
164
165            let dns_encode_token = Token::Bytes(dns_encode(ens_name).unwrap());
166            let tx_data_token = Token::Bytes(tx.data().unwrap().to_vec());
167
168            let tokens = vec![dns_encode_token, tx_data_token];
169
170            let encoded_data = abi::encode(&tokens);
171
172            let resolve_selector = "9061b923";
173
174            // selector("resolve(bytes,bytes)")
175            tx.set_data(Bytes::from(
176                [hex::decode(resolve_selector).unwrap(), encoded_data].concat(),
177            ));
178        }
179
180        // resolve
181        let mut data = self.call(&tx, None).await?;
182        if parse_bytes {
183            data = decode_bytes(ParamType::Bytes, &data)?;
184        }
185
186        Ok(decode_bytes(param, &data)?)
187    }
188
189    pub async fn get_resolver(&self, ens_name: &str) -> Result<H160, CCIPReadMiddlewareError<M>> {
190        let ens_addr = self.ens;
191
192        let names: Vec<&str> =
193            successors(Some(ens_name), |&last| last.split_once('.').map(|it| it.1)).collect();
194
195        for name in names {
196            if name.is_empty() || name.eq(".") {
197                return Ok(H160::zero());
198            }
199
200            if !ens_name.eq("eth") && name.eq("eth") {
201                return Ok(H160::zero());
202            }
203
204            let data = self
205                .call(&ens::get_resolver(ens_addr, name).into(), None)
206                .await?;
207
208            if data.0.is_empty() {
209                return Ok(H160::zero());
210            }
211
212            let resolver_address: Address = decode_bytes(ParamType::Address, &data)?;
213
214            if resolver_address != Address::zero() {
215                if name != ens_name && !self.supports_wildcard(resolver_address).await? {
216                    return Ok(H160::zero());
217                }
218                return Ok(resolver_address);
219            }
220        }
221
222        Ok(H160::zero())
223    }
224
225    #[cfg_attr(target_arch = "wasm32", async_recursion(?Send))]
226    #[cfg_attr(not(target_arch = "wasm32"), async_recursion)]
227    async fn _call(
228        &self,
229        transaction: &TypedTransaction,
230        block_id: Option<BlockId>,
231        attempt: u8,
232        requests_buffer: &mut Vec<CCIPRequest>,
233    ) -> Result<(Bytes, Vec<CCIPRequest>), CCIPReadMiddlewareError<M>> {
234        if attempt >= self.max_redirect_attempt {
235            // may need more info
236            return Err(CCIPReadMiddlewareError::MaxRedirectionError);
237        }
238
239        let tx_sender = match transaction.to().unwrap() {
240            NameOrAddress::Name(ens_name) => self.resolve_name(ens_name).await?,
241            NameOrAddress::Address(addr) => *addr,
242        };
243
244        let result = self
245            .inner()
246            .call(transaction, block_id)
247            .await
248            .or_else(|err| {
249                let Some(rpc_err) = err.as_error_response() else {
250                    return Err(CCIPReadMiddlewareError::MiddlewareError(err));
251                };
252
253                let Some(Value::String(data)) = rpc_err.clone().data else {
254                    return Err(CCIPReadMiddlewareError::MiddlewareError(err));
255                };
256
257                let bytes = Bytes::from_hex(data)?;
258
259                if !bytes.starts_with(OFFCHAIN_LOOKUP_SELECTOR) {
260                    return Err(CCIPReadMiddlewareError::MiddlewareError(err));
261                }
262
263                Ok(bytes)
264            })?;
265
266        if !matches!(block_id.unwrap_or(BlockId::Number(BlockNumber::Latest)), BlockId::Number(block) if block.is_latest())
267        {
268            return Ok((result, requests_buffer.to_vec()));
269        }
270
271        if tx_sender.is_zero() || result.len() % 32 != 4 {
272            return Ok((result, requests_buffer.to_vec()));
273        }
274
275        let output_types = vec![
276            ParamType::Address,                            // 'address'
277            ParamType::Array(Box::new(ParamType::String)), // 'string[]'
278            ParamType::Bytes,                              // 'bytes'
279            ParamType::FixedBytes(4),                      // 'bytes4'
280            ParamType::Bytes,                              // 'bytes'
281        ];
282
283        let decoded_data: Vec<Token> = abi::decode(&output_types, &result[4..])?;
284
285        #[allow(clippy::get_first)]
286        let (
287            Some(Token::Address(sender)),
288            Some(Token::Array(urls)),
289            Some(Token::Bytes(calldata)),
290            Some(Token::FixedBytes(callback_selector)),
291            Some(Token::Bytes(extra_data)),
292        ) = (
293            decoded_data.get(0),
294            decoded_data.get(1),
295            decoded_data.get(2),
296            decoded_data.get(3),
297            decoded_data.get(4),
298        )
299        else {
300            return Ok((result, requests_buffer.to_vec()));
301        };
302
303        let urls: Vec<String> = urls
304            .iter()
305            .cloned()
306            // NOTE: not sure about how good filter_map is here
307            //  i.e. should we return an error or handle it more gracefully?
308            //  for now, ignoring non-string values is definitely better than panicking
309            .filter_map(|t| t.into_string())
310            .collect();
311
312        if !sender.eq(&tx_sender) {
313            return Err(CCIPReadMiddlewareError::SenderError {
314                sender: format!("0x{:x}", sender),
315            });
316        }
317
318        let (ccip_result, requests) =
319            handle_ccip(&self.reqwest_client, sender, transaction, calldata, urls).await?;
320
321        requests_buffer.extend(requests);
322
323        if ccip_result.is_empty() {
324            return Err(CCIPReadMiddlewareError::GatewayNotFoundError);
325        }
326
327        let ccip_result_token = Token::Bytes(ethers_core::abi::Bytes::from(ccip_result.as_ref()));
328        let extra_data_token = Token::Bytes(extra_data.clone());
329
330        let encoded_data = abi::encode(&[ccip_result_token, extra_data_token]);
331
332        let mut callback_tx = transaction.clone();
333        callback_tx.set_data(Bytes::from(
334            [callback_selector.clone(), encoded_data.clone()].concat(),
335        ));
336
337        self._call(&callback_tx, block_id, attempt + 1, requests_buffer)
338            .await
339    }
340
341    /// Call the underlying middleware with the provided transaction and block,
342    /// returning both the result of the call and the CCIP requests made during the call
343    pub async fn call_ccip(
344        &self,
345        tx: &TypedTransaction,
346        block: Option<BlockId>,
347    ) -> Result<(Bytes, Vec<CCIPRequest>), CCIPReadMiddlewareError<M>> {
348        let mut requests = Vec::new();
349        self._call(tx, block, 0, &mut requests).await
350    }
351}
352
353/// Middleware implementation for CCIPReadMiddleware
354#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
355#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
356impl<M> Middleware for CCIPReadMiddleware<M>
357where
358    M: Middleware,
359{
360    type Error = CCIPReadMiddlewareError<M>;
361    type Provider = M::Provider;
362    type Inner = M;
363
364    /// Get a reference to the inner middleware
365    fn inner(&self) -> &M {
366        &self.provider
367    }
368
369    /// Call the underlying middleware with the provided transaction and block
370    async fn call(
371        &self,
372        tx: &TypedTransaction,
373        block: Option<BlockId>,
374    ) -> Result<Bytes, Self::Error> {
375        Ok(self.call_ccip(tx, block).await?.0)
376    }
377
378    /**
379    The following couple of methods were copied from ethers-rs, and modified to work with ENSIP-10
380    **/
381
382    /// Resolve a field of an ENS name
383    async fn resolve_field(&self, ens_name: &str, field: &str) -> Result<String, Self::Error> {
384        let field: String = self
385            .query_resolver_parameters(
386                ParamType::String,
387                ens_name,
388                ens::FIELD_SELECTOR,
389                Some(&ens::parameterhash(field)),
390            )
391            .await?;
392        Ok(field)
393    }
394
395    /// Resolve avatar field of an ENS name
396    async fn resolve_avatar(&self, ens_name: &str) -> Result<Url, Self::Error> {
397        let (field, owner) = try_join!(
398            self.resolve_field(ens_name, "avatar"),
399            self.resolve_name(ens_name)
400        )?;
401        let url = Url::from_str(&field)
402            .map_err(|e| CCIPReadMiddlewareError::URLParseError(e.to_string()))?;
403        match url.scheme() {
404            "https" | "data" => Ok(url),
405            "ipfs" => erc::http_link_ipfs(url).map_err(CCIPReadMiddlewareError::URLParseError),
406            "eip155" => {
407                let token = erc::ERCNFT::from_str(url.path())
408                    .map_err(CCIPReadMiddlewareError::URLParseError)?;
409                match token.type_ {
410                    erc::ERCNFTType::ERC721 => {
411                        let tx = TransactionRequest {
412                            data: Some(
413                                [&erc::ERC721_OWNER_SELECTOR[..], &token.id].concat().into(),
414                            ),
415                            to: Some(NameOrAddress::Address(token.contract)),
416                            ..Default::default()
417                        };
418                        let data = self.call(&tx.into(), None).await?;
419                        if decode_bytes::<Address>(ParamType::Address, &data)? != owner {
420                            return Err(CCIPReadMiddlewareError::NFTOwnerError(
421                                "Incorrect owner.".to_string(),
422                            ));
423                        }
424                    }
425                    erc::ERCNFTType::ERC1155 => {
426                        let tx = TransactionRequest {
427                            data: Some(
428                                [
429                                    &erc::ERC1155_BALANCE_SELECTOR[..],
430                                    &[0x0; 12],
431                                    &owner.0,
432                                    &token.id,
433                                ]
434                                .concat()
435                                .into(),
436                            ),
437                            to: Some(NameOrAddress::Address(token.contract)),
438                            ..Default::default()
439                        };
440                        let data = self.call(&tx.into(), None).await?;
441                        if decode_bytes::<u64>(ParamType::Uint(64), &data)? == 0 {
442                            return Err(CCIPReadMiddlewareError::NFTOwnerError(
443                                "Incorrect balance.".to_string(),
444                            ));
445                        }
446                    }
447                }
448
449                let image_url = self.resolve_nft(token).await?;
450                match image_url.scheme() {
451                    "https" | "data" => Ok(image_url),
452                    "ipfs" => erc::http_link_ipfs(image_url)
453                        .map_err(CCIPReadMiddlewareError::URLParseError),
454                    _ => Err(CCIPReadMiddlewareError::UnsupportedURLSchemeError),
455                }
456            }
457            _ => Err(CCIPReadMiddlewareError::UnsupportedURLSchemeError),
458        }
459    }
460
461    /// Resolve an ENS name to an address
462    async fn resolve_name(&self, ens_name: &str) -> Result<Address, Self::Error> {
463        self.query_resolver(ParamType::Address, ens_name, ens::ADDR_SELECTOR)
464            .await
465    }
466
467    /// Look up an address to find its primary ENS name
468    async fn lookup_address(&self, address: Address) -> Result<String, Self::Error> {
469        let ens_name = ens::reverse_address(address);
470        let domain: String = self
471            .query_resolver(ParamType::String, &ens_name, ens::NAME_SELECTOR)
472            .await?;
473        let reverse_address = self.resolve_name(&domain).await?;
474        if address != reverse_address {
475            Err(CCIPReadMiddlewareError::EnsNotOwned(domain))
476        } else {
477            Ok(domain)
478        }
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use ethers_core::types::TransactionRequest;
485    use ethers_providers::{JsonRpcError, MockResponse, Provider, MAINNET};
486
487    use super::*;
488
489    #[tokio::test]
490    async fn test_eip_2544_ens_wildcards() {
491        let provider = CCIPReadMiddleware::new(MAINNET.provider());
492
493        let ens_name = "1.offchainexample.eth";
494        let resolver_address = provider.get_resolver(ens_name).await.unwrap();
495        assert_eq!(
496        resolver_address,
497        Address::from_str("0xC1735677a60884ABbCF72295E88d47764BeDa282").unwrap(),
498        "Expected resolver_address to be 0xC1735677a60884ABbCF72295E88d47764BeDa282, but got {}",
499        resolver_address
500    );
501
502        let supports_wildcard = provider.supports_wildcard(resolver_address).await.unwrap();
503        assert!(
504            supports_wildcard,
505            "Wildcard is not supported, expected to be true"
506        );
507
508        let resolved_address = provider.resolve_name(ens_name).await.unwrap();
509        assert_eq!(
510        resolved_address,
511        Address::from_str("0x41563129cDbbD0c5D3e1c86cf9563926b243834d").unwrap(),
512        "Expected resolved_address to be 0x41563129cDbbD0c5D3e1c86cf9563926b243834d, but got {}",
513        resolved_address
514    );
515    }
516
517    #[tokio::test]
518    async fn test_ccip_call() {
519        let resolver_address = "0xC1735677a60884ABbCF72295E88d47764BeDa282";
520        let email = "nick@ens.domains";
521
522        let provider = CCIPReadMiddleware::new(MAINNET.provider());
523
524        let tx = TransactionRequest {
525        // parameters = text(bytes32 node, string calldata key) node: namehash('1.offchainexample.eth'), key: 'email'
526        // tx_data = selector(resolve(bytes,bytes)), namehash(name), parameters
527        // ensip10 interface + encode(dnsencode(name), tx_data)
528        data: Some(Bytes::from(hex::decode("9061b92300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001701310f6f6666636861696e6578616d706c650365746800000000000000000000000000000000000000000000000000000000000000000000000000000000008459d1d43c1c9fb8c1fe76f464ccec6d2c003169598fdfcbcb6bbddf6af9c097a39fa0048c00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000005656d61696c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap())),
529        to: Some(resolver_address.into()),
530        ..Default::default()
531    }.into();
532
533        let result = provider.call(&tx, None).await.unwrap();
534
535        let data: Bytes = decode_bytes(ParamType::Bytes, &result).unwrap();
536        let record: String = decode_bytes(ParamType::String, &data).unwrap();
537
538        assert_eq!(record, email);
539    }
540
541    #[tokio::test]
542    async fn test_mismatched_sender() {
543        let resolver_address = "0xC1735677a60884ABbCF72295E88d47764BeDa282";
544
545        let (provider, mock) = Provider::mocked();
546        let provider = CCIPReadMiddleware::new(provider);
547
548        let tx: TypedTransaction = TransactionRequest {
549            // parameters = text(bytes32 node, string calldata key) node: namehash('1.offchainexample.eth'), key: 'email'
550            // tx_data = selector(resolve(bytes,bytes)), namehash(name), parameters
551            // ensip10 interface + encode(dnsencode(name), tx_data)
552            data: Some(Bytes::from(hex::decode("9061b92300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001701310f6f6666636861696e6578616d706c650365746800000000000000000000000000000000000000000000000000000000000000000000000000000000008459d1d43c1c9fb8c1fe76f464ccec6d2c003169598fdfcbcb6bbddf6af9c097a39fa0048c00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000005656d61696c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap())),
553            to: Some(resolver_address.into()),
554            ..Default::default()
555        }.into();
556
557        let error_code = 3;
558        // sender information altered to c1735677a60884abbcf72295e88d47764beda283
559        let error_data = r#""0x556f1830000000000000000000000000c1735677a60884abbcf72295e88d47764beda28300000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000160f4d4d2f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004768747470733a2f2f6f6666636861696e2d7265736f6c7665722d6578616d706c652e75632e722e61707073706f742e636f6d2f7b73656e6465727d2f7b646174617d2e6a736f6e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001449061b92300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001701310f6f6666636861696e6578616d706c650365746800000000000000000000000000000000000000000000000000000000000000000000000000000000008459d1d43c1c9fb8c1fe76f464ccec6d2c003169598fdfcbcb6bbddf6af9c097a39fa0048c00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000005656d61696c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001449061b92300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001701310f6f6666636861696e6578616d706c650365746800000000000000000000000000000000000000000000000000000000000000000000000000000000008459d1d43c1c9fb8c1fe76f464ccec6d2c003169598fdfcbcb6bbddf6af9c097a39fa0048c00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000005656d61696c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000""#;
560        let error_message = "execution reverted";
561        let error = JsonRpcError {
562            code: error_code,
563            data: Some(serde_json::from_str(error_data).unwrap()),
564            message: error_message.to_string(),
565        };
566        mock.push_response(MockResponse::Error(error.clone()));
567
568        let result = provider.call(&tx, None).await;
569        assert!(result.is_err());
570        assert_eq!(
571            result.unwrap_err().to_string(),
572            format!(
573                "CCIP Read sender did not match {}",
574                "0xc1735677a60884abbcf72295e88d47764beda283"
575            )
576        );
577    }
578}