entropycli 1.0.8

Entropy CLI for developing using the Entropic Labs Entropy Beacon
use std::{path::PathBuf, str::FromStr, time::Duration};

use cosmrs::cosmwasm::{
    AccessConfig, AccessType, MsgInstantiateContract, MsgStoreCode, MsgStoreCodeResponse,
};
use cosmwasm_std::{Decimal, Uint128};
use indicatif::ProgressBar;

use crate::{
    commands::beacon::project_config::ProjectConfig,
    cosmos::wallet::Wallet,
    utils::wasm_fetch::{download_file, fetch_release_url},
    utils::CLITheme,
};

use entropy_beacon_cosmos::{beacon::BEACON_BASE_GAS, msg::InstantiateMsg};

#[allow(clippy::too_many_lines)]
pub async fn deploy_beacon(
    network: Option<String>,
    wallet: Option<String>,
    wasm_file: Option<impl Into<PathBuf>>,
    config: &mut ProjectConfig,
) {
    let theme = CLITheme::default();
    let (network_name, network) = match config.get_network(&network) {
        Ok((network_name, Some(network))) => (network_name, network),
        Ok((name, None)) => {
            println!(
                "{} {} {}",
                theme.error.apply_to("Network"),
                theme.highlight.apply_to(name),
                theme.error.apply_to("not found in config file.")
            );
            std::process::exit(1);
        }
        Err(_) => {
            println!(
                "{}",
                theme.error.apply_to("No network specified. Please specify a network with the --network flag or set a default network in the config file.")
            );
            std::process::exit(1);
        }
    };

    let wallet_name = wallet.or_else(|| config.default_wallet.clone()).unwrap_or_else(|| {
            println!(
                "{}",
                theme.error.apply_to("No wallet specified. Please specify a wallet with the --wallet flag or set a default wallet in the config file.")
            );
            std::process::exit(1);
        });

    let wallet = config
        .wallets
        .as_ref()
        .and_then(|wallets| wallets.get(&wallet_name));

    let wallet = match wallet {
        Some(Some(mnemonic)) => Wallet::new(mnemonic.clone(), network.clone()),
        Some(None) => {
            let mnemonic = std::env::var("MNEMONIC").unwrap_or_else(|_| {
                println!(
                    "{} {} {}",
                    theme.error.apply_to("Mnemonic for wallet"),
                    theme.highlight.apply_to(&wallet_name),
                    theme
                        .error
                        .apply_to("not found in config file or MNEMONIC environment variable.")
                );
                std::process::exit(1);
            });
            Wallet::new(mnemonic, network.clone())
        }
        None => {
            println!(
                "{} {} {}",
                theme.error.apply_to("Wallet"),
                theme.highlight.apply_to(&wallet_name),
                theme.error.apply_to("not found in config file.")
            );
            std::process::exit(1);
        }
    }
    .unwrap_or_else(|e| {
        println!(
            "{} {}",
            theme.error.apply_to("Error creating wallet:"),
            theme.highlight.apply_to(e)
        );
        std::process::exit(1);
    });
    let pb = ProgressBar::new(1);
    pb.enable_steady_tick(Duration::from_millis(80));
    pb.set_style(CLITheme::spinner());
    let wasm_file = if let Some(wasm_file) = wasm_file {
        wasm_file.into()
    } else {
        pb.set_message("Fetching latest release...");
        let wasm_download_url = fetch_release_url().await.unwrap_or_else(|err| {
            pb.set_style(CLITheme::failed_spinner());
            pb.set_prefix("");
            pb.finish_with_message(format!("{} {}", "Error fetching latest release:", err));
            std::process::exit(1);
        });
        pb.set_message("Downloading latest release...");
        let download_path = std::env::temp_dir().join("beacon.wasm");
        download_file(wasm_download_url, download_path)
            .await
            .unwrap_or_else(|err| {
                pb.set_style(CLITheme::failed_spinner());
                pb.set_prefix("");
                pb.finish_with_message(format!("{} {}", "Error downloading latest release:", err));
                std::process::exit(1);
            })
    };
    pb.set_message("Uploading beacon contract...");

    let wasm_bytes = std::fs::read(wasm_file).unwrap_or_else(|err| {
        pb.set_style(CLITheme::failed_spinner());
        pb.set_prefix("");
        pb.finish_with_message(format!("{} {}", "Error reading WASM file:", err));
        std::process::exit(1);
    });

    let msg = MsgStoreCode {
        sender: wallet.address.clone(),
        wasm_byte_code: wasm_bytes,
        instantiate_permission: Some(AccessConfig {
            permission: AccessType::OnlyAddress,
            address: wallet.address.clone(),
        }),
    };

    let hash = wallet
        .broadcast_msg(msg, None, None)
        .await
        .unwrap_or_else(|err| {
            pb.set_style(CLITheme::failed_spinner());
            pb.set_prefix("");
            pb.finish_with_message(format!("{} {}", "Error uploading beacon contract:", err));
            std::process::exit(1);
        });
    pb.set_message("Waiting for transaction to be included in block...");
    let res = wallet.wait_for_hash(hash).await.unwrap_or_else(|err| {
        pb.set_style(CLITheme::failed_spinner());
        pb.set_prefix("");
        pb.finish_with_message(format!(
            "{} {}",
            "Error waiting for transaction to be included in block:", err
        ));
        std::process::exit(1);
    });

    let res = MsgStoreCodeResponse::try_from(res).unwrap_or_else(|err| {
        pb.set_style(CLITheme::failed_spinner());
        pb.set_prefix("");
        pb.finish_with_message(format!(
            "{} {}",
            "Error decoding transaction response:", err
        ));
        std::process::exit(1);
    });

    pb.set_message("Instantiating beacon contract in test mode...");

    let subsidized_callbacks = wallet.network.subsidized_callbacks.unwrap_or(false);

    let gas_price =
        Decimal::from_str(wallet.network.gas_info.gas_price.to_string().as_str()).unwrap();

    let protocol_fee = if subsidized_callbacks {
        Uint128::zero()
    } else {
        Uint128::from(BEACON_BASE_GAS) * gas_price
    };
    #[allow(clippy::cast_possible_truncation)]
    let instantiate_msg = InstantiateMsg {
        whitelist_deposit_amt: Uint128::zero(),
        refund_increment_amt: Uint128::zero(),
        key_activation_delay: 0,
        protocol_fee: protocol_fee.u128() as u64,
        submitter_share: 100,
        native_denom: wallet.network.gas_info.denom.clone(),
        whitelisted_keys: vec![],
        belief_gas_price: gas_price,
        permissioned: false,
        test_mode: true,
        subsidize_callbacks: wallet.network.subsidized_callbacks.unwrap_or(false),
    };

    let msg = MsgInstantiateContract {
        sender: wallet.address.clone(),
        admin: Some(wallet.address.clone()),
        code_id: res.code_id,
        label: Some("Entropy Beacon (MOCK)".to_string()),
        msg: serde_json::to_string(&instantiate_msg)
            .unwrap()
            .into_bytes(),
        funds: vec![],
    };

    let hash = wallet
        .broadcast_msg(msg, None, None)
        .await
        .unwrap_or_else(|err| {
            pb.set_style(CLITheme::failed_spinner());
            pb.set_prefix("");
            pb.finish_with_message(format!(
                "{} {}",
                "Error instantiating mock beacon contract:", err
            ));
            std::process::exit(1);
        });

    pb.set_message("Waiting for transaction to be included in block...");
    let res = wallet.wait_for_hash(hash).await.unwrap_or_else(|err| {
        pb.set_style(CLITheme::failed_spinner());
        pb.set_prefix("");
        pb.finish_with_message(format!(
            "{} {}",
            "Error waiting for transaction to be included in block:", err
        ));
        std::process::exit(1);
    });

    let deployed_address = res.logs[0]
        .events
        .iter()
        .find(|e| e.type_ == "instantiate")
        .and_then(|e| e.attributes.get("_contract_address"))
        .unwrap_or_else(|| {
            pb.set_style(CLITheme::failed_spinner());
            pb.set_prefix("");
            pb.finish_with_message("Error decoding transaction response.");
            std::process::exit(1);
        });

    pb.set_style(CLITheme::success_spinner());
    pb.set_prefix("");
    pb.finish_with_message(format!(
        "{} {}\n",
        "Mock beacon contract instantiated at address:",
        theme.highlight.apply_to(deployed_address)
    ));
    config
        .get_network_mut(&Some(network_name))
        .unwrap()
        .1
        .unwrap()
        .deployed_beacon_address = Some(deployed_address.to_string());
}