diff --git a/crates/pop-cli/src/commands/call/chain.rs b/crates/pop-cli/src/commands/call/chain.rs index 27dbee6d..41348826 100644 --- a/crates/pop-cli/src/commands/call/chain.rs +++ b/crates/pop-cli/src/commands/call/chain.rs @@ -44,7 +44,13 @@ pub struct CallChainCommand { #[arg(short, long)] suri: Option, /// Use a browser extension wallet to sign the extrinsic. - #[arg(name = "use-wallet", short('w'), long, default_value = "false", conflicts_with = "suri")] + #[arg( + name = "use-wallet", + short = 'w', + long, + default_value = "false", + conflicts_with = "suri" + )] use_wallet: bool, /// SCALE encoded bytes representing the call data of the extrinsic. #[arg(name = "call", short, long, conflicts_with_all = ["pallet", "function", "args"])] diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index da12002a..45ba0a09 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -60,7 +60,13 @@ pub struct CallContractCommand { #[arg(short, long, default_value = DEFAULT_URI)] suri: String, /// Use a browser extension wallet to sign the extrinsic. - #[arg(name = "use-wallet", long, short('w'), default_value = "false", conflicts_with = "suri")] + #[arg( + name = "use-wallet", + long, + short = 'w', + default_value = "false", + conflicts_with = "suri" + )] use_wallet: bool, /// Submit an extrinsic for on-chain execution. #[arg(short = 'x', long)] @@ -394,7 +400,7 @@ impl CallContractCommand { // Perform signing steps with wallet integration, skipping secure signing for query-only // operations. if self.use_wallet { - self.execute_with_secure_signing(call_exec, cli).await?; + self.execute_with_wallet(call_exec, cli).await?; return self.finalize_execute_call(cli, prompt_to_repeat_call).await; } if self.dry_run { @@ -476,7 +482,7 @@ impl CallContractCommand { } /// Execute the smart contract call using wallet integration. - async fn execute_with_secure_signing( + async fn execute_with_wallet( &self, call_exec: CallExec, cli: &mut impl Cli, diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index df1edaf6..fed9c206 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -66,7 +66,7 @@ pub struct UpContractCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[clap(short, long, default_value = "//Alice")] suri: String, - /// Use your browser wallet to sign a transaction. + /// Use a browser extension wallet to sign the extrinsic. #[clap( name = "use-wallet", long, @@ -194,14 +194,18 @@ impl UpContractCommand { spinner.start("Uploading contract..."); if self.upload_only { - let result = upload_contract_signed(self.url.as_str(), payload).await; - if let Err(e) = result { - spinner.error(format!("An error occurred uploading your contract: {e}")); - terminate_node(process)?; - Cli.outro_cancel(FAILED)?; - return Ok(()); - } - let upload_result = result.expect("Error check above."); + let upload_result = match upload_contract_signed(self.url.as_str(), payload) + .await + { + Err(e) => { + spinner + .error(format!("An error occurred uploading your contract: {e}")); + terminate_node(process)?; + Cli.outro_cancel(FAILED)?; + return Ok(()); + }, + Ok(result) => result, + }; match get_code_hash_from_event(&upload_result, hash) { Ok(r) => { @@ -213,15 +217,19 @@ impl UpContractCommand { }, }; } else { - let result = instantiate_contract_signed(self.url.as_str(), payload).await; - if let Err(e) = result { - spinner.error(format!("An error occurred uploading your contract: {e}")); - terminate_node(process)?; - Cli.outro_cancel(FAILED)?; - return Ok(()); - } - - let contract_info = result.unwrap(); + let contract_info = + match instantiate_contract_signed(self.url.as_str(), payload).await { + Err(e) => { + spinner.error(format!( + "An error occurred uploading your contract: {e}" + )); + terminate_node(process)?; + Cli.outro_cancel(FAILED)?; + return Ok(()); + }, + Ok(result) => result, + }; + let hash = contract_info.code_hash.map(|code_hash| format!("{:?}", code_hash)); display_contract_info( &spinner, @@ -344,7 +352,7 @@ impl UpContractCommand { // get the call data and contract code hash async fn get_contract_data(&self) -> anyhow::Result<(Vec, [u8; 32])> { - let contract_code = get_contract_code(self.path.as_ref()).await?; + let contract_code = get_contract_code(self.path.as_ref())?; let hash = contract_code.code_hash(); if self.upload_only { let call_data = get_upload_payload(contract_code, self.url.as_str()).await?; @@ -358,7 +366,7 @@ impl UpContractCommand { // Frontend will do dry run and update call data. Weight::zero() }; - let call_data = get_instantiate_payload(instantiate_exec, weight_limit).await?; + let call_data = get_instantiate_payload(instantiate_exec, weight_limit)?; Ok((call_data, hash)) } } @@ -480,7 +488,6 @@ mod tests { #[tokio::test] async fn get_upload_and_instantiate_call_data_works() -> anyhow::Result<()> { let (contracts_node_process, port, temp_dir) = start_test_environment().await?; - let localhost_url = format!("ws://127.0.0.1:{}", port); sleep(Duration::from_secs(5)).await; get_upload_call_data_works(port, temp_dir.path().join("testing")).await?; @@ -525,7 +532,7 @@ mod tests { assert!(!retrieved_call_data.is_empty()); // Craft encoded call data for an upload code call. - let contract_code = get_contract_code(up_contract_opts.path.as_ref()).await?; + let contract_code = get_contract_code(up_contract_opts.path.as_ref())?; let storage_deposit_limit: Option = None; let upload_code = contract_extrinsics::extrinsic_calls::UploadCode::new( contract_code, @@ -575,8 +582,7 @@ mod tests { // Craft instantiate call data. let weight = Weight::from_parts(200_000_000, 30_000); let expected_call_data = - get_instantiate_payload(set_up_deployment(up_contract_opts.into()).await?, weight) - .await?; + get_instantiate_payload(set_up_deployment(up_contract_opts.into()).await?, weight)?; // Retrieved call data matches the one crafted above. assert_eq!(retrieved_call_data, expected_call_data); diff --git a/crates/pop-cli/src/common/wallet.rs b/crates/pop-cli/src/common/wallet.rs index 9f0e2871..1aedd029 100644 --- a/crates/pop-cli/src/common/wallet.rs +++ b/crates/pop-cli/src/common/wallet.rs @@ -22,13 +22,12 @@ pub async fn request_signature(call_data: Vec, rpc: String) -> anyhow::Resul let transaction_data = TransactionData::new(rpc, call_data); // Starts server with port 9090. let mut wallet = WalletIntegrationManager::new(ui, transaction_data, Some(9090)); - let url = wallet.server_url.clone(); - log::step(format!("Wallet signing portal started at http://{url}."))?; + let url = format!("http://{}", &wallet.server_url); + log::step(format!("Wallet signing portal started at {url}."))?; let spinner = spinner(); - spinner.start(format!("Opening browser to http://{url}")); - let res = open::that(format!("http://{url}")); - if let Err(e) = res { + spinner.start(format!("Opening browser to {url}")); + if let Err(e) = open::that(url) { spinner.error(format!("Failed to launch browser. Please open link manually. {e}")); } @@ -48,7 +47,7 @@ pub async fn request_signature(call_data: Vec, rpc: String) -> anyhow::Resul } spinner.stop(""); - let signed_payload = wallet.state.lock().await.signed_payload.clone(); + let signed_payload = wallet.state.lock().await.signed_payload.take(); Ok(signed_payload) } diff --git a/crates/pop-cli/src/wallet_integration.rs b/crates/pop-cli/src/wallet_integration.rs index dfe43cfe..92e3395e 100644 --- a/crates/pop-cli/src/wallet_integration.rs +++ b/crates/pop-cli/src/wallet_integration.rs @@ -13,9 +13,9 @@ use tokio::{ }; use tower_http::{cors::Any, services::ServeDir}; -/// Make frontend sourcing more flexible by allowing a custom route -/// to be defined. +/// Make frontend sourcing more flexible by allowing a custom route to be defined. pub trait Frontend { + /// Serves the content via a [Router]. fn serve_content(&self) -> Router; } @@ -28,13 +28,15 @@ pub struct TransactionData { } impl TransactionData { + /// Create a new transaction payload. + /// # Arguments + /// * `chain_rpc`: The RPC of the chain. + /// * `call_data`: the call data. + /// # Returns + /// The transaction payload to be sent to frontend for signing. pub fn new(chain_rpc: String, call_data: Vec) -> Self { Self { chain_rpc, call_data } } - #[allow(dead_code)] - pub fn call_data(&self) -> Vec { - self.call_data.clone() - } } /// Shared state between routes. Serves two purposes: @@ -80,6 +82,13 @@ impl WalletIntegrationManager { } /// Same as `new`, but allows specifying the address to bind to. + /// # Arguments + /// * `frontend`: A frontend with custom route to serve content. + /// * `payload`: Payload to be sent to the frontend for signing. + /// * `server_url`: The address to bind to. + /// + /// # Returns + /// A `WalletIntegrationManager` instance, with access to the state and task handle for the pub fn new_with_address( frontend: F, payload: TransactionData, @@ -97,7 +106,7 @@ impl WalletIntegrationManager { let payload = Arc::new(payload); let cors = tower_http::cors::CorsLayer::new() - .allow_origin(server_url.parse::().unwrap()) + .allow_origin(server_url.parse::().expect("invalid server url")) .allow_methods(Any) // Allow any HTTP method .allow_headers(Any); // Allow any headers (like 'Content-Type') @@ -235,6 +244,9 @@ pub struct FrontendFromDir { } #[allow(dead_code)] impl FrontendFromDir { + /// A new static server. + /// # Arguments + /// * `content`: A directory path. pub fn new(content: PathBuf) -> Self { Self { content } } @@ -253,6 +265,9 @@ pub struct FrontendFromString { #[allow(dead_code)] impl FrontendFromString { + /// A new static server. + /// # Arguments + /// * `content`: A hard-coded HTML string pub fn new(content: String) -> Self { Self { content } } diff --git a/crates/pop-contracts/src/node/mod.rs b/crates/pop-contracts/src/node.rs similarity index 98% rename from crates/pop-contracts/src/node/mod.rs rename to crates/pop-contracts/src/node.rs index 060dca16..158865d4 100644 --- a/crates/pop-contracts/src/node/mod.rs +++ b/crates/pop-contracts/src/node.rs @@ -23,6 +23,7 @@ use subxt::{dynamic::Value, SubstrateConfig}; use tokio::time::sleep; const BIN_NAME: &str = "substrate-contracts-node"; +const STARTUP: Duration = Duration::from_millis(20_000); /// Checks if the specified node is alive and responsive. /// @@ -132,8 +133,8 @@ pub async fn run_contracts_node( let process = command.spawn()?; - // Wait 20 secs until the node is ready - sleep(Duration::from_millis(20000)).await; + // Wait until the node is ready + sleep(STARTUP).await; let data = Value::from_bytes(subxt::utils::to_hex("initialize contracts node")); let payload = subxt::dynamic::tx("System", "remark", [data].to_vec()); diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index efd3817b..3ed614c7 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 + use crate::{ errors::Error, utils::{ @@ -9,9 +10,11 @@ use crate::{ }; use contract_extrinsics::{ events::{CodeStored, ContractInstantiated}, - extrinsic_calls::{Instantiate, InstantiateWithCode}, - BalanceVariant, ErrorVariant, ExtrinsicOptsBuilder, InstantiateCommandBuilder, InstantiateExec, - InstantiateExecResult, TokenMetadata, UploadCommandBuilder, UploadExec, UploadResult, WasmCode, + extrinsic_calls::{Instantiate, InstantiateWithCode, UploadCode}, + upload::Determinism, + BalanceVariant, Code, ErrorVariant, ExtrinsicOptsBuilder, InstantiateCommandBuilder, + InstantiateExec, InstantiateExecResult, TokenMetadata, UploadCommandBuilder, UploadExec, + UploadResult, WasmCode, }; use ink_env::{DefaultEnvironment, Environment}; use pop_common::{create_signer, DefaultConfig, Keypair}; @@ -121,11 +124,7 @@ pub async fn set_up_upload( /// * `url` - the rpc of the chain node. pub async fn get_upload_payload(code: WasmCode, url: &str) -> anyhow::Result> { let storage_deposit_limit: Option = None; - let upload_code = contract_extrinsics::extrinsic_calls::UploadCode::new( - code, - storage_deposit_limit, - contract_extrinsics::upload::Determinism::Enforced, - ); + let upload_code = UploadCode::new(code, storage_deposit_limit, Determinism::Enforced); let rpc_client = subxt::backend::rpc::RpcClient::from_url(url).await?; let client = subxt::OnlineClient::::from_rpc_client(rpc_client).await?; @@ -135,35 +134,37 @@ pub async fn get_upload_payload(code: WasmCode, url: &str) -> anyhow::Result, gas_limit: Weight, ) -> anyhow::Result> { let storage_deposit_limit: Option = None; let mut encoded_data = Vec::::new(); - match instantiate_exec.args().code() { - contract_extrinsics::Code::Upload(code) => InstantiateWithCode::new( - instantiate_exec.args().value(), + let args = instantiate_exec.args(); + match args.code() { + Code::Upload(code) => InstantiateWithCode::new( + args.value(), gas_limit, storage_deposit_limit, code.clone(), - instantiate_exec.args().data().into(), - instantiate_exec.args().salt().into(), + args.data().into(), + args.salt().into(), ) .build() .encode_call_data_to(&instantiate_exec.client().metadata(), &mut encoded_data), - contract_extrinsics::Code::Existing(hash) => Instantiate::new( - instantiate_exec.args().value(), + Code::Existing(hash) => Instantiate::new( + args.value(), gas_limit, storage_deposit_limit, hash, - instantiate_exec.args().data().into(), - instantiate_exec.args().salt().into(), + args.data().into(), + args.salt().into(), ) .build() .encode_call_data_to(&instantiate_exec.client().metadata(), &mut encoded_data), @@ -176,9 +177,7 @@ pub async fn get_instantiate_payload( /// /// # Arguments /// * `path` - path to the contract file. -pub async fn get_contract_code( - path: Option<&PathBuf>, -) -> anyhow::Result { +pub fn get_contract_code(path: Option<&PathBuf>) -> anyhow::Result { let manifest_path = get_manifest_path(path.map(|p| p as &Path))?; // signer does not matter for this @@ -285,7 +284,6 @@ pub async fn submit_signed_payload( /// blockchain. /// /// # Arguments -/// /// * `instantiate_exec` - the preprocessed data to instantiate a contract. pub async fn dry_run_gas_estimate_instantiate( instantiate_exec: &InstantiateExec, @@ -314,14 +312,15 @@ pub async fn dry_run_gas_estimate_instantiate( /// Result of a dry-run upload of a smart contract. pub struct UploadDryRunResult { + /// The key under which the new code is stored. pub code_hash: String, + /// The deposit that was reserved at the caller. Is zero when the code already existed. pub deposit: String, } /// Performs a dry-run for uploading a contract without modifying the state of the blockchain. /// /// # Arguments -/// /// * `upload_exec` - the preprocessed data to upload a contract. pub async fn dry_run_upload( upload_exec: &UploadExec, @@ -353,7 +352,6 @@ pub struct ContractInfo { /// Instantiate a contract. /// /// # Arguments -/// /// * `instantiate_exec` - the preprocessed data to instantiate a contract. /// * `gas_limit` - maximum amount of gas to be used for this call. pub async fn instantiate_smart_contract( @@ -373,7 +371,6 @@ pub async fn instantiate_smart_contract( /// Upload a contract. /// /// # Arguments -/// /// * `upload_exec` - the preprocessed data to upload a contract. pub async fn upload_smart_contract( upload_exec: &UploadExec, @@ -495,7 +492,7 @@ mod tests { url: Url::parse(CONTRACTS_NETWORK_URL)?, suri: "//Alice".to_string(), }; - let contract_code = get_contract_code(up_opts.path.as_ref()).await?; + let contract_code = get_contract_code(up_opts.path.as_ref())?; let call_data = get_upload_payload(contract_code, CONTRACTS_NETWORK_URL).await?; let payload_hash = BlakeTwo256::hash(&call_data); // We know that for the above opts the payload hash should be: