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

Extract alias logic into its own implementation. #1369

Merged
merged 9 commits into from
Jun 14, 2024
107 changes: 107 additions & 0 deletions cmd/soroban-cli/src/commands/config/alias.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::{
collections::HashMap,
fs::{self, create_dir_all, OpenOptions},
io::Write,
path::{Path, PathBuf},
};

use serde::{Deserialize, Serialize};
use stellar_strkey::DecodeError;

#[derive(Serialize, Deserialize, Default)]
pub struct Data {
ids: HashMap<String, String>,
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("cannot access config dir for alias file")]
CannotAccessConfigDir,
#[error("cannot parse contract ID {0}: {1}")]
CannotParseContractId(String, DecodeError),
}

impl Data {
pub fn load(config_dir: &Path, alias: Option<&str>) -> Result<Option<Self>, Error> {
fnando marked this conversation as resolved.
Show resolved Hide resolved
let Some(alias) = alias else {
return Ok(None);
};

let path = Self::alias_path(config_dir, alias);

if !path.exists() {
return Ok(None);
}

let content = fs::read_to_string(path)?;
let data: Self = serde_json::from_str(&content).unwrap_or_default();

Ok(Some(data))
}

pub fn alias_path(config_dir: &Path, alias: &str) -> PathBuf {
let file_name = format!("{alias}.json");
config_dir.join("contract-ids").join(file_name)
}

pub fn save_contract_id(
config_dir: &Path,
contract_id: &str,
alias: Option<&String>,
network_passphrase: &str,
) -> Result<(), Error> {
let Some(alias) = alias else {
return Ok(());
};

let path = Self::alias_path(config_dir, alias);
let dir = path.parent().ok_or(Error::CannotAccessConfigDir)?;

create_dir_all(dir).map_err(|_| Error::CannotAccessConfigDir)?;

let content = fs::read_to_string(&path).unwrap_or_default();
let mut data: Self = serde_json::from_str(&content).unwrap_or_default();

let mut to_file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(path)?;

data.ids
.insert(network_passphrase.into(), contract_id.into());

let content = serde_json::to_string(&data)?;

Ok(to_file.write_all(content.as_bytes())?)
}

pub fn get_contract_id(
alias: &str,
config_dir: &Path,
network_passphrase: &str,
) -> Result<Option<String>, Error> {
let Some(alias_data) = Self::load(config_dir, Some(alias))? else {
return Ok(None);
};

Ok(alias_data.ids.get(network_passphrase).cloned())
}

pub fn load_contract_id_or_default(
alias_or_contract_id: &str,
config_dir: &Path,
network_passphrase: &str,
) -> Result<[u8; 32], Error> {
let contract_id =
Self::get_contract_id(alias_or_contract_id, config_dir, network_passphrase)?
.unwrap_or_else(|| alias_or_contract_id.to_string());

soroban_spec_tools::utils::contract_id_from_str(&contract_id)
.map_err(|e| Error::CannotParseContractId(contract_id.clone(), e))
}
}
1 change: 1 addition & 0 deletions cmd/soroban-cli/src/commands/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use self::{network::Network, secret::Secret};

use super::{keys, network};

pub mod alias;
pub mod data;
pub mod locator;
pub mod secret;
Expand Down
58 changes: 10 additions & 48 deletions cmd/soroban-cli/src/commands/contract/deploy/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use std::array::TryFromSliceError;
use std::fmt::Debug;
use std::fs::{self, create_dir_all};
use std::io::Write;
use std::num::ParseIntError;
use std::path::PathBuf;
use std::{array::TryFromSliceError, fs::OpenOptions};

use clap::{arg, command, Parser};
use rand::Rng;
Expand All @@ -18,9 +15,8 @@ use soroban_env_host::{
HostError,
};

use crate::commands::contract::AliasData;
use crate::commands::{
config::data,
config::{alias, data},
contract::{self, id::wasm::get_contract_id},
global, network,
txn_result::{TxnEnvelopeResult, TxnResult},
Expand Down Expand Up @@ -108,16 +104,12 @@ pub enum Error {
Network(#[from] network::Error),
#[error(transparent)]
Wasm(#[from] wasm::Error),
#[error("cannot access config dir for alias file")]
CannotAccessConfigDir,
#[error(
"alias must be 1-30 chars long, and have only letters, numbers, underscores and dashes"
)]
InvalidAliasFormat { alias: String },
#[error(transparent)]
JsonSerialization(#[from] serde_json::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
Alias(#[from] alias::Error),
}

impl Cmd {
Expand All @@ -128,7 +120,13 @@ impl Cmd {
match res {
TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?),
TxnEnvelopeResult::Res(contract) => {
self.save_contract_id(&contract)?;
alias::Data::save_contract_id(
fnando marked this conversation as resolved.
Show resolved Hide resolved
&self.config.config_dir()?,
&contract,
self.alias.as_ref(),
leighmcculloch marked this conversation as resolved.
Show resolved Hide resolved
&self.config.get_network()?.network_passphrase,
)?;

println!("{contract}");
}
}
Expand All @@ -149,42 +147,6 @@ impl Cmd {
None => Ok(()),
}
}

fn alias_path_for(&self, alias: &str) -> Result<PathBuf, Error> {
let config_dir = self.config.config_dir()?;
let file_name = format!("{alias}.json");

Ok(config_dir.join("contract-ids").join(file_name))
}

fn save_contract_id(&self, contract: &String) -> Result<(), Error> {
let Some(alias) = &self.alias else {
return Ok(());
};

let file_path = self.alias_path_for(alias)?;
let dir = file_path.parent().ok_or(Error::CannotAccessConfigDir)?;

create_dir_all(dir).map_err(|_| Error::CannotAccessConfigDir)?;

let content = fs::read_to_string(&file_path).unwrap_or_default();
let mut data: AliasData = serde_json::from_str(&content).unwrap_or_default();

let mut to_file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(file_path)?;

data.ids.insert(
self.config.get_network()?.network_passphrase,
contract.into(),
);

let content = serde_json::to_string(&data)?;

Ok(to_file.write_all(content.as_bytes())?)
}
}

#[async_trait::async_trait]
Expand Down
53 changes: 7 additions & 46 deletions cmd/soroban-cli/src/commands/contract/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ use soroban_env_host::xdr::{
AccountEntry, AccountEntryExt, AccountId, DiagnosticEvent, Thresholds,
};
use soroban_spec::read::FromWasmError;
use stellar_strkey::DecodeError;

use super::super::{
config::{self, locator},
events,
};
use super::AliasData;
use crate::commands::config::alias;
use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult};
use crate::commands::NetworkRunnable;
use crate::get_spec::{self, get_remote_contract_spec};
Expand Down Expand Up @@ -98,8 +97,6 @@ pub enum Error {
filepath: std::path::PathBuf,
error: events::Error,
},
#[error("cannot parse contract ID {0}: {1}")]
CannotParseContractId(String, DecodeError),
#[error("function {0} was not found in the contract")]
FunctionNotFoundInContractSpec(String),
#[error("parsing contract spec: {0}")]
Expand Down Expand Up @@ -153,12 +150,8 @@ pub enum Error {
Network(#[from] network::Error),
#[error(transparent)]
GetSpecError(#[from] get_spec::Error),
#[error("unable to read alias file")]
UnableToReadAliasFile,
#[error("alias file not found")]
NoAliasFileFound,
#[error(transparent)]
JsonDeserialization(#[from] serde_json::Error),
Alias(#[from] alias::Error),
}

impl From<Infallible> for Error {
Expand Down Expand Up @@ -310,42 +303,6 @@ impl Cmd {
}
}

impl Cmd {
fn contract_id(&self) -> Result<[u8; 32], Error> {
let contract_id: String = match self.load_contract_id() {
Ok(Some(id)) => id.to_string(),
_ => self.contract_id.clone(),
};

soroban_spec_tools::utils::contract_id_from_str(&contract_id)
.map_err(|e| Error::CannotParseContractId(contract_id.clone(), e))
}

fn alias_path(&self) -> Result<PathBuf, Error> {
let config_dir = self.config.config_dir()?;
let file_name = format!("{}.json", self.contract_id);

Ok(config_dir.join("contract-ids").join(file_name))
}

fn load_contract_id(&self) -> Result<Option<String>, Error> {
let network = &self.config.get_network()?.network_passphrase;
let file_path = self.alias_path()?;

if !file_path.exists() {
return Ok(None);
}

let content = fs::read_to_string(file_path)?;
let data: AliasData = serde_json::from_str(&content)?;

match data.ids.get(network) {
Some(id) => Ok(Some(id.into())),
_ => Ok(None),
}
}
}

#[async_trait::async_trait]
impl NetworkRunnable for Cmd {
type Error = Error;
Expand All @@ -359,7 +316,11 @@ impl NetworkRunnable for Cmd {
let unwrap_config = config.unwrap_or(&self.config);
let network = unwrap_config.get_network()?;
tracing::trace!(?network);
let contract_id = self.contract_id()?;
let contract_id = alias::Data::load_contract_id_or_default(
fnando marked this conversation as resolved.
Show resolved Hide resolved
&self.contract_id,
&self.config.config_dir()?,
&network.network_passphrase,
)?;
let spec_entries = self.spec_entries()?;
if let Some(spec_entries) = &spec_entries {
// For testing wasm arg parsing
Expand Down
9 changes: 0 additions & 9 deletions cmd/soroban-cli/src/commands/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ pub mod optimize;
pub mod read;
pub mod restore;

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::commands::global;

#[derive(Debug, clap::Subcommand)]
Expand Down Expand Up @@ -169,8 +165,3 @@ pub enum SpecOutput {
/// Pretty print of contract spec entries
Docs,
}

#[derive(Serialize, Deserialize, Default)]
pub struct AliasData {
ids: HashMap<String, String>,
}
Loading