From 7456863c0389da619036df0f84ed26bde05b292f Mon Sep 17 00:00:00 2001 From: kayagokalp Date: Wed, 5 Jul 2023 15:33:37 +0300 Subject: [PATCH 1/3] feat: forc-wallet and forc-deploy integrated --- Cargo.lock | 192 ++++++++++++++++++++- forc-plugins/forc-client/Cargo.toml | 2 + forc-plugins/forc-client/src/cmd/deploy.rs | 3 + forc-plugins/forc-client/src/cmd/run.rs | 3 + forc-plugins/forc-client/src/op/deploy.rs | 15 +- forc-plugins/forc-client/src/op/run.rs | 14 +- forc-plugins/forc-client/src/util/tx.rs | 75 +++++++- 7 files changed, 293 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f1b7ad1486..fcb893a51ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1680,6 +1680,7 @@ dependencies = [ "forc-tracing", "forc-tx", "forc-util", + "forc-wallet", "fuel-abi-types 0.3.0", "fuel-core-client", "fuel-crypto", @@ -1690,6 +1691,7 @@ dependencies = [ "futures", "hex", "rand", + "rpassword", "serde", "serde_json", "sway-core", @@ -1844,6 +1846,29 @@ dependencies = [ "walkdir", ] +[[package]] +name = "forc-wallet" +version = "0.2.2" +dependencies = [ + "anyhow", + "clap 4.3.9", + "eth-keystore", + "fuel-crypto", + "fuel-types", + "fuels", + "fuels-core", + "futures", + "hex", + "home", + "rand", + "rpassword", + "serde_json", + "termion", + "tiny-bip39", + "tokio", + "url", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -1951,7 +1976,7 @@ dependencies = [ "rand", "serde", "serde_json", - "serde_with", + "serde_with 1.14.0", "tracing", ] @@ -2168,6 +2193,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "fuels" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa81d69072a9d4ba6fac79d9b5b66348daf95089ecda85a9231e41e5dd45bc85" +dependencies = [ + "fuel-core-client", + "fuel-tx", + "fuels-accounts", + "fuels-core", + "fuels-macros", + "fuels-programs", + "fuels-test-helpers", +] + [[package]] name = "fuels-accounts" version = "0.43.0" @@ -2257,6 +2297,60 @@ dependencies = [ "syn 2.0.22", ] +[[package]] +name = "fuels-programs" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e582cc270a794e02e40150f79d2a92830bf5adaaadf4147de02689ae17c287" +dependencies = [ + "async-trait", + "bytes", + "fuel-abi-types 0.3.0", + "fuel-tx", + "fuel-types", + "fuel-vm", + "fuels-accounts", + "fuels-core", + "hex", + "itertools", + "proc-macro2", + "rand", + "regex", + "serde", + "serde_json", + "sha2 0.10.7", + "strum", + "strum_macros 0.24.3", + "thiserror", + "tokio", +] + +[[package]] +name = "fuels-test-helpers" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b0f1475beb61ad66163dc57234170b7319aacf79d9147a28c7ff43cc6fd8f0" +dependencies = [ + "fuel-core-chain-config", + "fuel-core-client", + "fuel-core-types", + "fuel-tx", + "fuel-types", + "fuel-vm", + "fuels-accounts", + "fuels-core", + "futures", + "hex", + "portpicker", + "rand", + "serde", + "serde_json", + "serde_with 2.3.3", + "tempfile", + "tokio", + "which", +] + [[package]] name = "funty" version = "2.0.0" @@ -3727,6 +3821,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "object" version = "0.30.4" @@ -4155,6 +4255,15 @@ dependencies = [ "time 0.3.22", ] +[[package]] +name = "portpicker" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +dependencies = [ + "rand", +] + [[package]] name = "postcard" version = "1.0.4" @@ -4427,6 +4536,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_termios" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" +dependencies = [ + "redox_syscall 0.2.16", +] + [[package]] name = "redox_users" version = "0.3.5" @@ -4638,6 +4756,27 @@ dependencies = [ "text-size", ] +[[package]] +name = "rpassword" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +dependencies = [ + "libc", + "rtoolbox", + "winapi", +] + +[[package]] +name = "rtoolbox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -5007,6 +5146,15 @@ dependencies = [ "serde_with_macros", ] +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "serde", +] + [[package]] name = "serde_with_macros" version = "1.5.2" @@ -5691,6 +5839,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "termion" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659c1f379f3408c7e5e84c7d0da6d93404e3800b6b9d063ba24436419302ec90" +dependencies = [ + "libc", + "numtoa", + "redox_syscall 0.2.16", + "redox_termios", +] + [[package]] name = "test" version = "0.0.0" @@ -5827,6 +5987,25 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-bip39" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" +dependencies = [ + "anyhow", + "hmac", + "once_cell", + "pbkdf2 0.11.0", + "rand", + "rustc-hash", + "sha2 0.10.7", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -6471,6 +6650,17 @@ dependencies = [ "rustls-webpki", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "whoami" version = "1.4.1" diff --git a/forc-plugins/forc-client/Cargo.toml b/forc-plugins/forc-client/Cargo.toml index 94981f411f1..53a0cfa2b79 100644 --- a/forc-plugins/forc-client/Cargo.toml +++ b/forc-plugins/forc-client/Cargo.toml @@ -19,6 +19,7 @@ forc-pkg = { version = "0.41.0", path = "../../forc-pkg" } forc-tracing = { version = "0.41.0", path = "../../forc-tracing" } forc-tx = { version = "0.41.0", path = "../forc-tx" } forc-util = { version = "0.41.0", path = "../../forc-util" } +forc-wallet = { path = "../../../forc-wallet/" } fuel-abi-types = "0.3" fuel-core-client = { workspace = true } fuel-crypto = { workspace = true } @@ -29,6 +30,7 @@ fuels-core = { workspace = true } futures = "0.3" hex = "0.4.3" rand = "0.8" +rpassword = "7.2" serde = "1.0" serde_json = "1" sway-core = { version = "0.41.0", path = "../../sway-core" } diff --git a/forc-plugins/forc-client/src/cmd/deploy.rs b/forc-plugins/forc-client/src/cmd/deploy.rs index 6379c5fb709..65835fa0e6e 100644 --- a/forc-plugins/forc-client/src/cmd/deploy.rs +++ b/forc-plugins/forc-client/src/cmd/deploy.rs @@ -47,4 +47,7 @@ pub struct Command { pub unsigned: bool, /// Set the key to be used for signing. pub signing_key: Option, + /// Sign the deployment transaction manually. + #[clap(long)] + pub manual_signing: bool, } diff --git a/forc-plugins/forc-client/src/cmd/run.rs b/forc-plugins/forc-client/src/cmd/run.rs index a28d6fe08d8..a63fa8cab55 100644 --- a/forc-plugins/forc-client/src/cmd/run.rs +++ b/forc-plugins/forc-client/src/cmd/run.rs @@ -51,4 +51,7 @@ pub struct Command { pub unsigned: bool, /// Set the key to be used for signing. pub signing_key: Option, + /// Sign the deployment transaction manually. + #[clap(long)] + pub manual_signing: bool, } diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index 0a8ebdd50c3..4c30446ce24 100644 --- a/forc-plugins/forc-client/src/op/deploy.rs +++ b/forc-plugins/forc-client/src/op/deploy.rs @@ -2,7 +2,7 @@ use crate::{ cmd, util::{ pkg::built_pkgs, - tx::{TransactionBuilderExt, TX_SUBMIT_TIMEOUT_MS}, + tx::{TransactionBuilderExt, WalletSelectionMode, TX_SUBMIT_TIMEOUT_MS}, }, }; use anyhow::{bail, Context, Result}; @@ -178,12 +178,23 @@ pub async fn deploy_pkg( let contract_id = contract.id(&salt, &root, &state_root); info!("Contract id: 0x{}", hex::encode(contract_id)); + let wallet_mode = if command.manual_signing { + WalletSelectionMode::Manual + } else { + WalletSelectionMode::ForcWallet + }; + let tx = TransactionBuilder::create(bytecode.as_slice().into(), salt, storage_slots.clone()) .gas_limit(command.gas.limit) .gas_price(command.gas.price) .maturity(command.maturity.maturity.into()) .add_output(Output::contract_created(contract_id, state_root)) - .finalize_signed(client.clone(), command.unsigned, command.signing_key) + .finalize_signed( + client.clone(), + command.unsigned, + command.signing_key, + wallet_mode, + ) .await?; let tx = Transaction::from(tx); diff --git a/forc-plugins/forc-client/src/op/run.rs b/forc-plugins/forc-client/src/op/run.rs index 4bd434b7109..4c825395e44 100644 --- a/forc-plugins/forc-client/src/op/run.rs +++ b/forc-plugins/forc-client/src/op/run.rs @@ -2,7 +2,7 @@ use crate::{ cmd, util::{ pkg::built_pkgs, - tx::{TransactionBuilderExt, TX_SUBMIT_TIMEOUT_MS}, + tx::{TransactionBuilderExt, WalletSelectionMode, TX_SUBMIT_TIMEOUT_MS}, }, }; use anyhow::{anyhow, bail, Context, Result}; @@ -77,12 +77,22 @@ pub async fn run_pkg( .map_err(|e| anyhow!("Failed to parse contract id: {}", e)) }) .collect::>>()?; + let wallet_mode = if command.manual_signing { + WalletSelectionMode::Manual + } else { + WalletSelectionMode::ForcWallet + }; let tx = TransactionBuilder::script(compiled.bytecode.bytes.clone(), script_data) .gas_limit(command.gas.limit) .gas_price(command.gas.price) .maturity(command.maturity.maturity.into()) .add_contracts(contract_ids) - .finalize_signed(client.clone(), command.unsigned, command.signing_key) + .finalize_signed( + client.clone(), + command.unsigned, + command.signing_key, + wallet_mode, + ) .await?; if command.dry_run { info!("{:?}", tx); diff --git a/forc-plugins/forc-client/src/util/tx.rs b/forc-plugins/forc-client/src/util/tx.rs index 7ce61ba7ede..3e13bfa16b5 100644 --- a/forc-plugins/forc-client/src/util/tx.rs +++ b/forc-plugins/forc-client/src/util/tx.rs @@ -3,21 +3,28 @@ use std::{io::Write, str::FromStr}; use anyhow::{Error, Result}; use async_trait::async_trait; use fuel_core_client::client::FuelClient; -use fuel_crypto::{Message, SecretKey, Signature}; +use fuel_crypto::{Message, PublicKey, SecretKey, Signature}; use fuel_tx::{ field, Address, AssetId, Buildable, ContractId, Input, Output, TransactionBuilder, Witness, }; use fuel_vm::prelude::SerializableVec; use fuels_accounts::{provider::Provider, wallet::Wallet, ViewOnlyAccount}; use fuels_core::types::{ - bech32::Bech32Address, + bech32::{Bech32Address, FUEL_BECH32_HRP}, coin_type::CoinType, transaction_builders::{create_coin_input, create_coin_message_input}, }; +use forc_wallet::{account::derive_secret_key, utils::default_wallet_path}; + /// The maximum time to wait for a transaction to be included in a block by the node pub const TX_SUBMIT_TIMEOUT_MS: u64 = 30_000u64; +pub enum WalletSelectionMode { + ForcWallet, + Manual, +} + fn prompt_address() -> Result { print!("Please provide the address of the wallet you are going to sign this transaction with:"); std::io::stdout().flush()?; @@ -51,6 +58,7 @@ pub trait TransactionBuilderExt { client: FuelClient, unsigned: bool, signing_key: Option, + wallet_mode: WalletSelectionMode, ) -> Result; } @@ -121,13 +129,65 @@ impl TransactionBuild client: FuelClient, unsigned: bool, signing_key: Option, + wallet_mode: WalletSelectionMode, ) -> Result { let params = client.chain_info().await?.consensus_parameters.into(); let mut signature_witness_index = 0u8; - if !unsigned { + let signing_key = if !unsigned { + let key = match (wallet_mode, signing_key) { + (WalletSelectionMode::ForcWallet, None) => { + // TODO: This is a very simple TUI, we should consider adding a nice TUI + // capabilities for selections and answer collection. + let wallet_path = default_wallet_path(); + if !wallet_path.exists() { + anyhow::bail!("Cannot find a wallet at {wallet_path:?}\nPlease a generate new wallet with `forc wallet new`"); + } + let prompt = format!( + "\nPlease provide the password of your encrypted wallet vault at {wallet_path:?}:" + ); + let password = rpassword::prompt_password(prompt)?; + // TODO: List all derived wallets via forc-wallet and let the users choose + // account. + let account_index = 0; + let secret_key = derive_secret_key(&wallet_path, account_index, &password).map_err(|e| { + if e.to_string().contains("Mac Mismatch") { + anyhow::anyhow!("Failed to access forc-wallet vault. Please check your password") + }else { + e + } + })?; + + // TODO: Do this via forc-wallet once the functinoality is exposed. + let public_key = PublicKey::from(&secret_key); + let hashed = public_key.hash(); + let bech32 = Bech32Address::new(FUEL_BECH32_HRP, hashed); + // TODO: Check for balance and suggest using the faucet. + print!("Do you accept to sign this transaction with {bech32} [y/N]:"); + std::io::stdout().flush()?; + let mut ans = String::new(); + std::io::stdin().read_line(&mut ans)?; + // Pop trailing \n as users press enter to submit their answers. + ans.pop(); + // Trim the user input as it might have an additional space. + let ans = ans.trim(); + if ans != "y" && ans != "Y" { + anyhow::bail!("User refused to sign"); + } + + Some(secret_key) + } + (WalletSelectionMode::ForcWallet, Some(key)) => { + tracing::warn!( + "Signing key is provided while requesting to sign with forc-wallet. Using signing key" + ); + Some(key) + } + (WalletSelectionMode::Manual, None) => None, + (WalletSelectionMode::Manual, Some(key)) => Some(key), + }; // Get the address - let address = if let Some(signing_key) = signing_key { - Address::from(*signing_key.public_key().hash()) + let address = if let Some(key) = key { + Address::from(*key.public_key().hash()) } else { Address::from(prompt_address()?) }; @@ -143,7 +203,10 @@ impl TransactionBuild signature_witness_index, ) .await?; - } + key + } else { + None + }; let mut tx = self._finalize_without_signature(); From 2b52facef8a5ec1423a033af28e6191b81a3c911 Mon Sep 17 00:00:00 2001 From: kayagokalp Date: Wed, 5 Jul 2023 15:55:27 +0300 Subject: [PATCH 2/3] fix: use published forc-wallet version 0.2.3 --- Cargo.lock | 4 +++- forc-plugins/forc-client/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcb893a51ab..ef70ebd8144 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1848,7 +1848,9 @@ dependencies = [ [[package]] name = "forc-wallet" -version = "0.2.2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665edcb55f5e6e4f839f1219b4aeab4ef9db354fc5f2eb499b9b93b5b93d84e" dependencies = [ "anyhow", "clap 4.3.9", diff --git a/forc-plugins/forc-client/Cargo.toml b/forc-plugins/forc-client/Cargo.toml index 53a0cfa2b79..df3ceea927f 100644 --- a/forc-plugins/forc-client/Cargo.toml +++ b/forc-plugins/forc-client/Cargo.toml @@ -19,7 +19,7 @@ forc-pkg = { version = "0.41.0", path = "../../forc-pkg" } forc-tracing = { version = "0.41.0", path = "../../forc-tracing" } forc-tx = { version = "0.41.0", path = "../forc-tx" } forc-util = { version = "0.41.0", path = "../../forc-util" } -forc-wallet = { path = "../../../forc-wallet/" } +forc-wallet = "0.2.3" fuel-abi-types = "0.3" fuel-core-client = { workspace = true } fuel-crypto = { workspace = true } From ce781b6e666c2f688281486c6ce3ff2fa6c586c7 Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Thu, 6 Jul 2023 14:35:24 +0300 Subject: [PATCH 3/3] chore: update lock file --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c0f1958f380..54e7707ca44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1857,7 +1857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665edcb55f5e6e4f839f1219b4aeab4ef9db354fc5f2eb499b9b93b5b93d84e" dependencies = [ "anyhow", - "clap 4.3.9", + "clap 4.3.11", "eth-keystore", "fuel-crypto", "fuel-types",