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
57 changes: 10 additions & 47 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,7 +15,7 @@ use soroban_env_host::{
HostError,
};

use crate::commands::contract::AliasData;
use crate::commands::contract::{AliasData, AliasDataError};
use crate::commands::{
config::data,
contract::{self, id::wasm::get_contract_id},
Expand Down Expand Up @@ -108,16 +105,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),
AliasData(#[from] AliasDataError),
}

impl Cmd {
Expand All @@ -128,7 +121,13 @@ impl Cmd {
match res {
TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?),
TxnEnvelopeResult::Res(contract) => {
self.save_contract_id(&contract)?;
AliasData::save_contract_id(
&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 +148,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 super::{AliasData, AliasDataError};
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),
AliasData(#[from] AliasDataError),
}

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 = AliasData::load_contract_id_or_default(
&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
106 changes: 105 additions & 1 deletion cmd/soroban-cli/src/commands/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ pub mod optimize;
pub mod read;
pub mod restore;

use std::collections::HashMap;
use std::{
collections::HashMap,
fs::{self, create_dir_all, OpenOptions},
io::Write,
path::{Path, PathBuf},
};

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

use crate::commands::global;

Expand Down Expand Up @@ -174,3 +180,101 @@ pub enum SpecOutput {
pub struct AliasData {
ids: HashMap<String, String>,
}

#[derive(thiserror::Error, Debug)]
pub enum AliasDataError {
#[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 AliasData {
pub fn load(config_dir: &Path, alias: Option<&str>) -> Result<Option<Self>, AliasDataError> {
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<(), AliasDataError> {
let Some(alias) = alias else {
return Ok(());
};

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

create_dir_all(dir).map_err(|_| AliasDataError::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>, AliasDataError> {
let alias_data = Self::load(config_dir, Some(alias))?;
let Some(alias_data) = alias_data else {
return Ok(None);
};

match alias_data.ids.get(network_passphrase) {
Some(id) => Ok(Some(id.into())),
_ => Ok(None),
}
}
fnando marked this conversation as resolved.
Show resolved Hide resolved

pub fn load_contract_id_or_default(
alias: &str,
fnando marked this conversation as resolved.
Show resolved Hide resolved
config_dir: &Path,
network_passphrase: &str,
) -> Result<[u8; 32], AliasDataError> {
let contract_id = match Self::get_contract_id(alias, config_dir, network_passphrase)? {
None => alias.to_string(),
Some(id) => id,
};
fnando marked this conversation as resolved.
Show resolved Hide resolved

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