implemented migration into non-vesting mixnodes/delegations

This commit is contained in:
Jędrzej Stuczyński
2024-07-12 15:26:50 +01:00
parent 1287aa2a6c
commit 32dd987e12
16 changed files with 243 additions and 92 deletions
Generated
-17
View File
@@ -4999,23 +4999,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-network-statistics"
version = "1.1.34"
dependencies = [
"dirs 4.0.0",
"log",
"nym-bin-common",
"nym-statistics-common",
"nym-task",
"pretty_env_logger",
"rocket",
"serde",
"sqlx",
"thiserror",
"tokio",
]
[[package]]
name = "nym-node"
version = "1.1.4"
@@ -76,6 +76,12 @@ pub enum MixnetContractError {
#[error("Received multiple coin types during staking")]
MultipleDenoms,
#[error("Proxy address ({received}) is not set to the vesting contract ({vesting_contract})")]
ProxyIsNotVestingContract {
received: Addr,
vesting_contract: Addr,
},
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
@@ -226,6 +232,14 @@ pub enum MixnetContractError {
#[error("this operation is no longer allowed to be performed with vesting tokens. please move them to your liquid balance and try again")]
DisabledVestingOperation,
#[error(
"this mixnode has not been bonded with the vesting tokens or has already been migrated"
)]
NotAVestingMixnode,
#[error("this delegation has not been performed with the vesting tokens or has already been migrated")]
NotAVestingDelegation,
}
impl MixnetContractError {
@@ -269,6 +269,12 @@ pub enum ExecuteMsg {
owner: String,
},
// vesting migration:
MigrateVestedMixNode {},
MigratedVestedDelegation {
mix_id: MixId,
},
// testing-only
#[cfg(feature = "contract-testing")]
TestingResolveAllPendingEvents {
@@ -381,6 +387,9 @@ impl ExecuteMsg {
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => {
format!("withdrawing delegator reward from mixnode {mix_id} on behalf")
}
ExecuteMsg::MigrateVestedMixNode { .. } => "migrate vested mixnode".into(),
ExecuteMsg::MigratedVestedDelegation { .. } => "migrate vested delegation".to_string(),
#[cfg(feature = "contract-testing")]
ExecuteMsg::TestingResolveAllPendingEvents { .. } => {
"resolving all pending events".into()
@@ -167,3 +167,11 @@ pub fn new_track_undelegation_event() -> Event {
pub fn new_track_reward_event() -> Event {
Event::new(TRACK_REWARD_EVENT_TYPE)
}
pub fn new_track_migrate_mixnode_event() -> Event {
Event::new("track_migrate_vesting_mixnode")
}
pub fn new_track_migrate_delegation_event() -> Event {
Event::new("track_migrate_vesting_delegation")
}
@@ -136,6 +136,14 @@ pub enum ExecuteMsg {
address: String,
cap: PledgeCap,
},
TrackMigratedMixnode {
owner: String,
},
// no need to track migrated gateways as there are no vesting gateways on mainnet
TrackMigratedDelegation {
owner: String,
mix_id: MixId,
},
}
impl ExecuteMsg {
@@ -171,6 +179,10 @@ impl ExecuteMsg {
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
ExecuteMsg::TrackMigratedMixnode { .. } => "VestingExecuteMsg::TrackMigratedMixnode",
ExecuteMsg::TrackMigratedDelegation { .. } => {
"VestingExecuteMsg::TrackMigratedDelegation"
}
}
}
}
+2
View File
@@ -84,6 +84,7 @@ impl PendingEpochEventData {
mix_id,
amount,
proxy,
..
} => Ok(PendingEpochEventData::Delegate {
owner: owner.into_string(),
mix_id,
@@ -94,6 +95,7 @@ impl PendingEpochEventData {
owner,
mix_id,
proxy,
..
} => Ok(PendingEpochEventData::Undelegate {
owner: owner.into_string(),
mix_id,
+8
View File
@@ -253,6 +253,14 @@ pub fn execute(
crate::rewards::transactions::try_withdraw_delegator_reward(deps, info, mix_id)
}
// vesting migration:
ExecuteMsg::MigrateVestedMixNode { .. } => {
crate::vesting_migration::try_migrate_vested_mixnode(deps, info)
}
ExecuteMsg::MigratedVestedDelegation { mix_id } => {
crate::vesting_migration::try_migrate_vested_delegation(deps, info, mix_id)
}
// legacy vesting
ExecuteMsg::CreateFamilyOnBehalf { .. }
| ExecuteMsg::JoinFamilyOnBehalf { .. }
+1
View File
@@ -19,3 +19,4 @@ mod support;
#[cfg(feature = "contract-testing")]
mod testing;
mod vesting_migration;
@@ -43,6 +43,12 @@ pub(crate) fn rewarding_denom(storage: &dyn Storage) -> Result<String, MixnetCon
.map(|state| state.rewarding_denom)?)
}
pub(crate) fn vesting_contract_address(storage: &dyn Storage) -> Result<Addr, MixnetContractError> {
Ok(CONTRACT_STATE
.load(storage)
.map(|state| state.vesting_contract_address)?)
}
pub(crate) fn initialise_storage(
storage: &mut dyn Storage,
initial_state: ContractState,
+95
View File
@@ -0,0 +1,95 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::delegations::storage as delegations_storage;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
use crate::mixnodes::storage as mixnodes_storage;
use crate::support::helpers::{
ensure_bonded, ensure_epoch_in_progress_state, ensure_no_pending_pledge_changes,
};
use cosmwasm_std::{wasm_execute, DepsMut, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{Delegation, MixId};
use vesting_contract_common::messages::ExecuteMsg as VestingExecuteMsg;
pub(crate) fn try_migrate_vested_mixnode(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?.ok_or(
MixnetContractError::NoAssociatedMixNodeBond {
owner: info.sender.clone(),
},
)?;
let mix_id = mix_details.mix_id();
ensure_epoch_in_progress_state(deps.storage)?;
ensure_no_pending_pledge_changes(&mix_details.pending_changes)?;
ensure_bonded(&mix_details.bond_information)?;
let Some(proxy) = &mix_details.bond_information.proxy else {
return Err(MixnetContractError::NotAVestingMixnode);
};
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
if proxy != vesting_contract {
return Err(MixnetContractError::ProxyIsNotVestingContract {
received: proxy.clone(),
vesting_contract,
});
}
let mut updated_bond = mix_details.bond_information.clone();
updated_bond.proxy = None;
mixnodes_storage::mixnode_bonds().replace(
deps.storage,
mix_id,
Some(&updated_bond),
Some(&mix_details.bond_information),
)?;
Ok(Response::new().add_message(wasm_execute(
vesting_contract,
&VestingExecuteMsg::TrackMigratedMixnode {
owner: info.sender.into_string(),
},
vec![],
)?))
}
pub(crate) fn try_migrate_vested_delegation(
deps: DepsMut<'_>,
info: MessageInfo,
mix_id: MixId,
) -> Result<Response, MixnetContractError> {
ensure_epoch_in_progress_state(deps.storage)?;
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
let storage_key =
Delegation::generate_storage_key(mix_id, &info.sender, Some(&vesting_contract));
let Some(mut delegation) =
delegations_storage::delegations().may_load(deps.storage, storage_key.clone())?
else {
return Err(MixnetContractError::NotAVestingDelegation);
};
// sanity check that's meant to blow up the contract
assert_eq!(delegation.proxy, Some(vesting_contract.clone()));
// update the delegation and save it under the correct storage key
delegation.proxy = None;
let updated_storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
delegations_storage::delegations().remove(deps.storage, storage_key)?;
delegations_storage::delegations().save(deps.storage, updated_storage_key, &delegation)?;
Ok(Response::new().add_message(wasm_execute(
vesting_contract,
&VestingExecuteMsg::TrackMigratedDelegation {
owner: info.sender.into_string(),
mix_id,
},
vec![],
)?))
}
+6 -73
View File
@@ -70,55 +70,12 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, VestingContractError> {
match msg {
ExecuteMsg::CreateFamily { label } => try_create_family(info, deps, label),
ExecuteMsg::JoinFamily {
join_permit,
family_head,
} => try_join_family(info, deps, join_permit, family_head),
ExecuteMsg::LeaveFamily { family_head } => try_leave_family(info, deps, family_head),
ExecuteMsg::KickFamilyMember { member } => try_kick_family_member(info, deps, member),
ExecuteMsg::UpdateLockedPledgeCap { address, cap } => {
try_update_locked_pledge_cap(address, cap, info, deps)
}
ExecuteMsg::TrackReward { amount, address } => {
try_track_reward(deps, info, amount, &address)
}
ExecuteMsg::ClaimOperatorReward {} => try_claim_operator_reward(deps, info),
ExecuteMsg::ClaimDelegatorReward { mix_id } => {
try_claim_delegator_reward(deps, info, mix_id)
}
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
try_update_mixnode_config(new_config, info, deps)
}
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
try_update_mixnode_cost_params(new_costs, info, deps)
}
ExecuteMsg::UpdateMixnetAddress { address } => {
try_update_mixnet_address(address, info, deps)
}
ExecuteMsg::DelegateToMixnode {
mix_id,
amount,
on_behalf_of,
} => try_delegate_to_mixnode(mix_id, amount, on_behalf_of, info, env, deps),
ExecuteMsg::UndelegateFromMixnode {
mix_id,
on_behalf_of,
} => try_undelegate_from_mixnode(mix_id, on_behalf_of, info, deps),
ExecuteMsg::CreateAccount {
owner_address,
staking_address,
vesting_spec,
cap,
} => try_create_periodic_vesting_account(
&owner_address,
staking_address,
vesting_spec,
cap,
info,
env,
deps,
),
ExecuteMsg::WithdrawVestedCoins { amount } => {
try_withdraw_vested_coins(amount, env, info, deps)
}
@@ -127,47 +84,23 @@ pub fn execute(
mix_id,
amount,
} => try_track_undelegation(&owner, mix_id, amount, info, deps),
ExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount,
} => try_bond_mixnode(
mix_node,
cost_params,
owner_signature,
amount,
info,
env,
deps,
),
ExecuteMsg::PledgeMore { amount } => try_pledge_more(deps, env, info, amount),
ExecuteMsg::DecreasePledge { amount } => try_decrease_pledge(deps, info, amount),
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
try_track_unbond_mixnode(&owner, amount, info, deps)
}
ExecuteMsg::TrackDecreasePledge { owner, amount } => {
try_track_decrease_mixnode_pledge(&owner, amount, info, deps)
}
ExecuteMsg::BondGateway {
gateway,
owner_signature,
amount,
} => try_bond_gateway(gateway, owner_signature, amount, info, env, deps),
ExecuteMsg::UnbondGateway {} => try_unbond_gateway(info, deps),
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
try_track_unbond_gateway(&owner, amount, info, deps)
}
ExecuteMsg::UpdateGatewayConfig { new_config } => {
try_update_gateway_config(new_config, info, deps)
}
ExecuteMsg::TransferOwnership { to_address } => {
try_transfer_ownership(to_address, info, deps)
}
ExecuteMsg::UpdateStakingAddress { to_address } => {
try_update_staking_address(to_address, info, deps)
ExecuteMsg::TrackMigratedMixnode { owner } => try_track_migrate_mixnode(&owner, info, deps),
ExecuteMsg::TrackMigratedDelegation { owner, mix_id } => {
try_track_migrate_delegation(&owner, mix_id, info, deps)
}
_ => Err(VestingContractError::Other {
message: "the contract has been disabled".to_string(),
}),
}
}
@@ -61,6 +61,10 @@ pub trait MixnodeBondingAccount {
new_costs: MixNodeCostParams,
storage: &mut dyn Storage,
) -> Result<Response, VestingContractError>;
fn try_track_migrated_mixnode(
&self,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError>;
}
pub trait GatewayBondingAccount {
@@ -44,4 +44,9 @@ pub trait DelegatingAccount {
amount: Coin,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError>;
fn track_migrated_delegation(
&self,
mix_id: MixId,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError>;
}
+32 -2
View File
@@ -18,8 +18,9 @@ use mixnet_contract_common::{
use vesting_contract_common::events::{
new_ownership_transfer_event, new_periodic_vesting_account_event,
new_staking_address_update_event, new_track_gateway_unbond_event,
new_track_mixnode_pledge_decrease_event, new_track_mixnode_unbond_event,
new_track_reward_event, new_track_undelegation_event, new_vested_coins_withdraw_event,
new_track_migrate_mixnode_event, new_track_mixnode_pledge_decrease_event,
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
new_vested_coins_withdraw_event,
};
use vesting_contract_common::{Account, PledgeCap, VestingContractError, VestingSpecification};
@@ -255,6 +256,35 @@ pub fn try_track_unbond_gateway(
Ok(Response::new().add_event(new_track_gateway_unbond_event()))
}
/// Track vesting mixnode being converted into the usage of liquid tokens. invoked by the mixnet contract after successful migration.
pub fn try_track_migrate_mixnode(
owner: &str,
info: MessageInfo,
deps: DepsMut<'_>,
) -> Result<Response, VestingContractError> {
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
return Err(VestingContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(owner, deps.storage, deps.api)?;
account.try_track_migrated_mixnode(deps.storage)?;
Ok(Response::new().add_event(new_track_migrate_mixnode_event()))
}
/// Track vesting delegation being converted into the usage of liquid tokens. invoked by the mixnet contract after successful migration.
pub fn try_track_migrate_delegation(
owner: &str,
mix_id: MixId,
info: MessageInfo,
deps: DepsMut<'_>,
) -> Result<Response, VestingContractError> {
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
return Err(VestingContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(owner, deps.storage, deps.api)?;
account.track_migrated_delegation(mix_id, deps.storage)?;
Ok(Response::new().add_event(new_track_migrate_mixnode_event()))
}
/// Bond a mixnode, sends [mixnet_contract_common::ExecuteMsg::BondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
pub fn try_bond_mixnode(
mix_node: MixNode,
@@ -125,4 +125,25 @@ impl DelegatingAccount for Account {
self.save_balance(new_balance, storage)?;
Ok(())
}
fn track_migrated_delegation(
&self,
mix_id: MixId,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError> {
let delegation = self.total_delegations_for_mix(mix_id, storage)?;
if delegation.is_zero() {
return Err(VestingContractError::NoSuchDelegation(
self.owner_address.clone(),
mix_id,
));
}
// treat the tokens that were used for delegation as 'withdrawn'
let current_withdrawn = self.load_withdrawn(storage)?;
self.save_withdrawn(current_withdrawn + delegation, storage)?;
// remove the delegation data since it no longer belongs to the vesting contract
self.remove_delegations_for_mix(mix_id, storage)
}
}
@@ -221,4 +221,24 @@ impl MixnodeBondingAccount for Account {
.add_message(update_mixnode_costs_msg)
.add_event(new_vesting_update_mixnode_cost_params_event()))
}
fn try_track_migrated_mixnode(
&self,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError> {
let Some(pledge) = self.load_mixnode_pledge(storage)? else {
return Err(VestingContractError::NoBondFound(
self.owner_address().as_str().to_string(),
));
};
// treat the tokens that were used for bonding as 'withdrawn'
let current_withdrawn = self.load_withdrawn(storage)?;
self.save_withdrawn(current_withdrawn + pledge.amount.amount, storage)?;
// don't change the balance as the tokens are left in the mixnet contract
// remove the pledge data since it no longer belongs to the vesting account
self.remove_mixnode_pledge(storage)
}
}