1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! Module registry is a service that provides the ability to fetch modules from the StarkNet network.
//! It fetch contract class from the StarkNet network and compile it to the casm.

use anyhow::{bail, Result};
use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass;
use starknet::{
    core::types::{BlockId, BlockTag, ContractClass},
    providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider, Url},
};
use starknet_crypto::FieldElement;
use tracing::info;

use crate::conversion::flattened_sierra_to_compiled_class;

pub struct ModuleRegistry {
    provider: JsonRpcClient<HttpTransport>,
}

impl ModuleRegistry {
    pub fn new(url: Url) -> Self {
        let provider = JsonRpcClient::new(HttpTransport::new(url));
        Self { provider }
    }

    pub async fn get_module_class(&self, class_hash: FieldElement) -> Result<CasmContractClass> {
        info!(
            "Fetching contract class from module registry... Class hash: {}",
            class_hash
        );
        let contract_class = self
            ._starknet_get_class(BlockId::Tag(BlockTag::Latest), class_hash)
            .await?;
        info!("Contract class fetched successfully");
        let sierra = match contract_class {
            ContractClass::Sierra(sierra) => sierra,
            _ => bail!("cairo1 module should have sierra as class"),
        };
        flattened_sierra_to_compiled_class(&sierra)
    }

    async fn _starknet_get_class(
        &self,
        block_id: BlockId,
        class_hash: FieldElement,
    ) -> Result<ContractClass> {
        let contract_class = self.provider.get_class(block_id, class_hash).await?;
        Ok(contract_class)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::constant::TEST_CONTRACT_CASM;

    fn init() -> (ModuleRegistry, FieldElement) {
        let url = Url::parse(
            "https://starknet-sepolia.g.alchemy.com/v2/lINonYKIlp4NH9ZI6wvqJ4HeZj7T4Wm6",
        )
        .unwrap();
        let module_registry = ModuleRegistry::new(url);
        // This is test contract class hash
        let class_hash = FieldElement::from_hex_be(
            "0x054af96825d987ca89cf320f7c5a8031017815d884cff1592e8ff6da309f3ca6",
        )
        .unwrap();

        (module_registry, class_hash)
    }

    #[tokio::test]
    async fn test_get_module() {
        let (module_registry, class_hash) = init();
        let casm_from_rpc = module_registry.get_module_class(class_hash).await.unwrap();

        assert_eq!(casm_from_rpc, TEST_CONTRACT_CASM.clone());
    }

    #[tokio::test]
    async fn test_flattened_sierra_to_compiled_class() {
        let (module_registry, class_hash) = init();
        let contract_class = module_registry
            ._starknet_get_class(BlockId::Tag(BlockTag::Latest), class_hash)
            .await
            .unwrap();
        let sierra = match contract_class {
            ContractClass::Sierra(sierra) => sierra,
            _ => panic!("cairo1 module should have sierra as class"),
        };
        let casm_from_rpc = flattened_sierra_to_compiled_class(&sierra).unwrap();
        assert_eq!(casm_from_rpc, TEST_CONTRACT_CASM.clone());
    }
}