From e333aca8a1be7d7f54a2566b08d92791c608333f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 11 Oct 2024 17:34:37 +0100 Subject: [PATCH] introduced specialised subcommand to importer-cli to import mixnet/vesting contracts --- Cargo.lock | 2 + .../importer-cli/Cargo.toml | 3 + .../initialise_mixnet_vesting_with_states.rs | 161 +++++++++++++++++- .../importer-cli/src/commands/mod.rs | 10 ++ .../importer-cli/src/helpers.rs | 85 ++++++++- 5 files changed, 257 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00d23fa76e..09d749a7b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3315,8 +3315,10 @@ dependencies = [ "dirs 5.0.1", "importer-contract", "nym-bin-common", + "nym-mixnet-contract-common", "nym-network-defaults", "nym-validator-client", + "nym-vesting-contract-common", "serde", "serde_json", "tokio", diff --git a/tools/internal/contract-state-importer/importer-cli/Cargo.toml b/tools/internal/contract-state-importer/importer-cli/Cargo.toml index 45a2302209..db708db2e8 100644 --- a/tools/internal/contract-state-importer/importer-cli/Cargo.toml +++ b/tools/internal/contract-state-importer/importer-cli/Cargo.toml @@ -24,3 +24,6 @@ importer-contract = { path = "../importer-contract" } nym-validator-client = { path = "../../../../common/client-libs/validator-client" } nym-bin-common = { path = "../../../../common/bin-common", features = ["basic_tracing"] } nym-network-defaults = { path = "../../../../common/network-defaults" } + +nym-mixnet-contract-common = { path = "../../../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-vesting-contract-common = { path = "../../../../common/cosmwasm-smart-contracts/vesting-contract" } \ No newline at end of file diff --git a/tools/internal/contract-state-importer/importer-cli/src/commands/initialise_mixnet_vesting_with_states.rs b/tools/internal/contract-state-importer/importer-cli/src/commands/initialise_mixnet_vesting_with_states.rs index 6fd33e527d..9f4d8ee349 100644 --- a/tools/internal/contract-state-importer/importer-cli/src/commands/initialise_mixnet_vesting_with_states.rs +++ b/tools/internal/contract-state-importer/importer-cli/src/commands/initialise_mixnet_vesting_with_states.rs @@ -1,4 +1,163 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use clap::ArgGroup; +use crate::commands::prepare::create_importer_contract; +use crate::commands::set_state::set_importer_state; +use crate::commands::swap_contract::swap_contract; +use crate::helpers::{mixnet_contract_path, vesting_contract_path}; +use clap::Args; +use importer_contract::ExecuteMsg; +use nym_mixnet_contract_common::{Addr, ContractState}; +use nym_validator_client::nyxd::{AccountId, CosmWasmClient}; +use nym_validator_client::DirectSigningHttpRpcNyxdClient; +use serde::Serialize; +use std::path::PathBuf; +use tracing::info; + +#[derive(Args, Clone)] +pub struct InitialiseMixnetVestingWithStatesArgs { + /// Path to the .wasm file with the importer contract + /// If not provided, the CLI will attempt to traverse the parent directories until it finds + /// "target/wasm32-unknown-unknown/release/importer_contract.wasm" + #[clap(long)] + pub importer_contract_path: Option, + + /// Path to the file containing state dump of the mixnet contract + #[clap(long)] + pub mixnet_state: PathBuf, + + /// Path to the file containing state dump of the vesting contract + #[clap(long)] + pub vesting_state: PathBuf, + + /// Path to mixnet contract that will be uploaded and applied to the imported state + /// If not provided, the CLI will attempt to traverse the parent directories until it finds + /// "target/wasm32-unknown-unknown/release/mixnet_contract.wasm" + #[clap(long)] + pub mixnet_contract_path: Option, + + /// Path to the vesting contract that will be uploaded and applied to the imported state + /// If not provided, the CLI will attempt to traverse the parent directories until it finds + /// "target/wasm32-unknown-unknown/release/vesting_contract.wasm" + #[clap(long)] + pub vesting_contract_path: Option, +} + +pub async fn update_kv( + client: &DirectSigningHttpRpcNyxdClient, + contract: &AccountId, + key: &[u8], + value: &T, +) -> anyhow::Result<()> +where + T: Serialize, +{ + let msg = ExecuteMsg::from(vec![(key.to_vec(), serde_json::to_vec(value)?)]); + client + .execute(contract, &msg, None, "updating kv...", Vec::new()) + .await?; + Ok(()) +} + +#[allow(deprecated)] +pub async fn execute_initialise_mixnet_vesting_with_states( + args: InitialiseMixnetVestingWithStatesArgs, + client: DirectSigningHttpRpcNyxdClient, +) -> anyhow::Result<()> { + let mixnet_contract = mixnet_contract_path(args.mixnet_contract_path)?; + let vesting_contract = vesting_contract_path(args.vesting_contract_path)?; + + info!( + "using the following mixnet contract: {}", + mixnet_contract.display() + ); + info!( + "using the following vesting contract: {}", + vesting_contract.display() + ); + + let admin = client.address(); + + // 1. import mixnet state + info!("importing the mixnet contract state..."); + let mixnet_importer_address = + create_importer_contract(args.importer_contract_path.clone(), &client).await?; + set_importer_state( + args.mixnet_state, + Some(mixnet_importer_address.clone()), + &client, + ) + .await?; + + // 2. import vesting state + info!("importing the vesting contract state..."); + let vesting_importer_address = + create_importer_contract(args.importer_contract_path, &client).await?; + set_importer_state( + args.vesting_state, + Some(vesting_importer_address.clone()), + &client, + ) + .await?; + + // 3. adjust few state entries + // update vesting admin: + info!("adjusting internal contract states..."); + let new_vesting_admin = Addr::unchecked(admin.as_ref()); + update_kv( + &client, + &vesting_importer_address, + b"adm", + &new_vesting_admin, + ) + .await?; + + // update mixnet contract address in the vesting contract + let new_mixnet_contract_address = Addr::unchecked(mixnet_importer_address.as_ref()); + update_kv( + &client, + &vesting_importer_address, + b"mix", + &new_mixnet_contract_address, + ) + .await?; + + // update admins and vesting contract addresses in the mixnet contract + let raw_contract_state = client + .query_contract_raw(&mixnet_importer_address, b"state".to_vec()) + .await?; + let mut contract_state: ContractState = serde_json::from_slice(&raw_contract_state)?; + contract_state.vesting_contract_address = Addr::unchecked(vesting_importer_address.as_ref()); + contract_state.owner = Some(Addr::unchecked(admin.as_ref())); + contract_state.rewarding_validator_address = Addr::unchecked(admin.as_ref()); + update_kv(&client, &mixnet_importer_address, b"state", &contract_state).await?; + + let contract_admin = Some(Addr::unchecked(admin.as_ref())); + update_kv(&client, &mixnet_importer_address, b"admin", &contract_admin).await?; + + // 4. apply the correct contract codes + info!("swapping the mixnet contract to the correct .wasm..."); + swap_contract( + None, + Some(mixnet_contract), + Some(mixnet_importer_address.clone()), + None, + &client, + ) + .await?; + + info!("swapping the vesting contract to the correct .wasm..."); + swap_contract( + None, + Some(vesting_contract), + Some(vesting_importer_address.clone()), + None, + &client, + ) + .await?; + + info!("the contracts are ready"); + info!("MIXNET: {mixnet_importer_address} VESTING: {vesting_importer_address}"); + + Ok(()) +} diff --git a/tools/internal/contract-state-importer/importer-cli/src/commands/mod.rs b/tools/internal/contract-state-importer/importer-cli/src/commands/mod.rs index cd81aa3a14..e7dacbbea1 100644 --- a/tools/internal/contract-state-importer/importer-cli/src/commands/mod.rs +++ b/tools/internal/contract-state-importer/importer-cli/src/commands/mod.rs @@ -1,6 +1,9 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::commands::initialise_mixnet_vesting_with_states::{ + execute_initialise_mixnet_vesting_with_states, InitialiseMixnetVestingWithStatesArgs, +}; use crate::commands::initialise_with_state::{initialise_with_state, InitialiseWithStateArgs}; use crate::commands::prepare::{execute_prepare_contract, PrepareArgs}; use crate::commands::set_state::{execute_set_state, SetStateArgs}; @@ -50,6 +53,10 @@ pub(crate) enum Commands { /// Combines the functionalities of `prepare-contract`, `set-state` and `swap-contract` InitialiseWithState(InitialiseWithStateArgs), + + /// Specialised variant of `initialise-with-state` for the mixnet and vesting contracts that sets + /// internal state keys + InitialiseMixnetVestingWithStates(InitialiseMixnetVestingWithStatesArgs), } impl Cli { @@ -73,6 +80,9 @@ impl Cli { Commands::SetState(args) => execute_set_state(args, client).await, Commands::SwapContract(args) => execute_swap_contract(args, client).await, Commands::InitialiseWithState(args) => initialise_with_state(args, client).await, + Commands::InitialiseMixnetVestingWithStates(args) => { + execute_initialise_mixnet_vesting_with_states(args, client).await + } } } } diff --git a/tools/internal/contract-state-importer/importer-cli/src/helpers.rs b/tools/internal/contract-state-importer/importer-cli/src/helpers.rs index 7f876c181c..ad25324f4b 100644 --- a/tools/internal/contract-state-importer/importer-cli/src/helpers.rs +++ b/tools/internal/contract-state-importer/importer-cli/src/helpers.rs @@ -3,15 +3,63 @@ use crate::state::CachedState; use anyhow::bail; +use nym_validator_client::nyxd::cosmwasm_client::types::Model; use nym_validator_client::nyxd::AccountId; +use serde::de::DeserializeOwned; +use serde::Serialize; use std::env::current_dir; use std::fs; use std::path::PathBuf; use tracing::debug; +pub fn importer_contract_path(explicit: Option) -> anyhow::Result { + contract_path("importer_contract.wasm", explicit) +} + +pub fn mixnet_contract_path(explicit: Option) -> anyhow::Result { + contract_path("mixnet_contract.wasm", explicit) +} + +pub fn vesting_contract_path(explicit: Option) -> anyhow::Result { + contract_path("vesting_contract.wasm", explicit) +} + +fn find_contract_in(root: PathBuf, target_name: &str) -> anyhow::Result> { + for content in root.read_dir()? { + let dir_entry = content?; + + let path = dir_entry.path(); + debug!("checking {:?}", fs::canonicalize(path.clone())); + + let Ok(name) = dir_entry.file_name().into_string() else { + continue; + }; + + if name == "target" { + let maybe_contract_path = dir_entry + .path() + .join("wasm32-unknown-unknown") + .join("release") + .join(target_name); + + if maybe_contract_path.exists() { + return Ok(Some(maybe_contract_path)); + } + } + + if path.is_dir() { + if let Some(found) = find_contract_in(path, target_name)? { + return Ok(Some(found)); + } + } + } + + Ok(None) +} + // this only works if the cli is called from somewhere within the nym directory // (which realistically is going to be the case most of the time) -pub fn importer_contract_path(explicit: Option) -> anyhow::Result { +pub fn contract_path(target_name: &str, explicit: Option) -> anyhow::Result { if let Some(explicit) = explicit { return Ok(explicit); } @@ -29,16 +77,24 @@ pub fn importer_contract_path(explicit: Option) -> anyhow::Result) -> anyhow::Result { @@ -49,3 +105,26 @@ pub fn importer_contract_address(explicit: Option) -> anyhow::Result< let state = CachedState::load()?; Ok(state.importer_address) } + +pub fn find_value(raw_state: &[Model], key: &[u8]) -> anyhow::Result> +where + T: DeserializeOwned, +{ + let Some(entry) = raw_state.iter().find(|kv| kv.key == key) else { + return Ok(None); + }; + + Ok(Some(serde_json::from_slice(&entry.value)?)) +} + +pub fn update_value(raw_state: &mut [Model], key: &[u8], value: &T) -> anyhow::Result<()> +where + T: Serialize, +{ + let Some(entry) = raw_state.iter_mut().find(|kv| kv.key == key) else { + bail!("couldn't find corresponding state entry") + }; + + entry.value = serde_json::to_vec(value)?; + Ok(()) +}