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
103 changes: 103 additions & 0 deletions cmd/soroban-cli/src/commands/config/alias.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::{
collections::HashMap,
fs::{self, create_dir_all, OpenOptions},
io::Write,
path::PathBuf,
};

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

use crate::commands::config;

use super::Args;

#[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),
#[error(transparent)]
Config(#[from] config::Error),
}

impl Args {
fn load(&self, alias: &str) -> Result<Option<Data>, Error> {
let path = self.alias_path(alias)?;

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

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

Ok(Some(data))
}

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

pub fn save_contract_id(&self, contract_id: &str, alias: &str) -> Result<(), Error> {
let path = self.alias_path(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: Data = serde_json::from_str(&content).unwrap_or_default();

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

let network = self.get_network()?;

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

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

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

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

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

pub fn resolve_contract_id(
fnando marked this conversation as resolved.
Show resolved Hide resolved
&self,
alias_or_contract_id: &str,
network_passphrase: &str,
) -> Result<[u8; 32], Error> {
let contract_id = self
.get_contract_id(alias_or_contract_id, 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 @@ -15,6 +15,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
55 changes: 7 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,10 @@ impl Cmd {
match res {
TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?),
TxnEnvelopeResult::Res(contract) => {
self.save_contract_id(&contract)?;
if let Some(alias) = self.alias.clone() {
self.config.save_contract_id(&contract, &alias)?;
}

println!("{contract}");
}
}
Expand All @@ -149,42 +144,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
51 changes: 5 additions & 46 deletions cmd/soroban-cli/src/commands/contract/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ use soroban_env_host::{
};

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 @@ -95,8 +94,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 @@ -150,12 +147,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 @@ -307,42 +300,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 @@ -356,7 +313,9 @@ impl NetworkRunnable for Cmd {
let config = config.unwrap_or(&self.config);
let network = config.get_network()?;
tracing::trace!(?network);
let contract_id = self.contract_id()?;
let contract_id = self
.config
.resolve_contract_id(&self.contract_id, &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