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(contract invoke): support contract aliases when parsing address #1765

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 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
60 changes: 45 additions & 15 deletions cmd/soroban-cli/src/commands/contract/arg_parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ use ed25519_dalek::SigningKey;
use heck::ToKebabCase;

use crate::xdr::{
self, Hash, InvokeContractArgs, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal,
ScVec,
self, Hash, InvokeContractArgs, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec,
};

use crate::commands::txn_result::TxnResult;
use crate::config::{self};
use crate::config::{
self,
sc_address::{self, UnresolvedScAddress},
};
use soroban_spec_tools::Spec;

#[derive(thiserror::Error, Debug)]
Expand Down Expand Up @@ -43,6 +45,10 @@ pub enum Error {
MissingArgument(String),
#[error("")]
MissingFileArg(PathBuf),
#[error(transparent)]
ScAddress(#[from] sc_address::Error),
#[error(transparent)]
Config(#[from] config::Error),
}

pub fn build_host_function_parameters(
Expand Down Expand Up @@ -80,18 +86,18 @@ pub fn build_host_function_parameters(
.map(|i| {
let name = i.name.to_utf8_string()?;
if let Some(mut val) = matches_.get_raw(&name) {
let mut s = val.next().unwrap().to_string_lossy().to_string();
let mut s = val
.next()
.unwrap()
.to_string_lossy()
.trim_matches('"')
.to_string();
if matches!(i.type_, ScSpecTypeDef::Address) {
let cmd = crate::commands::keys::address::Cmd {
name: s.clone(),
hd_path: Some(0),
locator: config.locator.clone(),
};
if let Ok(address) = cmd.public_key() {
s = address.to_string();
}
if let Ok(key) = cmd.private_key() {
signers.push(key);
let addr = resolve_address(&s, config)?;
let signer = resolve_signer(&s, config);
s = addr;
if let Some(signer) = signer {
signers.push(signer);
}
}
spec.from_string(&s, &i.type_)
Expand Down Expand Up @@ -125,7 +131,7 @@ pub fn build_host_function_parameters(
})
.collect::<Result<Vec<_>, Error>>()?;

let contract_address_arg = ScAddress::Contract(Hash(contract_id.0));
let contract_address_arg = xdr::ScAddress::Contract(Hash(contract_id.0));
let function_symbol_arg = function
.try_into()
.map_err(|()| Error::FunctionNameTooLong(function.clone()))?;
Expand Down Expand Up @@ -246,3 +252,27 @@ pub fn output_to_string(
}
Ok(TxnResult::Res(res_str))
}

fn resolve_address(addr_or_alias: &str, config: &config::Args) -> Result<String, Error> {
let sc_address: UnresolvedScAddress = addr_or_alias.parse().unwrap();
let account = match sc_address {
UnresolvedScAddress::Resolved(addr) => addr.to_string(),
addr @ UnresolvedScAddress::Alias(_) => {
let addr = addr.resolve(&config.locator, &config.get_network()?.network_passphrase)?;
match addr {
xdr::ScAddress::Account(account) => account.to_string(),
contract @ xdr::ScAddress::Contract(_) => contract.to_string(),
}
}
};
Ok(account)
}

fn resolve_signer(addr_or_alias: &str, config: &config::Args) -> Option<SigningKey> {
let cmd = crate::commands::keys::address::Cmd {
name: addr_or_alias.to_string(),
hd_path: Some(0),
locator: config.locator.clone(),
};
cmd.private_key().ok()
}
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
pub struct Cmd {
/// Contract ID to fetch
#[arg(long = "id", env = "STELLAR_CONTRACT_ID")]
pub contract_id: config::ContractAddress,
pub contract_id: config::UnresolvedContract,
/// Where to write output otherwise stdout is used
#[arg(long, short = 'o')]
pub out_file: Option<std::path::PathBuf>,
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/info/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub struct Args {
pub wasm_hash: Option<String>,
/// Contract id or contract alias to get the data for
#[arg(long = "id", env = "STELLAR_CONTRACT_ID", group = "Source")]
pub contract_id: Option<config::ContractAddress>,
pub contract_id: Option<config::UnresolvedContract>,
#[command(flatten)]
pub network: network::Args,
#[command(flatten)]
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use soroban_spec_tools::contract;
pub struct Cmd {
/// Contract ID to invoke
#[arg(long = "id", env = "STELLAR_CONTRACT_ID")]
pub contract_id: config::ContractAddress,
pub contract_id: config::UnresolvedContract,
// For testing only
#[arg(skip)]
pub wasm: Option<std::path::PathBuf>,
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub struct Cmd {
num_args = 1..=6,
help_heading = "FILTERS"
)]
contract_ids: Vec<config::ContractAddress>,
contract_ids: Vec<config::UnresolvedContract>,
/// A set of (up to 4) topic filters to filter event topics on. A single
/// topic filter can contain 1-4 different segment filters, separated by
/// commas, with an asterisk (`*` character) indicating a wildcard segment.
Expand Down
4 changes: 2 additions & 2 deletions cmd/soroban-cli/src/commands/snapshot/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use crate::{
tx::builder,
utils::get_name_from_stellar_asset_contract_storage,
};
use crate::{config::address::Address, utils::http};
use crate::{config::address::UnresolvedMuxedAccount, utils::http};

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
pub enum Output {
Expand Down Expand Up @@ -407,7 +407,7 @@ impl Cmd {
// Resolve an account address to an account id. The address can be a
// G-address or a key name (as in `stellar keys address NAME`).
fn resolve_account(&self, address: &str) -> Option<AccountId> {
let address: Address = address.parse().ok()?;
let address: UnresolvedMuxedAccount = address.parse().ok()?;

Some(AccountId(xdr::PublicKey::PublicKeyTypeEd25519(
match address.resolve_muxed_account(&self.locator, None).ok()? {
Expand Down
42 changes: 26 additions & 16 deletions cmd/soroban-cli/src/config/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ use super::{locator, secret};

/// Address can be either a public key or eventually an alias of a address.
#[derive(Clone, Debug)]
pub enum Address {
MuxedAccount(xdr::MuxedAccount),
pub enum UnresolvedMuxedAccount {
Resolved(xdr::MuxedAccount),
AliasOrSecret(String),
}

impl Default for Address {
impl Default for UnresolvedMuxedAccount {
fn default() -> Self {
Address::AliasOrSecret(String::default())
UnresolvedMuxedAccount::AliasOrSecret(String::default())
}
}

Expand All @@ -27,37 +27,47 @@ pub enum Error {
CannotSign(xdr::MuxedAccount),
}

impl FromStr for Address {
impl FromStr for UnresolvedMuxedAccount {
type Err = Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
Ok(xdr::MuxedAccount::from_str(value).map_or_else(
|_| Address::AliasOrSecret(value.to_string()),
Address::MuxedAccount,
|_| UnresolvedMuxedAccount::AliasOrSecret(value.to_string()),
UnresolvedMuxedAccount::Resolved,
))
}
}

impl Address {
impl UnresolvedMuxedAccount {
pub fn resolve_muxed_account(
&self,
locator: &locator::Args,
hd_path: Option<usize>,
) -> Result<xdr::MuxedAccount, Error> {
match self {
Address::MuxedAccount(muxed_account) => Ok(muxed_account.clone()),
Address::AliasOrSecret(alias) => alias.parse().or_else(|_| {
Ok(xdr::MuxedAccount::Ed25519(
locator.read_identity(alias)?.public_key(hd_path)?.0.into(),
))
}),
UnresolvedMuxedAccount::Resolved(muxed_account) => Ok(muxed_account.clone()),
UnresolvedMuxedAccount::AliasOrSecret(alias) => {
Self::resolve_muxed_account_with_alias(alias, locator, hd_path)
}
}
}

pub fn resolve_muxed_account_with_alias(
alias: &str,
locator: &locator::Args,
hd_path: Option<usize>,
) -> Result<xdr::MuxedAccount, Error> {
alias.parse().or_else(|_| {
Ok(xdr::MuxedAccount::Ed25519(
locator.read_identity(alias)?.public_key(hd_path)?.0.into(),
))
})
}

pub fn resolve_secret(&self, locator: &locator::Args) -> Result<secret::Secret, Error> {
match &self {
Address::MuxedAccount(muxed_account) => Err(Error::CannotSign(muxed_account.clone())),
Address::AliasOrSecret(alias) => Ok(locator.read_identity(alias)?),
UnresolvedMuxedAccount::Resolved(muxed_account) => Err(Error::CannotSign(muxed_account.clone())),
UnresolvedMuxedAccount::AliasOrSecret(alias) => Ok(locator.read_identity(alias)?),
}
}
}
34 changes: 22 additions & 12 deletions cmd/soroban-cli/src/config/alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,49 @@ pub struct Data {

/// Address can be either a contract address, C.. or eventually an alias of a contract address.
#[derive(Clone, Debug)]
pub enum ContractAddress {
ContractId(stellar_strkey::Contract),
pub enum UnresolvedContract {
Resolved(stellar_strkey::Contract),
Alias(String),
}

impl Default for ContractAddress {
impl Default for UnresolvedContract {
fn default() -> Self {
ContractAddress::Alias(String::default())
UnresolvedContract::Alias(String::default())
}
}

impl FromStr for ContractAddress {
impl FromStr for UnresolvedContract {
type Err = Infallible;

fn from_str(value: &str) -> Result<Self, Self::Err> {
Ok(stellar_strkey::Contract::from_str(value).map_or_else(
|_| ContractAddress::Alias(value.to_string()),
ContractAddress::ContractId,
|_| UnresolvedContract::Alias(value.to_string()),
UnresolvedContract::Resolved,
))
}
}

impl ContractAddress {
impl UnresolvedContract {
pub fn resolve_contract_id(
&self,
locator: &locator::Args,
network_passphrase: &str,
) -> Result<stellar_strkey::Contract, locator::Error> {
match self {
ContractAddress::ContractId(muxed_account) => Ok(*muxed_account),
ContractAddress::Alias(alias) => locator
.get_contract_id(alias, network_passphrase)?
.ok_or_else(|| locator::Error::ContractNotFound(alias.to_owned())),
UnresolvedContract::Resolved(contract) => Ok(*contract),
UnresolvedContract::Alias(alias) => {
Self::resolve_alias(alias, locator, network_passphrase)
}
}
}

pub fn resolve_alias(
alias: &str,
locator: &locator::Args,
network_passphrase: &str,
) -> Result<stellar_strkey::Contract, locator::Error> {
locator
.get_contract_id(alias, network_passphrase)?
.ok_or_else(|| locator::Error::ContractNotFound(alias.to_owned()))
}
}
8 changes: 5 additions & 3 deletions cmd/soroban-cli/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use address::Address;
use clap::{arg, command};
use serde::{Deserialize, Serialize};
use std::{
Expand All @@ -19,11 +18,14 @@ pub mod alias;
pub mod data;
pub mod locator;
pub mod network;
pub mod sc_address;
pub mod secret;
pub mod sign_with;
pub mod upgrade_check;

pub use alias::ContractAddress;
pub use alias::UnresolvedContract;
pub use address::UnresolvedMuxedAccount;
pub use sc_address::UnresolvedScAddress;

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand Down Expand Up @@ -56,7 +58,7 @@ pub struct Args {
/// or a seed phrase (--source "kite urban…").
/// If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to
/// sign the final transaction. In that case, trying to sign with public key will fail.
pub source_account: Address,
pub source_account: UnresolvedMuxedAccount,

#[arg(long)]
/// If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0`
Expand Down
58 changes: 58 additions & 0 deletions cmd/soroban-cli/src/config/sc_address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::str::FromStr;

use crate::xdr;

use super::{address, locator, UnresolvedContract};

/// `ScAddress` can be either a resolved `xdr::ScAddress` or an alias of a `Contract` or `MuxedAccount`.
#[allow(clippy::module_name_repetitions)]
#[derive(Clone, Debug)]
pub enum UnresolvedScAddress {
Resolved(xdr::ScAddress),
Alias(String),
}

impl Default for UnresolvedScAddress {
fn default() -> Self {
UnresolvedScAddress::Alias(String::default())
}
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Locator(#[from] locator::Error),
#[error(transparent)]
Address(#[from] address::Error),
#[error("Account alias not Found{0}")]
AccountAliasNotFound(String),
}

impl FromStr for UnresolvedScAddress {
type Err = Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
Ok(xdr::ScAddress::from_str(value)
.map_or_else(|_| UnresolvedScAddress::Alias(value.to_string()), UnresolvedScAddress::Resolved))
}
}

impl UnresolvedScAddress {
pub fn resolve(
self,
locator: &locator::Args,
network_passphrase: &str,
) -> Result<xdr::ScAddress, Error> {
let alias = match self {
UnresolvedScAddress::Resolved(addr) => return Ok(addr),
UnresolvedScAddress::Alias(alias) => alias,
};
let contract = UnresolvedContract::resolve_alias(&alias, locator, network_passphrase);
let muxed_account = super::UnresolvedMuxedAccount::resolve_muxed_account_with_alias(&alias, locator, None);
match (contract, muxed_account) {
(Ok(contract), _) => Ok(xdr::ScAddress::Contract(xdr::Hash(contract.0))),
(_, Ok(muxed_account)) => Ok(xdr::ScAddress::Account(muxed_account.account_id())),
_ => Err(Error::AccountAliasNotFound(alias)),
}
}
}
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub struct Args {
required_unless_present = "wasm",
required_unless_present = "wasm_hash"
)]
pub contract_id: Option<alias::ContractAddress>,
pub contract_id: Option<alias::UnresolvedContract>,
/// Storage key (symbols only)
#[arg(long = "key", conflicts_with = "key_xdr")]
pub key: Option<Vec<String>>,
Expand Down
Loading