|
| 1 | +use crate::starknet_commands::declare::declare_with_artifacts; |
| 2 | +use anyhow::{Context, Result}; |
| 3 | +use clap::Args; |
| 4 | +use foundry_ui::UI; |
| 5 | +use shared::verify_and_warn_if_incompatible_rpc_version; |
| 6 | +use sncast::helpers::fee::FeeArgs; |
| 7 | +use sncast::helpers::rpc::{FreeProvider, RpcArgs}; |
| 8 | +use sncast::response::declare::DeclareResponse; |
| 9 | +use sncast::response::errors::{SNCastProviderError, StarknetCommandError}; |
| 10 | +use sncast::{Network, WaitForTx, get_block_id, get_provider}; |
| 11 | +use starknet::accounts::SingleOwnerAccount; |
| 12 | +use starknet::core::types::contract::{AbiEntry, CompiledClass, SierraClass, SierraClassDebugInfo}; |
| 13 | +use starknet::core::types::{ContractClass, FlattenedSierraClass}; |
| 14 | +use starknet::providers::Provider; |
| 15 | +use starknet::providers::jsonrpc::{HttpTransport, JsonRpcClient}; |
| 16 | +use starknet::signers::LocalWallet; |
| 17 | +use starknet_types_core::felt::Felt; |
| 18 | +use universal_sierra_compiler_api::{SierraType, compile_sierra}; |
| 19 | + |
| 20 | +#[derive(Args)] |
| 21 | +#[command(about = "Declare a contract by fetching it from a different Starknet instance", long_about = None)] |
| 22 | +pub struct DeclareFrom { |
| 23 | + /// Class hash of contract declared on a different Starknet instance |
| 24 | + #[arg(short = 'g', long)] |
| 25 | + pub class_hash: Felt, |
| 26 | + |
| 27 | + #[command(flatten)] |
| 28 | + pub fee_args: FeeArgs, |
| 29 | + |
| 30 | + /// Nonce of the transaction. If not provided, nonce will be set automatically |
| 31 | + #[arg(short, long)] |
| 32 | + pub nonce: Option<Felt>, |
| 33 | + |
| 34 | + #[command(flatten)] |
| 35 | + pub source_rpc: SourceRpcArgs, |
| 36 | + |
| 37 | + #[command(flatten)] |
| 38 | + pub rpc: RpcArgs, |
| 39 | + |
| 40 | + /// Block identifier from which the contract will be fetched. |
| 41 | + /// Possible values: `pre_confirmed`, `latest`, block hash (0x prefixed string) |
| 42 | + /// and block number (u64) |
| 43 | + #[arg(short, long, default_value = "latest")] |
| 44 | + pub block_id: String, |
| 45 | +} |
| 46 | + |
| 47 | +#[derive(Args, Clone, Debug, Default)] |
| 48 | +#[group(required = false, multiple = false)] |
| 49 | +pub struct SourceRpcArgs { |
| 50 | + /// RPC provider url address |
| 51 | + #[arg(short, long)] |
| 52 | + pub source_url: Option<String>, |
| 53 | + |
| 54 | + /// Use predefined network with a public provider. Note that this option may result in rate limits or other unexpected behavior |
| 55 | + #[arg(long)] |
| 56 | + pub source_network: Option<Network>, |
| 57 | +} |
| 58 | + |
| 59 | +impl SourceRpcArgs { |
| 60 | + pub async fn get_provider(&self, ui: &UI) -> Result<JsonRpcClient<HttpTransport>> { |
| 61 | + let url = self |
| 62 | + .get_url() |
| 63 | + .context("Either `--source-network` or `--source-url` must be provided")?; |
| 64 | + |
| 65 | + assert!(!url.is_empty(), "url cannot be empty"); |
| 66 | + |
| 67 | + let provider = get_provider(&url)?; |
| 68 | + verify_and_warn_if_incompatible_rpc_version(&provider, url, ui).await?; |
| 69 | + |
| 70 | + Ok(provider) |
| 71 | + } |
| 72 | + |
| 73 | + #[must_use] |
| 74 | + fn get_url(&self) -> Option<String> { |
| 75 | + if let Some(network) = self.source_network { |
| 76 | + let free_provider = FreeProvider::semi_random(); |
| 77 | + Some(network.url(&free_provider)) |
| 78 | + } else { |
| 79 | + self.source_url |
| 80 | + .as_ref() |
| 81 | + .map(std::string::ToString::to_string) |
| 82 | + } |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +pub async fn declare_from( |
| 87 | + declare_from: DeclareFrom, |
| 88 | + account: &SingleOwnerAccount<&JsonRpcClient<HttpTransport>, LocalWallet>, |
| 89 | + wait_config: WaitForTx, |
| 90 | + skip_on_already_declared: bool, |
| 91 | + source_provider: &JsonRpcClient<HttpTransport>, |
| 92 | + ui: &UI, |
| 93 | +) -> Result<DeclareResponse, StarknetCommandError> { |
| 94 | + let block_id = get_block_id(&declare_from.block_id)?; |
| 95 | + let class = source_provider |
| 96 | + .get_class(block_id, declare_from.class_hash) |
| 97 | + .await |
| 98 | + .map_err(SNCastProviderError::from) |
| 99 | + .map_err(StarknetCommandError::from)?; |
| 100 | + |
| 101 | + let flattened_sierra = match class { |
| 102 | + ContractClass::Sierra(class) => class, |
| 103 | + ContractClass::Legacy(_) => { |
| 104 | + return Err(StarknetCommandError::UnknownError(anyhow::anyhow!( |
| 105 | + "Declaring from Cairo 0 (legacy) contracts is not supported" |
| 106 | + ))); |
| 107 | + } |
| 108 | + }; |
| 109 | + let sierra: SierraClass = flattened_sierra_to_sierra(flattened_sierra) |
| 110 | + .expect("Failed to parse flattened sierra class"); |
| 111 | + |
| 112 | + let casm_json: String = compile_sierra( |
| 113 | + &serde_json::to_value(&sierra).expect("Failed to convert sierra to json value"), |
| 114 | + &SierraType::Contract, |
| 115 | + ) |
| 116 | + .expect("Failed to compile sierra to casm"); |
| 117 | + let casm: CompiledClass = serde_json::from_str(&casm_json) |
| 118 | + .expect("Failed to deserialize casm JSON into CompiledClass"); |
| 119 | + let sierra_class_hash = sierra.class_hash().map_err(anyhow::Error::from)?; |
| 120 | + |
| 121 | + if declare_from.class_hash != sierra_class_hash { |
| 122 | + return Err(StarknetCommandError::UnknownError(anyhow::anyhow!( |
| 123 | + "The provided sierra class hash {:#x} does not match the computed class hash {:#x} from the fetched contract.", |
| 124 | + declare_from.class_hash, |
| 125 | + sierra_class_hash |
| 126 | + ))); |
| 127 | + } |
| 128 | + |
| 129 | + declare_with_artifacts( |
| 130 | + sierra, |
| 131 | + casm, |
| 132 | + declare_from.fee_args, |
| 133 | + declare_from.nonce, |
| 134 | + account, |
| 135 | + wait_config, |
| 136 | + skip_on_already_declared, |
| 137 | + ui, |
| 138 | + ) |
| 139 | + .await |
| 140 | +} |
| 141 | + |
| 142 | +fn flattened_sierra_to_sierra(class: FlattenedSierraClass) -> Result<SierraClass> { |
| 143 | + Ok(SierraClass { |
| 144 | + sierra_program: class.sierra_program, |
| 145 | + sierra_program_debug_info: SierraClassDebugInfo { |
| 146 | + type_names: vec![], |
| 147 | + libfunc_names: vec![], |
| 148 | + user_func_names: vec![], |
| 149 | + }, |
| 150 | + contract_class_version: class.contract_class_version, |
| 151 | + entry_points_by_type: class.entry_points_by_type, |
| 152 | + abi: serde_json::from_str::<Vec<AbiEntry>>(&class.abi)?, |
| 153 | + }) |
| 154 | +} |
0 commit comments