Skip to content

Commit

Permalink
feat(console-wallet): add contract-definition init command (#4164)
Browse files Browse the repository at this point in the history
Description
---
- Adds `tari_console_wallet contract-definition init` command
- Moves `publish-contract-definition` command to subcommand of `contract-definition` i.e. `contract-definition publish`

Motivation and Context
---
Generates an initial contract definition spec file for the user to edit. All contract definition related commands are under the `contract-definition` subcommand.

```
USAGE:
    tari_console_wallet contract-definition <SUBCOMMAND>

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

SUBCOMMANDS:
    help       Print this message or the help of the given subcommand(s)
    init       Generates a new contract definition JSON spec file that can be edited and passed
                   to other contract definition commands
    publish    Creates and publishes a contract definition UTXO from the JSON spec file
```

How Has This Been Tested?
---
Manually
  • Loading branch information
sdbondi committed Jun 7, 2022
1 parent 859b7d3 commit 8685e2f
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 36 deletions.
129 changes: 98 additions & 31 deletions applications/tari_console_wallet/src/automation/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

use std::{
fs::File,
io::{BufReader, LineWriter, Write},
io::{BufReader, BufWriter, LineWriter, Write},
path::PathBuf,
time::{Duration, Instant},
};
Expand All @@ -47,7 +47,7 @@ use tari_core::transactions::{
use tari_crypto::ristretto::pedersen::PedersenCommitmentFactory;
use tari_utilities::{hex::Hex, ByteArray, Hashable};
use tari_wallet::{
assets::ContractDefinitionFileFormat,
assets::{ContractDefinitionFileFormat, ContractSpecificationFileFormat},
error::WalletError,
output_manager_service::handle::OutputManagerHandle,
transaction_service::handle::{TransactionEvent, TransactionServiceHandle},
Expand All @@ -62,7 +62,14 @@ use tokio::{

use super::error::CommandError;
use crate::{
cli::CliCommands,
automation::prompt::{HexArg, Prompt},
cli::{
CliCommands,
ContractDefinitionCommand,
ContractDefinitionSubcommand,
InitContractDefinitionArgs,
PublishContractDefinitionArgs,
},
utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY},
};

Expand Down Expand Up @@ -719,34 +726,8 @@ pub async fn command_runner(
.await
.map_err(CommandError::TransactionServiceError)?;
},
PublishContractDefinition(args) => {
// open the JSON file with the contract definition values
let file = File::open(&args.file_path).map_err(|e| CommandError::JSONFile(e.to_string()))?;
let file_reader = BufReader::new(file);

// parse the JSON file
let contract_definition: ContractDefinitionFileFormat =
serde_json::from_reader(file_reader).map_err(|e| CommandError::JSONFile(e.to_string()))?;
let contract_definition_features = ContractDefinition::from(contract_definition);
let contract_id_hex = contract_definition_features.calculate_contract_id().to_vec().to_hex();

// create the contract definition transaction
let mut asset_manager = wallet.asset_manager.clone();
let (tx_id, transaction) = asset_manager
.create_contract_definition(&contract_definition_features)
.await?;

// publish the contract definition transaction
let message = format!("Contract definition for contract with id={}", contract_id_hex);
transaction_service
.submit_transaction(tx_id, transaction, 0.into(), message)
.await?;

println!(
"Contract definition transaction submitted with tx_id={} for contract with contract_id={}",
tx_id, contract_id_hex
);
println!("Done!");
ContractDefinition(subcommand) => {
handle_contract_definition_command(&wallet, subcommand).await?;
},
}
}
Expand Down Expand Up @@ -789,6 +770,92 @@ pub async fn command_runner(
Ok(())
}

async fn handle_contract_definition_command(
wallet: &WalletSqlite,
command: ContractDefinitionCommand,
) -> Result<(), CommandError> {
match command.subcommand {
ContractDefinitionSubcommand::Init(args) => init_contract_definition_spec(args),
ContractDefinitionSubcommand::Publish(args) => publish_contract_definition(wallet, args).await,
}
}

fn init_contract_definition_spec(args: InitContractDefinitionArgs) -> Result<(), CommandError> {
if args.dest_path.exists() {
if args.force {
println!("{} exists and will be overwritten.", args.dest_path.to_string_lossy());
} else {
println!(
"{} exists. Use `--force` to overwrite.",
args.dest_path.to_string_lossy()
);
return Ok(());
}
}
let dest = args.dest_path;

let contract_name = Prompt::new("Contract name (max 32 characters):")
.skip_if_some(args.contract_name)
.get_result()?;
let contract_issuer = Prompt::new("Issuer public Key (hex):")
.skip_if_some(args.contract_issuer)
.get_result_parsed::<HexArg<_>>()?;
let runtime = Prompt::new("Contract runtime:")
.skip_if_some(args.runtime)
.with_default("/tari/wasm/v0.1".to_string())
.get_result()?;

let contract_definition = ContractDefinitionFileFormat {
contract_name,
contract_issuer: contract_issuer.into_inner(),
contract_spec: ContractSpecificationFileFormat {
runtime,
public_functions: vec![],
},
};

let file = File::create(&dest).map_err(|e| CommandError::JsonFile(e.to_string()))?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, &contract_definition).map_err(|e| CommandError::JsonFile(e.to_string()))?;
println!("Wrote {}", dest.to_string_lossy());
Ok(())
}

async fn publish_contract_definition(
wallet: &WalletSqlite,
args: PublishContractDefinitionArgs,
) -> Result<(), CommandError> {
// open the JSON file with the contract definition values
let file = File::open(&args.file_path).map_err(|e| CommandError::JsonFile(e.to_string()))?;
let file_reader = BufReader::new(file);

// parse the JSON file
let contract_definition: ContractDefinitionFileFormat =
serde_json::from_reader(file_reader).map_err(|e| CommandError::JsonFile(e.to_string()))?;
let contract_definition_features = ContractDefinition::from(contract_definition);
let contract_id_hex = contract_definition_features.calculate_contract_id().to_vec().to_hex();

// create the contract definition transaction
let mut asset_manager = wallet.asset_manager.clone();
let (tx_id, transaction) = asset_manager
.create_contract_definition(&contract_definition_features)
.await?;

// publish the contract definition transaction
let message = format!("Contract definition for contract with id={}", contract_id_hex);
let mut transaction_service = wallet.transaction_service.clone();
transaction_service
.submit_transaction(tx_id, transaction, 0.into(), message)
.await?;

println!(
"Contract definition transaction submitted with tx_id={} for contract with contract_id={}",
tx_id, contract_id_hex
);
println!("Done!");
Ok(())
}

fn write_utxos_to_csv_file(utxos: Vec<UnblindedOutput>, file_path: PathBuf) -> Result<(), CommandError> {
let factory = PedersenCommitmentFactory::default();
let file = File::create(file_path).map_err(|e| CommandError::CSVFile(e.to_string()))?;
Expand Down
11 changes: 9 additions & 2 deletions applications/tari_console_wallet/src/automation/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use std::num::{ParseFloatError, ParseIntError};
use std::{
io,
num::{ParseFloatError, ParseIntError},
};

use log::*;
use tari_common::exit_codes::{ExitCode, ExitError};
Expand All @@ -42,6 +45,8 @@ pub const LOG_TARGET: &str = "wallet::automation::error";
pub enum CommandError {
#[error("Argument error - were they in the right order?")]
Argument,
#[error("Invalid argument: {0}")]
InvalidArgument(String),
#[error("Tari value error `{0}`")]
MicroTariError(#[from] MicroTariError),
#[error("Transaction service error `{0}`")]
Expand Down Expand Up @@ -69,7 +74,9 @@ pub enum CommandError {
#[error("Error `{0}`")]
ShaError(String),
#[error("JSON file error `{0}`")]
JSONFile(String),
JsonFile(String),
#[error(transparent)]
IoError(#[from] io::Error),
}

impl From<CommandError> for ExitError {
Expand Down
1 change: 1 addition & 0 deletions applications/tari_console_wallet/src/automation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@

pub mod commands;
pub mod error;
mod prompt;
111 changes: 111 additions & 0 deletions applications/tari_console_wallet/src/automation/prompt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2022, The Tari Project
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
// disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
// products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use std::{io, io::Write, str::FromStr};

use tari_utilities::hex::{Hex, HexError};

use crate::automation::error::CommandError;

pub struct Prompt<'a> {
label: &'a str,
skip_if_some: Option<String>,
default: Option<String>,
}

impl<'a> Prompt<'a> {
pub fn new(label: &'a str) -> Self {
Self {
label,
default: None,
skip_if_some: None,
}
}

pub fn skip_if_some(mut self, value: Option<String>) -> Self {
self.skip_if_some = value;
self
}

pub fn with_default(mut self, default: String) -> Self {
self.default = Some(default);
self
}

pub fn get_result_parsed<T>(self) -> Result<T, CommandError>
where
T: FromStr,
T::Err: ToString,
{
let result = self.get_result()?;
let parsed = result
.parse()
.map_err(|e: T::Err| CommandError::InvalidArgument(e.to_string()))?;
Ok(parsed)
}

pub fn get_result(self) -> Result<String, CommandError> {
if let Some(value) = self.skip_if_some {
return Ok(value);
}
loop {
match self.default {
Some(ref default) => {
println!("{} (Default: {})", self.label, default);
},
None => {
println!("{}", self.label);
},
}
print!("> ");
io::stdout().flush()?;
let mut line_buf = String::new();
io::stdin().read_line(&mut line_buf)?;
println!();
let trimmed = line_buf.trim();
if trimmed.is_empty() {
match self.default {
Some(ref default) => return Ok(default.clone()),
None => continue,
}
} else {
return Ok(trimmed.to_string());
}
}
}
}

pub struct HexArg<T>(T);

impl<T> HexArg<T> {
pub fn into_inner(self) -> T {
self.0
}
}

impl<T: Hex> FromStr for HexArg<T> {
type Err = HexError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(T::from_hex(s)?))
}
}
34 changes: 32 additions & 2 deletions applications/tari_console_wallet/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub enum CliCommands {
FinaliseShaAtomicSwap(FinaliseShaAtomicSwapArgs),
ClaimShaAtomicSwapRefund(ClaimShaAtomicSwapRefundArgs),
RevalidateWalletDb,
PublishContractDefinition(PublishContractDefinitionArgs),
ContractDefinition(ContractDefinitionCommand),
}

#[derive(Debug, Args, Clone)]
Expand Down Expand Up @@ -192,12 +192,42 @@ fn parse_hex(s: &str) -> Result<Vec<u8>, HexError> {

#[derive(Debug, Args, Clone)]
pub struct ClaimShaAtomicSwapRefundArgs {
#[clap(short, long, parse(try_from_str = parse_hex), required=true )]
#[clap(short, long, parse(try_from_str = parse_hex), required = true)]
pub output_hash: Vec<Vec<u8>>,
#[clap(short, long, default_value = "Claimed HTLC atomic swap refund")]
pub message: String,
}

#[derive(Debug, Args, Clone)]
pub struct ContractDefinitionCommand {
#[clap(subcommand)]
pub subcommand: ContractDefinitionSubcommand,
}

#[derive(Debug, Subcommand, Clone)]
pub enum ContractDefinitionSubcommand {
/// Generates a new contract definition JSON spec file that can be edited and passed to other contract definition
/// commands.
Init(InitContractDefinitionArgs),
/// Creates and publishes a contract definition UTXO from the JSON spec file.
Publish(PublishContractDefinitionArgs),
}

#[derive(Debug, Args, Clone)]
pub struct InitContractDefinitionArgs {
/// The destination path of the contract definition to create
pub dest_path: PathBuf,
/// Force overwrite the destination file if it already exists
#[clap(short = 'f', long)]
pub force: bool,
#[clap(long, alias = "name")]
pub contract_name: Option<String>,
#[clap(long, alias = "issuer")]
pub contract_issuer: Option<String>,
#[clap(long, alias = "runtime")]
pub runtime: Option<String>,
}

#[derive(Debug, Args, Clone)]
pub struct PublishContractDefinitionArgs {
pub file_path: PathBuf,
Expand Down
2 changes: 1 addition & 1 deletion base_layer/wallet/src/assets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ pub use asset_manager_handle::AssetManagerHandle;
pub(crate) mod infrastructure;

mod contract_definition_file_format;
pub use contract_definition_file_format::ContractDefinitionFileFormat;
pub use contract_definition_file_format::{ContractDefinitionFileFormat, ContractSpecificationFileFormat};

0 comments on commit 8685e2f

Please sign in to comment.