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 9 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
23 changes: 22 additions & 1 deletion cmd/crates/soroban-test/tests/it/integration/custom_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use soroban_test::TestEnv;

use crate::integration::util::{deploy_custom, extend_contract};

use super::util::invoke_with_roundtrip;
use super::util::{invoke, invoke_with_roundtrip};

fn invoke_custom(e: &TestEnv, id: &str, func: &str) -> assert_cmd::Command {
let mut s = e.new_assert_cmd("contract");
Expand Down Expand Up @@ -40,7 +40,9 @@ async fn parse() {
negative_i32(sandbox, id).await;
negative_i64(sandbox, id).await;
account_address(sandbox, id).await;
// account_address_with_alias(sandbox, id).await;
contract_address(sandbox, id).await;
// contract_address_with_alias(sandbox, id).await;
bytes(sandbox, id).await;
const_enum(sandbox, id).await;
number_arg_return_ok(sandbox, id);
Expand Down Expand Up @@ -237,6 +239,11 @@ async fn account_address(sandbox: &TestEnv, id: &str) {
.await;
}

async fn account_address_with_alias(sandbox: &TestEnv, id: &str) {
let res = invoke(sandbox, id, "addresse", &json!("test").to_string()).await;
assert_eq!(sandbox.test_address(0), res);
}

async fn contract_address(sandbox: &TestEnv, id: &str) {
invoke_with_roundtrip(
sandbox,
Expand All @@ -247,6 +254,20 @@ async fn contract_address(sandbox: &TestEnv, id: &str) {
.await;
}

async fn contract_address_with_alias(sandbox: &TestEnv, id: &str) {
sandbox
.new_assert_cmd("contract")
.arg("alias")
.arg("add")
.arg("--alias=test_contract")
.arg("--address=CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE");
let res = invoke(sandbox, id, "addresse", &json!("test_contract").to_string()).await;
assert_eq!(
res,
"CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE"
);
}

async fn bytes(sandbox: &TestEnv, id: &str) {
invoke_with_roundtrip(sandbox, id, "bytes", json!("7374656c6c6172")).await;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use soroban_cli::{
contract::{self, fetch},
txn_result::TxnResult,
},
config::{address::Address, locator, secret},
config::{address::UnresolvedMuxedAccount, locator, secret},
};
use soroban_rpc::GetLatestLedgerResponse;
use soroban_test::{AssertExt, TestEnv, LOCAL_NETWORK_PASSPHRASE};
Expand Down
11 changes: 7 additions & 4 deletions cmd/crates/soroban-test/tests/it/integration/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@ pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types")
pub const CUSTOM_ACCOUNT: &Wasm = &Wasm::Custom("test-wasms", "test_custom_account");
pub const SWAP: &Wasm = &Wasm::Custom("test-wasms", "test_swap");

pub async fn invoke(sandbox: &TestEnv, id: &str, func: &str, data: &str) -> String {
sandbox
.invoke_with_test(&["--id", id, "--", func, &format!("--{func}"), data])
.await
.unwrap()
}
pub async fn invoke_with_roundtrip<D>(e: &TestEnv, id: &str, func: &str, data: D)
where
D: Display,
{
let data = data.to_string();
println!("{data}");
let res = e
.invoke_with_test(&["--id", id, "--", func, &format!("--{func}"), &data])
.await
.unwrap();
let res = invoke(e, id, func, &data).await;
assert_eq!(res, data);
}

Expand Down
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
44 changes: 28 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,49 @@ 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()))
}
}
Loading
Loading