Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions crates/sncast/src/helpers/block_explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,33 +129,34 @@ impl LinkProvider for OkLink {

#[cfg(test)]
mod tests {
use crate::response::deploy::DeployResponse;
use crate::{
Network,
helpers::block_explorer::Service,
response::{deploy::DeployResponse, explorer_link::OutputLink},
response::{deploy::StandardDeployResponse, explorer_link::OutputLink},
};
use conversions::padded_felt::PaddedFelt;
use regex::Regex;
use starknet::macros::felt;
use test_case::test_case;

const MAINNET_RESPONSE: DeployResponse = DeployResponse {
const MAINNET_RESPONSE: DeployResponse = DeployResponse::Deploy(StandardDeployResponse {
contract_address: PaddedFelt(felt!(
"0x03241d40a2af53a34274dd411e090ccac1ea80e0380a0303fe76d71b046cfecb"
)),
transaction_hash: PaddedFelt(felt!(
"0x7605291e593e0c6ad85681d09e27a601befb85033bdf1805aabf5d84617cf68"
)),
};
});

const SEPOLIA_RESPONSE: DeployResponse = DeployResponse {
const SEPOLIA_RESPONSE: DeployResponse = DeployResponse::Deploy(StandardDeployResponse {
contract_address: PaddedFelt(felt!(
"0x0716b5f1e3bd760c489272fd6530462a09578109049e26e3f4c70492676eae17"
)),
transaction_hash: PaddedFelt(felt!(
"0x1cde70aae10f79d2d1289c923a1eeca7b81a2a6691c32551ec540fa2cb29c33"
)),
};
});

async fn assert_valid_links(input: &str) {
let pattern = Regex::new(r"transaction: |contract: |class: ").unwrap();
Expand Down
85 changes: 81 additions & 4 deletions crates/sncast/src/main.rs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's lots of new code and logic here, which makes our branches even more overgrown 😅 . We should either move it to deploy function, or have separate wrapper (e.g. deploy_with_declare), which later calls deploy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way I wanted this to be implemented is that deploying with declaring is composed only using top level APIs, but they remain two entirely separate functions.

We could do a separate refactor of main and move out things that don't have to be there to separate modules. But I don't think hiding top level logic for functions is beneficial.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is copied from declare and could technically be shared, but otherwise logic for declaring in deploy and normal declare are slightly different

            let manifest_path = assert_manifest_path_exists()?;
            let package_metadata = get_package_metadata(&manifest_path, &declare.package)?;
            let artifacts = build_and_load_artifacts(
                &package_metadata,
                &BuildConfig {
                    scarb_toml_path: manifest_path,
                    json: cli.json,
                    profile: cli.profile.unwrap_or("release".to_string()),
                },
                false,
                ui,
            )
            .expect("Failed to build contract");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we have deploy_with_declare and deploy wrappers, which will share some common logic (most likely extracted into another function)?

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::starknet_commands::declare::declare;
use crate::starknet_commands::declare_from::DeclareFrom;
use crate::starknet_commands::deploy::DeployArguments;
use crate::starknet_commands::multicall;
Expand All @@ -11,6 +12,7 @@ use anyhow::{Context, Result, bail};
use camino::Utf8PathBuf;
use clap::{CommandFactory, Parser, Subcommand};
use configuration::load_config;
use conversions::IntoConv;
use data_transformer::transform;
use foundry_ui::components::warning::WarningMessage;
use foundry_ui::{Message, UI};
Expand All @@ -24,7 +26,10 @@ use sncast::helpers::rpc::generate_network_flag;
use sncast::helpers::scarb_utils::{
BuildConfig, assert_manifest_path_exists, build_and_load_artifacts, get_package_metadata,
};
use sncast::response::declare::{DeclareResponse, DeployCommandMessage};
use sncast::response::declare::{
AlreadyDeclaredResponse, DeclareResponse, DeclareTransactionResponse, DeployCommandMessage,
};
use sncast::response::deploy::{DeployResponse, DeployResponseWithDeclare};
use sncast::response::errors::handle_starknet_command_error;
use sncast::response::explorer_link::block_explorer_link_if_allowed;
use sncast::response::transformed_call::transform_response;
Expand Down Expand Up @@ -390,9 +395,11 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()>

Commands::Deploy(deploy) => {
let Deploy {
contract_identifier: identifier,
arguments,
fee_args,
rpc,
mut nonce,
..
} = deploy;

Expand All @@ -406,28 +413,98 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()>
)
.await?;

let (class_hash, declare_response) = if let Some(class_hash) = identifier.class_hash {
(class_hash, None)
} else if let Some(contract_name) = identifier.contract_name {
let manifest_path = assert_manifest_path_exists()?;
let package_metadata = get_package_metadata(&manifest_path, &None)?;
let artifacts = build_and_load_artifacts(
&package_metadata,
&BuildConfig {
scarb_toml_path: manifest_path,
json: cli.json,
profile: cli.profile.unwrap_or("release".to_string()),
},
false,
ui,
)
.expect("Failed to build contract");

let declare_result = declare(
contract_name,
fee_args.clone(),
nonce,
&account,
&artifacts,
WaitForTx {
wait: true,
wait_params: wait_config.wait_params,
show_ui_outputs: true,
},
true,
ui,
)
.await
.map_err(handle_starknet_command_error);

// Increment nonce after successful declare if it was explicitly provided
nonce = nonce.map(|n| n + Felt::ONE);

match declare_result {
Ok(DeclareResponse::AlreadyDeclared(AlreadyDeclaredResponse {
class_hash,
})) => (class_hash.into_(), None),
Ok(DeclareResponse::Success(declare_transaction_response)) => (
declare_transaction_response.class_hash.into_(),
Some(declare_transaction_response),
),
Err(err) => {
process_command_result::<DeclareTransactionResponse>(
"deploy",
Err(err),
ui,
None,
);
return Ok(());
}
}
} else {
unreachable!("One of class_hash or contract_name must be provided");
};

// safe to unwrap because "constructor" is a standardized name
let selector = get_selector_from_name("constructor").unwrap();

let contract_class = get_contract_class(deploy.class_hash, &provider).await?;
let contract_class = get_contract_class(class_hash, &provider).await?;

let arguments: Arguments = arguments.into();
let calldata = arguments.try_into_calldata(contract_class, &selector)?;

let result = starknet_commands::deploy::deploy(
deploy.class_hash,
class_hash,
&calldata,
deploy.salt,
deploy.unique,
fee_args,
deploy.nonce,
nonce,
&account,
wait_config,
ui,
)
.await
.map_err(handle_starknet_command_error);

let result = if let Some(declare_response) = declare_response {
result.map(|r| {
DeployResponse::DeployWithDeclare(DeployResponseWithDeclare::from_responses(
&r,
&declare_response,
))
})
} else {
result.map(DeployResponse::Deploy)
};

let block_explorer_link =
block_explorer_link_if_allowed(&result, provider.chain_id().await?, &rpc, &config);
process_command_result("deploy", result, ui, block_explorer_link);
Expand Down
135 changes: 107 additions & 28 deletions crates/sncast/src/response/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
use super::command::CommandResponse;
use crate::helpers::block_explorer::LinkProvider;
use crate::response::cast_message::SncastMessage;
use crate::response::declare::DeclareTransactionResponse;
use crate::response::explorer_link::OutputLink;
use conversions::string::IntoPaddedHexStr;
use conversions::{padded_felt::PaddedFelt, serde::serialize::CairoSerialize};
use foundry_ui::Message;
use foundry_ui::styling;
use indoc::formatdoc;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_json::json;

use super::{command::CommandResponse, explorer_link::OutputLink};
use crate::response::cast_message::SncastMessage;
use serde_json::{Value, json};

#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
pub struct DeployResponse {
pub contract_address: PaddedFelt,
pub transaction_hash: PaddedFelt,
#[serde(untagged)]
pub enum DeployResponse {
Deploy(StandardDeployResponse),
DeployWithDeclare(DeployResponseWithDeclare),
}

impl CommandResponse for DeployResponse {}

impl Message for SncastMessage<DeployResponse> {
fn text(&self) -> String {
styling::OutputBuilder::new()
.success_message("Deployment completed")
.blank_line()
.field(
"Contract Address",
&self.command_response.contract_address.into_padded_hex_str(),
)
.field(
"Transaction Hash",
&self.command_response.transaction_hash.into_padded_hex_str(),
)
.build()
match &self.command_response {
DeployResponse::Deploy(response) => response.text(),
DeployResponse::DeployWithDeclare(response) => response.text(),
}
}

fn json(&self) -> Value {
Expand All @@ -50,13 +43,99 @@ impl OutputLink for DeployResponse {
const TITLE: &'static str = "deployment";

fn format_links(&self, provider: Box<dyn LinkProvider>) -> String {
formatdoc!(
"
contract: {}
transaction: {}
",
provider.contract(self.contract_address),
provider.transaction(self.transaction_hash)
)
match self {
DeployResponse::Deploy(deploy) => {
formatdoc!(
"
contract: {}
transaction: {}
",
provider.contract(deploy.contract_address),
provider.transaction(deploy.transaction_hash)
)
}
DeployResponse::DeployWithDeclare(deploy_with_declare) => {
formatdoc!(
"
contract: {}
class: {}
declare transaction: {}
deploy transaction: {}
",
provider.contract(deploy_with_declare.contract_address),
provider.class(deploy_with_declare.class_hash),
provider.transaction(deploy_with_declare.declare_transaction_hash),
provider.transaction(deploy_with_declare.deploy_transaction_hash)
)
}
}
}
}

#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
pub struct StandardDeployResponse {
pub contract_address: PaddedFelt,
pub transaction_hash: PaddedFelt,
}

impl StandardDeployResponse {
fn text(&self) -> String {
styling::OutputBuilder::new()
.success_message("Deployment completed")
.blank_line()
.field(
"Contract Address",
&self.contract_address.into_padded_hex_str(),
)
.field(
"Transaction Hash",
&self.transaction_hash.into_padded_hex_str(),
)
.build()
}
}

#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
pub struct DeployResponseWithDeclare {
contract_address: PaddedFelt,
class_hash: PaddedFelt,
deploy_transaction_hash: PaddedFelt,
declare_transaction_hash: PaddedFelt,
}

impl DeployResponseWithDeclare {
#[must_use]
pub fn from_responses(
deploy: &StandardDeployResponse,
declare: &DeclareTransactionResponse,
) -> Self {
Self {
contract_address: deploy.contract_address,
class_hash: declare.class_hash,
deploy_transaction_hash: deploy.transaction_hash,
declare_transaction_hash: declare.transaction_hash,
}
}
}

impl DeployResponseWithDeclare {
fn text(&self) -> String {
styling::OutputBuilder::new()
.success_message("Deployment completed")
.blank_line()
.field(
"Contract Address",
&self.contract_address.into_padded_hex_str(),
)
.field("Class Hash", &self.class_hash.into_padded_hex_str())
.field(
"Declare Transaction Hash",
&self.declare_transaction_hash.into_padded_hex_str(),
)
.field(
"Deploy Transaction Hash",
&self.deploy_transaction_hash.into_padded_hex_str(),
)
.build()
}
}
23 changes: 17 additions & 6 deletions crates/sncast/src/starknet_commands/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use conversions::IntoConv;
use foundry_ui::UI;
use sncast::helpers::fee::{FeeArgs, FeeSettings};
use sncast::helpers::rpc::RpcArgs;
use sncast::response::deploy::DeployResponse;
use sncast::response::deploy::StandardDeployResponse;
use sncast::response::errors::StarknetCommandError;
use sncast::{WaitForTx, apply_optional_fields, handle_wait_for_tx};
use sncast::{extract_or_generate_salt, udc_uniqueness};
Expand All @@ -17,12 +17,23 @@ use starknet::providers::jsonrpc::HttpTransport;
use starknet::signers::LocalWallet;
use starknet_types_core::felt::Felt;

#[derive(Args, Debug, Clone)]
#[group(required = true, multiple = false)]
pub struct ContractIdentifier {
/// Class hash of contract to deploy
#[arg(short = 'g', long)]
pub class_hash: Option<Felt>,

/// Contract name
#[arg(long, conflicts_with = "class_hash")]
pub contract_name: Option<String>,
}

#[derive(Args)]
#[command(about = "Deploy a contract on Starknet")]
pub struct Deploy {
/// Class hash of contract to deploy
#[arg(short = 'g', long)]
pub class_hash: Felt,
#[command(flatten)]
pub contract_identifier: ContractIdentifier,

#[command(flatten)]
pub arguments: DeployArguments,
Expand Down Expand Up @@ -69,7 +80,7 @@ pub async fn deploy(
account: &SingleOwnerAccount<&JsonRpcClient<HttpTransport>, LocalWallet>,
wait_config: WaitForTx,
ui: &UI,
) -> Result<DeployResponse, StarknetCommandError> {
) -> Result<StandardDeployResponse, StarknetCommandError> {
let salt = extract_or_generate_salt(salt);

// TODO(#3628): Use `ContractFactory::new` once new UDC address is the default one in starknet-rs
Expand Down Expand Up @@ -114,7 +125,7 @@ pub async fn deploy(
Ok(result) => handle_wait_for_tx(
account.provider(),
result.transaction_hash,
DeployResponse {
StandardDeployResponse {
contract_address: get_udc_deployed_address(
salt,
class_hash,
Expand Down
Loading
Loading