Skip to content

Commit ac92c0d

Browse files
committed
Allow declaring with deployment
commit-id:82e786a7 # Conflicts: # crates/sncast/src/main.rs
1 parent 899b363 commit ac92c0d

File tree

6 files changed

+502
-47
lines changed

6 files changed

+502
-47
lines changed

crates/sncast/src/helpers/block_explorer.rs

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

130130
#[cfg(test)]
131131
mod tests {
132+
use crate::response::deploy::DeployResponseKind;
132133
use crate::{
133134
Network,
134135
helpers::block_explorer::Service,
@@ -139,23 +140,23 @@ mod tests {
139140
use starknet::macros::felt;
140141
use test_case::test_case;
141142

142-
const MAINNET_RESPONSE: DeployResponse = DeployResponse {
143+
const MAINNET_RESPONSE: DeployResponseKind = DeployResponseKind::Deploy(DeployResponse {
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: DeployResponseKind = DeployResponseKind::Deploy(DeployResponse {
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();
@@ -175,7 +176,7 @@ mod tests {
175176
#[tokio::test]
176177
#[test_case(Network::Mainnet, &MAINNET_RESPONSE; "mainnet")]
177178
#[test_case(Network::Sepolia, &SEPOLIA_RESPONSE; "sepolia")]
178-
async fn test_happy_case_starkscan(network: Network, response: &DeployResponse) {
179+
async fn test_happy_case_starkscan(network: Network, response: &DeployResponseKind) {
179180
let provider = Service::Voyager.as_provider(network).unwrap();
180181
let result = response.format_links(provider);
181182
assert_valid_links(&result).await;
@@ -184,7 +185,7 @@ mod tests {
184185
#[tokio::test]
185186
#[test_case(Network::Mainnet, &MAINNET_RESPONSE; "mainnet")]
186187
#[test_case(Network::Sepolia, &SEPOLIA_RESPONSE; "sepolia")]
187-
async fn test_happy_case_voyager(network: Network, response: &DeployResponse) {
188+
async fn test_happy_case_voyager(network: Network, response: &DeployResponseKind) {
188189
let provider = Service::Voyager.as_provider(network).unwrap();
189190
let result = response.format_links(provider);
190191
assert_valid_links(&result).await;

crates/sncast/src/main.rs

Lines changed: 80 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};
@@ -23,7 +25,10 @@ use sncast::helpers::output_format::output_format_from_json_flag;
2325
use sncast::helpers::scarb_utils::{
2426
BuildConfig, assert_manifest_path_exists, build_and_load_artifacts, get_package_metadata,
2527
};
26-
use sncast::response::declare::DeclareResponse;
28+
use sncast::response::declare::{
29+
AlreadyDeclaredResponse, DeclareResponse, DeclareTransactionResponse,
30+
};
31+
use sncast::response::deploy::{DeployResponseKind, DeployResponseWithDeclare};
2732
use sncast::response::errors::handle_starknet_command_error;
2833
use sncast::response::explorer_link::block_explorer_link_if_allowed;
2934
use sncast::response::transformed_call::transform_response;
@@ -361,9 +366,11 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()>
361366

362367
Commands::Deploy(deploy) => {
363368
let Deploy {
369+
identifier,
364370
arguments,
365371
fee_args,
366372
rpc,
373+
mut nonce,
367374
..
368375
} = deploy;
369376

@@ -377,28 +384,97 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()>
377384
)
378385
.await?;
379386

387+
let (class_hash, declare_response) = if let Some(class_hash) = identifier.class_hash {
388+
(class_hash, None)
389+
} else if let Some(contract_name) = identifier.contract_name {
390+
let manifest_path = assert_manifest_path_exists()?;
391+
let package_metadata = get_package_metadata(&manifest_path, &None)?;
392+
let artifacts = build_and_load_artifacts(
393+
&package_metadata,
394+
&BuildConfig {
395+
scarb_toml_path: manifest_path,
396+
json: cli.json,
397+
profile: cli.profile.unwrap_or("release".to_string()),
398+
},
399+
false,
400+
ui,
401+
)
402+
.expect("Failed to build contract");
403+
404+
let declare_result = declare(
405+
contract_name,
406+
fee_args.clone(),
407+
nonce,
408+
&account,
409+
&artifacts,
410+
WaitForTx {
411+
wait: true,
412+
wait_params: wait_config.wait_params,
413+
silent: true,
414+
},
415+
true,
416+
ui,
417+
)
418+
.await
419+
.map_err(handle_starknet_command_error);
420+
421+
// Increment nonce after successful declare if it was explicitly provided
422+
nonce = nonce.map(|n| n + Felt::ONE);
423+
424+
match declare_result {
425+
Ok(DeclareResponse::AlreadyDeclared(AlreadyDeclaredResponse {
426+
class_hash,
427+
})) => (class_hash.into_(), None),
428+
Ok(DeclareResponse::Success(declare_transaction_response)) => (
429+
declare_transaction_response.class_hash.into_(),
430+
Some(declare_transaction_response),
431+
),
432+
Err(err) => {
433+
process_command_result::<DeclareTransactionResponse>(
434+
"deploy",
435+
Err(err),
436+
ui,
437+
None,
438+
);
439+
return Ok(());
440+
}
441+
}
442+
} else {
443+
unreachable!("One of class_hash or contract_name must be provided");
444+
};
445+
380446
// safe to unwrap because "constructor" is a standardized name
381447
let selector = get_selector_from_name("constructor").unwrap();
382448

383-
let contract_class = get_contract_class(deploy.class_hash, &provider).await?;
449+
let contract_class = get_contract_class(class_hash, &provider).await?;
384450

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

388454
let result = starknet_commands::deploy::deploy(
389-
deploy.class_hash,
455+
class_hash,
390456
&calldata,
391457
deploy.salt,
392458
deploy.unique,
393459
fee_args,
394-
deploy.nonce,
460+
nonce,
395461
&account,
396462
wait_config,
397463
ui,
398464
)
399465
.await
400466
.map_err(handle_starknet_command_error);
401467

468+
let result = if let Some(declare_response) = declare_response {
469+
result.map(|r| {
470+
DeployResponseKind::DeployWithDeclare(
471+
DeployResponseWithDeclare::from_responses(&r, &declare_response),
472+
)
473+
})
474+
} else {
475+
result.map(DeployResponseKind::Deploy)
476+
};
477+
402478
let block_explorer_link =
403479
block_explorer_link_if_allowed(&result, provider.chain_id().await?, &rpc, &config);
404480
process_command_result("deploy", result, ui, block_explorer_link);
Lines changed: 105 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,138 @@
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;
12+
use serde_json::{Value, json};
1013

11-
use super::{command::CommandResponse, explorer_link::OutputLink};
12-
use crate::response::cast_message::SncastMessage;
14+
#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
15+
#[serde(untagged)]
16+
pub enum DeployResponseKind {
17+
Deploy(DeployResponse),
18+
DeployWithDeclare(DeployResponseWithDeclare),
19+
}
20+
21+
impl CommandResponse for DeployResponseKind {}
22+
23+
impl Message for SncastMessage<DeployResponseKind> {
24+
fn text(&self) -> String {
25+
match &self.command_response {
26+
DeployResponseKind::Deploy(response) => response.text(),
27+
DeployResponseKind::DeployWithDeclare(response) => response.text(),
28+
}
29+
}
30+
31+
fn json(&self) -> Value {
32+
serde_json::to_value(&self.command_response).unwrap_or_else(|err| {
33+
json!({
34+
"error": "Failed to serialize response",
35+
"command": self.command,
36+
"details": err.to_string()
37+
})
38+
})
39+
}
40+
}
41+
42+
impl OutputLink for DeployResponseKind {
43+
const TITLE: &'static str = "deployment";
44+
45+
fn format_links(&self, provider: Box<dyn LinkProvider>) -> String {
46+
match self {
47+
DeployResponseKind::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+
DeployResponseKind::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+
}
1374

1475
#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
1576
pub struct DeployResponse {
1677
pub contract_address: PaddedFelt,
1778
pub transaction_hash: PaddedFelt,
1879
}
1980

20-
impl CommandResponse for DeployResponse {}
21-
22-
impl Message for SncastMessage<DeployResponse> {
81+
impl DeployResponse {
2382
fn text(&self) -> String {
2483
styling::OutputBuilder::new()
2584
.success_message("Deployment completed")
2685
.blank_line()
2786
.field(
2887
"Contract Address",
29-
&self.command_response.contract_address.into_padded_hex_str(),
88+
&self.contract_address.into_padded_hex_str(),
3089
)
3190
.field(
3291
"Transaction Hash",
33-
&self.command_response.transaction_hash.into_padded_hex_str(),
92+
&self.transaction_hash.into_padded_hex_str(),
3493
)
3594
.build()
3695
}
96+
}
3797

38-
fn json(&self) -> Value {
39-
serde_json::to_value(&self.command_response).unwrap_or_else(|err| {
40-
json!({
41-
"error": "Failed to serialize response",
42-
"command": self.command,
43-
"details": err.to_string()
44-
})
45-
})
46-
}
98+
#[derive(Clone, Serialize, Deserialize, CairoSerialize, Debug, PartialEq)]
99+
pub struct DeployResponseWithDeclare {
100+
pub contract_address: PaddedFelt,
101+
pub class_hash: PaddedFelt,
102+
pub deploy_transaction_hash: PaddedFelt,
103+
pub declare_transaction_hash: PaddedFelt,
47104
}
48105

49-
impl OutputLink for DeployResponse {
50-
const TITLE: &'static str = "deployment";
106+
impl DeployResponseWithDeclare {
107+
#[must_use]
108+
pub fn from_responses(deploy: &DeployResponse, declare: &DeclareTransactionResponse) -> Self {
109+
Self {
110+
contract_address: deploy.contract_address,
111+
class_hash: declare.class_hash,
112+
deploy_transaction_hash: deploy.transaction_hash,
113+
declare_transaction_hash: declare.transaction_hash,
114+
}
115+
}
116+
}
51117

52-
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-
)
118+
impl DeployResponseWithDeclare {
119+
fn text(&self) -> String {
120+
styling::OutputBuilder::new()
121+
.success_message("Deployment completed")
122+
.blank_line()
123+
.field(
124+
"Contract Address",
125+
&self.contract_address.into_padded_hex_str(),
126+
)
127+
.field("Class Hash", &self.class_hash.into_padded_hex_str())
128+
.field(
129+
"Declare Transaction Hash",
130+
&self.declare_transaction_hash.into_padded_hex_str(),
131+
)
132+
.field(
133+
"Deploy Transaction Hash",
134+
&self.deploy_transaction_hash.into_padded_hex_str(),
135+
)
136+
.build()
61137
}
62138
}

crates/sncast/src/starknet_commands/deploy.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@ 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 DeployIdentifier {
23+
/// Class hash of contract to deploy
24+
#[arg(short = 'g', long)]
25+
pub class_hash: Option<Felt>,
26+
27+
#[arg(long, conflicts_with = "class_hash")]
28+
pub contract_name: Option<String>,
29+
}
30+
2031
#[derive(Args)]
2132
#[command(about = "Deploy a contract on Starknet")]
2233
pub struct Deploy {
23-
/// Class hash of contract to deploy
24-
#[arg(short = 'g', long)]
25-
pub class_hash: Felt,
34+
#[command(flatten)]
35+
pub identifier: DeployIdentifier,
2636

2737
#[command(flatten)]
2838
pub arguments: DeployArguments,

0 commit comments

Comments
 (0)