o2-deploy 0.1.20-rc

Contract deployment logic for Fuel O2 exchange
Documentation
use clap::Parser;
use fuels::{
    accounts::{
        provider::Provider,
        signers::private_key::PrivateKeySigner,
        wallet::Wallet,
    },
    crypto::SecretKey,
};
use o2_deploy::{
    DeployParams,
    MarketsConfigPartial,
    load_config_from_file,
};
use std::str::FromStr;

/// CLI for deploying O2 exchange contracts to Fuel.
#[derive(Parser)]
#[command(name = "o2-deploy", about = "Deploy O2 exchange contracts")]
struct Cli {
    /// Hex-encoded private key for signing deploy transactions.
    #[arg(long, env = "DEPLOY_KEY")]
    deploy_key: String,

    /// Fuel RPC URL for sending transactions.
    #[arg(
        long = "fuel-rpc",
        env = "FUEL_RPC",
        default_value = "http://127.0.0.1:4000"
    )]
    fuel_rpc: url::Url,

    /// Path to the deploy config JSON file.
    #[arg(long, env = "DEPLOY_CONFIG", default_value = "./deploy_config.json")]
    deploy_config: String,

    /// Output file path for the deploy result JSON.
    #[arg(long, env = "OUTPUT_FILE")]
    output: Option<String>,

    /// Deploy a whitelist contract.
    #[arg(long, env = "DEPLOY_WHITELIST", default_value = "false")]
    deploy_whitelist: bool,

    /// Deploy a blacklist contract.
    #[arg(long, env = "DEPLOY_BLACKLIST", default_value = "true")]
    deploy_blacklist: bool,

    /// If set, will attempt to upgrade bytecode of deployed contracts.
    #[arg(long, env, default_value = "false")]
    upgrade_bytecode: bool,

    /// Transfer proxy ownership to this address after deploy/upgrade.
    #[arg(long, env = "DEPLOY_NEW_PROXY_OWNER")]
    new_proxy_owner: Option<String>,

    /// Transfer non-proxy contract ownership to this address after deploy/upgrade.
    #[arg(long, env = "DEPLOY_NEW_CONTRACT_OWNER")]
    new_contract_owner: Option<String>,
}

fn parse_address(s: &str) -> anyhow::Result<fuels::types::Address> {
    let trimmed = s.strip_prefix("0x").unwrap_or(s);
    let bytes = hex::decode(trimmed)?;
    Ok(fuels::types::Address::new(
        bytes
            .try_into()
            .map_err(|_| anyhow::anyhow!("Invalid address length"))?,
    ))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    let cli = Cli::parse();

    let secret_key = SecretKey::from_str(&cli.deploy_key)
        .map_err(|e| anyhow::anyhow!("Failed to parse deploy key: {e}"))?;
    let provider = Provider::connect(cli.fuel_rpc.as_str()).await?;
    let wallet = Wallet::new(PrivateKeySigner::new(secret_key), provider);

    let deploy_config: MarketsConfigPartial = load_config_from_file(&cli.deploy_config)?;

    let new_proxy_owner = cli
        .new_proxy_owner
        .as_deref()
        .map(parse_address)
        .transpose()?;
    let new_contract_owner = cli
        .new_contract_owner
        .as_deref()
        .map(parse_address)
        .transpose()?;

    let params = DeployParams {
        deploy_config,
        output: cli.output,
        deploy_whitelist: cli.deploy_whitelist,
        deploy_blacklist: cli.deploy_blacklist,
        upgrade_bytecode: cli.upgrade_bytecode,
        new_proxy_owner,
        new_contract_owner,
    };

    let result = o2_deploy::deploy(wallet, params).await?;
    println!("{}", serde_json::to_string_pretty(&result)?);

    Ok(())
}