implemented migration into non-vesting mixnodes/delegations
This commit is contained in:
Generated
-17
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 { .. }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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![],
|
||||
)?))
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user