nym-validator-client 2.0.0

Client for interacting with Nyx Cosmos SDK blockchain
Documentation
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0

use crate::nyxd::contract_traits::NymContractsProvider;
use crate::nyxd::cosmwasm_client::types::ExecuteResult;
use crate::nyxd::error::NyxdError;
use crate::nyxd::{Coin, Fee, SigningCosmWasmClient};
use crate::signing::signer::OfflineSigner;
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing};
use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
use nym_coconut_dkg_common::types::{DealingIndex, EncodedBTEPublicKeyWithProof, EpochId};
use nym_coconut_dkg_common::verification_key::VerificationKeyShare;
use nym_contracts_common::IdentityKey;

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait DkgSigningClient {
    async fn execute_dkg_contract(
        &self,
        fee: Option<Fee>,
        msg: DkgExecuteMsg,
        memo: String,
        funds: Vec<Coin>,
    ) -> Result<ExecuteResult, NyxdError>;

    async fn initiate_dkg(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::InitiateDkg {};

        self.execute_dkg_contract(fee, req, "initiating the DKG".to_string(), vec![])
            .await
    }

    async fn advance_dkg_epoch_state(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::AdvanceEpochState {};

        self.execute_dkg_contract(fee, req, "advancing DKG state".to_string(), vec![])
            .await
    }

    async fn register_dealer(
        &self,
        bte_key: EncodedBTEPublicKeyWithProof,
        identity_key: IdentityKey,
        announce_address: String,
        resharing: bool,
        fee: Option<Fee>,
    ) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::RegisterDealer {
            bte_key_with_proof: bte_key,
            identity_key,
            announce_address,
            resharing,
        };

        self.execute_dkg_contract(fee, req, "registering as a dealer".to_string(), vec![])
            .await
    }

    async fn submit_dealing_metadata(
        &self,
        dealing_index: DealingIndex,
        chunks: Vec<DealingChunkInfo>,
        resharing: bool,
        fee: Option<Fee>,
    ) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::CommitDealingsMetadata {
            dealing_index,
            chunks,
            resharing,
        };

        self.execute_dkg_contract(fee, req, "dealing metadata commitment".to_string(), vec![])
            .await
    }

    async fn submit_dealing_chunk(
        &self,
        chunk: PartialContractDealing,
        fee: Option<Fee>,
    ) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::CommitDealingsChunk { chunk };

        self.execute_dkg_contract(fee, req, "dealing chunk commitment".to_string(), vec![])
            .await
    }

    async fn submit_verification_key_share(
        &self,
        share: VerificationKeyShare,
        resharing: bool,
        fee: Option<Fee>,
    ) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::CommitVerificationKeyShare { share, resharing };

        self.execute_dkg_contract(
            fee,
            req,
            "verification key share commitment".to_string(),
            vec![],
        )
        .await
    }

    async fn verify_verification_key_share(
        &self,
        owner: &AccountId,
        resharing: bool,
        epoch_id: EpochId,
        fee: Option<Fee>,
    ) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::VerifyVerificationKeyShare {
            owner: owner.to_string(),
            resharing,
            epoch_id,
        };

        self.execute_dkg_contract(
            fee,
            req,
            "verification key VerifyVerificationKeyShare".to_string(),
            vec![],
        )
        .await
    }

    async fn trigger_dkg_reset(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::TriggerReset {};

        self.execute_dkg_contract(fee, req, "trigger DKG reset".to_string(), vec![])
            .await
    }

    async fn trigger_dkg_resharing(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::TriggerResharing {};

        self.execute_dkg_contract(fee, req, "trigger DKG resharing".to_string(), vec![])
            .await
    }

    /// The admin escape hatch: forces a DKG reset from any epoch state, including mid-exchange.
    async fn trigger_dkg_forced_reset(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::TriggerForcedReset {};

        self.execute_dkg_contract(fee, req, "trigger forced DKG reset".to_string(), vec![])
            .await
    }

    /// Admin-only: moves the ceremony to its next phase without waiting for the phase to
    /// complete or its deadline to pass.
    async fn force_advance_dkg_epoch_state(
        &self,
        fee: Option<Fee>,
    ) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::ForceAdvanceEpochState {};

        self.execute_dkg_contract(
            fee,
            req,
            "force advance DKG epoch state".to_string(),
            vec![],
        )
        .await
    }

    async fn transfer_ownership(
        &self,
        transfer_to: String,
        fee: Option<Fee>,
    ) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::TransferOwnership { transfer_to };

        self.execute_dkg_contract(fee, req, "".to_string(), vec![])
            .await
    }
    async fn update_announce_address(
        &self,
        new_address: String,
        fee: Option<Fee>,
    ) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::UpdateAnnounceAddress { new_address };

        self.execute_dkg_contract(fee, req, "".to_string(), vec![])
            .await
    }

    /// Admin-only: hands the DKG admin role over to another address.
    async fn update_dkg_admin(
        &self,
        admin: String,
        fee: Option<Fee>,
    ) -> Result<ExecuteResult, NyxdError> {
        let req = DkgExecuteMsg::UpdateAdmin { admin };

        self.execute_dkg_contract(fee, req, "updating DKG admin".to_string(), vec![])
            .await
    }
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<C> DkgSigningClient for C
where
    C: SigningCosmWasmClient + NymContractsProvider + Sync,
    NyxdError: From<<Self as OfflineSigner>::Error>,
{
    async fn execute_dkg_contract(
        &self,
        fee: Option<Fee>,
        msg: DkgExecuteMsg,
        memo: String,
        funds: Vec<Coin>,
    ) -> Result<ExecuteResult, NyxdError> {
        let dkg_contract_address = self
            .dkg_contract_address()
            .ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract"))?;

        let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
        let signer_address = &self.signer_addresses()[0];

        self.execute(signer_address, dkg_contract_address, &msg, fee, memo, funds)
            .await
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::nyxd::contract_traits::tests::IgnoreValue;
    use nym_coconut_dkg_common::msg::ExecuteMsg;

    // it's enough that this compiles and clippy is happy about it
    #[allow(dead_code)]
    fn all_execute_variants_are_covered<C: DkgSigningClient + Send + Sync>(
        client: C,
        msg: DkgExecuteMsg,
    ) {
        match msg {
            DkgExecuteMsg::InitiateDkg {} => client.initiate_dkg(None).ignore(),
            DkgExecuteMsg::RegisterDealer {
                bte_key_with_proof,
                identity_key,
                announce_address,
                resharing,
            } => client
                .register_dealer(
                    bte_key_with_proof,
                    identity_key,
                    announce_address,
                    resharing,
                    None,
                )
                .ignore(),
            DkgExecuteMsg::CommitDealingsMetadata {
                dealing_index,
                chunks,
                resharing,
            } => client
                .submit_dealing_metadata(dealing_index, chunks, resharing, None)
                .ignore(),
            DkgExecuteMsg::CommitDealingsChunk { chunk } => {
                client.submit_dealing_chunk(chunk, None).ignore()
            }
            DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client
                .submit_verification_key_share(share, resharing, None)
                .ignore(),
            DkgExecuteMsg::VerifyVerificationKeyShare {
                owner,
                resharing,
                epoch_id,
            } => client
                .verify_verification_key_share(&owner.parse().unwrap(), resharing, epoch_id, None)
                .ignore(),
            DkgExecuteMsg::AdvanceEpochState {} => client.advance_dkg_epoch_state(None).ignore(),
            DkgExecuteMsg::TriggerReset {} => client.trigger_dkg_reset(None).ignore(),
            DkgExecuteMsg::TriggerResharing {} => client.trigger_dkg_resharing(None).ignore(),
            DkgExecuteMsg::TriggerForcedReset {} => client.trigger_dkg_forced_reset(None).ignore(),
            DkgExecuteMsg::ForceAdvanceEpochState {} => {
                client.force_advance_dkg_epoch_state(None).ignore()
            }
            ExecuteMsg::TransferOwnership { transfer_to } => {
                client.transfer_ownership(transfer_to, None).ignore()
            }
            ExecuteMsg::UpdateAnnounceAddress { new_address } => {
                client.update_announce_address(new_address, None).ignore()
            }
            ExecuteMsg::UpdateAdmin { admin } => client.update_dkg_admin(admin, None).ignore(),
        };
    }
}