Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: AWS kms signer support for forc-client #6578

Merged
merged 21 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
373 changes: 371 additions & 2 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ anyhow = "1.0"
assert-json-diff = "2.0"
async-trait = "0.1"
atty = "0.2"
aws-config = "1.5.6"
aws-sdk-kms = "1.44"
kayagokalp marked this conversation as resolved.
Show resolved Hide resolved
byte-unit = "5.1"
bytecount = "0.6"
bytes = "1.7"
Expand Down Expand Up @@ -158,6 +160,7 @@ indoc = "2.0"
insta = "1.40"
ipfs-api-backend-hyper = "0.6"
itertools = "0.13"
k256 = "0.13"
lazy_static = "1.4"
libp2p-identity = "0.2"
libtest-mimic = "0.7"
Expand Down
3 changes: 3 additions & 0 deletions forc-plugins/forc-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ repository.workspace = true
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
aws-config.workspace = true
aws-sdk-kms.workspace = true
chrono = { workspace = true, features = ["std"] }
clap = { workspace = true, features = ["derive", "env"] }
devault.workspace = true
Expand All @@ -32,6 +34,7 @@ fuels-accounts.workspace = true
fuels-core.workspace = true
futures.workspace = true
hex.workspace = true
k256.workspace = true
rand.workspace = true
rpassword.workspace = true
serde.workspace = true
Expand Down
4 changes: 4 additions & 0 deletions forc-plugins/forc-client/src/cmd/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}
61 changes: 28 additions & 33 deletions forc-plugins/forc-client/src/op/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
};
Expand All @@ -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, Account, ViewOnlyAccount};
use fuels_core::types::{transaction::TxPolicies, transaction_builders::CreateTransactionBuilder};
use futures::FutureExt;
use pkg::{manifest::build_profile::ExperimentalFlags, BuildProfile, BuiltPackage};
Expand Down Expand Up @@ -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<ContractId> {
Expand All @@ -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
Expand All @@ -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();

Expand All @@ -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<ContractId> {
fuels::macros::abigen!(Contract(
name = "ProxyContract",
Expand Down Expand Up @@ -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 =
Expand All @@ -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();

Expand All @@ -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)
Expand Down Expand Up @@ -1059,7 +1058,7 @@ pub async fn deploy(command: cmd::Deploy) -> Result<Vec<DeployedContract>> {
}

// 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 {
Expand Down Expand Up @@ -1087,13 +1086,13 @@ pub async fn deploy(command: cmd::Deploy) -> Result<Vec<DeployedContract>> {
&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 {
Expand All @@ -1108,13 +1107,8 @@ pub async fn deploy(command: cmd::Deploy) -> Result<Vec<DeployedContract>> {
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 {
Expand All @@ -1128,7 +1122,7 @@ pub async fn deploy(command: cmd::Deploy) -> Result<Vec<DeployedContract>> {
pkg_name,
&deployed_contract_id,
&provider,
&signing_key,
&account,
)
.await?;

Expand Down Expand Up @@ -1158,7 +1152,7 @@ async fn confirm_transaction_details(
pkgs_to_deploy: &[&Arc<BuiltPackage>],
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
Expand Down Expand Up @@ -1203,27 +1197,28 @@ 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 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()?;
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
Expand All @@ -1232,7 +1227,7 @@ pub async fn deploy_pkg(
compiled: &BuiltPackage,
salt: Salt,
provider: &Provider,
signing_key: &SecretKey,
account: &ForcClientAccount,
) -> Result<fuel_tx::ContractId> {
let manifest = &compiled.descriptor.manifest_file;
let node_url = provider.url();
Expand All @@ -1255,10 +1250,10 @@ 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);

Expand Down
83 changes: 54 additions & 29 deletions forc-plugins/forc-client/src/op/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@ 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};
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 pkg::{manifest::build_profile::ExperimentalFlags, BuiltPackage};
use std::time::Duration;
use std::{path::PathBuf, str::FromStr};
Expand Down Expand Up @@ -51,10 +58,10 @@ pub async fn run(command: cmd::Run) -> Result<Vec<RanScript>> {
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
Expand All @@ -77,13 +84,34 @@ pub async fn run(command: cmd::Run) -> Result<Vec<RanScript>> {
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<RanScript> {
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)) => {
Expand Down Expand Up @@ -116,31 +144,28 @@ pub async fn run_pkg(
})
.collect::<Result<Vec<ContractId>>>()?;

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?
JoshuaBatty marked this conversation as resolved.
Show resolved Hide resolved
let script_binary = compiled.bytecode.bytes.clone();
let external_contracts = contract_ids
.into_iter()
.map(Bech32ContractId::from)
.collect::<Vec<_>>();
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?;

let tx = tb.build(provider).await?;

if command.dry_run {
info!("{:?}", tx);
Ok(RanScript { receipts: vec![] })
Expand Down
Loading
Loading