diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index ab4919fe98d..bae5045569f 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, + prompt_forc_wallet_password, select_account, update_proxy_contract_target, + SignerSelectionMode, }, }, }; @@ -24,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}; +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}; @@ -158,7 +162,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 +176,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 +186,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 +204,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 +934,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 +953,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 +970,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 +1061,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 +1089,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 +1110,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 +1125,7 @@ pub async fn deploy(command: cmd::Deploy) -> Result> { pkg_name, &deployed_contract_id, &provider, - &signing_key, + &account, ) .await?; @@ -1158,7 +1155,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 +1200,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 +1228,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 +1251,11 @@ 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?; + 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 cda23fb14cf..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, TransactionBuilderExt, WalletSelectionMode}, + 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}; @@ -51,10 +59,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 @@ -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: &WalletSelectionMode, + 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 new file mode 100644 index 00000000000..c9a5262cf18 --- /dev/null +++ b/forc-plugins/forc-client/src/util/account.rs @@ -0,0 +1,65 @@ +use async_trait::async_trait; +use fuel_crypto::{Message, Signature}; +use fuels::{ + prelude::*, + types::{coin_type_id::CoinTypeId, input::Input}, +}; +use fuels_accounts::{wallet::WalletUnlocked, Account}; + +#[derive(Clone, Debug)] +pub enum ForcClientAccount { + Wallet(WalletUnlocked), + KmsSigner, +} + +#[async_trait] +impl Account for ForcClientAccount { + async fn get_asset_inputs_for_amount( + &self, + asset_id: AssetId, + amount: u64, + excluded_coins: Option>, + ) -> Result> { + match self { + ForcClientAccount::Wallet(wallet) => { + wallet + .get_asset_inputs_for_amount(asset_id, amount, excluded_coins) + .await + } + 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!(), + } + } +} + +#[async_trait] +impl Signer for ForcClientAccount { + async fn sign(&self, message: Message) -> Result { + match self { + ForcClientAccount::Wallet(wallet) => wallet.sign(message).await, + 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 d8024081b28..e53af5a7a52 100644 --- a/forc-plugins/forc-client/src/util/mod.rs +++ b/forc-plugins/forc-client/src/util/mod.rs @@ -1,5 +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 4371df2fc13..28ea59c80c5 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 anyhow::{Error, Result}; -use async_trait::async_trait; +use crate::{ + constants::DEFAULT_PRIVATE_KEY, + util::{account::ForcClientAccount, target::Target}, +}; +use anyhow::Result; use dialoguer::{theme::ColorfulTheme, Confirm, Password, Select}; use forc_tracing::{println_action_green, println_warning}; use forc_wallet::{ @@ -11,47 +13,24 @@ 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::SecretKey; +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 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 WalletSelectionMode { +pub enum SignerSelectionMode { /// Holds the password of forc-wallet instance. ForcWallet(String), 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) @@ -121,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, @@ -180,16 +152,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 +221,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 +254,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::*;