Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable pop up without project type specification #403

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions crates/pop-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0

use crate::{cache, cli::Cli};
use crate::{cache, cli::Cli, common::builds::get_project_path};
use clap::Subcommand;
use pop_common::templates::Template;
use serde_json::{json, Value};
Expand All @@ -15,33 +15,32 @@

#[derive(Subcommand)]
#[command(subcommand_required = true)]
pub(crate) enum Command {
/// Set up the environment for development by installing required packages.
#[clap(alias = "i")]
Install(install::InstallArgs),
/// Generate a new parachain, pallet or smart contract.
#[clap(alias = "n")]
#[cfg(any(feature = "parachain", feature = "contract"))]
New(new::NewArgs),
#[clap(alias = "b", about = about_build())]
#[cfg(any(feature = "parachain", feature = "contract"))]
Build(build::BuildArgs),
/// Call a chain or a smart contract.
#[clap(alias = "c")]
#[cfg(any(feature = "parachain", feature = "contract"))]
Call(call::CallArgs),
/// Launch a local network or deploy a smart contract.
#[clap(alias = "u")]
#[clap(alias = "u", about = about_up())]
#[cfg(any(feature = "parachain", feature = "contract"))]
Up(up::UpArgs),
/// Test a smart contract.
#[clap(alias = "t")]
#[cfg(feature = "contract")]
Test(test::TestArgs),
/// Remove generated/cached artifacts.
#[clap(alias = "C")]
Clean(clean::CleanArgs),
}

Check warning on line 43 in crates/pop-cli/src/commands/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

large size difference between variants

warning: large size difference between variants --> crates/pop-cli/src/commands/mod.rs:18:1 | 18 | / pub(crate) enum Command { 19 | | /// Set up the environment for development by installing required packages. 20 | | #[clap(alias = "i")] 21 | | Install(install::InstallArgs), ... | 32 | | Call(call::CallArgs), | | -------------------- the second-largest variant contains at least 296 bytes ... | 35 | | Up(up::UpArgs), | | -------------- the largest variant contains at least 592 bytes ... | 42 | | Clean(clean::CleanArgs), 43 | | } | |_^ the entire enum is at least 592 bytes | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant = note: `#[warn(clippy::large_enum_variant)]` on by default help: consider boxing the large fields to reduce the total size of the enum | 35 | Up(Box<up::UpArgs>), | ~~~~~~~~~~~~~~~

/// Help message for the build command.
fn about_build() -> &'static str {
Expand All @@ -53,6 +52,16 @@
return "Build a smart contract or Rust package.";
}

/// Help message for the up command.
fn about_up() -> &'static str {
AlexD10S marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(all(feature = "parachain", feature = "contract"))]
return "Deploy a smart contract or launch a local network.";
#[cfg(all(feature = "parachain", not(feature = "contract")))]
return "Launch a local network.";
#[cfg(all(feature = "contract", not(feature = "parachain")))]
AlexD10S marked this conversation as resolved.
Show resolved Hide resolved
return "Deploy a smart contract.";
}

impl Command {
/// Executes the command.
pub(crate) async fn execute(self) -> anyhow::Result<Value> {
Expand Down Expand Up @@ -101,10 +110,23 @@
},
#[cfg(any(feature = "parachain", feature = "contract"))]
Self::Up(args) => match args.command {
#[cfg(feature = "parachain")]
up::Command::Parachain(cmd) => cmd.execute().await.map(|_| Value::Null),
#[cfg(feature = "contract")]
up::Command::Contract(cmd) => cmd.execute().await.map(|_| Value::Null),
None => up::Command::execute(args).await.map(|t| json!(t)),
Some(cmd) => match cmd {
#[cfg(feature = "parachain")]
up::Command::Network(mut cmd) => {
cmd.valid = true;
cmd.execute().await.map(|_| Value::Null)
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
},
// TODO: Deprecated, will be removed in v0.8.0.
#[cfg(feature = "parachain")]
up::Command::Parachain(cmd) => cmd.execute().await.map(|_| Value::Null),
// TODO: Deprecated, will be removed in v0.8.0.
#[cfg(feature = "contract")]
up::Command::Contract(mut cmd) => {
cmd.path = get_project_path(args.path, args.path_pos);
cmd.execute().await.map(|_| Value::Null)
},
},
},
#[cfg(feature = "contract")]
Self::Test(args) => match args.command {
Expand Down
64 changes: 31 additions & 33 deletions crates/pop-cli/src/commands/up/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use crate::{
cli::{traits::Cli as _, Cli},
common::{
builds::get_project_path,
contracts::{check_contracts_node_and_prompt, has_contract_been_built, terminate_node},
wallet::request_signature,
},
Expand All @@ -29,47 +28,46 @@ const COMPLETE: &str = "🚀 Deployment complete";
const DEFAULT_URL: &str = "ws://localhost:9944/";
const DEFAULT_PORT: u16 = 9944;
const FAILED: &str = "🚫 Deployment failed.";
const HELP_HEADER: &str = "Smart contract deployment options";

#[derive(Args, Clone)]
#[clap(next_help_heading = HELP_HEADER)]
pub struct UpContractCommand {
/// Path to the contract build directory.
#[arg(short, long)]
path: Option<PathBuf>,
/// Directory path without flag for your project [default: current directory]
#[arg(value_name = "PATH", index = 1, conflicts_with = "path")]
pub path_pos: Option<PathBuf>,
#[clap(skip)]
pub(crate) path: Option<PathBuf>,
/// The name of the contract constructor to call.
#[clap(short, long, default_value = "new")]
constructor: String,
pub(crate) constructor: String,
/// The constructor arguments, encoded as strings.
#[clap(short, long, num_args = 0..,)]
args: Vec<String>,
pub(crate) args: Vec<String>,
/// Transfers an initial balance to the instantiated contract.
#[clap(short, long, default_value = "0")]
value: String,
pub(crate) value: String,
/// Maximum amount of gas to be used for this command.
/// If not specified it will perform a dry-run to estimate the gas consumed for the
/// instantiation.
#[clap(name = "gas", short, long)]
gas_limit: Option<u64>,
pub(crate) gas_limit: Option<u64>,
/// Maximum proof size for the instantiation.
/// If not specified it will perform a dry-run to estimate the proof size required.
#[clap(short = 'P', long)]
proof_size: Option<u64>,
pub(crate) proof_size: Option<u64>,
/// A salt used in the address derivation of the new contract. Use to create multiple
/// instances of the same contract code from the same account.
#[clap(short = 'S', long, value_parser = parse_hex_bytes)]
salt: Option<Bytes>,
pub(crate) salt: Option<Bytes>,
/// Websocket endpoint of a chain.
#[clap(short, long, value_parser, default_value = DEFAULT_URL)]
url: Url,
pub(crate) url: Url,
/// Secret key URI for the account deploying the contract.
///
/// e.g.
/// - for a dev account "//Alice"
/// - with a password "//Alice///SECRET_PASSWORD"
#[clap(short, long, default_value = "//Alice")]
suri: String,
pub(crate) suri: String,
/// Use a browser extension wallet to sign the extrinsic.
#[clap(
name = "use-wallet",
Expand All @@ -78,36 +76,37 @@ pub struct UpContractCommand {
short('w'),
conflicts_with = "suri"
)]
use_wallet: bool,
pub(crate) use_wallet: bool,
/// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction.
#[clap(short = 'D', long)]
dry_run: bool,
pub(crate) dry_run: bool,
/// Uploads the contract only, without instantiation.
#[clap(short = 'U', long)]
upload_only: bool,
pub(crate) upload_only: bool,
/// Automatically source or update the needed binary required without prompting for
/// confirmation.
#[clap(short = 'y', long)]
skip_confirm: bool,
pub(crate) skip_confirm: bool,
// Deprecation flag, used to specify whether the deprecation warning is shown.
#[clap(skip)]
pub(crate) valid: bool,
}

impl UpContractCommand {
/// Executes the command.
pub(crate) async fn execute(mut self) -> anyhow::Result<()> {
Cli.intro("Deploy a smart contract")?;

let project_path = get_project_path(self.path.clone(), self.path_pos.clone());
// Show warning if specified as deprecated.
if !self.valid {
Cli.warning("DEPRECATION: Please use `pop up` (or simply `pop u`) in future...")?;
}
// Check if build exists in the specified "Contract build directory"
if !has_contract_been_built(project_path.as_deref()) {
if !has_contract_been_built(self.path.as_deref()) {
// Build the contract in release mode
Cli.warning("NOTE: contract has not yet been built.")?;
let spinner = spinner();
spinner.start("Building contract in RELEASE mode...");
let result = match build_smart_contract(
project_path.as_deref().map(|v| v),
true,
Verbosity::Quiet,
) {
let result = match build_smart_contract(self.path.as_deref(), true, Verbosity::Quiet) {
Ok(result) => result,
Err(e) => {
Cli.outro_cancel(format!("🚫 An error occurred building your contract: {e}\nUse `pop build` to retry with build output."))?;
Expand Down Expand Up @@ -369,8 +368,7 @@ impl UpContractCommand {

// get the call data and contract code hash
async fn get_contract_data(&self) -> anyhow::Result<(Vec<u8>, [u8; 32])> {
let project_path = get_project_path(self.path.clone(), self.path_pos.clone());
let contract_code = get_contract_code(project_path.as_ref())?;
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?;
Expand Down Expand Up @@ -445,7 +443,6 @@ mod tests {
fn default_up_contract_command() -> UpContractCommand {
UpContractCommand {
path: None,
path_pos: None,
constructor: "new".to_string(),
args: vec![],
value: "0".to_string(),
Expand All @@ -458,6 +455,7 @@ mod tests {
upload_only: false,
skip_confirm: false,
use_wallet: false,
valid: true,
}
}

Expand Down Expand Up @@ -521,8 +519,7 @@ mod tests {
let localhost_url = format!("ws://127.0.0.1:{}", port);

let up_contract_opts = UpContractCommand {
path: Some(temp_dir.clone()),
path_pos: Some(temp_dir),
path: Some(temp_dir),
constructor: "new".to_string(),
args: vec![],
value: "0".to_string(),
Expand All @@ -535,6 +532,7 @@ mod tests {
upload_only: true,
skip_confirm: true,
use_wallet: true,
valid: true,
};

let rpc_client = subxt::backend::rpc::RpcClient::from_url(&up_contract_opts.url).await?;
Expand Down Expand Up @@ -573,8 +571,7 @@ mod tests {
let localhost_url = format!("ws://127.0.0.1:{}", port);

let up_contract_opts = UpContractCommand {
path: Some(temp_dir.clone()),
path_pos: Some(temp_dir),
path: Some(temp_dir),
constructor: "new".to_string(),
args: vec!["false".to_string()],
value: "0".to_string(),
Expand All @@ -587,6 +584,7 @@ mod tests {
upload_only: false,
skip_confirm: true,
use_wallet: true,
valid: true,
};

// Retrieve call data based on the above command options.
Expand Down
132 changes: 123 additions & 9 deletions crates/pop-cli/src/commands/up/mod.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,143 @@
// SPDX-License-Identifier: GPL-3.0

use crate::{
cli::{self, Cli},
common::builds::get_project_path,
};
use clap::{Args, Subcommand};
use std::path::PathBuf;

#[cfg(feature = "contract")]
mod contract;
#[cfg(feature = "parachain")]
mod parachain;
mod network;

/// Arguments for launching or deploying.
#[derive(Args)]
/// Arguments for launching or deploying a project.
#[derive(Args, Clone)]
#[command(args_conflicts_with_subcommands = true)]
pub(crate) struct UpArgs {
/// Path to the project directory.
// TODO: Introduce the short option in v0.8.0 once deprecated parachain command is removed.
#[arg(long, global = true)]
pub path: Option<PathBuf>,

/// Directory path without flag for your project [default: current directory]
#[arg(value_name = "PATH", index = 1, global = true, conflicts_with = "path")]
pub path_pos: Option<PathBuf>,

#[command(flatten)]
#[cfg(feature = "contract")]
pub(crate) contract: contract::UpContractCommand,

#[command(subcommand)]
pub(crate) command: Command,
pub(crate) command: Option<Command>,
}

/// Launch a local network or deploy a smart contract.
#[derive(Subcommand)]
#[derive(Subcommand, Clone)]
pub(crate) enum Command {
#[cfg(feature = "parachain")]
/// Launch a local network.
#[clap(alias = "p")]
Parachain(parachain::ZombienetCommand),
#[clap(alias = "n")]
Network(network::ZombienetCommand),
#[cfg(feature = "parachain")]
/// [DEPRECATED] Launch a local network (will be removed in v0.8.0).
#[clap(alias = "p", hide = true)]
Parachain(network::ZombienetCommand),
#[cfg(feature = "contract")]
/// Deploy a smart contract.
#[clap(alias = "c")]
/// [DEPRECATED] Deploy a smart contract (will be removed in v0.8.0).
#[clap(alias = "c", hide = true)]
Contract(contract::UpContractCommand),
}

impl Command {
/// Executes the command.
pub(crate) async fn execute(args: UpArgs) -> anyhow::Result<&'static str> {
Self::execute_project_deployment(args, &mut Cli).await
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
}

/// Identifies the project type and executes the appropriate deployment process.
async fn execute_project_deployment(
args: UpArgs,
cli: &mut impl cli::traits::Cli,
) -> anyhow::Result<&'static str> {
let project_path = get_project_path(args.path.clone(), args.path_pos.clone());
// If only contract feature enabled, deploy a contract
#[cfg(feature = "contract")]
if pop_contracts::is_supported(project_path.as_deref())? {
let mut cmd = args.contract;
cmd.path = project_path;
cmd.valid = true; // To handle deprecated command, remove in v0.8.0.
cmd.execute().await?;
return Ok("contract");
}
cli.warning("No contract detected. Ensure you are in a valid project directory.")?;
Ok("")
}
}

#[cfg(test)]
mod tests {
use super::{contract::UpContractCommand, *};

use cli::MockCli;
use duct::cmd;
use pop_contracts::{mock_build_process, new_environment};
use std::env;
use url::Url;

fn create_up_args(project_path: PathBuf) -> anyhow::Result<UpArgs> {
Ok(UpArgs {
path: Some(project_path),
path_pos: None,
contract: UpContractCommand {
path: None,
constructor: "new".to_string(),
args: vec!["false".to_string()],
value: "0".to_string(),
gas_limit: None,
proof_size: None,
salt: None,
url: Url::parse("wss://rpc2.paseo.popnetwork.xyz")?,
suri: "//Alice".to_string(),
use_wallet: false,
dry_run: true,
upload_only: true,
skip_confirm: false,
valid: false,
},
command: None,
})
}

#[tokio::test]
async fn detects_contract_correctly() -> anyhow::Result<()> {
let temp_dir = new_environment("testing")?;
let mut current_dir = env::current_dir().expect("Failed to get current directory");
current_dir.pop();
mock_build_process(
temp_dir.path().join("testing"),
current_dir.join("pop-contracts/tests/files/testing.contract"),
current_dir.join("pop-contracts/tests/files/testing.json"),
)?;
let args = create_up_args(temp_dir.path().join("testing"))?;
let mut cli = MockCli::new();
assert_eq!(Command::execute_project_deployment(args, &mut cli).await?, "contract");
cli.verify()
}

#[tokio::test]
async fn detects_rust_project_correctly() -> anyhow::Result<()> {
let temp_dir = tempfile::tempdir()?;
let name = "hello_world";
let path = temp_dir.path();
let project_path = path.join(name);
let args = create_up_args(project_path)?;

cmd("cargo", ["new", name, "--bin"]).dir(&path).run()?;
let mut cli = MockCli::new()
.expect_warning("No contract detected. Ensure you are in a valid project directory.");
assert_eq!(Command::execute_project_deployment(args, &mut cli).await?, "");
cli.verify()
}
}
Loading
Loading