Skip to content

Commit

Permalink
feat: improve asset parsing to use named keys as issuer (#1762)
Browse files Browse the repository at this point in the history
Previously the issuer had to be a fully resolved `xdr::AccountId`, now it  can be an `Address`, which then can be resolved. Thus allowing the following: `USDC:circle`

---------

Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com>
Co-authored-by: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 28, 2025
1 parent 9b0e523 commit a0c5dbb
Show file tree
Hide file tree
Showing 13 changed files with 97 additions and 53 deletions.
21 changes: 15 additions & 6 deletions cmd/crates/soroban-test/tests/it/integration/tx/operations.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use soroban_cli::{
config::locator,
tx::{builder, ONE_XLM},
utils::contract_id_hash_from_asset,
xdr::{self, ReadXdr, SequenceNumber},
Expand Down Expand Up @@ -267,19 +268,23 @@ async fn account_merge_with_alias() {
#[tokio::test]
async fn set_trustline_flags() {
let sandbox = &TestEnv::new();
let (test, issuer) = setup_accounts(sandbox);
let asset = format!("usdc:{issuer}");
issue_asset(sandbox, &test, &asset, 100_000, 100).await;
let (test, test1_address) = setup_accounts(sandbox);
let asset = "usdc:test1";
issue_asset(sandbox, &test, asset, 100_000, 100).await;
sandbox
.new_assert_cmd("contract")
.arg("asset")
.arg("deploy")
.arg("--asset")
.arg(&asset)
.arg(asset)
.assert()
.success();
let id = contract_id_hash_from_asset(
asset.parse::<builder::Asset>().unwrap(),
&format!("usdc:{test1_address}")
.parse::<builder::Asset>()
.unwrap()
.resolve(&locator::Args::default())
.unwrap(),
&sandbox.network.network_passphrase,
);
// sandbox
Expand Down Expand Up @@ -542,7 +547,11 @@ async fn change_trust() {

// wrap_cmd(&asset).run().await.unwrap();
let id = contract_id_hash_from_asset(
asset.parse::<builder::Asset>().unwrap(),
&asset
.parse::<builder::Asset>()
.unwrap()
.resolve(&locator::Args::default())
.unwrap(),
&sandbox.network.network_passphrase,
);
sandbox
Expand Down
14 changes: 8 additions & 6 deletions cmd/crates/soroban-test/tests/it/integration/wrap.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use soroban_cli::{tx::builder, utils::contract_id_hash_from_asset};
use soroban_cli::{config::locator, tx::builder, utils::contract_id_hash_from_asset};
use soroban_test::{AssertExt, TestEnv, LOCAL_NETWORK_PASSPHRASE};

#[tokio::test]
Expand All @@ -12,21 +12,23 @@ async fn burn() {
.arg("test")
.assert()
.stdout_as_str();
let asset = format!("native:{address}");
let asset = "native";
sandbox
.new_assert_cmd("contract")
.arg("asset")
.arg("deploy")
.arg("--source=test")
.arg("--asset")
.arg(&asset)
.arg(asset)
.assert()
.success();
// wrap_cmd(&asset).run().await.unwrap();
let asset: builder::Asset = asset.parse().unwrap();
let asset = asset
.parse::<builder::Asset>()
.unwrap()
.resolve(&locator::Args::default())
.unwrap();
let hash = contract_id_hash_from_asset(&asset, &network_passphrase);
let id = stellar_strkey::Contract(hash.0).to_string();
println!("{id}, {address}");
sandbox
.new_assert_cmd("contract")
.args([
Expand Down
6 changes: 4 additions & 2 deletions cmd/soroban-cli/src/commands/contract/deploy/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub enum Error {
#[error(transparent)]
Builder(#[from] builder::Error),
#[error(transparent)]
Asset(#[from] builder::asset::Error),
#[error(transparent)]
Locator(#[from] locator::Error),
}

Expand Down Expand Up @@ -119,7 +121,7 @@ impl NetworkRunnable for Cmd {
) -> Result<Self::Result, Error> {
let config = config.unwrap_or(&self.config);
// Parse asset
let asset = &self.asset;
let asset = self.asset.resolve(&config.locator)?;

let network = config.get_network()?;
let client = network.rpc_client()?;
Expand All @@ -134,7 +136,7 @@ impl NetworkRunnable for Cmd {
.await?;
let sequence: i64 = account_details.seq_num.into();
let network_passphrase = &network.network_passphrase;
let contract_id = contract_id_hash_from_asset(asset, network_passphrase);
let contract_id = contract_id_hash_from_asset(&asset, network_passphrase);
let tx = build_wrap_token_tx(
asset,
&contract_id,
Expand Down
7 changes: 6 additions & 1 deletion cmd/soroban-cli/src/commands/contract/id/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub enum Error {
ConfigError(#[from] config::Error),
#[error(transparent)]
Xdr(#[from] crate::xdr::Error),
#[error(transparent)]
Asset(#[from] builder::asset::Error),
}
impl Cmd {
pub fn run(&self) -> Result<(), Error> {
Expand All @@ -30,7 +32,10 @@ impl Cmd {

pub fn contract_address(&self) -> Result<stellar_strkey::Contract, Error> {
let network = self.config.get_network()?;
let contract_id = contract_id_hash_from_asset(&self.asset, &network.network_passphrase);
let contract_id = contract_id_hash_from_asset(
&self.asset.resolve(&self.config.locator)?,
&network.network_passphrase,
);
Ok(stellar_strkey::Contract(contract_id.0))
}
}
4 changes: 3 additions & 1 deletion cmd/soroban-cli/src/commands/snapshot/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,9 @@ impl Cmd {
get_name_from_stellar_asset_contract_storage(storage)
{
let asset: builder::Asset = name.parse()?;
if let Some(issuer) = match asset.into() {
if let Some(issuer) = match asset
.resolve(&global_args.locator)?
{
Asset::Native => None,
Asset::CreditAlphanum4(a4) => Some(a4.issuer),
Asset::CreditAlphanum12(a12) => Some(a12.issuer),
Expand Down
8 changes: 7 additions & 1 deletion cmd/soroban-cli/src/commands/tx/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
},
fee,
rpc::{self, Client, GetTransactionResponse},
tx::builder::{self, TxExt},
tx::builder::{self, asset, TxExt},
xdr::{self, Limits, WriteXdr},
};

Expand Down Expand Up @@ -39,6 +39,8 @@ pub enum Error {
#[error(transparent)]
Address(#[from] address::Error),
#[error(transparent)]
Asset(#[from] asset::Error),
#[error(transparent)]
TxXdr(#[from] super::xdr::Error),
}

Expand Down Expand Up @@ -144,4 +146,8 @@ impl Args {
};
Ok(super::xdr::add_op(tx_env, op)?)
}

pub fn resolve_asset(&self, asset: &builder::Asset) -> Result<xdr::Asset, Error> {
Ok(asset.resolve(&self.config.locator)?)
}
}
12 changes: 7 additions & 5 deletions cmd/soroban-cli/src/commands/tx/new/change_trust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ pub struct Args {
pub limit: i64,
}

impl From<&Cmd> for xdr::OperationBody {
fn from(cmd: &Cmd) -> Self {
let line = match cmd.op.line.0.clone() {
impl TryFrom<&Cmd> for xdr::OperationBody {
type Error = tx::args::Error;
fn try_from(cmd: &Cmd) -> Result<Self, Self::Error> {
let asset = cmd.tx.resolve_asset(&cmd.op.line)?;
let line = match asset {
xdr::Asset::CreditAlphanum4(asset) => xdr::ChangeTrustAsset::CreditAlphanum4(asset),
xdr::Asset::CreditAlphanum12(asset) => xdr::ChangeTrustAsset::CreditAlphanum12(asset),
xdr::Asset::Native => xdr::ChangeTrustAsset::Native,
};
xdr::OperationBody::ChangeTrust(xdr::ChangeTrustOp {
Ok(xdr::OperationBody::ChangeTrust(xdr::ChangeTrustOp {
line,
limit: cmd.op.limit,
})
}))
}
}
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/tx/new/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl TryFrom<&Cmd> for OperationBody {
Ok(match cmd {
Cmd::AccountMerge(cmd) => cmd.try_into()?,
Cmd::BumpSequence(cmd) => cmd.into(),
Cmd::ChangeTrust(cmd) => cmd.into(),
Cmd::ChangeTrust(cmd) => cmd.try_into()?,
Cmd::CreateAccount(cmd) => cmd.try_into()?,
Cmd::ManageData(cmd) => cmd.into(),
Cmd::Payment(cmd) => cmd.try_into()?,
Expand Down
18 changes: 14 additions & 4 deletions cmd/soroban-cli/src/commands/tx/new/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,21 @@ pub struct Args {

impl TryFrom<&Cmd> for xdr::OperationBody {
type Error = tx::args::Error;
fn try_from(cmd: &Cmd) -> Result<Self, Self::Error> {
fn try_from(
Cmd {
tx,
op:
Args {
destination,
asset,
amount,
},
}: &Cmd,
) -> Result<Self, Self::Error> {
Ok(xdr::OperationBody::Payment(xdr::PaymentOp {
destination: cmd.tx.resolve_muxed_address(&cmd.op.destination)?,
asset: cmd.op.asset.clone().into(),
amount: cmd.op.amount.into(),
destination: tx.resolve_muxed_address(destination)?,
asset: tx.resolve_asset(asset)?,
amount: amount.into(),
}))
}
}
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl TryFrom<&Cmd> for xdr::OperationBody {
Ok(xdr::OperationBody::SetTrustLineFlags(
xdr::SetTrustLineFlagsOp {
trustor: cmd.tx.resolve_account_id(&cmd.op.trustor)?,
asset: cmd.op.asset.clone().into(),
asset: cmd.tx.resolve_asset(&cmd.op.asset)?,
clear_flags,
set_flags,
},
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/tx/op/add/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl TryFrom<&Cmd> for OperationBody {
Ok(match &cmd {
Cmd::AccountMerge(account_merge::Cmd { op, .. }) => op.try_into()?,
Cmd::BumpSequence(bump_sequence::Cmd { op, .. }) => op.into(),
Cmd::ChangeTrust(change_trust::Cmd { op, .. }) => op.into(),
Cmd::ChangeTrust(change_trust::Cmd { op, .. }) => op.try_into()?,
Cmd::CreateAccount(create_account::Cmd { op, .. }) => op.try_into()?,
Cmd::ManageData(manage_data::Cmd { op, .. }) => op.into(),
Cmd::Payment(payment::Cmd { op, .. }) => op.try_into()?,
Expand Down
50 changes: 28 additions & 22 deletions cmd/soroban-cli/src/tx/builder/asset.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,56 @@
use std::str::FromStr;

use crate::xdr::{self, AlphaNum12, AlphaNum4, AssetCode};
use crate::{
config::{address, locator},
xdr::{self, AlphaNum12, AlphaNum4, AssetCode},
};

#[derive(Clone, Debug)]
pub struct Asset(pub xdr::Asset);
pub enum Asset {
Asset(AssetCode, address::UnresolvedMuxedAccount),
Native,
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("cannot parse asset: {0}, expected format: 'native' or 'code:issuer'")]
CannotParseAsset(String),

#[error(transparent)]
Xdr(#[from] xdr::Error),
#[error(transparent)]
Address(#[from] address::Error),
}

impl FromStr for Asset {
type Err = Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
if value == "native" {
return Ok(Asset(xdr::Asset::Native));
return Ok(Asset::Native);
}
let mut iter = value.splitn(2, ':');
let (Some(code), Some(issuer), None) = (iter.next(), iter.next(), iter.next()) else {
return Err(Error::CannotParseAsset(value.to_string()));
};
let issuer = issuer.parse()?;
Ok(Asset(match code.parse()? {
AssetCode::CreditAlphanum4(asset_code) => {
xdr::Asset::CreditAlphanum4(AlphaNum4 { asset_code, issuer })
}
AssetCode::CreditAlphanum12(asset_code) => {
xdr::Asset::CreditAlphanum12(AlphaNum12 { asset_code, issuer })
}
}))
Ok(Asset::Asset(code.parse()?, issuer.parse()?))
}
}

impl From<Asset> for xdr::Asset {
fn from(builder: Asset) -> Self {
builder.0
}
}

impl From<&Asset> for xdr::Asset {
fn from(builder: &Asset) -> Self {
builder.clone().into()
impl Asset {
pub fn resolve(&self, locator: &locator::Args) -> Result<xdr::Asset, Error> {
Ok(match self {
Asset::Asset(code, issuer) => {
let issuer = issuer.resolve_muxed_account(locator, None)?.account_id();
match code.clone() {
AssetCode::CreditAlphanum4(asset_code) => {
xdr::Asset::CreditAlphanum4(AlphaNum4 { asset_code, issuer })
}
AssetCode::CreditAlphanum12(asset_code) => {
xdr::Asset::CreditAlphanum12(AlphaNum12 { asset_code, issuer })
}
}
}
Asset::Native => xdr::Asset::Native,
})
}
}
4 changes: 2 additions & 2 deletions cmd/soroban-cli/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,13 @@ pub fn is_hex_string(s: &str) -> bool {
}

pub fn contract_id_hash_from_asset(
asset: impl Into<Asset>,
asset: &Asset,
network_passphrase: &str,
) -> stellar_strkey::Contract {
let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into());
let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId {
network_id,
contract_id_preimage: ContractIdPreimage::Asset(asset.into()),
contract_id_preimage: ContractIdPreimage::Asset(asset.clone()),
});
let preimage_xdr = preimage
.to_xdr(Limits::none())
Expand Down

0 comments on commit a0c5dbb

Please sign in to comment.