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 ec40ab1..7ef3dfb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,6 +154,10 @@ 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" + )] + Upgrade(Upgrade), } #[cfg_attr(target_arch = "wasm32", tokio::main(flavor = "current_thread"))] @@ -217,6 +221,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/declare.rs b/src/subcommands/declare.rs index d846f38..7ccf6bc 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: Option, } enum Declarable { @@ -65,6 +70,24 @@ enum Declarable { impl Declare { pub async fn run(self) -> Result<()> { + let result = self.run_as_lib().await?; + + if let Some(transaction_hash) = result.transaction_hash { + eprintln!( + "Contract declaration transaction: {}", + format!("{:#064x}", transaction_hash).bright_yellow() + ); + } + + 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 +144,10 @@ impl Declare { // TODO: add option to skip checking if Self::check_already_declared(&provider, class_hash).await? { - return Ok(()); + return Ok(DeclareOutput { + class_hash, + transaction_hash: None, + }); } // Reconstructs an original Sierra class just for CASM compilation purposes. It's a @@ -205,7 +231,10 @@ impl Declare { format!("{}", felt_to_bigdecimal(estimated_fee, 18)) .bright_yellow(), ); - return Ok(()); + return Ok(DeclareOutput { + class_hash, + transaction_hash: None, + }); } TokenFeeSetting::Manual(fee) => { let declaration = if let Some(l1_gas) = fee.l1_gas { @@ -245,43 +274,39 @@ impl Declare { if self.simulate { print_colored_json(&declaration.simulate(false, false).await?)?; - return Ok(()); + return Ok(DeclareOutput { + class_hash, + transaction_hash: None, + }); + } + + 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.send().await + ( + 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: Some(declaration_tx_hash.1), + }) } async fn check_already_declared

(provider: P, class_hash: Felt) -> Result 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..595fa95 --- /dev/null +++ b/src/subcommands/upgrade.rs @@ -0,0 +1,123 @@ +use anyhow::Result; +use clap::Parser; +use colored::Colorize; +use starknet::core::types::Felt; +use std::path::PathBuf; + +use crate::{ + account::AccountArgs, + casm::CasmArgs, + fee::FeeArgs, + subcommands::{Declare, Invoke}, + verbosity::VerbosityArgs, + ProviderArgs, +}; + +#[derive(Debug, Parser)] +pub struct Upgrade { + // SHARED ARGS + #[clap(flatten)] + provider: ProviderArgs, + #[clap(flatten)] + account: AccountArgs, + #[clap(flatten)] + fee: FeeArgs, + #[clap(flatten)] + verbosity: VerbosityArgs, + #[clap( + long, + short, + help = "Wait for each transaction to confirm before proceeding" + )] + watch: bool, + #[clap( + long, + env = "STARKNET_POLL_INTERVAL", + default_value = "5000", + help = "Transaction result poll interval in milliseconds" + )] + 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, + #[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" + )] + upgrade_selector: String, +} + +impl Upgrade { + pub async fn run(self) -> Result<()> { + self.verbosity.setup_logging(); + + // 1. Declare the new class + let declare_cmd = Declare { + provider: self.provider.clone(), + account: self.account.clone(), + casm: self.casm, + fee: self.fee.clone(), + no_abi: self.no_abi, + simulate: self.simulate, + nonce: self.nonce, + watch: true, + poll_interval: self.poll_interval, + file: self.file, + verbosity: self.verbosity.clone(), + }; + + eprintln!("Declaring new contract class..."); + let declare_result = declare_cmd.run_as_lib().await?; + + 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.upgrade_selector, + format!("{:#064x}", class_hash), + ]; + + let invoke_cmd = Invoke { + provider: self.provider, + account: self.account, + fee: self.fee, + simulate: self.simulate, + 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!("{}", format!("{:#064x}", class_hash).bright_yellow()); + + Ok(()) + } +}