Skip to content

Commit

Permalink
Extract alias logic into its own implementation. (#1369)
Browse files Browse the repository at this point in the history
* Extract alias logic into its own implementation.

* Apply pr feedback.

* Name the argument explicitly.

* Do not use an Option on Data::load().

* Move functions to config instead.

* Remove guard from function and bring it to the caller.
  • Loading branch information
fnando authored Jun 14, 2024
1 parent fb6527f commit 52dce61
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 103 deletions.
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(
&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>,
}

0 comments on commit 52dce61

Please sign in to comment.