From a6531e654adb74d7503720ba14a36c0a53d690bc Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Thu, 19 Sep 2024 15:36:28 -0700 Subject: [PATCH 01/16] refactor: sign transacitons with ForcClientAccounts --- forc-plugins/forc-client/src/op/deploy.rs | 58 +++--- forc-plugins/forc-client/src/op/run/mod.rs | 8 +- forc-plugins/forc-client/src/util/account.rs | 60 ++++++ forc-plugins/forc-client/src/util/mod.rs | 1 + forc-plugins/forc-client/src/util/tx.rs | 197 +++---------------- 5 files changed, 114 insertions(+), 210 deletions(-) create mode 100644 forc-plugins/forc-client/src/util/account.rs diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index ab4919fe98d..87a95486070 100644 --- a/forc-plugins/forc-client/src/op/deploy.rs +++ b/forc-plugins/forc-client/src/op/deploy.rs @@ -2,12 +2,13 @@ use crate::{ cmd, constants::TX_SUBMIT_TIMEOUT_MS, util::{ + account::ForcClientAccount, node_url::get_node_url, pkg::{built_pkgs, create_proxy_contract, update_proxy_address_in_manifest}, target::Target, tx::{ - bech32_from_secret, prompt_forc_wallet_password, select_secret_key, - update_proxy_contract_target, WalletSelectionMode, + bech32_from_secret, prompt_forc_wallet_password, select_account, + update_proxy_contract_target, SignerSelectionMode, }, }, }; @@ -26,7 +27,7 @@ use fuels::{ programs::contract::{LoadConfiguration, StorageConfiguration}, types::{bech32::Bech32ContractId, transaction_builders::Blob}, }; -use fuels_accounts::{provider::Provider, wallet::WalletUnlocked, Account}; +use fuels_accounts::{provider::Provider, wallet::WalletUnlocked, Account, ViewOnlyAccount}; use fuels_core::types::{transaction::TxPolicies, transaction_builders::CreateTransactionBuilder}; use futures::FutureExt; use pkg::{manifest::build_profile::ExperimentalFlags, BuildProfile, BuiltPackage}; @@ -158,7 +159,7 @@ async fn deploy_chunked( command: &cmd::Deploy, compiled: &BuiltPackage, salt: Salt, - signing_key: &SecretKey, + account: &ForcClientAccount, provider: &Provider, pkg_name: &str, ) -> anyhow::Result { @@ -172,7 +173,6 @@ async fn deploy_chunked( None => "".to_string(), }; - let wallet = WalletUnlocked::new_from_private_key(*signing_key, Some(provider.clone())); let blobs = compiled .bytecode .bytes @@ -183,7 +183,7 @@ async fn deploy_chunked( let tx_policies = tx_policies_from_cmd(command); let contract_id = fuels::programs::contract::Contract::loader_from_blobs(blobs, salt, storage_slots)? - .deploy(&wallet, tx_policies) + .deploy(account, tx_policies) .await? .into(); @@ -201,7 +201,7 @@ async fn deploy_new_proxy( pkg_name: &str, impl_contract: &fuel_tx::ContractId, provider: &Provider, - signing_key: &SecretKey, + account: &ForcClientAccount, ) -> Result { fuels::macros::abigen!(Contract( name = "ProxyContract", @@ -931,8 +931,7 @@ async fn deploy_new_proxy( }"#, )); let proxy_dir_output = create_proxy_contract(pkg_name)?; - let address = bech32_from_secret(signing_key)?; - let wallet = WalletUnlocked::new_from_private_key(*signing_key, Some(provider.clone())); + let address = account.address(); let storage_path = proxy_dir_output.join("proxy-storage_slots.json"); let storage_configuration = @@ -951,7 +950,7 @@ async fn deploy_new_proxy( proxy_dir_output.join("proxy.bin"), configuration, )? - .deploy(&wallet, tx_policies) + .deploy(account, tx_policies) .await? .into(); @@ -968,7 +967,7 @@ async fn deploy_new_proxy( ); let proxy_contract_bech_id: Bech32ContractId = proxy_contract_id.into(); - let instance = ProxyContract::new(&proxy_contract_bech_id, wallet); + let instance = ProxyContract::new(&proxy_contract_bech_id, account.clone()); instance.methods().initialize_proxy().call().await?; println_action_green("Initialized", &format!("proxy contract for {pkg_name}")); Ok(proxy_contract_id) @@ -1059,7 +1058,7 @@ pub async fn deploy(command: cmd::Deploy) -> Result> { } // Confirmation step. Summarize the transaction(s) for the deployment. - let (provider, signing_key) = + let (provider, account) = confirm_transaction_details(&pkgs_to_deploy, &command, node_url.clone()).await?; for pkg in pkgs_to_deploy { @@ -1087,13 +1086,13 @@ pub async fn deploy(command: cmd::Deploy) -> Result> { &command, pkg, salt, - &signing_key, + &account, &provider, &pkg.descriptor.name, ) .await? } else { - deploy_pkg(&command, pkg, salt, &provider, &signing_key).await? + deploy_pkg(&command, pkg, salt, &provider, &account).await? }; let proxy_id = match &pkg.descriptor.manifest_file.proxy { @@ -1108,13 +1107,8 @@ pub async fn deploy(command: cmd::Deploy) -> Result> { let proxy_contract = ContractId::from_str(proxy_addr).map_err(|e| anyhow::anyhow!(e))?; - update_proxy_contract_target( - &provider, - signing_key, - proxy_contract, - deployed_contract_id, - ) - .await?; + update_proxy_contract_target(&account, proxy_contract, deployed_contract_id) + .await?; Some(proxy_contract) } Some(forc_pkg::manifest::Proxy { @@ -1128,7 +1122,7 @@ pub async fn deploy(command: cmd::Deploy) -> Result> { pkg_name, &deployed_contract_id, &provider, - &signing_key, + &account, ) .await?; @@ -1158,7 +1152,7 @@ async fn confirm_transaction_details( pkgs_to_deploy: &[&Arc], command: &cmd::Deploy, node_url: String, -) -> Result<(Provider, SecretKey)> { +) -> Result<(Provider, ForcClientAccount)> { // Confirmation step. Summarize the transaction(s) for the deployment. let mut tx_count = 0; let tx_summary = pkgs_to_deploy @@ -1203,27 +1197,26 @@ async fn confirm_transaction_details( let provider = Provider::connect(node_url.clone()).await?; let wallet_mode = if command.default_signer || command.signing_key.is_some() { - WalletSelectionMode::Manual + SignerSelectionMode::Manual } else { println_action_green("", &format!("Wallet: {}", default_wallet_path().display())); let password = prompt_forc_wallet_password()?; - WalletSelectionMode::ForcWallet(password) + SignerSelectionMode::ForcWallet(password) }; // TODO: Display the estimated gas cost of the transaction(s). // https://github.com/FuelLabs/sway/issues/6277 - let signing_key = select_secret_key( + let account = select_account( &wallet_mode, command.default_signer || command.unsigned, command.signing_key, &provider, tx_count, ) - .await? - .ok_or_else(|| anyhow::anyhow!("failed to select a signer for the transaction"))?; + .await?; - Ok((provider.clone(), signing_key)) + Ok((provider.clone(), account)) } /// Deploy a single pkg given deploy command and the manifest file @@ -1232,7 +1225,7 @@ pub async fn deploy_pkg( compiled: &BuiltPackage, salt: Salt, provider: &Provider, - signing_key: &SecretKey, + account: &ForcClientAccount, ) -> Result { let manifest = &compiled.descriptor.manifest_file; let node_url = provider.url(); @@ -1255,10 +1248,9 @@ pub async fn deploy_pkg( storage_slots.clone(), tx_policies, ); - let wallet = WalletUnlocked::new_from_private_key(*signing_key, Some(provider.clone())); - wallet.add_witnesses(&mut tb)?; - wallet.adjust_for_fee(&mut tb, 0).await?; + account.add_witnesses(&mut tb)?; + account.adjust_for_fee(&mut tb, 0).await?; let tx = tb.build(provider).await?; let tx = Transaction::from(tx); diff --git a/forc-plugins/forc-client/src/op/run/mod.rs b/forc-plugins/forc-client/src/op/run/mod.rs index cda23fb14cf..d3c5f692465 100644 --- a/forc-plugins/forc-client/src/op/run/mod.rs +++ b/forc-plugins/forc-client/src/op/run/mod.rs @@ -6,7 +6,7 @@ use crate::{ gas::get_script_gas_used, node_url::get_node_url, pkg::built_pkgs, - tx::{prompt_forc_wallet_password, TransactionBuilderExt, WalletSelectionMode}, + tx::{prompt_forc_wallet_password, SignerSelectionMode, TransactionBuilderExt}, }, }; use anyhow::{anyhow, bail, Context, Result}; @@ -51,10 +51,10 @@ pub async fn run(command: cmd::Run) -> Result> { let build_opts = build_opts_from_cmd(&command); let built_pkgs_with_manifest = built_pkgs(&curr_dir, &build_opts)?; let wallet_mode = if command.default_signer || command.signing_key.is_some() { - WalletSelectionMode::Manual + SignerSelectionMode::Manual } else { let password = prompt_forc_wallet_password()?; - WalletSelectionMode::ForcWallet(password) + SignerSelectionMode::ForcWallet(password) }; for built in built_pkgs_with_manifest { if built @@ -81,7 +81,7 @@ pub async fn run_pkg( command: &cmd::Run, manifest: &PackageManifestFile, compiled: &BuiltPackage, - wallet_mode: &WalletSelectionMode, + wallet_mode: &SignerSelectionMode, ) -> Result { let node_url = get_node_url(&command.node, &manifest.network)?; diff --git a/forc-plugins/forc-client/src/util/account.rs b/forc-plugins/forc-client/src/util/account.rs new file mode 100644 index 00000000000..a895f763379 --- /dev/null +++ b/forc-plugins/forc-client/src/util/account.rs @@ -0,0 +1,60 @@ +use fuels::{ + prelude::*, + types::{coin_type_id::CoinTypeId, input::Input}, +}; +use fuels_accounts::{wallet::WalletUnlocked, Account}; + +/// Accounts that can be used with forc-client. +#[derive(Clone, Debug)] +pub enum ForcClientAccount { + Wallet(WalletUnlocked), + KmsSigner, +} + +impl Account for ForcClientAccount { + /// Returns a vector consisting of `Input::Coin`s and `Input::Message`s for the given + /// asset ID and amount. The `witness_index` is the position of the witness (signature) + /// in the transaction\'s list of witnesses. In the validation process, the node will + /// use the witness at this index to validate the coins returned by this method. + #[must_use] + #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] + fn get_asset_inputs_for_amount<'life0, 'async_trait>( + &'life0 self, + asset_id: AssetId, + amount: u64, + excluded_coins: Option>, + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future>> + + ::core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + match self { + ForcClientAccount::Wallet(wallet) => { + wallet.get_asset_inputs_for_amount(asset_id, amount, excluded_coins) + } + ForcClientAccount::KmsSigner => todo!(), + } + } +} + +impl ViewOnlyAccount for ForcClientAccount { + fn address(&self) -> &Bech32Address { + match self { + ForcClientAccount::Wallet(wallet) => wallet.address(), + ForcClientAccount::KmsSigner => todo!(), + } + } + + fn try_provider(&self) -> Result<&Provider> { + match self { + ForcClientAccount::Wallet(wallet) => wallet.try_provider(), + ForcClientAccount::KmsSigner => todo!(), + } + } +} diff --git a/forc-plugins/forc-client/src/util/mod.rs b/forc-plugins/forc-client/src/util/mod.rs index d8024081b28..f5f881b1722 100644 --- a/forc-plugins/forc-client/src/util/mod.rs +++ b/forc-plugins/forc-client/src/util/mod.rs @@ -1,3 +1,4 @@ +pub mod account; pub(crate) mod encode; pub(crate) mod gas; pub(crate) mod node_url; diff --git a/forc-plugins/forc-client/src/util/tx.rs b/forc-plugins/forc-client/src/util/tx.rs index 4371df2fc13..477c80013ab 100644 --- a/forc-plugins/forc-client/src/util/tx.rs +++ b/forc-plugins/forc-client/src/util/tx.rs @@ -1,6 +1,8 @@ -use crate::{constants::DEFAULT_PRIVATE_KEY, util::target::Target}; +use crate::{ + constants::DEFAULT_PRIVATE_KEY, + util::{account::ForcClientAccount, target::Target}, +}; use anyhow::{Error, Result}; -use async_trait::async_trait; use dialoguer::{theme::ColorfulTheme, Confirm, Password, Select}; use forc_tracing::{println_action_green, println_warning}; use forc_wallet::{ @@ -11,25 +13,19 @@ use forc_wallet::{ new::{new_wallet_cli, New}, utils::default_wallet_path, }; -use fuel_crypto::{Message, PublicKey, SecretKey, Signature}; -use fuel_tx::{ - field, Address, AssetId, Buildable, ContractId, Input, Output, TransactionBuilder, Witness, -}; +use fuel_crypto::{PublicKey, SecretKey, Signature}; +use fuel_tx::{AssetId, ContractId}; use fuels::{macros::abigen, programs::responses::CallResponse}; use fuels_accounts::{ provider::Provider, wallet::{Wallet, WalletUnlocked}, ViewOnlyAccount, }; -use fuels_core::types::{ - bech32::{Bech32Address, FUEL_BECH32_HRP}, - coin_type::CoinType, - transaction_builders::{create_coin_input, create_coin_message_input}, -}; +use fuels_core::types::bech32::{Bech32Address, FUEL_BECH32_HRP}; use std::{collections::BTreeMap, io::Write, path::Path, str::FromStr}; #[derive(PartialEq, Eq)] -pub enum WalletSelectionMode { +pub enum SignerSelectionMode { /// Holds the password of forc-wallet instance. ForcWallet(String), Manual, @@ -180,16 +176,16 @@ pub fn format_base_asset_account_balances( } // TODO: Simplify the function signature once https://github.com/FuelLabs/sway/issues/6071 is closed. -pub(crate) async fn select_secret_key( - wallet_mode: &WalletSelectionMode, +pub(crate) async fn select_account( + wallet_mode: &SignerSelectionMode, default_sign: bool, signing_key: Option, provider: &Provider, tx_count: usize, -) -> Result> { +) -> Result { let chain_info = provider.chain_info().await?; - let signing_key = match wallet_mode { - WalletSelectionMode::ForcWallet(password) => { + match wallet_mode { + SignerSelectionMode::ForcWallet(password) => { let wallet_path = default_wallet_path(); check_and_create_wallet_at_default_path(&wallet_path)?; // TODO: This is a very simple TUI, we should consider adding a nice TUI @@ -249,24 +245,26 @@ pub(crate) async fn select_secret_key( anyhow::bail!("User refused to sign"); } - Some(secret_key) + let wallet = WalletUnlocked::new_from_private_key(secret_key, Some(provider.clone())); + Ok(ForcClientAccount::Wallet(wallet)) } - WalletSelectionMode::Manual => select_manual_secret_key(default_sign, signing_key), - }; - Ok(signing_key) + SignerSelectionMode::Manual => { + let secret_key = select_manual_secret_key(default_sign, signing_key) + .ok_or_else(|| anyhow::anyhow!("missing manual secret key"))?; + let wallet = WalletUnlocked::new_from_private_key(secret_key, Some(provider.clone())); + Ok(ForcClientAccount::Wallet(wallet)) + } + } } pub async fn update_proxy_contract_target( - provider: &Provider, - secret_key: SecretKey, + account: &ForcClientAccount, proxy_contract_id: ContractId, new_target: ContractId, ) -> Result> { abigen!(Contract(name = "ProxyContract", abi = "{\"programType\":\"contract\",\"specVersion\":\"1\",\"encodingVersion\":\"1\",\"concreteTypes\":[{\"type\":\"()\",\"concreteTypeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"},{\"type\":\"enum standards::src5::AccessError\",\"concreteTypeId\":\"3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d\",\"metadataTypeId\":1},{\"type\":\"enum standards::src5::State\",\"concreteTypeId\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\",\"metadataTypeId\":2},{\"type\":\"enum std::option::Option\",\"concreteTypeId\":\"0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8\",\"metadataTypeId\":4,\"typeArguments\":[\"29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54\"]},{\"type\":\"enum sway_libs::ownership::errors::InitializationError\",\"concreteTypeId\":\"1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893\",\"metadataTypeId\":5},{\"type\":\"enum sway_libs::upgradability::errors::SetProxyOwnerError\",\"concreteTypeId\":\"3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74\",\"metadataTypeId\":6},{\"type\":\"str\",\"concreteTypeId\":\"8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a\"},{\"type\":\"struct std::contract_id::ContractId\",\"concreteTypeId\":\"29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54\",\"metadataTypeId\":9},{\"type\":\"struct sway_libs::upgradability::events::ProxyOwnerSet\",\"concreteTypeId\":\"96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247\",\"metadataTypeId\":10},{\"type\":\"struct sway_libs::upgradability::events::ProxyTargetSet\",\"concreteTypeId\":\"1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8\",\"metadataTypeId\":11}],\"metadataTypes\":[{\"type\":\"b256\",\"metadataTypeId\":0},{\"type\":\"enum standards::src5::AccessError\",\"metadataTypeId\":1,\"components\":[{\"name\":\"NotOwner\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"enum standards::src5::State\",\"metadataTypeId\":2,\"components\":[{\"name\":\"Uninitialized\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"},{\"name\":\"Initialized\",\"typeId\":3},{\"name\":\"Revoked\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"enum std::identity::Identity\",\"metadataTypeId\":3,\"components\":[{\"name\":\"Address\",\"typeId\":8},{\"name\":\"ContractId\",\"typeId\":9}]},{\"type\":\"enum std::option::Option\",\"metadataTypeId\":4,\"components\":[{\"name\":\"None\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"},{\"name\":\"Some\",\"typeId\":7}],\"typeParameters\":[7]},{\"type\":\"enum sway_libs::ownership::errors::InitializationError\",\"metadataTypeId\":5,\"components\":[{\"name\":\"CannotReinitialized\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"enum sway_libs::upgradability::errors::SetProxyOwnerError\",\"metadataTypeId\":6,\"components\":[{\"name\":\"CannotUninitialize\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"generic T\",\"metadataTypeId\":7},{\"type\":\"struct std::address::Address\",\"metadataTypeId\":8,\"components\":[{\"name\":\"bits\",\"typeId\":0}]},{\"type\":\"struct std::contract_id::ContractId\",\"metadataTypeId\":9,\"components\":[{\"name\":\"bits\",\"typeId\":0}]},{\"type\":\"struct sway_libs::upgradability::events::ProxyOwnerSet\",\"metadataTypeId\":10,\"components\":[{\"name\":\"new_proxy_owner\",\"typeId\":2}]},{\"type\":\"struct sway_libs::upgradability::events::ProxyTargetSet\",\"metadataTypeId\":11,\"components\":[{\"name\":\"new_target\",\"typeId\":9}]}],\"functions\":[{\"inputs\":[],\"name\":\"proxy_target\",\"output\":\"0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Returns the target contract of the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Returns\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * [Option] - The new proxy contract to which all fallback calls will be passed or `None`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"read\"]}]},{\"inputs\":[{\"name\":\"new_target\",\"concreteTypeId\":\"29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54\"}],\"name\":\"set_proxy_target\",\"output\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Change the target contract of the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Additional Information\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method can only be called by the `proxy_owner`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Arguments\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * `new_target`: [ContractId] - The new proxy contract to which all fallback calls will be passed.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Reverts\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When not called by `proxy_owner`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Write: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"read\",\"write\"]}]},{\"inputs\":[],\"name\":\"proxy_owner\",\"output\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Returns the owner of the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Returns\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * [State] - Represents the state of ownership for this contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"read\"]}]},{\"inputs\":[],\"name\":\"initialize_proxy\",\"output\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Initializes the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Additional Information\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method sets the storage values using the values of the configurable constants `INITIAL_TARGET` and `INITIAL_OWNER`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This then allows methods that write to storage to be called.\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method can only be called once.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Reverts\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When `storage::SRC14.proxy_owner` is not [State::Uninitialized].\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Writes: `2`\"]},{\"name\":\"storage\",\"arguments\":[\"write\"]}]},{\"inputs\":[{\"name\":\"new_proxy_owner\",\"concreteTypeId\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\"}],\"name\":\"set_proxy_owner\",\"output\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Changes proxy ownership to the passed State.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Additional Information\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method can be used to transfer ownership between Identities or to revoke ownership.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Arguments\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * `new_proxy_owner`: [State] - The new state of the proxy ownership.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Reverts\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When the sender is not the current proxy owner.\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When the new state of the proxy ownership is [State::Uninitialized].\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Writes: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"write\"]}]}],\"loggedTypes\":[{\"logId\":\"4571204900286667806\",\"concreteTypeId\":\"3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d\"},{\"logId\":\"2151606668983994881\",\"concreteTypeId\":\"1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8\"},{\"logId\":\"2161305517876418151\",\"concreteTypeId\":\"1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893\"},{\"logId\":\"4354576968059844266\",\"concreteTypeId\":\"3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74\"},{\"logId\":\"10870989709723147660\",\"concreteTypeId\":\"96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247\"},{\"logId\":\"10098701174489624218\",\"concreteTypeId\":\"8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a\"}],\"messagesTypes\":[],\"configurables\":[{\"name\":\"INITIAL_TARGET\",\"concreteTypeId\":\"0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8\",\"offset\":13368},{\"name\":\"INITIAL_OWNER\",\"concreteTypeId\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\",\"offset\":13320}]}",)); - let wallet = WalletUnlocked::new_from_private_key(secret_key, Some(provider.clone())); - - let proxy_contract = ProxyContract::new(proxy_contract_id, wallet); + let proxy_contract = ProxyContract::new(proxy_contract_id, account.clone()); let result = proxy_contract .methods() @@ -280,153 +278,6 @@ pub async fn update_proxy_contract_target( Ok(result) } -#[async_trait] -pub trait TransactionBuilderExt { - fn add_contract(&mut self, contract_id: ContractId) -> &mut Self; - fn add_contracts(&mut self, contract_ids: Vec) -> &mut Self; - fn add_inputs(&mut self, inputs: Vec) -> &mut Self; - async fn fund( - &mut self, - address: Address, - provider: Provider, - signature_witness_index: u16, - ) -> Result<&mut Self>; - async fn finalize_signed( - &mut self, - client: Provider, - default_signature: bool, - signing_key: Option, - wallet_mode: &WalletSelectionMode, - ) -> Result; -} - -#[async_trait] -impl TransactionBuilderExt for TransactionBuilder { - fn add_contract(&mut self, contract_id: ContractId) -> &mut Self { - let input_index = self - .inputs() - .len() - .try_into() - .expect("limit of 256 inputs exceeded"); - self.add_input(fuel_tx::Input::contract( - fuel_tx::UtxoId::new(fuel_tx::Bytes32::zeroed(), 0), - fuel_tx::Bytes32::zeroed(), - fuel_tx::Bytes32::zeroed(), - fuel_tx::TxPointer::new(0u32.into(), 0), - contract_id, - )) - .add_output(fuel_tx::Output::Contract( - fuel_tx::output::contract::Contract { - input_index, - balance_root: fuel_tx::Bytes32::zeroed(), - state_root: fuel_tx::Bytes32::zeroed(), - }, - )) - } - fn add_contracts(&mut self, contract_ids: Vec) -> &mut Self { - for contract_id in contract_ids { - self.add_contract(contract_id); - } - self - } - fn add_inputs(&mut self, inputs: Vec) -> &mut Self { - for input in inputs { - self.add_input(input); - } - self - } - async fn fund( - &mut self, - address: Address, - provider: Provider, - signature_witness_index: u16, - ) -> Result<&mut Self> { - let asset_id = *provider.base_asset_id(); - let wallet = Wallet::from_address(Bech32Address::from(address), Some(provider)); - - let amount = 1_000_000; - let filter = None; - let inputs: Vec<_> = wallet - .get_spendable_resources(asset_id, amount, filter) - .await? - .into_iter() - .map(|coin_type| match coin_type { - CoinType::Coin(coin) => create_coin_input(coin, signature_witness_index), - CoinType::Message(message) => { - create_coin_message_input(message, signature_witness_index) - } - }) - .collect(); - let output = Output::change(wallet.address().into(), 0, asset_id); - - self.add_inputs(inputs).add_output(output); - - Ok(self) - } - async fn finalize_signed( - &mut self, - provider: Provider, - default_sign: bool, - signing_key: Option, - wallet_mode: &WalletSelectionMode, - ) -> Result { - let chain_info = provider.chain_info().await?; - let params = chain_info.consensus_parameters; - let signing_key = - select_secret_key(wallet_mode, default_sign, signing_key, &provider, 1).await?; - // Get the address - let address = if let Some(key) = signing_key { - Address::from(*key.public_key().hash()) - } else { - // TODO: Remove this path https://github.com/FuelLabs/sway/issues/6071 - Address::from(prompt_address()?) - }; - - // Insert dummy witness for signature - let signature_witness_index = self.witnesses().len().try_into()?; - self.add_witness(Witness::default()); - - // Add input coin and output change - self.fund( - address, - provider, - signature_witness_index, - ) - .await.map_err(|e| if e.to_string().contains("not enough coins to fit the target") { - anyhow::anyhow!("Deployment failed due to insufficient funds. Please be sure to have enough coins to pay for deployment transaction.") - } else { - e - })?; - - let mut tx = self.finalize_without_signature_inner(); - - let signature = if let Some(signing_key) = signing_key { - let message = Message::from_bytes(*tx.id(¶ms.chain_id())); - Signature::sign(&signing_key, &message) - } else { - prompt_signature(tx.id(¶ms.chain_id()))? - }; - - let witness = Witness::from(signature.as_ref()); - tx.replace_witness(signature_witness_index, witness); - tx.precompute(¶ms.chain_id()) - .map_err(anyhow::Error::msg)?; - - Ok(tx) - } -} - -pub trait TransactionExt { - fn replace_witness(&mut self, witness_index: u16, witness: Witness) -> &mut Self; -} - -impl TransactionExt for T { - fn replace_witness(&mut self, index: u16, witness: Witness) -> &mut Self { - self.witnesses_mut()[index as usize] = witness; - self - } -} - #[cfg(test)] mod tests { use super::*; From b1ef91cea1ff0ba3eb3af2ea3df392ce57229222 Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Sun, 22 Sep 2024 00:12:34 -0700 Subject: [PATCH 02/16] refactor forc-run as well --- forc-plugins/forc-client/src/op/deploy.rs | 13 +++- forc-plugins/forc-client/src/op/run/mod.rs | 81 +++++++++++++------- forc-plugins/forc-client/src/util/account.rs | 30 ++++++++ forc-plugins/forc-client/src/util/gas.rs | 53 ------------- forc-plugins/forc-client/src/util/mod.rs | 1 - forc-plugins/forc-client/src/util/tx.rs | 32 +------- 6 files changed, 97 insertions(+), 113 deletions(-) delete mode 100644 forc-plugins/forc-client/src/util/gas.rs diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index 87a95486070..bae5045569f 100644 --- a/forc-plugins/forc-client/src/op/deploy.rs +++ b/forc-plugins/forc-client/src/op/deploy.rs @@ -7,8 +7,8 @@ use crate::{ pkg::{built_pkgs, create_proxy_contract, update_proxy_address_in_manifest}, target::Target, tx::{ - bech32_from_secret, prompt_forc_wallet_password, select_account, - update_proxy_contract_target, SignerSelectionMode, + prompt_forc_wallet_password, select_account, update_proxy_contract_target, + SignerSelectionMode, }, }, }; @@ -25,9 +25,12 @@ use fuel_tx::{Salt, Transaction}; use fuel_vm::prelude::*; use fuels::{ programs::contract::{LoadConfiguration, StorageConfiguration}, - types::{bech32::Bech32ContractId, transaction_builders::Blob}, + types::{ + bech32::Bech32ContractId, + transaction_builders::{Blob, TransactionBuilder}, + }, }; -use fuels_accounts::{provider::Provider, wallet::WalletUnlocked, Account, ViewOnlyAccount}; +use fuels_accounts::{provider::Provider, Account, ViewOnlyAccount}; use fuels_core::types::{transaction::TxPolicies, transaction_builders::CreateTransactionBuilder}; use futures::FutureExt; use pkg::{manifest::build_profile::ExperimentalFlags, BuildProfile, BuiltPackage}; @@ -1251,6 +1254,8 @@ pub async fn deploy_pkg( account.add_witnesses(&mut tb)?; account.adjust_for_fee(&mut tb, 0).await?; + tb.add_signer(account.clone())?; + let tx = tb.build(provider).await?; let tx = Transaction::from(tx); diff --git a/forc-plugins/forc-client/src/op/run/mod.rs b/forc-plugins/forc-client/src/op/run/mod.rs index d3c5f692465..7d786b481dd 100644 --- a/forc-plugins/forc-client/src/op/run/mod.rs +++ b/forc-plugins/forc-client/src/op/run/mod.rs @@ -3,10 +3,9 @@ use crate::{ cmd, constants::TX_SUBMIT_TIMEOUT_MS, util::{ - gas::get_script_gas_used, node_url::get_node_url, pkg::built_pkgs, - tx::{prompt_forc_wallet_password, SignerSelectionMode, TransactionBuilderExt}, + tx::{prompt_forc_wallet_password, select_account, SignerSelectionMode}, }, }; use anyhow::{anyhow, bail, Context, Result}; @@ -14,8 +13,17 @@ use forc_pkg::{self as pkg, fuel_core_not_running, PackageManifestFile}; use forc_tracing::println_warning; use forc_util::tx_utils::format_log_receipts; use fuel_core_client::client::FuelClient; -use fuel_tx::{ContractId, Transaction, TransactionBuilder}; -use fuels_accounts::provider::Provider; +use fuel_tx::{ContractId, Transaction}; +use fuels::{ + programs::calls::{traits::TransactionTuner, ScriptCall}, + types::{ + bech32::Bech32ContractId, + transaction::TxPolicies, + transaction_builders::{BuildableTransaction, VariableOutputPolicy}, + }, +}; +use fuels_accounts::{provider::Provider, Account}; +use fuels_core::types::transaction_builders::TransactionBuilder; use pkg::{manifest::build_profile::ExperimentalFlags, BuiltPackage}; use std::time::Duration; use std::{path::PathBuf, str::FromStr}; @@ -77,13 +85,34 @@ pub async fn run(command: cmd::Run) -> Result> { Ok(receipts) } +fn tx_policies_from_cmd(cmd: &cmd::Run) -> TxPolicies { + let mut tx_policies = TxPolicies::default(); + if let Some(max_fee) = cmd.gas.max_fee { + tx_policies = tx_policies.with_max_fee(max_fee); + } + if let Some(script_gas_limit) = cmd.gas.script_gas_limit { + tx_policies = tx_policies.with_script_gas_limit(script_gas_limit); + } + tx_policies +} + pub async fn run_pkg( command: &cmd::Run, manifest: &PackageManifestFile, compiled: &BuiltPackage, - wallet_mode: &SignerSelectionMode, + signer_mode: &SignerSelectionMode, ) -> Result { let node_url = get_node_url(&command.node, &manifest.network)?; + let provider = Provider::connect(node_url.clone()).await?; + let tx_count = 1; + let account = select_account( + &signer_mode, + command.default_signer || command.unsigned, + command.signing_key, + &provider, + tx_count, + ) + .await?; let script_data = match (&command.data, &command.args) { (None, Some(args)) => { @@ -116,31 +145,29 @@ pub async fn run_pkg( }) .collect::>>()?; - let mut tb = TransactionBuilder::script(compiled.bytecode.bytes.clone(), script_data); - tb.maturity(command.maturity.maturity.into()) - .add_contracts(contract_ids); - - let provider = Provider::connect(node_url.clone()).await?; - - let script_gas_limit = if compiled.bytecode.bytes.is_empty() { - 0 - } else if let Some(script_gas_limit) = command.gas.script_gas_limit { - script_gas_limit - // Dry run tx and get `gas_used` - } else { - get_script_gas_used(tb.clone().finalize_without_signature_inner(), &provider).await? + let script_binary = compiled.bytecode.bytes.clone(); + let external_contracts = contract_ids + .into_iter() + .map(|contract| Bech32ContractId::from(contract)) + .collect::>(); + let call = ScriptCall { + script_binary, + encoded_args: Ok(script_data), + inputs: vec![], + outputs: vec![], + external_contracts, }; - tb.script_gas_limit(script_gas_limit); - - let tx = tb - .finalize_signed( - Provider::connect(node_url.clone()).await?, - command.default_signer, - command.signing_key, - wallet_mode, - ) + let tx_policies = tx_policies_from_cmd(command); + let mut tb = call + .transaction_builder(tx_policies, VariableOutputPolicy::EstimateMinimum, &account) .await?; + account.add_witnesses(&mut tb)?; + account.adjust_for_fee(&mut tb, 0).await?; + tb.add_signer(account)?; + + let tx = tb.build(provider).await?; + if command.dry_run { info!("{:?}", tx); Ok(RanScript { receipts: vec![] }) diff --git a/forc-plugins/forc-client/src/util/account.rs b/forc-plugins/forc-client/src/util/account.rs index a895f763379..12d09e83e31 100644 --- a/forc-plugins/forc-client/src/util/account.rs +++ b/forc-plugins/forc-client/src/util/account.rs @@ -1,3 +1,4 @@ +use fuel_crypto::{Message, Signature}; use fuels::{ prelude::*, types::{coin_type_id::CoinTypeId, input::Input}, @@ -58,3 +59,32 @@ impl ViewOnlyAccount for ForcClientAccount { } } } + +impl Signer for ForcClientAccount { + fn sign<'life0, 'async_trait>( + &'life0 self, + message: Message, + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future> + + ::core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + match self { + ForcClientAccount::Wallet(wallet) => wallet.sign(message), + ForcClientAccount::KmsSigner => todo!(), + } + } + + fn address(&self) -> &Bech32Address { + match self { + ForcClientAccount::Wallet(wallet) => wallet.address(), + ForcClientAccount::KmsSigner => todo!(), + } + } +} diff --git a/forc-plugins/forc-client/src/util/gas.rs b/forc-plugins/forc-client/src/util/gas.rs deleted file mode 100644 index 9667bee8674..00000000000 --- a/forc-plugins/forc-client/src/util/gas.rs +++ /dev/null @@ -1,53 +0,0 @@ -use anyhow::Result; - -use fuel_tx::{ - field::{Inputs, Witnesses}, - Buildable, Chargeable, Input, Script, TxPointer, -}; -use fuels_accounts::provider::Provider; -use fuels_core::types::transaction::ScriptTransaction; - -fn no_spendable_input<'a, I: IntoIterator>(inputs: I) -> bool { - !inputs.into_iter().any(|i| { - matches!( - i, - Input::CoinSigned(_) - | Input::CoinPredicate(_) - | Input::MessageCoinSigned(_) - | Input::MessageCoinPredicate(_) - ) - }) -} - -pub(crate) async fn get_script_gas_used(mut tx: Script, provider: &Provider) -> Result { - let no_spendable_input = no_spendable_input(tx.inputs()); - let base_asset_id = provider.base_asset_id(); - if no_spendable_input { - tx.inputs_mut().push(Input::coin_signed( - Default::default(), - Default::default(), - 1_000_000_000, - *base_asset_id, - TxPointer::default(), - 0, - )); - - // Add an empty `Witness` for the `coin_signed` we just added - // and increase the witness limit - tx.witnesses_mut().push(Default::default()); - } - let consensus_params = provider.consensus_parameters(); - - // Get `max_gas` used by everything except the script execution. Add `1` because of rounding. - let max_gas_per_tx = consensus_params.tx_params().max_gas_per_tx(); - let max_gas = tx.max_gas(consensus_params.gas_costs(), consensus_params.fee_params()) + 1; - // Increase `script_gas_limit` to the maximum allowed value. - tx.set_script_gas_limit(max_gas_per_tx - max_gas); - let script_tx = ScriptTransaction::from(tx); - - let tolerance = 0.1; - let estimated_tx_cost = provider - .estimate_transaction_cost(script_tx, Some(tolerance), None) - .await?; - Ok(estimated_tx_cost.gas_used) -} diff --git a/forc-plugins/forc-client/src/util/mod.rs b/forc-plugins/forc-client/src/util/mod.rs index f5f881b1722..e53af5a7a52 100644 --- a/forc-plugins/forc-client/src/util/mod.rs +++ b/forc-plugins/forc-client/src/util/mod.rs @@ -1,6 +1,5 @@ pub mod account; pub(crate) mod encode; -pub(crate) mod gas; pub(crate) mod node_url; pub(crate) mod pkg; pub(crate) mod target; diff --git a/forc-plugins/forc-client/src/util/tx.rs b/forc-plugins/forc-client/src/util/tx.rs index 477c80013ab..28ea59c80c5 100644 --- a/forc-plugins/forc-client/src/util/tx.rs +++ b/forc-plugins/forc-client/src/util/tx.rs @@ -2,7 +2,7 @@ use crate::{ constants::DEFAULT_PRIVATE_KEY, util::{account::ForcClientAccount, target::Target}, }; -use anyhow::{Error, Result}; +use anyhow::Result; use dialoguer::{theme::ColorfulTheme, Confirm, Password, Select}; use forc_tracing::{println_action_green, println_warning}; use forc_wallet::{ @@ -13,7 +13,7 @@ use forc_wallet::{ new::{new_wallet_cli, New}, utils::default_wallet_path, }; -use fuel_crypto::{PublicKey, SecretKey, Signature}; +use fuel_crypto::SecretKey; use fuel_tx::{AssetId, ContractId}; use fuels::{macros::abigen, programs::responses::CallResponse}; use fuels_accounts::{ @@ -21,8 +21,8 @@ use fuels_accounts::{ wallet::{Wallet, WalletUnlocked}, ViewOnlyAccount, }; -use fuels_core::types::bech32::{Bech32Address, FUEL_BECH32_HRP}; -use std::{collections::BTreeMap, io::Write, path::Path, str::FromStr}; +use fuels_core::types::bech32::Bech32Address; +use std::{collections::BTreeMap, path::Path, str::FromStr}; #[derive(PartialEq, Eq)] pub enum SignerSelectionMode { @@ -31,23 +31,6 @@ pub enum SignerSelectionMode { 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()?; - let mut buf = String::new(); - std::io::stdin().read_line(&mut buf)?; - Bech32Address::from_str(buf.trim()).map_err(Error::msg) -} - -fn prompt_signature(tx_id: fuel_tx::Bytes32) -> Result { - println!("Transaction id to sign: {tx_id}"); - print!("Please provide the signature:"); - std::io::stdout().flush()?; - let mut buf = String::new(); - std::io::stdin().read_line(&mut buf)?; - Signature::from_str(buf.trim()).map_err(Error::msg) -} - fn ask_user_yes_no_question(question: &str) -> Result { let answer = Confirm::with_theme(&ColorfulTheme::default()) .with_prompt(question) @@ -117,13 +100,6 @@ pub(crate) fn secret_key_from_forc_wallet( Ok(secret_key) } -pub(crate) fn bech32_from_secret(secret_key: &SecretKey) -> Result { - let public_key = PublicKey::from(secret_key); - let hashed = public_key.hash(); - let bech32 = Bech32Address::new(FUEL_BECH32_HRP, hashed); - Ok(bech32) -} - pub(crate) fn select_manual_secret_key( default_signer: bool, signing_key: Option, From 95e1ad2b3a7225a1e9d1c0bb076e5ba16d56cc0a Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Sun, 22 Sep 2024 00:28:18 -0700 Subject: [PATCH 03/16] simplfy lifetimes --- forc-plugins/forc-client/src/util/account.rs | 47 +++++--------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/forc-plugins/forc-client/src/util/account.rs b/forc-plugins/forc-client/src/util/account.rs index 12d09e83e31..c9a5262cf18 100644 --- a/forc-plugins/forc-client/src/util/account.rs +++ b/forc-plugins/forc-client/src/util/account.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use fuel_crypto::{Message, Signature}; use fuels::{ prelude::*, @@ -5,39 +6,25 @@ use fuels::{ }; use fuels_accounts::{wallet::WalletUnlocked, Account}; -/// Accounts that can be used with forc-client. #[derive(Clone, Debug)] pub enum ForcClientAccount { Wallet(WalletUnlocked), KmsSigner, } +#[async_trait] impl Account for ForcClientAccount { - /// Returns a vector consisting of `Input::Coin`s and `Input::Message`s for the given - /// asset ID and amount. The `witness_index` is the position of the witness (signature) - /// in the transaction\'s list of witnesses. In the validation process, the node will - /// use the witness at this index to validate the coins returned by this method. - #[must_use] - #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] - fn get_asset_inputs_for_amount<'life0, 'async_trait>( - &'life0 self, + async fn get_asset_inputs_for_amount( + &self, asset_id: AssetId, amount: u64, excluded_coins: Option>, - ) -> ::core::pin::Pin< - Box< - dyn ::core::future::Future>> - + ::core::marker::Send - + 'async_trait, - >, - > - where - 'life0: 'async_trait, - Self: 'async_trait, - { + ) -> Result> { match self { ForcClientAccount::Wallet(wallet) => { - wallet.get_asset_inputs_for_amount(asset_id, amount, excluded_coins) + wallet + .get_asset_inputs_for_amount(asset_id, amount, excluded_coins) + .await } ForcClientAccount::KmsSigner => todo!(), } @@ -60,23 +47,11 @@ impl ViewOnlyAccount for ForcClientAccount { } } +#[async_trait] impl Signer for ForcClientAccount { - fn sign<'life0, 'async_trait>( - &'life0 self, - message: Message, - ) -> ::core::pin::Pin< - Box< - dyn ::core::future::Future> - + ::core::marker::Send - + 'async_trait, - >, - > - where - 'life0: 'async_trait, - Self: 'async_trait, - { + async fn sign(&self, message: Message) -> Result { match self { - ForcClientAccount::Wallet(wallet) => wallet.sign(message), + ForcClientAccount::Wallet(wallet) => wallet.sign(message).await, ForcClientAccount::KmsSigner => todo!(), } } From 11aec6600265a64ad264e33bdc4d108898a56af4 Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Sun, 22 Sep 2024 03:06:26 -0700 Subject: [PATCH 04/16] add aws signer --- Cargo.lock | 393 ++++++++++++++++++++++- forc-plugins/forc-client/Cargo.toml | 5 + forc-plugins/forc-client/src/util/aws.rs | 220 +++++++++++++ forc-plugins/forc-client/src/util/mod.rs | 1 + 4 files changed, 617 insertions(+), 2 deletions(-) create mode 100644 forc-plugins/forc-client/src/util/aws.rs diff --git a/Cargo.lock b/Cargo.lock index e18e19a0114..b2a752e1cfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,6 +272,328 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "aws-config" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "848d7b9b605720989929279fa644ce8f244d0ce3146fcca5b70e4eb7b3c020fc" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.12", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid 1.10.0", +] + +[[package]] +name = "aws-sdk-kms" +version = "1.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6550445e0913c9383375f4a5a2f550817567a19a178107fce1e1afd767f802a" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a9d27ed1c12b1140c47daf1bc541606c43fdafd918c4797d520db0043ceef2" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44514a6ca967686cde1e2a1b81df6ef1883d0e3e570da8d8bc5c491dcb6fc29b" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7a4d279762a35b9df97209f6808b95d4fe78547fe2316b4d200a0283960c5a" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8db6904450bafe7473c6ca9123f88cc11089e41a025408f992db4e22d3be68" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac 0.12.1", + "http 0.2.12", + "http 1.1.0", + "once_cell", + "percent-encoding", + "sha2 0.10.8", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce695746394772e7000b39fe073095db6d45a862d0767dd5ad0ac0d7f8eb87" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.1", + "httparse", + "hyper 0.14.30", + "hyper-rustls 0.24.2", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.12", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.1.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03701449087215b5369c7ea17fef0dd5d24cb93439ec5af0c7615f58c3f22605" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.1.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.73" @@ -318,6 +640,16 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.6.0" @@ -546,6 +878,16 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "cast" version = "0.3.0" @@ -2050,10 +2392,13 @@ version = "0.63.6" dependencies = [ "anyhow", "async-trait", + "aws-config", + "aws-sdk-kms", "chrono", "clap 4.5.16", "devault", "dialoguer", + "ecdsa", "forc", "forc-pkg", "forc-tracing 0.63.6", @@ -2071,11 +2416,13 @@ dependencies = [ "fuels-core", "futures", "hex", + "k256", "portpicker", "rand", "regex", "rexpect 0.5.0", "rpassword", + "secp256k1 0.29.1", "serde", "serde_json", "sway-core", @@ -3895,9 +4242,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if 1.0.0", "ecdsa", @@ -4868,6 +5215,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + [[package]] name = "overload" version = "0.1.1" @@ -5692,6 +6045,12 @@ dependencies = [ "regex-syntax 0.8.4", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -6320,6 +6679,15 @@ dependencies = [ "secp256k1-sys 0.8.1", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "secp256k1-sys 0.10.1", +] + [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -6338,6 +6706,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -8130,6 +8507,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "vt100" version = "0.15.2" @@ -8720,6 +9103,12 @@ version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/forc-plugins/forc-client/Cargo.toml b/forc-plugins/forc-client/Cargo.toml index 09ba62cf416..a6d13f861ae 100644 --- a/forc-plugins/forc-client/Cargo.toml +++ b/forc-plugins/forc-client/Cargo.toml @@ -11,10 +11,13 @@ repository.workspace = true [dependencies] anyhow = "1" async-trait = "0.1.58" +aws-config = "1.5.6" +aws-sdk-kms = "1.44.0" chrono = { version = "0.4", default-features = false, features = ["std"] } clap = { version = "4.5.4", features = ["derive", "env"] } devault = "0.1" dialoguer = "0.11" +ecdsa = "0.16.9" forc = { version = "0.63.6", path = "../../forc" } forc-pkg = { version = "0.63.6", path = "../../forc-pkg" } forc-tracing = { version = "0.63.6", path = "../../forc-tracing" } @@ -32,8 +35,10 @@ fuels-accounts = { workspace = true } fuels-core = { workspace = true } futures = "0.3" hex = "0.4.3" +k256 = "0.13.4" rand = "0.8" rpassword = "7.2" +secp256k1 = "0.29.1" serde = "1.0" serde_json = "1" sway-core = { version = "0.63.6", path = "../../sway-core" } diff --git a/forc-plugins/forc-client/src/util/aws.rs b/forc-plugins/forc-client/src/util/aws.rs new file mode 100644 index 00000000000..9e9abea5351 --- /dev/null +++ b/forc-plugins/forc-client/src/util/aws.rs @@ -0,0 +1,220 @@ +use async_trait::async_trait; +use aws_config::{default_provider::credentials::DefaultCredentialsChain, Region, SdkConfig}; +use aws_sdk_kms::config::Credentials; +use aws_sdk_kms::operation::get_public_key::GetPublicKeyOutput; +use aws_sdk_kms::primitives::Blob; +use aws_sdk_kms::types::{MessageType, SigningAlgorithmSpec}; +use aws_sdk_kms::{config::BehaviorVersion, Client}; +use fuel_crypto::coins_bip32::prelude::k256::pkcs8::spki; +use fuel_crypto::{Message, PublicKey}; +use fuels::types::bech32::{Bech32Address, FUEL_BECH32_HRP}; +use fuels_core::traits::Signer; + +#[derive(Debug, Clone)] +pub struct AwsConfig { + sdk_config: SdkConfig, +} + +impl AwsConfig { + pub async fn from_env() -> Self { + let loader = aws_config::defaults(BehaviorVersion::latest()) + .credentials_provider(DefaultCredentialsChain::builder().build().await); + + let loader = match std::env::var("E2E_TEST_AWS_ENDPOINT") { + Ok(url) => loader.endpoint_url(url), + _ => loader, + }; + + Self { + sdk_config: loader.load().await, + } + } + + pub async fn for_testing(url: String) -> Self { + let sdk_config = aws_config::defaults(BehaviorVersion::latest()) + .credentials_provider(Credentials::new( + "test", + "test", + None, + None, + "Static Credentials", + )) + .endpoint_url(url) + .region(Region::new("us-east-1")) // placeholder region for test + .load() + .await; + + Self { sdk_config } + } + + pub fn url(&self) -> Option<&str> { + self.sdk_config.endpoint_url() + } + + pub fn region(&self) -> Option<&Region> { + self.sdk_config.region() + } +} + +#[derive(Clone)] +pub struct AwsClient { + client: Client, +} + +impl AwsClient { + pub async fn new(config: AwsConfig) -> Self { + let config = config.sdk_config; + let client = Client::new(&config); + + Self { client } + } + + pub fn inner(&self) -> &Client { + &self.client + } +} + +#[derive(Clone)] +pub struct AwsSigner { + kms: Client, + key_id: String, + public_key_bytes: Vec, + bech: Bech32Address, +} + +async fn request_get_pubkey( + kms: &Client, + key_id: String, +) -> Result { + kms.get_public_key() + .key_id(key_id) + .send() + .await + .map_err(Into::into) +} + +/// Decode an AWS KMS Pubkey response. +fn decode_pubkey(resp: &GetPublicKeyOutput) -> Result<&[u8], anyhow::Error> { + let raw = resp + .public_key + .as_ref() + .ok_or(anyhow::anyhow!("public key not found"))?; + let spki = spki::SubjectPublicKeyInfoRef::try_from(raw.as_ref())?; + let bytes = spki.subject_public_key.raw_bytes(); + Ok(bytes) +} + +async fn sign_with_kms( + client: &aws_sdk_kms::Client, + key_id: &str, + public_key_bytes: &[u8], + message: Message, +) -> anyhow::Result { + use k256::{ + ecdsa::{RecoveryId, VerifyingKey}, + pkcs8::DecodePublicKey, + }; + + let reply = client + .sign() + .key_id(key_id) + .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) + .message_type(MessageType::Digest) + .message(Blob::new(*message)) + .send() + .await + .inspect_err(|err| tracing::error!("Failed to sign with AWS KMS: {err:?}"))?; + let signature_der = reply + .signature + .ok_or_else(|| anyhow::anyhow!("no signature returned from AWS KMS"))? + .into_inner(); + // https://stackoverflow.com/a/71475108 + let sig = k256::ecdsa::Signature::from_der(&signature_der) + .map_err(|_| anyhow::anyhow!("invalid DER signature from AWS KMS"))?; + let sig = sig.normalize_s().unwrap_or(sig); + + // This is a hack to get the recovery id. The signature should be normalized + // before computing the recovery id, but aws kms doesn't support this, and + // instead always computes the recovery id from non-normalized signature. + // So instead the recovery id is determined by checking which variant matches + // the original public key. + + let recid1 = RecoveryId::new(false, false); + let recid2 = RecoveryId::new(true, false); + + let rec1 = VerifyingKey::recover_from_prehash(&*message, &sig, recid1); + let rec2 = VerifyingKey::recover_from_prehash(&*message, &sig, recid2); + + let correct_public_key = k256::PublicKey::from_public_key_der(public_key_bytes) + .map_err(|_| anyhow::anyhow!("invalid DER public key from AWS KMS"))? + .into(); + + let recovery_id = if rec1.map(|r| r == correct_public_key).unwrap_or(false) { + recid1 + } else if rec2.map(|r| r == correct_public_key).unwrap_or(false) { + recid2 + } else { + anyhow::bail!("Invalid signature generated (reduced-x form coordinate)"); + }; + + // Insert the recovery id into the signature + debug_assert!( + !recovery_id.is_x_reduced(), + "reduced-x form coordinates are caught by the if-else chain above" + ); + let v = recovery_id.is_y_odd() as u8; + let mut signature = <[u8; 64]>::from(sig.to_bytes()); + signature[32] = (v << 7) | (signature[32] & 0x7f); + Ok(fuel_crypto::Signature::from_bytes(signature)) +} + +impl AwsSigner { + pub async fn new(kms: Client, key_id: String) -> Result { + let resp = request_get_pubkey(&kms, key_id.clone()).await?; + let public_key_bytes = decode_pubkey(&resp)?.to_vec(); + let public_key = PublicKey::try_from(public_key_bytes.as_slice()).unwrap(); + let hashed = public_key.hash(); + let bech = Bech32Address::new(FUEL_BECH32_HRP, hashed); + Ok(Self { + kms, + key_id, + public_key_bytes, + bech, + }) + } + + /// Sign a digest with the key associated with a key ID. + pub async fn sign_message_with_key( + &self, + key_id: String, + message: Message, + ) -> Result { + sign_with_kms(&self.kms, &key_id, &self.public_key_bytes, message).await + } + + /// Sign a digest with this signer's key + pub async fn sign_message( + &self, + message: Message, + ) -> Result { + self.sign_message_with_key(self.key_id.clone(), message) + .await + } +} + +#[async_trait] +impl Signer for AwsSigner { + async fn sign( + &self, + message: Message, + ) -> Result { + let sig = self.sign_message(message).await.map_err(|_| { + fuels_core::types::errors::Error::Other("aws signer failed".to_string()) + })?; + Ok(sig) + } + + fn address(&self) -> &Bech32Address { + &self.bech + } +} diff --git a/forc-plugins/forc-client/src/util/mod.rs b/forc-plugins/forc-client/src/util/mod.rs index e53af5a7a52..2e58fc41c79 100644 --- a/forc-plugins/forc-client/src/util/mod.rs +++ b/forc-plugins/forc-client/src/util/mod.rs @@ -1,4 +1,5 @@ pub mod account; +pub mod aws; pub(crate) mod encode; pub(crate) mod node_url; pub(crate) mod pkg; From 6e9d182cdde7afcdf9d075d98e1c6f03d72a2e4a Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Sun, 22 Sep 2024 03:08:08 -0700 Subject: [PATCH 05/16] clippy fmt --- forc-plugins/forc-client/src/op/run/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forc-plugins/forc-client/src/op/run/mod.rs b/forc-plugins/forc-client/src/op/run/mod.rs index 7d786b481dd..37592efb54f 100644 --- a/forc-plugins/forc-client/src/op/run/mod.rs +++ b/forc-plugins/forc-client/src/op/run/mod.rs @@ -106,7 +106,7 @@ pub async fn run_pkg( let provider = Provider::connect(node_url.clone()).await?; let tx_count = 1; let account = select_account( - &signer_mode, + signer_mode, command.default_signer || command.unsigned, command.signing_key, &provider, @@ -148,7 +148,7 @@ pub async fn run_pkg( let script_binary = compiled.bytecode.bytes.clone(); let external_contracts = contract_ids .into_iter() - .map(|contract| Bech32ContractId::from(contract)) + .map(Bech32ContractId::from) .collect::>(); let call = ScriptCall { script_binary, From 5bf6a037d6c3297f28b7c56ddc2ea99c8e3b41f4 Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Sun, 22 Sep 2024 22:42:51 -0700 Subject: [PATCH 06/16] add aws signer --- forc-plugins/forc-client/src/util/account.rs | 14 ++++++++------ forc-plugins/forc-client/src/util/aws.rs | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/forc-plugins/forc-client/src/util/account.rs b/forc-plugins/forc-client/src/util/account.rs index c9a5262cf18..d705d7a6d45 100644 --- a/forc-plugins/forc-client/src/util/account.rs +++ b/forc-plugins/forc-client/src/util/account.rs @@ -6,10 +6,12 @@ use fuels::{ }; use fuels_accounts::{wallet::WalletUnlocked, Account}; +use super::aws::AwsSigner; + #[derive(Clone, Debug)] pub enum ForcClientAccount { Wallet(WalletUnlocked), - KmsSigner, + KmsSigner(AwsSigner), } #[async_trait] @@ -26,7 +28,7 @@ impl Account for ForcClientAccount { .get_asset_inputs_for_amount(asset_id, amount, excluded_coins) .await } - ForcClientAccount::KmsSigner => todo!(), + ForcClientAccount::KmsSigner(_account) => todo!(), } } } @@ -35,14 +37,14 @@ impl ViewOnlyAccount for ForcClientAccount { fn address(&self) -> &Bech32Address { match self { ForcClientAccount::Wallet(wallet) => wallet.address(), - ForcClientAccount::KmsSigner => todo!(), + ForcClientAccount::KmsSigner(_account) => todo!(), } } fn try_provider(&self) -> Result<&Provider> { match self { ForcClientAccount::Wallet(wallet) => wallet.try_provider(), - ForcClientAccount::KmsSigner => todo!(), + ForcClientAccount::KmsSigner(_account) => todo!(), } } } @@ -52,14 +54,14 @@ impl Signer for ForcClientAccount { async fn sign(&self, message: Message) -> Result { match self { ForcClientAccount::Wallet(wallet) => wallet.sign(message).await, - ForcClientAccount::KmsSigner => todo!(), + ForcClientAccount::KmsSigner(_account) => todo!(), } } fn address(&self) -> &Bech32Address { match self { ForcClientAccount::Wallet(wallet) => wallet.address(), - ForcClientAccount::KmsSigner => todo!(), + ForcClientAccount::KmsSigner(_account) => todo!(), } } } diff --git a/forc-plugins/forc-client/src/util/aws.rs b/forc-plugins/forc-client/src/util/aws.rs index 9e9abea5351..3f749b68bae 100644 --- a/forc-plugins/forc-client/src/util/aws.rs +++ b/forc-plugins/forc-client/src/util/aws.rs @@ -74,7 +74,7 @@ impl AwsClient { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct AwsSigner { kms: Client, key_id: String, From 6f29e3fdad33e37a2f23412fedd40eaad2bcd3a4 Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Sun, 22 Sep 2024 23:59:28 -0700 Subject: [PATCH 07/16] add aws kms signer --- forc-plugins/forc-client/src/cmd/deploy.rs | 4 + forc-plugins/forc-client/src/op/deploy.rs | 2 + forc-plugins/forc-client/src/util/account.rs | 16 ++-- forc-plugins/forc-client/src/util/aws.rs | 86 ++++++++++++++------ forc-plugins/forc-client/src/util/tx.rs | 14 +++- 5 files changed, 93 insertions(+), 29 deletions(-) diff --git a/forc-plugins/forc-client/src/cmd/deploy.rs b/forc-plugins/forc-client/src/cmd/deploy.rs index edb9e76d7f0..5553a2fe06b 100644 --- a/forc-plugins/forc-client/src/cmd/deploy.rs +++ b/forc-plugins/forc-client/src/cmd/deploy.rs @@ -87,4 +87,8 @@ pub struct Command { /// Disable the "new encoding" feature #[clap(long)] pub no_encoding_v1: bool, + + /// AWS KMS signer arn. If present forc-deploy will automatically use AWS KMS signer instead of forc-wallet. + #[clap(long)] + pub aws_kms_signer: Option, } diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index bae5045569f..cca4560cf9e 100644 --- a/forc-plugins/forc-client/src/op/deploy.rs +++ b/forc-plugins/forc-client/src/op/deploy.rs @@ -1201,6 +1201,8 @@ async fn confirm_transaction_details( let wallet_mode = if command.default_signer || command.signing_key.is_some() { SignerSelectionMode::Manual + } else if let Some(arn) = &command.aws_kms_signer { + SignerSelectionMode::AwsSigner(arn.clone()) } else { println_action_green("", &format!("Wallet: {}", default_wallet_path().display())); let password = prompt_forc_wallet_password()?; diff --git a/forc-plugins/forc-client/src/util/account.rs b/forc-plugins/forc-client/src/util/account.rs index d705d7a6d45..7ca1e0e9fcd 100644 --- a/forc-plugins/forc-client/src/util/account.rs +++ b/forc-plugins/forc-client/src/util/account.rs @@ -28,7 +28,11 @@ impl Account for ForcClientAccount { .get_asset_inputs_for_amount(asset_id, amount, excluded_coins) .await } - ForcClientAccount::KmsSigner(_account) => todo!(), + ForcClientAccount::KmsSigner(account) => { + account + .get_asset_inputs_for_amount(asset_id, amount, excluded_coins) + .await + } } } } @@ -37,14 +41,16 @@ impl ViewOnlyAccount for ForcClientAccount { fn address(&self) -> &Bech32Address { match self { ForcClientAccount::Wallet(wallet) => wallet.address(), - ForcClientAccount::KmsSigner(_account) => todo!(), + ForcClientAccount::KmsSigner(account) => { + fuels_accounts::ViewOnlyAccount::address(account) + } } } fn try_provider(&self) -> Result<&Provider> { match self { ForcClientAccount::Wallet(wallet) => wallet.try_provider(), - ForcClientAccount::KmsSigner(_account) => todo!(), + ForcClientAccount::KmsSigner(account) => Ok(account.provider()), } } } @@ -54,14 +60,14 @@ impl Signer for ForcClientAccount { async fn sign(&self, message: Message) -> Result { match self { ForcClientAccount::Wallet(wallet) => wallet.sign(message).await, - ForcClientAccount::KmsSigner(_account) => todo!(), + ForcClientAccount::KmsSigner(account) => account.sign(message).await, } } fn address(&self) -> &Bech32Address { match self { ForcClientAccount::Wallet(wallet) => wallet.address(), - ForcClientAccount::KmsSigner(_account) => todo!(), + ForcClientAccount::KmsSigner(account) => fuels_core::traits::Signer::address(account), } } } diff --git a/forc-plugins/forc-client/src/util/aws.rs b/forc-plugins/forc-client/src/util/aws.rs index 3f749b68bae..7c68c1fdb55 100644 --- a/forc-plugins/forc-client/src/util/aws.rs +++ b/forc-plugins/forc-client/src/util/aws.rs @@ -5,9 +5,13 @@ use aws_sdk_kms::operation::get_public_key::GetPublicKeyOutput; use aws_sdk_kms::primitives::Blob; use aws_sdk_kms::types::{MessageType, SigningAlgorithmSpec}; use aws_sdk_kms::{config::BehaviorVersion, Client}; -use fuel_crypto::coins_bip32::prelude::k256::pkcs8::spki; -use fuel_crypto::{Message, PublicKey}; +use fuel_crypto::Message; +use fuels::prelude::*; use fuels::types::bech32::{Bech32Address, FUEL_BECH32_HRP}; +use fuels::types::coin_type_id::CoinTypeId; +use fuels::types::input::Input; +use fuels_accounts::provider::Provider; +use fuels_accounts::{Account, ViewOnlyAccount}; use fuels_core::traits::Signer; #[derive(Debug, Clone)] @@ -56,13 +60,13 @@ impl AwsConfig { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct AwsClient { client: Client, } impl AwsClient { - pub async fn new(config: AwsConfig) -> Self { + pub fn new(config: AwsConfig) -> Self { let config = config.sdk_config; let client = Client::new(&config); @@ -76,16 +80,17 @@ impl AwsClient { #[derive(Clone, Debug)] pub struct AwsSigner { - kms: Client, + kms: AwsClient, key_id: String, - public_key_bytes: Vec, bech: Bech32Address, + public_key_bytes: Vec, + provider: Provider, } async fn request_get_pubkey( kms: &Client, key_id: String, -) -> Result { +) -> std::result::Result { kms.get_public_key() .key_id(key_id) .send() @@ -94,14 +99,12 @@ async fn request_get_pubkey( } /// Decode an AWS KMS Pubkey response. -fn decode_pubkey(resp: &GetPublicKeyOutput) -> Result<&[u8], anyhow::Error> { +fn decode_pubkey(resp: &GetPublicKeyOutput) -> std::result::Result, anyhow::Error> { let raw = resp .public_key .as_ref() .ok_or(anyhow::anyhow!("public key not found"))?; - let spki = spki::SubjectPublicKeyInfoRef::try_from(raw.as_ref())?; - let bytes = spki.subject_public_key.raw_bytes(); - Ok(bytes) + Ok(raw.clone().into_inner()) } async fn sign_with_kms( @@ -169,17 +172,26 @@ async fn sign_with_kms( } impl AwsSigner { - pub async fn new(kms: Client, key_id: String) -> Result { - let resp = request_get_pubkey(&kms, key_id.clone()).await?; - let public_key_bytes = decode_pubkey(&resp)?.to_vec(); - let public_key = PublicKey::try_from(public_key_bytes.as_slice()).unwrap(); + pub async fn new( + kms: AwsClient, + key_id: String, + provider: Provider, + ) -> std::result::Result { + use k256::pkcs8::DecodePublicKey; + + let resp = request_get_pubkey(kms.inner(), key_id.clone()).await?; + let public_key_bytes = decode_pubkey(&resp)?; + let k256_public_key = k256::PublicKey::from_public_key_der(&public_key_bytes)?; + + let public_key = fuel_crypto::PublicKey::from(k256_public_key); let hashed = public_key.hash(); let bech = Bech32Address::new(FUEL_BECH32_HRP, hashed); Ok(Self { kms, key_id, - public_key_bytes, bech, + public_key_bytes, + provider, }) } @@ -188,26 +200,27 @@ impl AwsSigner { &self, key_id: String, message: Message, - ) -> Result { - sign_with_kms(&self.kms, &key_id, &self.public_key_bytes, message).await + ) -> std::result::Result { + sign_with_kms(self.kms.inner(), &key_id, &self.public_key_bytes, message).await } /// Sign a digest with this signer's key pub async fn sign_message( &self, message: Message, - ) -> Result { + ) -> std::result::Result { self.sign_message_with_key(self.key_id.clone(), message) .await } + + pub fn provider(&self) -> &Provider { + &self.provider + } } #[async_trait] impl Signer for AwsSigner { - async fn sign( - &self, - message: Message, - ) -> Result { + async fn sign(&self, message: Message) -> Result { let sig = self.sign_message(message).await.map_err(|_| { fuels_core::types::errors::Error::Other("aws signer failed".to_string()) })?; @@ -218,3 +231,30 @@ impl Signer for AwsSigner { &self.bech } } + +impl ViewOnlyAccount for AwsSigner { + fn address(&self) -> &Bech32Address { + &self.bech + } + + fn try_provider(&self) -> Result<&Provider> { + Ok(&self.provider) + } +} + +#[async_trait] +impl Account for AwsSigner { + async fn get_asset_inputs_for_amount( + &self, + asset_id: AssetId, + amount: u64, + excluded_coins: Option>, + ) -> Result> { + Ok(self + .get_spendable_resources(asset_id, amount, excluded_coins) + .await? + .into_iter() + .map(Input::resource_signed) + .collect::>()) + } +} diff --git a/forc-plugins/forc-client/src/util/tx.rs b/forc-plugins/forc-client/src/util/tx.rs index 28ea59c80c5..d4d315b5685 100644 --- a/forc-plugins/forc-client/src/util/tx.rs +++ b/forc-plugins/forc-client/src/util/tx.rs @@ -1,6 +1,6 @@ use crate::{ constants::DEFAULT_PRIVATE_KEY, - util::{account::ForcClientAccount, target::Target}, + util::{account::ForcClientAccount, aws::AwsSigner, target::Target}, }; use anyhow::Result; use dialoguer::{theme::ColorfulTheme, Confirm, Password, Select}; @@ -24,10 +24,14 @@ use fuels_accounts::{ use fuels_core::types::bech32::Bech32Address; use std::{collections::BTreeMap, path::Path, str::FromStr}; +use super::aws::{AwsClient, AwsConfig}; + #[derive(PartialEq, Eq)] pub enum SignerSelectionMode { /// Holds the password of forc-wallet instance. ForcWallet(String), + /// Holds ARN of the AWS signer. + AwsSigner(String), Manual, } @@ -230,6 +234,14 @@ pub(crate) async fn select_account( let wallet = WalletUnlocked::new_from_private_key(secret_key, Some(provider.clone())); Ok(ForcClientAccount::Wallet(wallet)) } + SignerSelectionMode::AwsSigner(arn) => { + let aws_config = AwsConfig::from_env().await; + let aws_client = AwsClient::new(aws_config); + let aws_signer = AwsSigner::new(aws_client, arn.clone(), provider.clone()).await?; + + let account = ForcClientAccount::KmsSigner(aws_signer); + Ok(account) + } } } From 642f980b51cf46e2fa6bc9f0945e695b66b419b2 Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Mon, 23 Sep 2024 00:29:58 -0700 Subject: [PATCH 08/16] add signer once witness is added --- forc-plugins/forc-client/src/op/deploy.rs | 6 +----- forc-plugins/forc-client/src/op/run/mod.rs | 2 -- forc-plugins/forc-client/src/util/account.rs | 6 ++++++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index cca4560cf9e..ee4e5f49fbe 100644 --- a/forc-plugins/forc-client/src/op/deploy.rs +++ b/forc-plugins/forc-client/src/op/deploy.rs @@ -25,10 +25,7 @@ use fuel_tx::{Salt, Transaction}; use fuel_vm::prelude::*; use fuels::{ programs::contract::{LoadConfiguration, StorageConfiguration}, - types::{ - bech32::Bech32ContractId, - transaction_builders::{Blob, TransactionBuilder}, - }, + types::{bech32::Bech32ContractId, transaction_builders::Blob}, }; use fuels_accounts::{provider::Provider, Account, ViewOnlyAccount}; use fuels_core::types::{transaction::TxPolicies, transaction_builders::CreateTransactionBuilder}; @@ -1256,7 +1253,6 @@ pub async fn deploy_pkg( account.add_witnesses(&mut tb)?; account.adjust_for_fee(&mut tb, 0).await?; - tb.add_signer(account.clone())?; let tx = tb.build(provider).await?; let tx = Transaction::from(tx); diff --git a/forc-plugins/forc-client/src/op/run/mod.rs b/forc-plugins/forc-client/src/op/run/mod.rs index 37592efb54f..5e58835fca0 100644 --- a/forc-plugins/forc-client/src/op/run/mod.rs +++ b/forc-plugins/forc-client/src/op/run/mod.rs @@ -23,7 +23,6 @@ use fuels::{ }, }; use fuels_accounts::{provider::Provider, Account}; -use fuels_core::types::transaction_builders::TransactionBuilder; use pkg::{manifest::build_profile::ExperimentalFlags, BuiltPackage}; use std::time::Duration; use std::{path::PathBuf, str::FromStr}; @@ -164,7 +163,6 @@ pub async fn run_pkg( account.add_witnesses(&mut tb)?; account.adjust_for_fee(&mut tb, 0).await?; - tb.add_signer(account)?; let tx = tb.build(provider).await?; diff --git a/forc-plugins/forc-client/src/util/account.rs b/forc-plugins/forc-client/src/util/account.rs index 7ca1e0e9fcd..125634ec356 100644 --- a/forc-plugins/forc-client/src/util/account.rs +++ b/forc-plugins/forc-client/src/util/account.rs @@ -35,6 +35,12 @@ impl Account for ForcClientAccount { } } } + + fn add_witnesses(&self, tb: &mut Tb) -> Result<()> { + tb.add_signer(self.clone())?; + + Ok(()) + } } impl ViewOnlyAccount for ForcClientAccount { From 2804a3e2b59a2d9d73c68761666d77cfd7bf6add Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Mon, 23 Sep 2024 01:36:10 -0700 Subject: [PATCH 09/16] update test --- forc-plugins/forc-client/tests/deploy.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/forc-plugins/forc-client/tests/deploy.rs b/forc-plugins/forc-client/tests/deploy.rs index 0a31fb91ccb..9457bd6815c 100644 --- a/forc-plugins/forc-client/tests/deploy.rs +++ b/forc-plugins/forc-client/tests/deploy.rs @@ -2,7 +2,7 @@ use forc::cli::shared::Pkg; use forc_client::{ cmd, op::{deploy, DeployedContract}, - util::tx::update_proxy_contract_target, + util::{account::ForcClientAccount, tx::update_proxy_contract_target}, NodeTarget, }; use forc_pkg::manifest::Proxy; @@ -597,16 +597,13 @@ async fn test_non_owner_fails_to_set_target() { let dummy_contract_id_target = ContractId::default(); abigen!(Contract(name = "ProxyContract", abi = "{\"programType\":\"contract\",\"specVersion\":\"1\",\"encodingVersion\":\"1\",\"concreteTypes\":[{\"type\":\"()\",\"concreteTypeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"},{\"type\":\"enum standards::src5::AccessError\",\"concreteTypeId\":\"3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d\",\"metadataTypeId\":1},{\"type\":\"enum standards::src5::State\",\"concreteTypeId\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\",\"metadataTypeId\":2},{\"type\":\"enum std::option::Option\",\"concreteTypeId\":\"0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8\",\"metadataTypeId\":4,\"typeArguments\":[\"29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54\"]},{\"type\":\"enum sway_libs::ownership::errors::InitializationError\",\"concreteTypeId\":\"1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893\",\"metadataTypeId\":5},{\"type\":\"enum sway_libs::upgradability::errors::SetProxyOwnerError\",\"concreteTypeId\":\"3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74\",\"metadataTypeId\":6},{\"type\":\"str\",\"concreteTypeId\":\"8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a\"},{\"type\":\"struct std::contract_id::ContractId\",\"concreteTypeId\":\"29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54\",\"metadataTypeId\":9},{\"type\":\"struct sway_libs::upgradability::events::ProxyOwnerSet\",\"concreteTypeId\":\"96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247\",\"metadataTypeId\":10},{\"type\":\"struct sway_libs::upgradability::events::ProxyTargetSet\",\"concreteTypeId\":\"1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8\",\"metadataTypeId\":11}],\"metadataTypes\":[{\"type\":\"b256\",\"metadataTypeId\":0},{\"type\":\"enum standards::src5::AccessError\",\"metadataTypeId\":1,\"components\":[{\"name\":\"NotOwner\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"enum standards::src5::State\",\"metadataTypeId\":2,\"components\":[{\"name\":\"Uninitialized\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"},{\"name\":\"Initialized\",\"typeId\":3},{\"name\":\"Revoked\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"enum std::identity::Identity\",\"metadataTypeId\":3,\"components\":[{\"name\":\"Address\",\"typeId\":8},{\"name\":\"ContractId\",\"typeId\":9}]},{\"type\":\"enum std::option::Option\",\"metadataTypeId\":4,\"components\":[{\"name\":\"None\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"},{\"name\":\"Some\",\"typeId\":7}],\"typeParameters\":[7]},{\"type\":\"enum sway_libs::ownership::errors::InitializationError\",\"metadataTypeId\":5,\"components\":[{\"name\":\"CannotReinitialized\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"enum sway_libs::upgradability::errors::SetProxyOwnerError\",\"metadataTypeId\":6,\"components\":[{\"name\":\"CannotUninitialize\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"generic T\",\"metadataTypeId\":7},{\"type\":\"struct std::address::Address\",\"metadataTypeId\":8,\"components\":[{\"name\":\"bits\",\"typeId\":0}]},{\"type\":\"struct std::contract_id::ContractId\",\"metadataTypeId\":9,\"components\":[{\"name\":\"bits\",\"typeId\":0}]},{\"type\":\"struct sway_libs::upgradability::events::ProxyOwnerSet\",\"metadataTypeId\":10,\"components\":[{\"name\":\"new_proxy_owner\",\"typeId\":2}]},{\"type\":\"struct sway_libs::upgradability::events::ProxyTargetSet\",\"metadataTypeId\":11,\"components\":[{\"name\":\"new_target\",\"typeId\":9}]}],\"functions\":[{\"inputs\":[],\"name\":\"proxy_target\",\"output\":\"0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Returns the target contract of the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Returns\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * [Option] - The new proxy contract to which all fallback calls will be passed or `None`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"read\"]}]},{\"inputs\":[{\"name\":\"new_target\",\"concreteTypeId\":\"29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54\"}],\"name\":\"set_proxy_target\",\"output\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Change the target contract of the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Additional Information\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method can only be called by the `proxy_owner`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Arguments\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * `new_target`: [ContractId] - The new proxy contract to which all fallback calls will be passed.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Reverts\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When not called by `proxy_owner`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Write: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"read\",\"write\"]}]},{\"inputs\":[],\"name\":\"proxy_owner\",\"output\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Returns the owner of the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Returns\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * [State] - Represents the state of ownership for this contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"read\"]}]},{\"inputs\":[],\"name\":\"initialize_proxy\",\"output\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Initializes the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Additional Information\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method sets the storage values using the values of the configurable constants `INITIAL_TARGET` and `INITIAL_OWNER`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This then allows methods that write to storage to be called.\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method can only be called once.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Reverts\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When `storage::SRC14.proxy_owner` is not [State::Uninitialized].\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Writes: `2`\"]},{\"name\":\"storage\",\"arguments\":[\"write\"]}]},{\"inputs\":[{\"name\":\"new_proxy_owner\",\"concreteTypeId\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\"}],\"name\":\"set_proxy_owner\",\"output\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Changes proxy ownership to the passed State.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Additional Information\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method can be used to transfer ownership between Identities or to revoke ownership.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Arguments\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * `new_proxy_owner`: [State] - The new state of the proxy ownership.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Reverts\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When the sender is not the current proxy owner.\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When the new state of the proxy ownership is [State::Uninitialized].\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Writes: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"write\"]}]}],\"loggedTypes\":[{\"logId\":\"4571204900286667806\",\"concreteTypeId\":\"3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d\"},{\"logId\":\"2151606668983994881\",\"concreteTypeId\":\"1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8\"},{\"logId\":\"2161305517876418151\",\"concreteTypeId\":\"1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893\"},{\"logId\":\"4354576968059844266\",\"concreteTypeId\":\"3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74\"},{\"logId\":\"10870989709723147660\",\"concreteTypeId\":\"96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247\"},{\"logId\":\"10098701174489624218\",\"concreteTypeId\":\"8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a\"}],\"messagesTypes\":[],\"configurables\":[{\"name\":\"INITIAL_TARGET\",\"concreteTypeId\":\"0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8\",\"offset\":13368},{\"name\":\"INITIAL_OWNER\",\"concreteTypeId\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\",\"offset\":13320}]}",)); + let wallet = WalletUnlocked::new_from_private_key(attacker_secret_key, Some(provider.clone())); + let attacker_account = ForcClientAccount::Wallet(wallet); // Try to change target of the proxy with a random wallet which is not the owner of the proxy. - let res = update_proxy_contract_target( - &provider, - attacker_secret_key, - proxy_id, - dummy_contract_id_target, - ) - .await - .err() - .unwrap(); + let res = update_proxy_contract_target(&attacker_account, proxy_id, dummy_contract_id_target) + .await + .err() + .unwrap(); node.kill().unwrap(); assert!(res From fc88a3e0b17cbff200ec699f80cd0df1be1476e8 Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Mon, 23 Sep 2024 01:45:13 -0700 Subject: [PATCH 10/16] remove unused secp256k1 crate --- Cargo.lock | 1 - Cargo.toml | 3 +-- forc-plugins/forc-client/Cargo.toml | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67f49332f54..5820d93cc39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2748,7 +2748,6 @@ dependencies = [ "regex", "rexpect 0.5.0", "rpassword", - "secp256k1 0.29.1", "serde", "serde_json", "sway-core", diff --git a/Cargo.toml b/Cargo.toml index 579bc8dc40b..57113cf7a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -160,7 +160,7 @@ indoc = "2.0" insta = "1.40" ipfs-api-backend-hyper = "0.6" itertools = "0.13" -k256 = "0.13.4" +k256 = "0.13" lazy_static = "1.4" libp2p-identity = "0.2" libtest-mimic = "0.7" @@ -196,7 +196,6 @@ revm = "14.0" ropey = "1.5" rpassword = "7.2" rustc-hash = "1.1" -secp256k1 = "0.29.1" semver = "1.0" serde = "1.0" serde_ignored = "0.1" diff --git a/forc-plugins/forc-client/Cargo.toml b/forc-plugins/forc-client/Cargo.toml index 50aca2f88e3..f63bb0b99c9 100644 --- a/forc-plugins/forc-client/Cargo.toml +++ b/forc-plugins/forc-client/Cargo.toml @@ -37,7 +37,6 @@ hex.workspace = true k256.workspace = true rand.workspace = true rpassword.workspace = true -secp256k1.workspace = true serde.workspace = true serde_json.workspace = true sway-core.workspace = true From 23d2040861f2336b076bed001828850674da00e3 Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Mon, 23 Sep 2024 02:01:15 -0700 Subject: [PATCH 11/16] remove pin to patch version for aws-config --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 57113cf7a2a..716493b94c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,7 +114,7 @@ anyhow = "1.0" assert-json-diff = "2.0" async-trait = "0.1" atty = "0.2" -aws-config = "1.5.6" +aws-config = "1.5" aws-sdk-kms = "1.44" byte-unit = "5.1" bytecount = "0.6" From b71bc4a3ae4dde81b45270f4285ca0e0b7c9dc1c Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Mon, 23 Sep 2024 16:53:25 -0700 Subject: [PATCH 12/16] clippy --- forc-plugins/forc-client/src/util/tx.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/forc-plugins/forc-client/src/util/tx.rs b/forc-plugins/forc-client/src/util/tx.rs index d226b5bf678..19b252d39ff 100644 --- a/forc-plugins/forc-client/src/util/tx.rs +++ b/forc-plugins/forc-client/src/util/tx.rs @@ -21,7 +21,6 @@ use fuels_accounts::{ wallet::{Wallet, WalletUnlocked}, ViewOnlyAccount, }; -use fuels_core::types::bech32::Bech32Address; use std::{collections::BTreeMap, path::Path, str::FromStr}; use super::aws::{AwsClient, AwsConfig}; From 3a2e117e1499842a5db30a188415ce5b0b66512a Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Mon, 23 Sep 2024 17:11:44 -0700 Subject: [PATCH 13/16] clippy2 --- forc-plugins/forc-client/src/util/tx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forc-plugins/forc-client/src/util/tx.rs b/forc-plugins/forc-client/src/util/tx.rs index 19b252d39ff..d5a002e809f 100644 --- a/forc-plugins/forc-client/src/util/tx.rs +++ b/forc-plugins/forc-client/src/util/tx.rs @@ -268,8 +268,8 @@ pub async fn update_proxy_contract_target( #[cfg(test)] mod tests { use super::*; - use std::collections::BTreeMap; - use std::collections::HashMap; + use fuels::types::bech32::Bech32Address; + use std::collections::{BTreeMap, HashMap}; #[test] fn test_format_base_asset_account_balances() { From e9984f1d3311e5012bb304de393def7fe41c4365 Mon Sep 17 00:00:00 2001 From: Kaya Gokalp Date: Tue, 24 Sep 2024 12:37:46 -0700 Subject: [PATCH 14/16] add doc comments --- forc-plugins/forc-client/src/util/account.rs | 6 ++++++ forc-plugins/forc-client/src/util/aws.rs | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/forc-plugins/forc-client/src/util/account.rs b/forc-plugins/forc-client/src/util/account.rs index 125634ec356..98ee86fe6b9 100644 --- a/forc-plugins/forc-client/src/util/account.rs +++ b/forc-plugins/forc-client/src/util/account.rs @@ -9,8 +9,14 @@ use fuels_accounts::{wallet::WalletUnlocked, Account}; use super::aws::AwsSigner; #[derive(Clone, Debug)] +/// Set of different signers available to be used with `forc-client` operations. pub enum ForcClientAccount { + /// Local signer, where the private key owned locally. This can be + /// generated through forc-wallet integration or manually by providing + /// a private-key. Wallet(WalletUnlocked), + /// A KMS Signer specifcally using AWS KMS service. The signing key is + /// is managed by another entity. KmsSigner(AwsSigner), } diff --git a/forc-plugins/forc-client/src/util/aws.rs b/forc-plugins/forc-client/src/util/aws.rs index 7c68c1fdb55..90da533815c 100644 --- a/forc-plugins/forc-client/src/util/aws.rs +++ b/forc-plugins/forc-client/src/util/aws.rs @@ -14,12 +14,16 @@ use fuels_accounts::provider::Provider; use fuels_accounts::{Account, ViewOnlyAccount}; use fuels_core::traits::Signer; +/// AWS configuration for the `AwsSigner` to be created. +/// De-facto way of creating the configuration is to load it from env. #[derive(Debug, Clone)] pub struct AwsConfig { sdk_config: SdkConfig, } impl AwsConfig { + /// Load configuration from environment variables. + /// For more details see: https://docs.rs/aws-config/latest/aws_config/ pub async fn from_env() -> Self { let loader = aws_config::defaults(BehaviorVersion::latest()) .credentials_provider(DefaultCredentialsChain::builder().build().await); @@ -60,6 +64,7 @@ impl AwsConfig { } } +/// A configured `AwsClient` which allows using the AWS KMS SDK. #[derive(Clone, Debug)] pub struct AwsClient { client: Client, @@ -78,6 +83,10 @@ impl AwsClient { } } +/// A signer which is capable of signing `fuel_crypto::Message`s using AWS KMS. +/// This is both a `Signer` and `Account`, which means it is directly usable +/// with most of the fuels-* calls, without any additional operations on the +/// representation. #[derive(Clone, Debug)] pub struct AwsSigner { kms: AwsClient, @@ -204,7 +213,7 @@ impl AwsSigner { sign_with_kms(self.kms.inner(), &key_id, &self.public_key_bytes, message).await } - /// Sign a digest with this signer's key + /// Sign a digest with this signer's key. pub async fn sign_message( &self, message: Message, From f99b9232c854d20401e41ab16d1af50cd93de1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaya=20G=C3=B6kalp?= Date: Tue, 24 Sep 2024 12:42:56 -0700 Subject: [PATCH 15/16] Apply suggestions from code review --- forc-plugins/forc-client/src/util/account.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/forc-plugins/forc-client/src/util/account.rs b/forc-plugins/forc-client/src/util/account.rs index 98ee86fe6b9..79d34f0cf58 100644 --- a/forc-plugins/forc-client/src/util/account.rs +++ b/forc-plugins/forc-client/src/util/account.rs @@ -11,12 +11,14 @@ use super::aws::AwsSigner; #[derive(Clone, Debug)] /// Set of different signers available to be used with `forc-client` operations. pub enum ForcClientAccount { - /// Local signer, where the private key owned locally. This can be - /// generated through forc-wallet integration or manually by providing + /// Local signer where the private key owned locally. This can be + /// generated through `forc-wallet` integration or manually by providing /// a private-key. Wallet(WalletUnlocked), - /// A KMS Signer specifcally using AWS KMS service. The signing key is - /// is managed by another entity. + /// A KMS Signer specifically using AWS KMS service. The signing key is + /// is managed by another entity for KMS signers. Messages are + /// signed by the KMS entity. Signed transactions are retrieved + /// and submitted to the node by `forc-client`. KmsSigner(AwsSigner), } From d012f5d399ba290495d6569d21b2d64021a36b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaya=20G=C3=B6kalp?= Date: Tue, 24 Sep 2024 14:41:25 -0700 Subject: [PATCH 16/16] Apply suggestions from code review --- forc-plugins/forc-client/src/util/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forc-plugins/forc-client/src/util/account.rs b/forc-plugins/forc-client/src/util/account.rs index 79d34f0cf58..17ecc6781b8 100644 --- a/forc-plugins/forc-client/src/util/account.rs +++ b/forc-plugins/forc-client/src/util/account.rs @@ -15,7 +15,7 @@ pub enum ForcClientAccount { /// generated through `forc-wallet` integration or manually by providing /// a private-key. Wallet(WalletUnlocked), - /// A KMS Signer specifically using AWS KMS service. The signing key is + /// A KMS Signer specifically using AWS KMS service. The signing key /// is managed by another entity for KMS signers. Messages are /// signed by the KMS entity. Signed transactions are retrieved /// and submitted to the node by `forc-client`.