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

soroban-cli: Add Auth-next signing support #749

Merged
merged 21 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from 10 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
9 changes: 6 additions & 3 deletions cmd/soroban-cli/src/commands/config/identity/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ impl Cmd {
Ok(())
}

pub fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
pub fn private_key(&self) -> Result<ed25519_dalek::Keypair, Error> {
let res = if let Some(name) = &self.name {
self.locator.read_identity(name)?
} else {
Secret::test_seed_phrase()?
};
let key = res.key_pair(self.hd_path)?;
Ok(res.key_pair(self.hd_path)?)
}

pub fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
Ok(stellar_strkey::ed25519::PublicKey::from_payload(
key.public.as_bytes(),
self.private_key()?.public.as_bytes(),
)?)
}
}
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/bump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ impl Cmd {
};

let (result, _meta, events) = client
.prepare_and_send_transaction(&tx, &key, &network.network_passphrase, None)
.prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None)
.await?;

tracing::debug!(?result);
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ impl Cmd {
&key,
)?;
client
.prepare_and_send_transaction(&tx, &key, &network.network_passphrase, None)
.prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None)
.await?;
Ok(stellar_strkey::Contract(contract_id.0).to_string())
}
Expand Down
1 change: 1 addition & 0 deletions cmd/soroban-cli/src/commands/contract/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ impl Cmd {
.prepare_and_send_transaction(
&tx_without_preflight,
&key,
&[],
&network.network_passphrase,
None,
)
Expand Down
75 changes: 46 additions & 29 deletions cmd/soroban-cli/src/commands/contract/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::str::FromStr;
use std::{fmt::Debug, fs, io, rc::Rc};

use clap::{arg, command, Parser};
use ed25519_dalek::Keypair;
use heck::ToKebabCase;
use soroban_env_host::{
budget::Budget,
Expand Down Expand Up @@ -135,6 +136,8 @@ pub enum Error {
MissingResult,
#[error(transparent)]
StrVal(#[from] soroban_spec_tools::Error),
#[error("error loading signing key: {0}")]
SignatureError(#[from] ed25519_dalek::SignatureError),
#[error(transparent)]
Config(#[from] config::Error),
#[error("unexpected ({length}) simulate transaction result length")]
Expand Down Expand Up @@ -166,7 +169,7 @@ impl Cmd {
&self,
contract_id: [u8; 32],
spec_entries: &[ScSpecEntry],
) -> Result<(String, Spec, ScVec), Error> {
) -> Result<(String, Spec, ScVec, Vec<Keypair>), Error> {
let spec = Spec(Some(spec_entries.to_vec()));
let mut cmd = clap::Command::new(self.contract_id.clone())
.no_binary_name(true)
Expand All @@ -182,32 +185,39 @@ impl Cmd {

let func = spec.find_function(function)?;
// create parsed_args in same order as the inputs to func
let parsed_args = func
.inputs
.iter()
.map(|i| {
let name = i.name.to_string().unwrap();
if let Some(mut val) = matches_.get_raw(&name) {
let mut s = val.next().unwrap().to_string_lossy().to_string();
if matches!(i.type_, ScSpecTypeDef::Address) {
let cmd = crate::commands::config::identity::address::Cmd {
name: Some(s.clone()),
hd_path: Some(0),
locator: self.config.locator.clone(),
};
if let Ok(address) = cmd.public_key() {
s = address.to_string();
}
let mut signers: Vec<Keypair> = vec![];
let mut parsed_args: Vec<ScVal> = vec![];
for i in func.inputs.iter() {
let name = i.name.to_string().unwrap();
if let Some(mut val) = matches_.get_raw(&name) {
let mut s = val.next().unwrap().to_string_lossy().to_string();
if matches!(i.type_, ScSpecTypeDef::Address) {
let cmd = crate::commands::config::identity::address::Cmd {
name: Some(s.clone()),
hd_path: Some(0),
locator: self.config.locator.clone(),
};
if let Ok(key) = cmd.private_key() {
// TODO: Once we upgrade to ed25519_dalek@2.0.0, switch this to
// `key.clone()`
let signer = Keypair::from_bytes(&key.to_bytes())?;
signers.push(signer);
s = stellar_strkey::ed25519::PublicKey::from_payload(
key.public.as_bytes(),
)?
.to_string();
}
spec.from_string(&s, &i.type_)
.map_err(|error| Error::CannotParseArg { arg: name, error })
} else if matches!(i.type_, ScSpecTypeDef::Option(_)) {
Ok(ScVal::Void)
} else {
Err(Error::MissingArgument(name))
}
})
.collect::<Result<Vec<_>, Error>>()?;
let arg = spec
.from_string(&s, &i.type_)
.map_err(|error| Error::CannotParseArg { arg: name, error })?;
parsed_args.push(arg);
} else if matches!(i.type_, ScSpecTypeDef::Option(_)) {
parsed_args.push(ScVal::Void);
} else {
return Err(Error::MissingArgument(name));
}
}

// Add the contract ID and the function name to the arguments
let mut complete_args = vec![
Expand All @@ -230,6 +240,7 @@ impl Cmd {
current: complete_args_len,
maximum: ScVec::default().max_len(),
})?,
signers,
))
}

Expand Down Expand Up @@ -270,7 +281,7 @@ impl Cmd {
};

// Get the ledger footprint
let (function, spec, host_function_params) =
let (function, spec, host_function_params, signers) =
self.build_host_function_parameters(contract_id, &spec_entries)?;
let tx = build_invoke_contract_tx(
host_function_params.clone(),
Expand All @@ -280,7 +291,13 @@ impl Cmd {
)?;

let (result, meta, events) = client
.prepare_and_send_transaction(&tx, &key, &network.network_passphrase, Some(log_events))
.prepare_and_send_transaction(
&tx,
&key,
&signers,
&network.network_passphrase,
Some(log_events),
)
.await?;

tracing::debug!(?result);
Expand Down Expand Up @@ -343,7 +360,7 @@ impl Cmd {
ledger_info.timestamp += 5;
h.set_ledger_info(ledger_info.clone());

let (function, spec, host_function_params) =
let (function, spec, host_function_params, _signers) =
self.build_host_function_parameters(contract_id, &spec_entries)?;
h.set_diagnostic_level(DiagnosticLevel::Debug);
let resv = h
Expand Down Expand Up @@ -476,7 +493,7 @@ fn build_invoke_contract_tx(
parameters: ScVec,
sequence: i64,
fee: u32,
key: &ed25519_dalek::Keypair,
key: &Keypair,
) -> Result<Transaction, Error> {
let op = Operation {
source_account: None,
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ impl Cmd {
tracing::trace!(?keys);

client
.get_ledger_entries(keys)
.get_ledger_entries(&keys)
.await?
.entries
.unwrap_or_default()
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl Cmd {
};

let (result, _meta, events) = client
.prepare_and_send_transaction(&tx, &key, &network.network_passphrase, None)
.prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None)
.await?;

tracing::debug!(?result);
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/lab/token/wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl Cmd {
)?;

client
.prepare_and_send_transaction(&tx, &key, network_passphrase, None)
.prepare_and_send_transaction(&tx, &key, &[], network_passphrase, None)
.await?;

Ok(stellar_strkey::Contract(contract_id.0).to_string())
Expand Down
74 changes: 72 additions & 2 deletions cmd/soroban-cli/src/fee.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{commands::HEADING_RPC, rpc};
use clap::arg;

use crate::commands::HEADING_RPC;
use soroban_env_host::{
fees::FeeConfiguration,
xdr::{self, ReadXdr},
};

#[derive(Debug, clap::Args, Clone)]
#[group(skip)]
Expand All @@ -15,3 +18,70 @@ impl Default for Args {
Self { fee: 100 }
}
}

pub async fn get_fee_configuration(
client: &rpc::Client,
) -> Result<(FeeConfiguration, u32), rpc::Error> {
let response = client
.get_ledger_entries(
&vec![
xdr::ConfigSettingId::ContractComputeV0,
xdr::ConfigSettingId::ContractLedgerCostV0,
xdr::ConfigSettingId::ContractHistoricalDataV0,
xdr::ConfigSettingId::ContractMetaDataV0,
xdr::ConfigSettingId::ContractBandwidthV0,
]
.iter()
.map(|config_setting_id| {
xdr::LedgerKey::ConfigSetting(xdr::LedgerKeyConfigSetting {
config_setting_id: *config_setting_id,
})
})
.collect::<Vec<_>>(),
)
.await?;

let entries = response
.entries
.unwrap_or_default()
.iter()
.map(|e| xdr::LedgerEntryData::from_xdr_base64(&e.xdr))
.map(|e| match e {
Ok(xdr::LedgerEntryData::ConfigSetting(config_setting)) => Ok(config_setting),
Err(e) => Err(rpc::Error::Xdr(e)),
Ok(_) => Err(rpc::Error::Xdr(xdr::Error::Invalid)),
})
.collect::<Result<Vec<_>, _>>()?;

if entries.len() != 5 {
return Err(rpc::Error::InvalidResponse);
}

let [
xdr::ConfigSettingEntry::ContractComputeV0(compute),
xdr::ConfigSettingEntry::ContractLedgerCostV0(ledger_cost),
xdr::ConfigSettingEntry::ContractHistoricalDataV0(historical_data),
xdr::ConfigSettingEntry::ContractMetaDataV0(metadata),
xdr::ConfigSettingEntry::ContractBandwidthV0(bandwidth),
] = &entries[..] else {
return Err(rpc::Error::InvalidResponse);
};

// Taken from Stellar Core's InitialSorobanNetworkConfig in NetworkConfig.h
let fee_configuration = FeeConfiguration {
fee_per_instruction_increment: compute.fee_rate_per_instructions_increment,
fee_per_read_entry: ledger_cost.fee_read_ledger_entry,
fee_per_write_entry: ledger_cost.fee_write_ledger_entry,
fee_per_read_1kb: ledger_cost.fee_read1_kb,
fee_per_write_1kb: ledger_cost.fee_write1_kb,
fee_per_historical_1kb: historical_data.fee_historical1_kb,
fee_per_metadata_1kb: metadata.fee_extended_meta_data1_kb,
fee_per_propagate_1kb: bandwidth.fee_propagate_data1_kb,
};

let latest_ledger_seq = response
.latest_ledger
.parse::<u32>()
.map_err(|_| rpc::Error::Xdr(xdr::Error::Invalid))?;
Ok((fee_configuration, latest_ledger_seq))
}
37 changes: 28 additions & 9 deletions cmd/soroban-cli/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ use termcolor::{Color, ColorChoice, StandardStream, WriteColor};
use termcolor_output::colored;
use tokio::time::sleep;

use crate::utils::{self, contract_spec};
use crate::{
fee::get_fee_configuration,
utils::{self, contract_spec},
};

mod transaction;
use transaction::assemble;
use transaction::{assemble, sign_soroban_authorizations};

const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");

Expand Down Expand Up @@ -70,6 +73,8 @@ pub enum Error {
MissingResult,
#[error("Failed to read Error response from server")]
MissingError,
#[error("Missing signing key for account {address}")]
MissingSignerForAddress { address: String },
#[error("cursor is not valid")]
InvalidCursor,
#[error("unexpected ({length}) simulate transaction result length")]
Expand Down Expand Up @@ -446,7 +451,7 @@ impl Client {
))),
});
let keys = Vec::from([key]);
let response = self.get_ledger_entries(keys).await?;
let response = self.get_ledger_entries(&keys).await?;
let entries = response.entries.unwrap_or_default();
if entries.is_empty() {
return Err(Error::MissingResult);
Expand Down Expand Up @@ -567,14 +572,28 @@ impl Client {
pub async fn prepare_and_send_transaction(
&self,
tx_without_preflight: &Transaction,
key: &ed25519_dalek::Keypair,
source_key: &ed25519_dalek::Keypair,
signers: &[ed25519_dalek::Keypair],
network_passphrase: &str,
log_events: Option<LogEvents>,
) -> Result<(TransactionResult, TransactionMeta, Vec<DiagnosticEvent>), Error> {
let (fee_configuration, ledger_seq) = get_fee_configuration(self).await?;
let unsigned_tx = self
.prepare_transaction(tx_without_preflight, log_events)
.await?;
let tx = utils::sign_transaction(key, &unsigned_tx, network_passphrase)?;
let (part_signed_tx, signed_auth_entries) = sign_soroban_authorizations(
&unsigned_tx,
source_key,
signers,
ledger_seq + 60, // ~5 minutes of ledgers
network_passphrase,
)?;
let fee_ready_txn = if signed_auth_entries.is_empty() {
part_signed_tx
} else {
transaction::update_fee(&part_signed_tx, &fee_configuration)?
};
let tx = utils::sign_transaction(source_key, &fee_ready_txn, network_passphrase)?;
self.send_transaction(&tx).await
}

Expand All @@ -587,10 +606,10 @@ impl Client {

pub async fn get_ledger_entries(
&self,
keys: Vec<LedgerKey>,
keys: &[LedgerKey],
) -> Result<GetLedgerEntriesResponse, Error> {
let mut base64_keys: Vec<String> = vec![];
for k in &keys {
for k in keys {
let base64_result = k.to_xdr_base64();
if base64_result.is_err() {
return Err(Error::Xdr(XdrError::Invalid));
Expand Down Expand Up @@ -653,7 +672,7 @@ impl Client {
durability: xdr::ContractDataDurability::Persistent,
body_type: xdr::ContractEntryBodyType::DataEntry,
});
let contract_ref = self.get_ledger_entries(Vec::from([contract_key])).await?;
let contract_ref = self.get_ledger_entries(&[contract_key]).await?;
let entries = contract_ref.entries.unwrap_or_default();
if entries.is_empty() {
return Err(Error::MissingResult);
Expand Down Expand Up @@ -688,7 +707,7 @@ impl Client {
hash,
body_type: xdr::ContractEntryBodyType::DataEntry,
});
let contract_data = self.get_ledger_entries(Vec::from([code_key])).await?;
let contract_data = self.get_ledger_entries(&[code_key]).await?;
let entries = contract_data.entries.unwrap_or_default();
if entries.is_empty() {
return Err(Error::MissingResult);
Expand Down
Loading