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
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 @@ -393,9 +398,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 @@ -404,28 +411,98 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()>
let account =
get_account(&config, &provider, &rpc, config.keystore.as_ref(), ui).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)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this feature work for workspaces?

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,
Copy link
Contributor

Choose a reason for hiding this comment

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

What if someone passes the --wait flag?

},
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");
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:

Suggested change
unreachable!("One of class_hash or contract_name must be provided");
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),
Comment on lines +17 to +18
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:

Suggested change
Deploy(StandardDeployResponse),
DeployWithDeclare(DeployResponseWithDeclare),
Standard(StandardDeployResponse),
WithDeclare(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>,
}

Comment on lines +20 to +31
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Usage of id and group seems suitable here:

Suggested change
#[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, Debug, Clone)]
#[group(id = "contract_identifier", required = true, multiple = false)]
pub struct ContractIdentifier {
/// Class hash of contract to deploy
#[arg(short = 'g', long, group = "contract_identifier")]
pub class_hash: Option<Felt>,
/// Contract name
#[arg(long, group = "contract_identifier")]
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