From 30e93c33bba54d768d97cf5e3f2e16223f7ebb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 6 Jan 2022 13:03:14 +0100 Subject: [PATCH] Feature/configure profit (#1008) * Introduce a method to update mixnode configuration Right now, only for profit_margin_percent * Check that the new profit margin is valid * Extend a bit the test coverage of mixnode update * Create validator client function * [ci skip] Generate TS types * Update wallet * Update the bond height as well, as if a rebond was made Co-authored-by: neacsu --- .../validator-client/src/nymd/fee/helpers.rs | 3 + .../validator-client/src/nymd/mod.rs | 25 ++++ common/mixnet-contract/src/msg.rs | 3 + contracts/mixnet/src/contract.rs | 8 ++ contracts/mixnet/src/mixnodes/transactions.rs | 107 ++++++++++++++++++ nym-wallet/src-tauri/src/main.rs | 1 + .../src-tauri/src/operations/mixnet/bond.rs | 11 ++ nym-wallet/src/types/rust/operation.ts | 1 + 8 files changed, 159 insertions(+) diff --git a/common/client-libs/validator-client/src/nymd/fee/helpers.rs b/common/client-libs/validator-client/src/nymd/fee/helpers.rs index cc16e21cb5..0ddbf15356 100644 --- a/common/client-libs/validator-client/src/nymd/fee/helpers.rs +++ b/common/client-libs/validator-client/src/nymd/fee/helpers.rs @@ -20,6 +20,7 @@ pub enum Operation { BondMixnodeOnBehalf, UnbondMixnode, UnbondMixnodeOnBehalf, + UpdateMixnodeConfig, DelegateToMixnode, DelegateToMixnodeOnBehalf, UndelegateFromMixnode, @@ -57,6 +58,7 @@ impl fmt::Display for Operation { Operation::BondMixnode => f.write_str("BondMixnode"), Operation::BondMixnodeOnBehalf => f.write_str("BondMixnodeOnBehalf"), Operation::UnbondMixnode => f.write_str("UnbondMixnode"), + Operation::UpdateMixnodeConfig => f.write_str("UpdateMixnodeConfig"), Operation::UnbondMixnodeOnBehalf => f.write_str("UnbondMixnodeOnBehalf"), Operation::BondGateway => f.write_str("BondGateway"), Operation::BondGatewayOnBehalf => f.write_str("BondGatewayOnBehalf"), @@ -94,6 +96,7 @@ impl Operation { Operation::BondMixnodeOnBehalf => 200_000u64.into(), Operation::UnbondMixnode => 175_000u64.into(), Operation::UnbondMixnodeOnBehalf => 175_000u64.into(), + Operation::UpdateMixnodeConfig => 175_000u64.into(), Operation::DelegateToMixnode => 175_000u64.into(), Operation::DelegateToMixnodeOnBehalf => 175_000u64.into(), Operation::UndelegateFromMixnode => 175_000u64.into(), diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 72f72a220f..71c8766cf7 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -773,6 +773,31 @@ impl NymdClient { .await } + /// Update the configuration of a mixnode. Right now, only possible for profit margin. + pub async fn update_mixnode_config( + &self, + profit_margin_percent: u8, + ) -> Result + where + C: SigningCosmWasmClient + Sync, + { + let fee = self.operation_fee(Operation::UpdateMixnodeConfig); + + let req = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }; + self.client + .execute( + self.address(), + self.mixnet_contract_address()?, + &req, + fee, + "Updating mixnode configuration from rust!", + Vec::new(), + ) + .await + } + /// Delegates specified amount of stake to particular mixnode. pub async fn delegate_to_mixnode( &self, diff --git a/common/mixnet-contract/src/msg.rs b/common/mixnet-contract/src/msg.rs index c744a4f56e..8f74824ec0 100644 --- a/common/mixnet-contract/src/msg.rs +++ b/common/mixnet-contract/src/msg.rs @@ -20,6 +20,9 @@ pub enum ExecuteMsg { owner_signature: String, }, UnbondMixnode {}, + UpdateMixnodeConfig { + profit_margin_percent: u8, + }, BondGateway { gateway: Gateway, owner_signature: String, diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index d46c7aa3c7..2cf4d65686 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -104,6 +104,14 @@ pub fn execute( ExecuteMsg::UnbondMixnode {} => { crate::mixnodes::transactions::try_remove_mixnode(deps, info) } + ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + } => crate::mixnodes::transactions::try_update_mixnode_config( + deps, + env, + info, + profit_margin_percent, + ), ExecuteMsg::BondGateway { gateway, owner_signature, diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index 3f7c3d40e3..5ec7cdc33a 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -204,6 +204,42 @@ pub(crate) fn _try_remove_mixnode( Ok(response) } +pub(crate) fn try_update_mixnode_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + profit_margin_percent: u8, +) -> Result { + let owner = deps.api.addr_validate(info.sender.as_ref())?; + let mix_identity = storage::mixnodes() + .idx + .owner + .item(deps.storage, owner.clone())? + .ok_or(ContractError::NoAssociatedMixNodeBond { owner })? + .1 + .identity() + .clone(); + + // We don't have to check lower bound as its an u8 + if profit_margin_percent > 100 { + return Err(ContractError::InvalidProfitMarginPercent( + profit_margin_percent, + )); + } + + storage::mixnodes().update(deps.storage, &mix_identity, |mixnode_bond_opt| { + mixnode_bond_opt + .map(|mut mixnode_bond| { + mixnode_bond.mix_node.profit_margin_percent = profit_margin_percent; + mixnode_bond.block_height = env.block.height; + mixnode_bond + }) + .ok_or(ContractError::NoBondFound) + })?; + + Ok(Response::new()) +} + fn validate_mixnode_pledge( mut pledge: Vec, minimum_pledge: Uint128, @@ -587,6 +623,77 @@ pub mod tests { ); } + #[test] + fn updating_mixnode_config() { + let sender = "bob"; + let mut deps = test_helpers::init_contract(); + let info = mock_info(sender, &[]); + + // try updating a non existing mixnode bond + let msg = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent: 10, + }; + let ret = execute(deps.as_mut(), mock_env(), info.clone(), msg); + assert_eq!( + ret, + Err(ContractError::NoAssociatedMixNodeBond { + owner: Addr::unchecked(sender) + }) + ); + + test_helpers::add_mixnode( + sender, + tests::fixtures::good_mixnode_pledge(), + deps.as_mut(), + ); + + // check the initial profit margin is set to the fixture value + let fixture_profit_margin = tests::fixtures::mix_node_fixture().profit_margin_percent; + assert_eq!( + fixture_profit_margin, + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("bob")) + .unwrap() + .unwrap() + .1 + .mix_node + .profit_margin_percent + ); + + // try updating with an invalid value + let profit_margin_percent = 101; + let msg = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }; + let ret = execute(deps.as_mut(), mock_env(), info.clone(), msg); + assert_eq!( + ret, + Err(ContractError::InvalidProfitMarginPercent( + profit_margin_percent + )) + ); + + let profit_margin_percent = fixture_profit_margin + 10; + let msg = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }; + execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!( + profit_margin_percent, + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("bob")) + .unwrap() + .unwrap() + .1 + .mix_node + .profit_margin_percent + ); + } + #[test] fn validating_mixnode_bond() { // you must send SOME funds diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index e402137e39..a52b9a4a91 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -36,6 +36,7 @@ fn main() { mixnet::bond::bond_mixnode, mixnet::bond::unbond_gateway, mixnet::bond::unbond_mixnode, + mixnet::bond::update_mixnode, mixnet::bond::mixnode_bond_details, mixnet::bond::gateway_bond_details, mixnet::delegate::delegate_to_mixnode, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index b1801b576f..fce6c4219a 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -50,6 +50,17 @@ pub async fn bond_mixnode( Ok(()) } +#[tauri::command] +pub async fn update_mixnode( + profit_margin_percent: u8, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state) + .update_mixnode_config(profit_margin_percent) + .await?; + Ok(()) +} + #[tauri::command] pub async fn mixnode_bond_details( state: tauri::State<'_, Arc>>, diff --git a/nym-wallet/src/types/rust/operation.ts b/nym-wallet/src/types/rust/operation.ts index 545c7bbe81..6c8d38c221 100644 --- a/nym-wallet/src/types/rust/operation.ts +++ b/nym-wallet/src/types/rust/operation.ts @@ -8,6 +8,7 @@ export type Operation = | "BondMixnodeOnBehalf" | "UnbondMixnode" | "UnbondMixnodeOnBehalf" + | "UpdateMixnodeConfig" | "DelegateToMixnode" | "DelegateToMixnodeOnBehalf" | "UndelegateFromMixnode"