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(cli): setup skeleton for quartz tool #105

Merged
merged 10 commits into from
Jul 23, 2024
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"apps/mtcs/enclave",
"apps/transfers/enclave",
"cli",
"core/light-client-proofs/*",
"core/quartz",
"cosmwasm/packages/*",
Expand Down
19 changes: 19 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "quartz"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
keywords = ["blockchain", "cosmos", "tendermint", "cycles", "quartz"]
readme = "README.md"

[dependencies]
clap.workspace = true
color-eyre.workspace = true
displaydoc.workspace = true
serde.workspace = true
thiserror.workspace = true
tracing.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }
77 changes: 77 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# quartz CLI

A CLI tool to manage Quartz applications. The `quartz` CLI tool is designed to streamline the development and deployment
process of Quartz applications.

It provides helpful information about each command and its options. To get a list of all available subcommands and their
descriptions, use the `--help` flag:

```shell
$ quartz --help

Quartz 0.1.0
A CLI tool to manage Quartz applications

USAGE:
quartz [SUBCOMMAND]

OPTIONS:
-h, --help Print help information
-V, --version Print version information

SUBCOMMANDS:
init Create base Quartz app directory from template
build Build the contract and enclave binaries
start Configure Gramine, sign, and start the enclave binary
deploy Deploy the WASM binary to the blockchain and call instantiate
run Run the enclave handler, and expose a public query server for users
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 3 commands would most likely be confusing for any newcomer to Quartz. I'd recommend having enclave and contract subcommands like so:

quartz enclave start
quartz contract deploy

Also, please remind me why we have a start and run command for the enclave? That would most likely be confusing for users.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just removed run (f894c15). 👍

Regarding enclave/contract commands, I think it would be better instead to rename start to start-enclave and deploy to deploy-contract, because we only have one subcommand per command currently. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we plan on only having a single command for each going forward? If so then your proposal makes sense to me!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I am not sure TBH. 😅 IMHO it's easier to start with single commands for now because I cannot think of concrete examples for multiple sub-commands. Actually we could have separate build commands (e.g. quartz {enclave,contract} build) instead of quartz build in the future. 🤔 I guess what you proposed initially makes sense. Will update. 🙏

Copy link
Member Author

@hu55a1n1 hu55a1n1 Jul 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handshake Run the handshake between the contract and enclave
```

## Installation

To install Quartz, ensure you have Rust and Cargo installed. Then run:

```shell
cargo install quartz
```

## Usage of subcommands

### Init

Initialize a new Quartz app directory structure with optional name and path arguments.

#### Usage

```shell
$ quartz init --help
quartz-init
Create base Quartz app directory from template

USAGE:
quartz init [OPTIONS]

OPTIONS:
-n, --name <NAME> Set the name of the Quartz app [default: <name of parent directory>]
-p, --path <PATH> Set the path where the Quartz app will be created [default: .]
-h, --help Print help information
```

#### Example

```shell
quartz init --name <app_name> --path <path>
```

This command will create the following directory structure at the specified path (or the current directory if no path is
provided):

```shell
$ tree /<path>/<app-name> -L 1
apps/transfers/
├── contracts/
├── enclave/
├── frontend/
└── README.md
```
42 changes: 42 additions & 0 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use std::path::PathBuf;

use clap::{Parser, Subcommand};
use tracing::metadata::LevelFilter;

#[derive(clap::Args, Debug, Clone)]
pub struct Verbosity {
/// Increase verbosity, can be repeated up to 2 times
#[arg(long, short, action = clap::ArgAction::Count)]
pub verbose: u8,
}

impl Verbosity {
pub fn to_level_filter(&self) -> LevelFilter {
match self.verbose {
0 => LevelFilter::INFO,
1 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
}
}
}

#[derive(Debug, Parser)]
#[command(version, long_about = None)]
pub struct Cli {
/// Increase log verbosity
#[clap(flatten)]
pub verbose: Verbosity,

/// Main command
#[command(subcommand)]
pub command: Command,
}

#[derive(Debug, Subcommand)]
pub enum Command {
Init {
hu55a1n1 marked this conversation as resolved.
Show resolved Hide resolved
/// path to create & init a quartz app, defaults to current path if unspecified
#[clap(long)]
path: Option<PathBuf>,
},
}
8 changes: 8 additions & 0 deletions cli/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use displaydoc::Display;
use thiserror::Error;

#[derive(Debug, Display, Error)]
pub enum Error {
/// specified path `{0}` is not a directory
PathNotDir(String),
}
22 changes: 22 additions & 0 deletions cli/src/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::{cli::Verbosity, error::Error, request::Request, response::Response};

pub mod init;

pub trait Handler {
type Error;
type Response;

fn handle(self, verbosity: Verbosity) -> Result<Self::Response, Self::Error>;
}

impl Handler for Request {
type Error = Error;
type Response = Response;

fn handle(self, verbosity: Verbosity) -> Result<Self::Response, Self::Error> {
match self {
Request::Init(request) => request.handle(verbosity),
}
.map(Into::into)
}
}
16 changes: 16 additions & 0 deletions cli/src/handler/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use tracing::trace;

use crate::{
cli::Verbosity, error::Error, handler::Handler, request::init::InitRequest,
response::init::InitResponse,
};

impl Handler for InitRequest {
type Error = Error;
type Response = InitResponse;

fn handle(self, _verbosity: Verbosity) -> Result<Self::Response, Self::Error> {
trace!("initializing directory structure...");
todo!()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At what point will this be filled out, if not in this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping once we had the skeleton in place, we (the team) could work on the commands in parallel. (we could also copy the work that @dangush is doing on the cycles-protocol repo, e.g. https://github.com/informalsystems/cycles-protocol/pull/14)
I'd be happy to implement quartz init myself in a separate PR. 🙏

}
}
48 changes: 48 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![warn(
clippy::checked_conversions,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
trivial_casts,
trivial_numeric_casts,
rust_2018_idioms,
unused_lifetimes,
unused_import_braces,
unused_qualifications
)]

pub mod cli;
pub mod error;
pub mod handler;
pub mod request;
pub mod response;

use clap::Parser;
use color_eyre::eyre::Result;
use tracing_subscriber::{util::SubscriberInitExt, EnvFilter};

use crate::{cli::Cli, handler::Handler, request::Request};

fn main() -> Result<()> {
color_eyre::install()?;

let args = Cli::parse();

let env_filter = EnvFilter::builder()
.with_default_directive(args.verbose.to_level_filter().into())
.from_env_lossy();

tracing_subscriber::fmt()
.with_target(false)
.with_writer(std::io::stderr)
.with_env_filter(env_filter)
.finish()
.init();

let request = Request::try_from(args.command)?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to parse the input args and convert them into Requests which are correct-by-construction types that the tool can handle (so ideally all validation happens during this conversion). Each Request defines an associated Handler (i.e. logic) and Response. All handlers are free to log to the terminal and these logs are sent to stderror. Handlers must use Responses to output to stdout.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth converting your GH issue comment into a comment in the code 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request.handle(args.verbose)?;

Ok(())
}
19 changes: 19 additions & 0 deletions cli/src/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::{cli::Command, error::Error, request::init::InitRequest};

pub mod init;

#[derive(Clone, Debug)]
pub enum Request {
Init(InitRequest),
}

impl TryFrom<Command> for Request {
type Error = Error;

fn try_from(cmd: Command) -> Result<Self, Self::Error> {
match cmd {
Command::Init { path } => InitRequest::try_from(path),
}
.map(Into::into)
}
}
30 changes: 30 additions & 0 deletions cli/src/request/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::path::PathBuf;

use crate::{error::Error, request::Request};

#[derive(Clone, Debug)]
pub struct InitRequest {
// TODO(hu55a1n1): remove `allow(unused)` here once init handler is implemented
#[allow(unused)]
directory: PathBuf,
}

impl TryFrom<Option<PathBuf>> for InitRequest {
type Error = Error;

fn try_from(path: Option<PathBuf>) -> Result<Self, Self::Error> {
if let Some(path) = path {
if !path.is_dir() {
return Err(Error::PathNotDir(format!("{}", path.display())));
}
}

todo!()
}
}

impl From<InitRequest> for Request {
fn from(request: InitRequest) -> Self {
Self::Init(request)
}
}
10 changes: 10 additions & 0 deletions cli/src/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use serde::Serialize;

use crate::response::init::InitResponse;

pub mod init;

#[derive(Clone, Debug, Serialize)]
pub enum Response {
Init(InitResponse),
}
12 changes: 12 additions & 0 deletions cli/src/response/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use serde::Serialize;

use crate::response::Response;

#[derive(Clone, Debug, Serialize)]
pub struct InitResponse;

impl From<InitResponse> for Response {
fn from(response: InitResponse) -> Self {
Self::Init(response)
}
}
Loading