Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This repository contains a helper to aid in generating the contents of Software

## Usage

<!-- TODO: this whole thing needs to be adjusted to avoid the specific steps for the outdated Evmos implementation -->

**NOTE:** Because the Commonwealth integration is not yet implemented (an API key is already requested)
it is not possible to run all of this in one go.

Expand Down Expand Up @@ -53,15 +55,17 @@ Options:
The tool is using OpenAI's LLMs to generate a summary of the changes in the release(s).
To use this feature, ensure that you run the binary in an environment where `OPENAI_API_KEY` is set.

- **Configured `.evmosd` Home**
- **Configured `.appd` Home**

To generate a shell command that can be instantly used,
the tool is checking `$HOME/.evmosd` for the configured keyring.
the tool is checking the given home directory for the node
to extract the configured keyring.
This keyring is then used to get the list of available keys.
It is checked, which of those keys hold a balance on the selected network (mainnet/testnet)
and lets the user select the one to execute the command with if there are multiple.
To use this feature, ensure that you have your `$HOME/.evmosd` configuration set
so that the configured keyring holds your mainnet or testnet keys.

To use this feature, ensure that you have your `$HOME/.appd` configuration set
so that the configured keyring holds your desired keys.

## Installation

Expand Down
8 changes: 5 additions & 3 deletions src/evmosd.rs → src/appd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use crate::errors::ConfigError;
use serde::Deserialize;
use std::path::Path;

/// The client configuration for the `evmosd` node.
#[derive(Clone, Deserialize)]
/// The client configuration for the used node binary.
///
/// TODO: check if this can be removed
#[derive(Clone, Default, Deserialize)]
pub struct ClientConfig {
#[serde(rename = "chain-id")]
pub chain_id: String,
Expand All @@ -15,7 +17,7 @@ pub struct ClientConfig {
pub broadcast_mode: String,
}

/// This method returns the client configuration for the `evmosd` node.
/// This method returns the client configuration for the used node binary.
pub fn get_client_config(path: &Path) -> Result<ClientConfig, ConfigError> {
Ok(toml::from_str::<ClientConfig>(
std::fs::read_to_string(path)?.as_str(),
Expand Down
32 changes: 12 additions & 20 deletions src/balance.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use crate::errors::KeysError;
use crate::http::get_body;
use crate::network::{get_denom, Network};
use crate::{config::NetworkConfig, errors::KeysError, http::get_body};
use serde::{Deserialize, Serialize};
use url::Url;

const BALANCES_ENDPOINT: &str = "cosmos/bank/v1beta1/balances/";

Expand All @@ -20,15 +17,11 @@ struct Balance {
}

/// Checks if a given address has a non-zero balance on the given network.
pub async fn has_balance(
address: &str,
network: &Network,
base_url: &Url,
) -> Result<bool, KeysError> {
let native_denom = get_denom(*network);
let balances_endpoint = base_url
pub async fn has_balance(address: &str, network_config: &NetworkConfig) -> Result<bool, KeysError> {
let balances_endpoint = network_config
.rest
.join(BALANCES_ENDPOINT)?
.join(format!("{}/by_denom?denom={}", address, native_denom).as_str())?;
.join(format!("{}/by_denom?denom={}", address, network_config.fee_denom).as_str())?;

let balance: BalanceResponse =
serde_json::from_str(get_body(balances_endpoint).await?.as_str())?;
Expand All @@ -39,9 +32,8 @@ pub async fn has_balance(
#[cfg(test)]
mod tests {
use super::*;
use crate::network::Network;
use serde_json::Value;
use std::str::FromStr;
use url::Url;
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};

Expand Down Expand Up @@ -72,15 +64,15 @@ mod tests {

#[tokio::test]
async fn test_has_balance() {
let network = Network::LocalNode;
let mock_server = setup_mock_api().await;
let mock_path =
Url::from_str(mock_server.uri().as_str()).expect("failed to parse mock server uri");

let network_config = NetworkConfig {
rest: Url::parse(mock_server.uri().as_str()).unwrap(),
..NetworkConfig::default()
};

assert!(
has_balance(TEST_ADDRESS, &network, &mock_path)
.await
.unwrap(),
has_balance(TEST_ADDRESS, &network_config).await.unwrap(),
"expected a non-zero balance"
);
}
Expand Down
29 changes: 7 additions & 22 deletions src/block.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::errors::BlockError;
use crate::{http::get_body, network::Network};
use crate::{errors::BlockError, http::get_body};
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
use regex::Regex;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -64,11 +63,7 @@ pub fn round_to_nearest_500(height: u64) -> u64 {

/// Gets the latest block from the Evmos network.
async fn get_latest_block(base_url: &Url) -> Result<Block, BlockError> {
process_block_body(
get_body(
base_url.join(LATEST_BLOCK_ENDPOINT)?
).await?
)
process_block_body(get_body(base_url.join(LATEST_BLOCK_ENDPOINT)?).await?)
}

/// Gets the block at the given height from the Evmos network.
Expand All @@ -77,22 +72,12 @@ async fn get_block(base_url: &Url, height: u64) -> Result<Block, BlockError> {
get_body(
base_url
.join(BLOCKS_ENDPOINT)?
.join(height.to_string().as_str())?
).await?
.join(height.to_string().as_str())?,
)
.await?,
)
}

/// Returns the appropriate REST provider for the given network.
pub fn get_rest_provider(network: Network) -> Url {
let base_url = match network {
Network::LocalNode => "http://localhost:1317",
Network::Mainnet => "https://rest.evmos.lava.build",
Network::Testnet => "https://rest.evmos-testnet.lava.build",
};

Url::parse(base_url).unwrap()
}

/// Processes the block body.
fn process_block_body(body: String) -> Result<Block, BlockError> {
let body: BlockResponse = serde_json::from_str(&body)?;
Expand Down Expand Up @@ -200,7 +185,7 @@ mod tests {
assert_eq!(block.height, 18748834, "expected a different block height");
assert_eq!(
block.time,
Utc.with_ymd_and_hms(2024, 01, 05, 04, 39, 20).unwrap(),
Utc.with_ymd_and_hms(2024, 1, 5, 4, 39, 20).unwrap(),
"expected a different block time",
);
}
Expand All @@ -216,7 +201,7 @@ mod tests {
assert_eq!(block.height, 18500000, "expected a different block height");
assert_eq!(
block.time,
Utc.with_ymd_and_hms(2023, 11, 07, 02, 41, 36).unwrap(),
Utc.with_ymd_and_hms(2023, 11, 7, 2, 41, 36).unwrap(),
"expected a different block time",
);
}
Expand Down
24 changes: 12 additions & 12 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use crate::evmosd::get_client_config;
use crate::appd::get_client_config;
use crate::{
command,
commonwealth::check_commonwealth_link,
errors::{CommandError, ProposalError},
helper::{get_helper_from_inputs, get_helper_from_json},
inputs, keys,
llm::OpenAIModel,
network::Network,
proposal, utils,
};
use clap::{Args, Parser, Subcommand};
Expand Down Expand Up @@ -35,6 +33,8 @@ pub enum SubCommand {
#[derive(Debug, Clone, Args)]
pub struct GenerateProposalArgs {
/// The LLM model to use for summarizing the release notes.
///
/// TODO: enable using e.g. claude or cursor-agent in headless mode
#[clap(short, long, default_value_t = OpenAIModel::Gpt4o)]
model: OpenAIModel,
}
Expand All @@ -50,6 +50,9 @@ pub struct GenerateCommandArgs {
}

/// Runs the logic for the `generate-command` sub-command.
///
/// TODO: this should be updated to use the new Cosmos SDK v50 based approach of the
/// `MsgSoftwareUpgrade` from the `x/upgrade` module.
pub async fn generate_command(args: GenerateCommandArgs) -> Result<(), CommandError> {
let helper_config_path = match args.config {
Some(config_file_name) => config_file_name,
Expand All @@ -59,23 +62,20 @@ pub async fn generate_command(args: GenerateCommandArgs) -> Result<(), CommandEr
let mut upgrade_helper = get_helper_from_json(&helper_config_path)?;
let client_config = get_client_config(
upgrade_helper
.evmosd_home
.network_config
.path
.join("config/client.toml")
.as_path(),
)?;

if upgrade_helper.network == Network::Mainnet {
// TODO: remove commonwealth logic.
if upgrade_helper.network_config.name == "Mainnet" {
let commonwealth_link = inputs::choose_commonwealth_link().await?;
check_commonwealth_link(&commonwealth_link, &upgrade_helper).await?;
upgrade_helper.commonwealth_link = Some(commonwealth_link.clone());
}

let keys_with_balances = keys::get_keys_with_balances(keys::FilterKeysConfig {
config: client_config.clone(),
home: upgrade_helper.evmosd_home.clone(),
network: upgrade_helper.network,
})
.await?;
let keys_with_balances =
keys::get_keys_with_balances(&client_config, &upgrade_helper.network_config).await?;
let key = inputs::get_key(keys_with_balances)?;

// Prepare command to submit proposal
Expand Down
72 changes: 25 additions & 47 deletions src/command.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::appd::ClientConfig;
use crate::errors::PrepareError;
use crate::evmosd::ClientConfig;
use crate::helper::UpgradeHelper;
use crate::network::{get_denom, Network};
use crate::release::{get_asset_string, get_instance, get_release};
use handlebars::{no_escape, Handlebars};
use serde_json::json;
Expand All @@ -14,13 +13,15 @@ pub async fn prepare_command(
key: &str,
) -> Result<String, PrepareError> {
let mut description = get_description_from_md(&helper.proposal_file_name)?;
let release = get_release(&get_instance(), helper.target_version.as_str()).await?;
let release = get_release(
&get_instance(),
helper.upgrade_config.target_version.as_str(),
)
.await?;
let assets = get_asset_string(&release).await?;
let denom = get_denom(helper.network);

// TODO: get fees from network conditions?
let fees = format!("10000000000{}", denom);
let tm_rpc = get_rpc_url(helper.network);
let fees = format!("10000000000{}", helper.network_config.fee_denom);

let mut handlebars = Handlebars::new();
handlebars.set_strict_mode(true);
Expand All @@ -47,17 +48,18 @@ pub async fn prepare_command(

let data = json!({
"assets": assets,
"chain_id": helper.chain_id,
"bin": helper.network_config.binary,
"chain_id": helper.network_config.chain_id,
"commonwealth": helper.commonwealth_link,
"description": description.replace('\n', "\\n"), // NOTE: this is necessary to not print the actual new lines when rendering the template.
"fees": fees,
"height": helper.upgrade_height,
"home": helper.evmosd_home,
"height": helper.upgrade_config.upgrade_height,
"home": helper.network_config.path,
"key": key,
"keyring": client_config.keyring_backend,
"title": helper.proposal_name,
"tm_rpc": tm_rpc,
"version": helper.target_version,
"title": helper.upgrade_config.upgrade_name,
"tm_rpc": helper.network_config.cosmos_rpc,
"version": helper.upgrade_config.target_version,
});

let command = handlebars.render("command", &data)?;
Expand All @@ -70,33 +72,24 @@ fn get_description_from_md(filename: &str) -> io::Result<String> {
std::fs::read_to_string(filename)
}

/// Returns the RPC URL based on the network.
fn get_rpc_url(network: Network) -> String {
match network {
Network::Mainnet => "https://tm.evmos.lava.build:443".to_string(),
Network::Testnet => "https://tm.evmos-testnet.lava.build:443".to_string(),
Network::LocalNode => "http://localhost:26657".to_string(),
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::network::Network;
use crate::config::{NetworkConfig, UpgradeConfig};
use chrono::Utc;
use std::path::PathBuf;

#[tokio::test]
async fn test_prepare_command() {
let helper = UpgradeHelper::new(
PathBuf::from("./.evmosd"),
Network::Testnet,
"v13.0.0",
"v14.0.0",
Utc::now(),
60,
"",
);
let nc = NetworkConfig::default();
let uc = UpgradeConfig {
previous_version: "v13.0.0".to_string(),
target_version: "v14.0.0".to_string(),
upgrade_time: Utc::now(),
upgrade_height: 60,
..UpgradeConfig::default()
};

let helper = UpgradeHelper::new(&nc, &uc);

let client_config = ClientConfig {
chain_id: "evmos_9000-4".to_string(),
Expand Down Expand Up @@ -141,19 +134,4 @@ mod tests {
"description should be err, but is not"
);
}

#[test]
fn test_get_rpc_url() {
let rpc = get_rpc_url(Network::Mainnet);
assert_eq!(rpc, "https://tm.evmos.lava.build:443", "rpc does not match");

let rpc = get_rpc_url(Network::Testnet);
assert_eq!(
rpc, "https://tm.evmos-testnet.lava.build:443",
"rpc does not match"
);

let rpc = get_rpc_url(Network::LocalNode);
assert_eq!(rpc, "http://localhost:26657", "rpc does not match");
}
}
Loading
Loading