Skip to content

Commit 859e21b

Browse files
committed
Allow declaring with deployment
commit-id:82e786a7 # Conflicts: # crates/sncast/src/main.rs
1 parent 41b8753 commit 859e21b

File tree

7 files changed

+513
-53
lines changed

7 files changed

+513
-53
lines changed

crates/sncast/src/helpers/block_explorer.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,33 +129,34 @@ impl LinkProvider for OkLink {
129129

130130
#[cfg(test)]
131131
mod tests {
132+
use crate::response::deploy::DeployResponse;
132133
use crate::{
133134
Network,
134135
helpers::block_explorer::Service,
135-
response::{deploy::DeployResponse, explorer_link::OutputLink},
136+
response::{deploy::StandardDeployResponse, explorer_link::OutputLink},
136137
};
137138
use conversions::padded_felt::PaddedFelt;
138139
use regex::Regex;
139140
use starknet::macros::felt;
140141
use test_case::test_case;
141142

142-
const MAINNET_RESPONSE: DeployResponse = DeployResponse {
143+
const MAINNET_RESPONSE: DeployResponse = DeployResponse::Deploy(StandardDeployResponse {
143144
contract_address: PaddedFelt(felt!(
144145
"0x03241d40a2af53a34274dd411e090ccac1ea80e0380a0303fe76d71b046cfecb"
145146
)),
146147
transaction_hash: PaddedFelt(felt!(
147148
"0x7605291e593e0c6ad85681d09e27a601befb85033bdf1805aabf5d84617cf68"
148149
)),
149-
};
150+
});
150151

151-
const SEPOLIA_RESPONSE: DeployResponse = DeployResponse {
152+
const SEPOLIA_RESPONSE: DeployResponse = DeployResponse::Deploy(StandardDeployResponse {
152153
contract_address: PaddedFelt(felt!(
153154
"0x0716b5f1e3bd760c489272fd6530462a09578109049e26e3f4c70492676eae17"
154155
)),
155156
transaction_hash: PaddedFelt(felt!(
156157
"0x1cde70aae10f79d2d1289c923a1eeca7b81a2a6691c32551ec540fa2cb29c33"
157158
)),
158-
};
159+
});
159160

160161
async fn assert_valid_links(input: &str) {
161162
let pattern = Regex::new(r"transaction: |contract: |class: ").unwrap();

crates/sncast/src/main.rs

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::starknet_commands::declare::declare;
12
use crate::starknet_commands::declare_from::DeclareFrom;
23
use crate::starknet_commands::deploy::DeployArguments;
34
use crate::starknet_commands::multicall;
@@ -11,6 +12,7 @@ use anyhow::{Context, Result, bail};
1112
use camino::Utf8PathBuf;
1213
use clap::{CommandFactory, Parser, Subcommand};
1314
use configuration::load_config;
15+
use conversions::IntoConv;
1416
use data_transformer::transform;
1517
use foundry_ui::components::warning::WarningMessage;
1618
use foundry_ui::{Message, UI};
@@ -24,7 +26,10 @@ use sncast::helpers::rpc::generate_network_flag;
2426
use sncast::helpers::scarb_utils::{
2527
BuildConfig, assert_manifest_path_exists, build_and_load_artifacts, get_package_metadata,
2628
};
27-
use sncast::response::declare::{DeclareResponse, DeployCommandMessage};
29+
use sncast::response::declare::{
30+
AlreadyDeclaredResponse, DeclareResponse, DeclareTransactionResponse, DeployCommandMessage,
31+
};
32+
use sncast::response::deploy::{DeployResponse, DeployResponseWithDeclare};
2833
use sncast::response::errors::handle_starknet_command_error;
2934
use sncast::response::explorer_link::block_explorer_link_if_allowed;
3035
use sncast::response::transformed_call::transform_response;
@@ -390,9 +395,11 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()>
390395

391396
Commands::Deploy(deploy) => {
392397
let Deploy {
398+
contract_identifier: identifier,
393399
arguments,
394400
fee_args,
395401
rpc,
402+
mut nonce,
396403
..
397404
} = deploy;
398405

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

416+
let (class_hash, declare_response) = if let Some(class_hash) = identifier.class_hash {
417+
(class_hash, None)
418+
} else if let Some(contract_name) = identifier.contract_name {
419+
let manifest_path = assert_manifest_path_exists()?;
420+
let package_metadata = get_package_metadata(&manifest_path, &None)?;
421+
let artifacts = build_and_load_artifacts(
422+
&package_metadata,
423+
&BuildConfig {
424+
scarb_toml_path: manifest_path,
425+
json: cli.json,
426+
profile: cli.profile.unwrap_or("release".to_string()),
427+
},
428+
false,
429+
ui,
430+
)
431+
.expect("Failed to build contract");
432+
433+
let declare_result = declare(
434+
contract_name,
435+
fee_args.clone(),
436+
nonce,
437+
&account,
438+
&artifacts,
439+
WaitForTx {
440+
wait: true,
441+
wait_params: wait_config.wait_params,
442+
show_ui_outputs: true,
443+
},
444+
true,
445+
ui,
446+
)
447+
.await
448+
.map_err(handle_starknet_command_error);
449+
450+
// Increment nonce after successful declare if it was explicitly provided
451+
nonce = nonce.map(|n| n + Felt::ONE);
452+
453+
match declare_result {
454+
Ok(DeclareResponse::AlreadyDeclared(AlreadyDeclaredResponse {
455+
class_hash,
456+
})) => (class_hash.into_(), None),
457+
Ok(DeclareResponse::Success(declare_transaction_response)) => (
458+
declare_transaction_response.class_hash.into_(),
459+
Some(declare_transaction_response),
460+
),
461+
Err(err) => {
462+
process_command_result::<DeclareTransactionResponse>(
463+
"deploy",
464+
Err(err),
465+
ui,
466+
None,
467+
);
468+
return Ok(());
469+
}
470+
}
471+
} else {
472+
unreachable!("One of class_hash or contract_name must be provided");
473+
};
474+
409475
// safe to unwrap because "constructor" is a standardized name
410476
let selector = get_selector_from_name("constructor").unwrap();
411477

412-
let contract_class = get_contract_class(deploy.class_hash, &provider).await?;
478+
let contract_class = get_contract_class(class_hash, &provider).await?;
413479

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

417483
let result = starknet_commands::deploy::deploy(
418-
deploy.class_hash,
484+
class_hash,
419485
&calldata,
420486
deploy.salt,
421487
deploy.unique,
422488
fee_args,
423-
deploy.nonce,
489+
nonce,
424490
&account,
425491
wait_config,
426492
ui,
427493
)
428494
.await
429495
.map_err(handle_starknet_command_error);
430496

497+
let result = if let Some(declare_response) = declare_response {
498+
result.map(|r| {
499+
DeployResponse::DeployWithDeclare(DeployResponseWithDeclare::from_responses(
500+
&r,
501+
&declare_response,
502+
))
503+
})
504+
} else {
505+
result.map(DeployResponse::Deploy)
506+
};
507+
431508
let block_explorer_link =
432509
block_explorer_link_if_allowed(&result, provider.chain_id().await?, &rpc, &config);
433510
process_command_result("deploy", result, ui, block_explorer_link);
Lines changed: 107 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,31 @@
1+
use super::command::CommandResponse;
12
use crate::helpers::block_explorer::LinkProvider;
3+
use crate::response::cast_message::SncastMessage;
4+
use crate::response::declare::DeclareTransactionResponse;
5+
use crate::response::explorer_link::OutputLink;
26
use conversions::string::IntoPaddedHexStr;
37
use conversions::{padded_felt::PaddedFelt, serde::serialize::CairoSerialize};
48
use foundry_ui::Message;
59
use foundry_ui::styling;
610
use indoc::formatdoc;
711
use serde::{Deserialize, Serialize};
8-
use serde_json::Value;
9-
use serde_json::json;
10-
11-
use super::{command::CommandResponse, explorer_link::OutputLink};
12-
use crate::response::cast_message::SncastMessage;
12+
use serde_json::{Value, json};
1313

1414
#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
15-
pub struct DeployResponse {
16-
pub contract_address: PaddedFelt,
17-
pub transaction_hash: PaddedFelt,
15+
#[serde(untagged)]
16+
pub enum DeployResponse {
17+
Deploy(StandardDeployResponse),
18+
DeployWithDeclare(DeployResponseWithDeclare),
1819
}
1920

2021
impl CommandResponse for DeployResponse {}
2122

2223
impl Message for SncastMessage<DeployResponse> {
2324
fn text(&self) -> String {
24-
styling::OutputBuilder::new()
25-
.success_message("Deployment completed")
26-
.blank_line()
27-
.field(
28-
"Contract Address",
29-
&self.command_response.contract_address.into_padded_hex_str(),
30-
)
31-
.field(
32-
"Transaction Hash",
33-
&self.command_response.transaction_hash.into_padded_hex_str(),
34-
)
35-
.build()
25+
match &self.command_response {
26+
DeployResponse::Deploy(response) => response.text(),
27+
DeployResponse::DeployWithDeclare(response) => response.text(),
28+
}
3629
}
3730

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

5245
fn format_links(&self, provider: Box<dyn LinkProvider>) -> String {
53-
formatdoc!(
54-
"
55-
contract: {}
56-
transaction: {}
57-
",
58-
provider.contract(self.contract_address),
59-
provider.transaction(self.transaction_hash)
60-
)
46+
match self {
47+
DeployResponse::Deploy(deploy) => {
48+
formatdoc!(
49+
"
50+
contract: {}
51+
transaction: {}
52+
",
53+
provider.contract(deploy.contract_address),
54+
provider.transaction(deploy.transaction_hash)
55+
)
56+
}
57+
DeployResponse::DeployWithDeclare(deploy_with_declare) => {
58+
formatdoc!(
59+
"
60+
contract: {}
61+
class: {}
62+
declare transaction: {}
63+
deploy transaction: {}
64+
",
65+
provider.contract(deploy_with_declare.contract_address),
66+
provider.class(deploy_with_declare.class_hash),
67+
provider.transaction(deploy_with_declare.declare_transaction_hash),
68+
provider.transaction(deploy_with_declare.deploy_transaction_hash)
69+
)
70+
}
71+
}
72+
}
73+
}
74+
75+
#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
76+
pub struct StandardDeployResponse {
77+
pub contract_address: PaddedFelt,
78+
pub transaction_hash: PaddedFelt,
79+
}
80+
81+
impl StandardDeployResponse {
82+
fn text(&self) -> String {
83+
styling::OutputBuilder::new()
84+
.success_message("Deployment completed")
85+
.blank_line()
86+
.field(
87+
"Contract Address",
88+
&self.contract_address.into_padded_hex_str(),
89+
)
90+
.field(
91+
"Transaction Hash",
92+
&self.transaction_hash.into_padded_hex_str(),
93+
)
94+
.build()
95+
}
96+
}
97+
98+
#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
99+
pub struct DeployResponseWithDeclare {
100+
contract_address: PaddedFelt,
101+
class_hash: PaddedFelt,
102+
deploy_transaction_hash: PaddedFelt,
103+
declare_transaction_hash: PaddedFelt,
104+
}
105+
106+
impl DeployResponseWithDeclare {
107+
#[must_use]
108+
pub fn from_responses(
109+
deploy: &StandardDeployResponse,
110+
declare: &DeclareTransactionResponse,
111+
) -> Self {
112+
Self {
113+
contract_address: deploy.contract_address,
114+
class_hash: declare.class_hash,
115+
deploy_transaction_hash: deploy.transaction_hash,
116+
declare_transaction_hash: declare.transaction_hash,
117+
}
118+
}
119+
}
120+
121+
impl DeployResponseWithDeclare {
122+
fn text(&self) -> String {
123+
styling::OutputBuilder::new()
124+
.success_message("Deployment completed")
125+
.blank_line()
126+
.field(
127+
"Contract Address",
128+
&self.contract_address.into_padded_hex_str(),
129+
)
130+
.field("Class Hash", &self.class_hash.into_padded_hex_str())
131+
.field(
132+
"Declare Transaction Hash",
133+
&self.declare_transaction_hash.into_padded_hex_str(),
134+
)
135+
.field(
136+
"Deploy Transaction Hash",
137+
&self.deploy_transaction_hash.into_padded_hex_str(),
138+
)
139+
.build()
61140
}
62141
}

crates/sncast/src/starknet_commands/deploy.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use conversions::IntoConv;
44
use foundry_ui::UI;
55
use sncast::helpers::fee::{FeeArgs, FeeSettings};
66
use sncast::helpers::rpc::RpcArgs;
7-
use sncast::response::deploy::DeployResponse;
7+
use sncast::response::deploy::StandardDeployResponse;
88
use sncast::response::errors::StarknetCommandError;
99
use sncast::{WaitForTx, apply_optional_fields, handle_wait_for_tx};
1010
use sncast::{extract_or_generate_salt, udc_uniqueness};
@@ -17,12 +17,23 @@ use starknet::providers::jsonrpc::HttpTransport;
1717
use starknet::signers::LocalWallet;
1818
use starknet_types_core::felt::Felt;
1919

20+
#[derive(Args, Debug, Clone)]
21+
#[group(required = true, multiple = false)]
22+
pub struct ContractIdentifier {
23+
/// Class hash of contract to deploy
24+
#[arg(short = 'g', long)]
25+
pub class_hash: Option<Felt>,
26+
27+
/// Contract name
28+
#[arg(long, conflicts_with = "class_hash")]
29+
pub contract_name: Option<String>,
30+
}
31+
2032
#[derive(Args)]
2133
#[command(about = "Deploy a contract on Starknet")]
2234
pub struct Deploy {
23-
/// Class hash of contract to deploy
24-
#[arg(short = 'g', long)]
25-
pub class_hash: Felt,
35+
#[command(flatten)]
36+
pub contract_identifier: ContractIdentifier,
2637

2738
#[command(flatten)]
2839
pub arguments: DeployArguments,
@@ -69,7 +80,7 @@ pub async fn deploy(
6980
account: &SingleOwnerAccount<&JsonRpcClient<HttpTransport>, LocalWallet>,
7081
wait_config: WaitForTx,
7182
ui: &UI,
72-
) -> Result<DeployResponse, StarknetCommandError> {
83+
) -> Result<StandardDeployResponse, StarknetCommandError> {
7384
let salt = extract_or_generate_salt(salt);
7485

7586
// TODO(#3628): Use `ContractFactory::new` once new UDC address is the default one in starknet-rs
@@ -114,7 +125,7 @@ pub async fn deploy(
114125
Ok(result) => handle_wait_for_tx(
115126
account.provider(),
116127
result.transaction_hash,
117-
DeployResponse {
128+
StandardDeployResponse {
118129
contract_address: get_udc_deployed_address(
119130
salt,
120131
class_hash,

0 commit comments

Comments
 (0)