From 345920c10fa9141ccbaee5ea5d9764c5c23df2b7 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 01/10] Implement starkli upgrade --- src/main.rs | 3 ++ src/subcommands/deploy.rs | 24 +++++----- src/subcommands/invoke.rs | 18 ++++---- src/subcommands/mod.rs | 3 ++ src/subcommands/upgrade.rs | 95 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 21 deletions(-) create mode 100644 src/subcommands/upgrade.rs diff --git a/src/main.rs b/src/main.rs index ec40ab1..ffe6b73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,6 +154,8 @@ enum Subcommands { They might break or be removed anytime." )] Lab(Lab), + #[clap(about = "Upgrade a contract by deploying a new class and invoking the upgrade entrypoint")] + Upgrade(Upgrade), } #[cfg_attr(target_arch = "wasm32", tokio::main(flavor = "current_thread"))] @@ -217,6 +219,7 @@ async fn run_command(cli: Cli) -> Result<()> { Subcommands::Deploy(cmd) => cmd.run().await, Subcommands::Completions(cmd) => cmd.run(), Subcommands::Lab(cmd) => cmd.run(), + Subcommands::Upgrade(cmd) => cmd.run().await, }, } } diff --git a/src/subcommands/deploy.rs b/src/subcommands/deploy.rs index 66d5acd..e04eb51 100644 --- a/src/subcommands/deploy.rs +++ b/src/subcommands/deploy.rs @@ -23,34 +23,34 @@ const DEFAULT_UDC_ADDRESS: Felt = #[derive(Debug, Parser)] pub struct Deploy { #[clap(flatten)] - provider: ProviderArgs, + pub provider: ProviderArgs, #[clap(flatten)] - account: AccountArgs, + pub account: AccountArgs, #[clap(long, help = "Do not derive contract address from deployer address")] - not_unique: bool, + pub not_unique: bool, #[clap(flatten)] - fee: FeeArgs, + pub fee: FeeArgs, #[clap(long, help = "Simulate the transaction only")] - simulate: bool, + pub simulate: bool, #[clap(long, help = "Use the given salt to compute contract deploy address")] - salt: Option, + pub salt: Option, #[clap(long, help = "Provide transaction nonce manually")] - nonce: Option, + pub nonce: Option, #[clap(long, short, help = "Wait for the transaction to confirm")] - watch: bool, + pub watch: bool, #[clap( long, env = "STARKNET_POLL_INTERVAL", default_value = "5000", help = "Transaction result poll interval in milliseconds" )] - poll_interval: u64, + pub poll_interval: u64, #[clap(help = "Class hash")] - class_hash: String, + pub class_hash: String, #[clap(help = "Raw constructor arguments")] - ctor_args: Vec, + pub ctor_args: Vec, #[clap(flatten)] - verbosity: VerbosityArgs, + pub verbosity: VerbosityArgs, } impl Deploy { diff --git a/src/subcommands/invoke.rs b/src/subcommands/invoke.rs index 9268319..2dd3f82 100644 --- a/src/subcommands/invoke.rs +++ b/src/subcommands/invoke.rs @@ -22,28 +22,28 @@ use crate::{ #[derive(Debug, Parser)] pub struct Invoke { #[clap(flatten)] - provider: ProviderArgs, + pub provider: ProviderArgs, #[clap(flatten)] - account: AccountArgs, + pub account: AccountArgs, #[clap(flatten)] - fee: FeeArgs, + pub fee: FeeArgs, #[clap(long, help = "Simulate the transaction only")] - simulate: bool, + pub simulate: bool, #[clap(long, help = "Provide transaction nonce manually")] - nonce: Option, + pub nonce: Option, #[clap(long, short, help = "Wait for the transaction to confirm")] - watch: bool, + pub watch: bool, #[clap( long, env = "STARKNET_POLL_INTERVAL", default_value = "5000", help = "Transaction result poll interval in milliseconds" )] - poll_interval: u64, + pub poll_interval: u64, #[clap(help = "One or more contract calls. See documentation for more details")] - calls: Vec, + pub calls: Vec, #[clap(flatten)] - verbosity: VerbosityArgs, + pub verbosity: VerbosityArgs, } impl Invoke { diff --git a/src/subcommands/mod.rs b/src/subcommands/mod.rs index c7eeb25..dd0ab5f 100644 --- a/src/subcommands/mod.rs +++ b/src/subcommands/mod.rs @@ -99,3 +99,6 @@ pub use transaction_status::TransactionStatus; mod abi; pub use abi::Abi; + +mod upgrade; +pub use upgrade::Upgrade; diff --git a/src/subcommands/upgrade.rs b/src/subcommands/upgrade.rs new file mode 100644 index 0000000..3bf0aa1 --- /dev/null +++ b/src/subcommands/upgrade.rs @@ -0,0 +1,95 @@ +use std::{sync::Arc, time::Duration}; + +use anyhow::Result; +use clap::Parser; +use colored::Colorize; +use starknet::{ + contract::ContractFactory, + core::types::{Call, Felt}, + macros::felt, + signers::SigningKey, +}; + +use crate::{ + account::AccountArgs, + fee::{FeeArgs}, + utils::{felt_to_bigdecimal, print_colored_json, watch_tx}, + verbosity::VerbosityArgs, + ProviderArgs, + subcommands::{Deploy, Invoke}, +}; + +#[derive(Debug, Parser)] +pub struct Upgrade { + #[clap(flatten)] + provider: ProviderArgs, + #[clap(flatten)] + account: AccountArgs, + #[clap(flatten)] + fee: FeeArgs, + #[clap(flatten)] + verbosity: VerbosityArgs, + #[clap(long, help = "Do not derive contract address from deployer address")] + not_unique: bool, + #[clap(long, help = "Use the given salt to compute contract deploy address")] + salt: Option, + #[clap(long, help = "Provide transaction nonce manually for the first transaction")] + nonce: Option, + #[clap(long, short, help = "Wait for the transactions to confirm")] + watch: bool, + #[clap(help = "Class hash for the new implementation")] + class_hash: String, + #[clap(help = "Raw constructor arguments for the new implementation")] + ctor_args: Vec, + #[clap(help = "Address of the upgradeable contract to call upgrade on")] + upgrade_contract: String, + #[clap(long, default_value = "upgrade", help = "Selector for the upgrade entrypoint")] + selector: String, + #[clap(help = "Additional arguments for the upgrade call (e.g. new class hash)")] + upgrade_args: Vec, +} + +impl Upgrade { + pub async fn run(self) -> Result<()> { + self.verbosity.setup_logging(); + + // 1. Deploy new class using Deploy subcommand + let deploy_cmd = Deploy { + provider: self.provider.clone(), + account: self.account.clone(), + not_unique: self.not_unique, + fee: self.fee.clone(), + simulate: false, + salt: self.salt.clone(), + nonce: self.nonce, + watch: self.watch, + poll_interval: 5000, + class_hash: self.class_hash.clone(), + ctor_args: self.ctor_args.clone(), + verbosity: self.verbosity.clone(), + }; + deploy_cmd.run().await?; + + // 2. Invoke upgrade entrypoint using Invoke subcommand + let mut calls = vec![self.upgrade_contract, self.selector]; + if self.upgrade_args.is_empty() { + calls.push(self.class_hash); + } else { + calls.extend(self.upgrade_args); + } + + let invoke_cmd = Invoke { + provider: self.provider.clone(), + account: self.account, + fee: self.fee, + simulate: false, + nonce: None, + watch: self.watch, + poll_interval: 5000, + calls, + verbosity: self.verbosity, + }; + invoke_cmd.run().await?; + Ok(()) + } +} From 6dab38da949a2da9fe98eb245f4b91e7afceb6dd Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 02/10] Small cleanup --- src/subcommands/upgrade.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/subcommands/upgrade.rs b/src/subcommands/upgrade.rs index 3bf0aa1..85fad5e 100644 --- a/src/subcommands/upgrade.rs +++ b/src/subcommands/upgrade.rs @@ -1,19 +1,11 @@ -use std::{sync::Arc, time::Duration}; use anyhow::Result; use clap::Parser; -use colored::Colorize; -use starknet::{ - contract::ContractFactory, - core::types::{Call, Felt}, - macros::felt, - signers::SigningKey, -}; +use starknet::core::types::Felt; use crate::{ account::AccountArgs, fee::{FeeArgs}, - utils::{felt_to_bigdecimal, print_colored_json, watch_tx}, verbosity::VerbosityArgs, ProviderArgs, subcommands::{Deploy, Invoke}, @@ -47,6 +39,13 @@ pub struct Upgrade { selector: String, #[clap(help = "Additional arguments for the upgrade call (e.g. new class hash)")] upgrade_args: Vec, + #[clap( + long, + env = "STARKNET_POLL_INTERVAL", + default_value = "5000", + help = "Transaction result poll interval in milliseconds" + )] + pub poll_interval: u64, } impl Upgrade { @@ -63,7 +62,7 @@ impl Upgrade { salt: self.salt.clone(), nonce: self.nonce, watch: self.watch, - poll_interval: 5000, + poll_interval: self.poll_interval, class_hash: self.class_hash.clone(), ctor_args: self.ctor_args.clone(), verbosity: self.verbosity.clone(), @@ -83,9 +82,9 @@ impl Upgrade { account: self.account, fee: self.fee, simulate: false, - nonce: None, + nonce: self.nonce.map(|n| n + 1), watch: self.watch, - poll_interval: 5000, + poll_interval: self.poll_interval, calls, verbosity: self.verbosity, }; From 916293392b36fce9f1054f2ce746671ef5c98011 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 03/10] Use declare instead of deploy --- src/subcommands/declare.rs | 113 +++++++++++++++++++++++-------------- src/subcommands/upgrade.rs | 92 +++++++++++++++++------------- 2 files changed, 124 insertions(+), 81 deletions(-) diff --git a/src/subcommands/declare.rs b/src/subcommands/declare.rs index d846f38..3d0ff98 100644 --- a/src/subcommands/declare.rs +++ b/src/subcommands/declare.rs @@ -28,35 +28,40 @@ use crate::{ #[derive(Debug, Parser)] pub struct Declare { #[clap(flatten)] - provider: ProviderArgs, + pub provider: ProviderArgs, #[clap(flatten)] - account: AccountArgs, + pub account: AccountArgs, #[clap(flatten)] - casm: CasmArgs, + pub casm: CasmArgs, #[clap(flatten)] - fee: FeeArgs, + pub fee: FeeArgs, #[clap(long, help = "Do not publish the ABI of the class")] - no_abi: bool, + pub no_abi: bool, #[clap(long, help = "Simulate the transaction only")] - simulate: bool, + pub simulate: bool, #[clap(long, help = "Provide transaction nonce manually")] - nonce: Option, + pub nonce: Option, #[clap(long, short, help = "Wait for the transaction to confirm")] - watch: bool, + pub watch: bool, #[clap( long, env = "STARKNET_POLL_INTERVAL", default_value = "5000", help = "Transaction result poll interval in milliseconds" )] - poll_interval: u64, + pub poll_interval: u64, #[clap( value_parser = ExpandedPathbufParser, help = "Path to contract artifact file" )] - file: PathBuf, + pub file: PathBuf, #[clap(flatten)] - verbosity: VerbosityArgs, + pub verbosity: VerbosityArgs, +} + +pub struct DeclareOutput { + pub class_hash: Felt, + pub transaction_hash: Felt, } enum Declarable { @@ -65,6 +70,40 @@ enum Declarable { impl Declare { pub async fn run(self) -> Result<()> { + let watch = self.watch; + let poll_interval = self.poll_interval; + let provider_args = self.provider.clone(); + + let result = self.run_as_lib().await?; + + eprintln!( + "Contract declaration transaction: {}", + format!("{:#064x}", result.transaction_hash).bright_yellow() + ); + + if watch { + let provider = provider_args.into_provider()?; + eprintln!( + "Waiting for transaction {} to confirm...", + format!("{:#064x}", result.transaction_hash).bright_yellow(), + ); + watch_tx( + &provider, + result.transaction_hash, + Duration::from_millis(poll_interval), + ) + .await?; + } + + eprintln!("Class hash declared:"); + + // Only the class hash goes to stdout so this can be easily scripted + println!("{}", format!("{:#064x}", result.class_hash).bright_yellow()); + + Ok(()) + } + + pub async fn run_as_lib(self) -> Result { self.verbosity.setup_logging(); let fee_setting = self.fee.into_setting()?; @@ -121,7 +160,13 @@ impl Declare { // TODO: add option to skip checking if Self::check_already_declared(&provider, class_hash).await? { - return Ok(()); + // This is not ideal, but we need to get the transaction hash + // even if the class is already declared. + // When a class is already declared, there's no transaction. + // We can't proceed with the upgrade without a tx hash to watch. + // For now, we return an error. A better solution would be to + // find the original declaration transaction. + anyhow::bail!("class already declared"); } // Reconstructs an original Sierra class just for CASM compilation purposes. It's a @@ -205,7 +250,10 @@ impl Declare { format!("{}", felt_to_bigdecimal(estimated_fee, 18)) .bright_yellow(), ); - return Ok(()); + return Ok(DeclareOutput { + class_hash, + transaction_hash: Felt::default(), + }); } TokenFeeSetting::Manual(fee) => { let declaration = if let Some(l1_gas) = fee.l1_gas { @@ -245,43 +293,24 @@ impl Declare { if self.simulate { print_colored_json(&declaration.simulate(false, false).await?)?; - return Ok(()); + // Simulating doesn't return a tx hash, so we can't proceed. + anyhow::bail!("cannot use `upgrade` with `--simulate`"); } - declaration.send().await + let declaration_result = declaration.send().await.map_err(account_error_mapper)?; + + (declaration_result.class_hash, declaration_result.transaction_hash) } - } - .map_err(account_error_mapper)? - .transaction_hash; + }; (class_hash, declare_tx) } }; - eprintln!( - "Contract declaration transaction: {}", - format!("{:#064x}", declaration_tx_hash).bright_yellow() - ); - - if self.watch { - eprintln!( - "Waiting for transaction {} to confirm...", - format!("{:#064x}", declaration_tx_hash).bright_yellow(), - ); - watch_tx( - &provider, - declaration_tx_hash, - Duration::from_millis(self.poll_interval), - ) - .await?; - } - - eprintln!("Class hash declared:"); - - // Only the class hash goes to stdout so this can be easily scripted - println!("{}", format!("{:#064x}", class_hash).bright_yellow()); - - Ok(()) + Ok(DeclareOutput { + class_hash, + transaction_hash: declaration_tx_hash.1, + }) } async fn check_already_declared

(provider: P, class_hash: Felt) -> Result diff --git a/src/subcommands/upgrade.rs b/src/subcommands/upgrade.rs index 85fad5e..d46ef3f 100644 --- a/src/subcommands/upgrade.rs +++ b/src/subcommands/upgrade.rs @@ -1,18 +1,21 @@ - use anyhow::Result; use clap::Parser; use starknet::core::types::Felt; +use std::{path::PathBuf, time::Duration}; use crate::{ account::AccountArgs, - fee::{FeeArgs}, + casm::CasmArgs, + fee::FeeArgs, + subcommands::{Declare, Invoke}, + utils::watch_tx, verbosity::VerbosityArgs, ProviderArgs, - subcommands::{Deploy, Invoke}, }; #[derive(Debug, Parser)] pub struct Upgrade { + // SHARED ARGS #[clap(flatten)] provider: ProviderArgs, #[clap(flatten)] @@ -21,74 +24,85 @@ pub struct Upgrade { fee: FeeArgs, #[clap(flatten)] verbosity: VerbosityArgs, - #[clap(long, help = "Do not derive contract address from deployer address")] - not_unique: bool, - #[clap(long, help = "Use the given salt to compute contract deploy address")] - salt: Option, - #[clap(long, help = "Provide transaction nonce manually for the first transaction")] - nonce: Option, - #[clap(long, short, help = "Wait for the transactions to confirm")] + #[clap(long, short, help = "Wait for each transaction to confirm before proceeding")] watch: bool, - #[clap(help = "Class hash for the new implementation")] - class_hash: String, - #[clap(help = "Raw constructor arguments for the new implementation")] - ctor_args: Vec, - #[clap(help = "Address of the upgradeable contract to call upgrade on")] - upgrade_contract: String, - #[clap(long, default_value = "upgrade", help = "Selector for the upgrade entrypoint")] - selector: String, - #[clap(help = "Additional arguments for the upgrade call (e.g. new class hash)")] - upgrade_args: Vec, #[clap( long, env = "STARKNET_POLL_INTERVAL", default_value = "5000", help = "Transaction result poll interval in milliseconds" )] - pub poll_interval: u64, + poll_interval: u64, + + // DECLARE-SPECIFIC ARGS + #[clap(help = "Path to the contract artifact file (json)")] + file: PathBuf, + #[clap(flatten)] + casm: CasmArgs, + #[clap(long, help = "Do not publish the ABI of the class")] + no_abi: bool, + #[clap(long, help = "Provide transaction nonce manually for the declare transaction")] + nonce: Option, + + // UPGRADE-SPECIFIC ARGS + #[clap(help = "Address of the upgradeable contract to call upgrade on")] + upgrade_contract: String, + #[clap(long, default_value = "upgrade", help = "Selector for the upgrade entrypoint")] + selector: String, } impl Upgrade { pub async fn run(self) -> Result<()> { self.verbosity.setup_logging(); - // 1. Deploy new class using Deploy subcommand - let deploy_cmd = Deploy { + // 1. Declare the new class + let declare_cmd = Declare { provider: self.provider.clone(), account: self.account.clone(), - not_unique: self.not_unique, + casm: self.casm, fee: self.fee.clone(), - simulate: false, - salt: self.salt.clone(), + no_abi: self.no_abi, + simulate: false, // Not supported for upgrade nonce: self.nonce, - watch: self.watch, + watch: true, // We handle watch logic manually poll_interval: self.poll_interval, - class_hash: self.class_hash.clone(), - ctor_args: self.ctor_args.clone(), + file: self.file, verbosity: self.verbosity.clone(), }; - deploy_cmd.run().await?; - // 2. Invoke upgrade entrypoint using Invoke subcommand - let mut calls = vec![self.upgrade_contract, self.selector]; - if self.upgrade_args.is_empty() { - calls.push(self.class_hash); - } else { - calls.extend(self.upgrade_args); - } + eprintln!("Declaring new contract class..."); + let declare_result = declare_cmd.run_as_lib().await?; + eprintln!("Declaration transaction: {:#064x}", declare_result.transaction_hash); + + let class_hash = declare_result.class_hash; + eprintln!("Successfully declared class: {:#064x}", class_hash); + + // 2. Invoke the upgrade function + eprintln!("\nInvoking upgrade function on contract: {}", self.upgrade_contract.as_str()); + + let calls = vec![ + self.upgrade_contract, + self.selector, + format!("{:#064x}", class_hash), + ]; let invoke_cmd = Invoke { - provider: self.provider.clone(), + provider: self.provider, account: self.account, fee: self.fee, - simulate: false, + simulate: false, // Not supported for upgrade nonce: self.nonce.map(|n| n + 1), watch: self.watch, poll_interval: self.poll_interval, calls, verbosity: self.verbosity, }; + invoke_cmd.run().await?; + + eprintln!("\nUpgrade complete. New class hash:"); + println!("{:#064x}", class_hash); + Ok(()) } } From 2ba86182eab6d1f723c53e2ec55a56bd5bbebabe Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 04/10] Fix fmt --- src/subcommands/upgrade.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/subcommands/upgrade.rs b/src/subcommands/upgrade.rs index d46ef3f..0c45ac4 100644 --- a/src/subcommands/upgrade.rs +++ b/src/subcommands/upgrade.rs @@ -1,14 +1,13 @@ use anyhow::Result; use clap::Parser; use starknet::core::types::Felt; -use std::{path::PathBuf, time::Duration}; +use std::path::PathBuf; use crate::{ account::AccountArgs, casm::CasmArgs, fee::FeeArgs, subcommands::{Declare, Invoke}, - utils::watch_tx, verbosity::VerbosityArgs, ProviderArgs, }; From 59b3e5d7719b78b3aca2e89aac2d4dafe529bbd7 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 05/10] Rename to upgrade_selector --- src/subcommands/upgrade.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subcommands/upgrade.rs b/src/subcommands/upgrade.rs index 0c45ac4..5b8747f 100644 --- a/src/subcommands/upgrade.rs +++ b/src/subcommands/upgrade.rs @@ -47,7 +47,7 @@ pub struct Upgrade { #[clap(help = "Address of the upgradeable contract to call upgrade on")] upgrade_contract: String, #[clap(long, default_value = "upgrade", help = "Selector for the upgrade entrypoint")] - selector: String, + upgrade_selector: String, } impl Upgrade { @@ -81,7 +81,7 @@ impl Upgrade { let calls = vec![ self.upgrade_contract, - self.selector, + self.upgrade_selector, format!("{:#064x}", class_hash), ]; From 3ef6aaebcba1672632d6b7c0a276a1b3eae21ff8 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 06/10] Update docs --- book/src/ref/commands.md | 1 + src/main.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/book/src/ref/commands.md b/book/src/ref/commands.md index 7f0f112..b122cdd 100644 --- a/book/src/ref/commands.md +++ b/book/src/ref/commands.md @@ -28,6 +28,7 @@ Starkli offers the following commands: - invoke - declare - deploy +- upgrade - completions To check usage of each command, run with the `--help` option. diff --git a/src/main.rs b/src/main.rs index ffe6b73..75cb89c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,7 +154,7 @@ enum Subcommands { They might break or be removed anytime." )] Lab(Lab), - #[clap(about = "Upgrade a contract by deploying a new class and invoking the upgrade entrypoint")] + #[clap(about = "Upgrade a contract by declaring a new class and invoking the upgrade entrypoint")] Upgrade(Upgrade), } From 5e86d5dca47da94177fb717b6bb09a5a5303a3f3 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 07/10] Adjust log output --- src/subcommands/upgrade.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/subcommands/upgrade.rs b/src/subcommands/upgrade.rs index 5b8747f..73a96a7 100644 --- a/src/subcommands/upgrade.rs +++ b/src/subcommands/upgrade.rs @@ -1,5 +1,6 @@ use anyhow::Result; use clap::Parser; +use colored::Colorize; use starknet::core::types::Felt; use std::path::PathBuf; @@ -100,7 +101,7 @@ impl Upgrade { invoke_cmd.run().await?; eprintln!("\nUpgrade complete. New class hash:"); - println!("{:#064x}", class_hash); + println!("{}", format!("{:#064x}", class_hash).bright_yellow()); Ok(()) } From 601ce759a3ac7c66f22bbb7010a0bb1beb399b6b Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 08/10] Adjust simulation and already declared paths --- src/main.rs | 4 ++- src/subcommands/declare.rs | 53 ++++++++++++++------------------------ src/subcommands/upgrade.rs | 31 ++++++++++++++++------ 3 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/main.rs b/src/main.rs index 75cb89c..7ef3dfb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,7 +154,9 @@ enum Subcommands { They might break or be removed anytime." )] Lab(Lab), - #[clap(about = "Upgrade a contract by declaring a new class and invoking the upgrade entrypoint")] + #[clap( + about = "Upgrade a contract by declaring a new class and invoking the upgrade entrypoint" + )] Upgrade(Upgrade), } diff --git a/src/subcommands/declare.rs b/src/subcommands/declare.rs index 3d0ff98..c150429 100644 --- a/src/subcommands/declare.rs +++ b/src/subcommands/declare.rs @@ -61,7 +61,7 @@ pub struct Declare { pub struct DeclareOutput { pub class_hash: Felt, - pub transaction_hash: Felt, + pub transaction_hash: Option, } enum Declarable { @@ -70,29 +70,13 @@ enum Declarable { impl Declare { pub async fn run(self) -> Result<()> { - let watch = self.watch; - let poll_interval = self.poll_interval; - let provider_args = self.provider.clone(); - let result = self.run_as_lib().await?; - eprintln!( - "Contract declaration transaction: {}", - format!("{:#064x}", result.transaction_hash).bright_yellow() - ); - - if watch { - let provider = provider_args.into_provider()?; + if let Some(transaction_hash) = result.transaction_hash { eprintln!( - "Waiting for transaction {} to confirm...", - format!("{:#064x}", result.transaction_hash).bright_yellow(), + "Contract declaration transaction: {}", + format!("{:#064x}", transaction_hash).bright_yellow() ); - watch_tx( - &provider, - result.transaction_hash, - Duration::from_millis(poll_interval), - ) - .await?; } eprintln!("Class hash declared:"); @@ -160,13 +144,10 @@ impl Declare { // TODO: add option to skip checking if Self::check_already_declared(&provider, class_hash).await? { - // This is not ideal, but we need to get the transaction hash - // even if the class is already declared. - // When a class is already declared, there's no transaction. - // We can't proceed with the upgrade without a tx hash to watch. - // For now, we return an error. A better solution would be to - // find the original declaration transaction. - anyhow::bail!("class already declared"); + return Ok(DeclareOutput { + class_hash, + transaction_hash: None, + }); } // Reconstructs an original Sierra class just for CASM compilation purposes. It's a @@ -252,7 +233,7 @@ impl Declare { ); return Ok(DeclareOutput { class_hash, - transaction_hash: Felt::default(), + transaction_hash: None, }); } TokenFeeSetting::Manual(fee) => { @@ -293,13 +274,19 @@ impl Declare { if self.simulate { print_colored_json(&declaration.simulate(false, false).await?)?; - // Simulating doesn't return a tx hash, so we can't proceed. - anyhow::bail!("cannot use `upgrade` with `--simulate`"); + return Ok(DeclareOutput { + class_hash, + transaction_hash: None, + }); } - let declaration_result = declaration.send().await.map_err(account_error_mapper)?; + let declaration_result = + declaration.send().await.map_err(account_error_mapper)?; - (declaration_result.class_hash, declaration_result.transaction_hash) + ( + declaration_result.class_hash, + declaration_result.transaction_hash, + ) } }; @@ -309,7 +296,7 @@ impl Declare { Ok(DeclareOutput { class_hash, - transaction_hash: declaration_tx_hash.1, + transaction_hash: Some(declaration_tx_hash.1), }) } diff --git a/src/subcommands/upgrade.rs b/src/subcommands/upgrade.rs index 73a96a7..595fa95 100644 --- a/src/subcommands/upgrade.rs +++ b/src/subcommands/upgrade.rs @@ -24,7 +24,11 @@ pub struct Upgrade { fee: FeeArgs, #[clap(flatten)] verbosity: VerbosityArgs, - #[clap(long, short, help = "Wait for each transaction to confirm before proceeding")] + #[clap( + long, + short, + help = "Wait for each transaction to confirm before proceeding" + )] watch: bool, #[clap( long, @@ -41,13 +45,22 @@ pub struct Upgrade { casm: CasmArgs, #[clap(long, help = "Do not publish the ABI of the class")] no_abi: bool, - #[clap(long, help = "Provide transaction nonce manually for the declare transaction")] + #[clap( + long, + help = "Provide transaction nonce manually for the declare transaction" + )] nonce: Option, + #[clap(long, default_value = "false", help = "Simulate the transaction only")] + simulate: bool, // UPGRADE-SPECIFIC ARGS #[clap(help = "Address of the upgradeable contract to call upgrade on")] upgrade_contract: String, - #[clap(long, default_value = "upgrade", help = "Selector for the upgrade entrypoint")] + #[clap( + long, + default_value = "upgrade", + help = "Selector for the upgrade entrypoint" + )] upgrade_selector: String, } @@ -62,9 +75,9 @@ impl Upgrade { casm: self.casm, fee: self.fee.clone(), no_abi: self.no_abi, - simulate: false, // Not supported for upgrade + simulate: self.simulate, nonce: self.nonce, - watch: true, // We handle watch logic manually + watch: true, poll_interval: self.poll_interval, file: self.file, verbosity: self.verbosity.clone(), @@ -72,13 +85,15 @@ impl Upgrade { eprintln!("Declaring new contract class..."); let declare_result = declare_cmd.run_as_lib().await?; - eprintln!("Declaration transaction: {:#064x}", declare_result.transaction_hash); let class_hash = declare_result.class_hash; eprintln!("Successfully declared class: {:#064x}", class_hash); // 2. Invoke the upgrade function - eprintln!("\nInvoking upgrade function on contract: {}", self.upgrade_contract.as_str()); + eprintln!( + "\nInvoking upgrade function on contract: {}", + self.upgrade_contract.as_str() + ); let calls = vec![ self.upgrade_contract, @@ -90,7 +105,7 @@ impl Upgrade { provider: self.provider, account: self.account, fee: self.fee, - simulate: false, // Not supported for upgrade + simulate: self.simulate, nonce: self.nonce.map(|n| n + 1), watch: self.watch, poll_interval: self.poll_interval, From 18b9556c332078a67bebfd278da9d76b112c2c15 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 09/10] Cargo fmt --- src/subcommands/declare.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subcommands/declare.rs b/src/subcommands/declare.rs index c150429..f2078b3 100644 --- a/src/subcommands/declare.rs +++ b/src/subcommands/declare.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{path::PathBuf, sync::Arc}; use anyhow::Result; use clap::Parser; @@ -20,7 +20,7 @@ use crate::{ error::account_error_mapper, fee::{FeeArgs, FeeSetting, TokenFeeSetting}, path::ExpandedPathbufParser, - utils::{felt_to_bigdecimal, print_colored_json, watch_tx}, + utils::{felt_to_bigdecimal, print_colored_json}, verbosity::VerbosityArgs, ProviderArgs, }; From a7f54a3dee84ca7197b444f489ca557df069dc66 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Tue, 17 Jun 2025 10:46:53 -0400 Subject: [PATCH 10/10] Add watch_tx back to declare --- src/subcommands/declare.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/subcommands/declare.rs b/src/subcommands/declare.rs index f2078b3..7ccf6bc 100644 --- a/src/subcommands/declare.rs +++ b/src/subcommands/declare.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, sync::Arc}; +use std::{path::PathBuf, sync::Arc, time::Duration}; use anyhow::Result; use clap::Parser; @@ -20,7 +20,7 @@ use crate::{ error::account_error_mapper, fee::{FeeArgs, FeeSetting, TokenFeeSetting}, path::ExpandedPathbufParser, - utils::{felt_to_bigdecimal, print_colored_json}, + utils::{felt_to_bigdecimal, print_colored_json, watch_tx}, verbosity::VerbosityArgs, ProviderArgs, }; @@ -283,6 +283,15 @@ impl Declare { let declaration_result = declaration.send().await.map_err(account_error_mapper)?; + if self.watch { + watch_tx( + &provider, + declaration_result.transaction_hash, + Duration::from_millis(self.poll_interval), + ) + .await?; + } + ( declaration_result.class_hash, declaration_result.transaction_hash,